Search code examples
modelicaopenmodelicadymola

Wrong pump curve - head_nominal must be monotonically decreasing with increasing V_flow_nominal


I am trying to build a model in OpenModelica ConnectionEditor which allows me to do pump sizing to select the amount of pumps and specific pumps that fit best for a given cooling cycle.

My model is based on Modelica.Fluid components entirely, only the coefficients for the VolumeFlowRate pressure drop and the PrescribedPump with PumpCharacteristics.quadraticFlow are changed to fit the real system. The idea of my model is to find the operating point for each cooling cycle with a separate pump and the pressure decreasing components in it. This then allows for the correct choice of pumps.Modelica Graphical setup

But now I realized that the behaviour of my pump is completely not the same as the real pump! By plotting a parametric plot where I plot dp_pump against V_flow of the pump I found out that the pump specific curve is not a parabola as the points that I put in but rather linear. Also the pumps should all be the same but actually end up having wrong and different specific curves. The best fit is the blue line, which somehow matches the starting point at around 0.6 bar and the 33l/min at around 0bar. plotted specific pump curves

My guess is that this problem is probably related to the fact, that it is hard for modelica to compute with non monotonically decreasing pump curves. That is also why I chose the compile error message as the title of this thread.

So is there a concrete way on how to implement non-monotonically decreasing curves, as they exist with centrifugal pumps? or is there a clever workaround to best fit a pump curve but it still being monotonically decreasing?

See here a picture of one of the pump curves that are used: real specific pump curve of a Bosch pump

The modelica code:
model ModelicaTest3_Kreislauf_mab
  extends Modelica;
  replaceable package Medium = Modelica.Media.Water.StandardWater;
  //package Medium = Modelica.Media.Incompressible.Examples.Glycol47;
  //Modelica.Units.SI.VolumeFlowRate V_flow(displayUnit = "l/min") = 60000;
  //Modelica.Units.Conversions.to_litre, Modelica.Units.Conversions.to_minute,
  //V_flow(displayUnit = "l/min")
  inner Modelica.Fluid.System system annotation(
    Placement(visible = true, transformation(origin = {-86, 84}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  Modelica.Fluid.Fittings.GenericResistances.VolumeFlowRate ELK_Waermepumpe(redeclare package Medium = Medium, V_flow(displayUnit = "l/min"), a = 5.04000E+08, b = 5.22000E+05) annotation(
    Placement(visible = true, transformation(origin = {24, 56}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  Modelica.Fluid.Sources.FixedBoundary source(redeclare package Medium = Medium, T = Modelica.Units.Conversions.from_degC(20), nPorts = 1, p = system.p_ambient, use_T = true) annotation(
    Placement(visible = true, transformation(origin = {8, 126}, extent = {{-100, -80}, {-80, -60}}, rotation = 0)));
  Modelica.Fluid.Machines.PrescribedPump BoschPumpe(redeclare package Medium = Medium, N_nominal = 1500, T_start = system.T_start, V(displayUnit = "l") = 0.001, V_flow(displayUnit = "l/min"), checkValve = true, checkValveHomotopy = Modelica.Fluid.Types.CheckValveHomotopyType.Closed, energyDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, redeclare function flowCharacteristic = Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.quadraticFlow(V_flow_nominal = {0.000074, 0.000362, 0.000558}, head_nominal = {6.707441, 4.954128, 0.468909}), massDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, nParallel = 1, p_b_start = 60000, use_N_in = false) annotation(
    Placement(visible = true, transformation(origin = {24, 126}, extent = {{-68, -80}, {-48, -60}}, rotation = 0)));
  Modelica.Fluid.Machines.PrescribedPump BoschPumpe1(redeclare package Medium = Medium, N_nominal = 1500, T_start = system.T_start, V(displayUnit = "l") = 0.001, V_flow(displayUnit = "l/min"), checkValve = true, checkValveHomotopy = Modelica.Fluid.Types.CheckValveHomotopyType.Closed, energyDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, redeclare function flowCharacteristic = Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.quadraticFlow(V_flow_nominal = {0.000074, 0.000362, 0.000558}, head_nominal = {6.707441, 4.954128, 0.468909}), massDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, nParallel = 1, p_b_start = 60000, use_N_in = false) annotation(
    Placement(visible = true, transformation(origin = {24, 88}, extent = {{-68, -80}, {-48, -60}}, rotation = 0)));
  Modelica.Fluid.Sources.FixedBoundary fixedBoundary(redeclare package Medium = Medium, T = Modelica.Units.Conversions.from_degC(20), nPorts = 1, p = system.p_ambient, use_T = true) annotation(
    Placement(visible = true, transformation(origin = {8, 88}, extent = {{-100, -80}, {-80, -60}}, rotation = 0)));
  Modelica.Fluid.Sources.FixedBoundary fixedBoundary1(redeclare package Medium = Medium, T = system.T_ambient, nPorts = 3, p = system.p_ambient) annotation(
    Placement(visible = true, transformation(origin = {0, 48}, extent = {{100, -40}, {80, -20}}, rotation = 0)));
  Modelica.Fluid.Machines.PrescribedPump BoschPumpe2(redeclare package Medium = Medium, N_nominal = 1500, T_start = system.T_start, V(displayUnit = "l") = 0.001, V_flow(displayUnit = "l/min"), checkValve = true, checkValveHomotopy = Modelica.Fluid.Types.CheckValveHomotopyType.Closed, energyDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, redeclare function flowCharacteristic = Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.quadraticFlow(V_flow_nominal = {0.000074, 0.000362, 0.000558}, head_nominal = {6.707441, 4.954128, 0.468909}), massDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, nParallel = 1, p_b_start = 60000, use_N_in = false) annotation(
    Placement(visible = true, transformation(origin = {24, 50}, extent = {{-68, -80}, {-48, -60}}, rotation = 0)));
  Modelica.Fluid.Sources.FixedBoundary fixedBoundary2(redeclare package Medium = Medium, T = Modelica.Units.Conversions.from_degC(20), nPorts = 1, p = system.p_ambient, use_T = true) annotation(
    Placement(visible = true, transformation(origin = {8, 50}, extent = {{-100, -80}, {-80, -60}}, rotation = 0)));
  Modelica.Fluid.Fittings.GenericResistances.VolumeFlowRate DCDC_Wandler(redeclare package Medium = Medium, V_flow(displayUnit = "l/min"), a = 4.716e+9, b = -3.981e+7) annotation(
    Placement(visible = true, transformation(origin = {8, 18}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  Modelica.Fluid.Fittings.GenericResistances.VolumeFlowRate Motor(redeclare package Medium = Medium, V_flow(displayUnit = "l/min"), a = 9e+9, b = 1.92e+8) annotation(
    Placement(visible = true, transformation(origin = {44, 18}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  Modelica.Fluid.Fittings.GenericResistances.VolumeFlowRate Inverter(redeclare package Medium = Medium, V_flow(displayUnit = "l/min"), a = 5.35200e+9, b = -2.89020e+7) annotation(
    Placement(visible = true, transformation(origin = {8, -20}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
  Modelica.Fluid.Fittings.GenericResistances.VolumeFlowRate OBC(redeclare package Medium = Medium, V_flow(displayUnit = "l/min"), a = 4.32000e+09, b = 1.22280e+07) annotation(
    Placement(visible = true, transformation(origin = {44, -20}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
equation
  connect(BoschPumpe.port_a, source.ports[1]) annotation(
    Line(points = {{-44, 56}, {-72, 56}}, color = {0, 127, 255}));
  connect(BoschPumpe.port_b, ELK_Waermepumpe.port_a) annotation(
    Line(points = {{-24, 56}, {14, 56}}, color = {0, 127, 255}));
  connect(BoschPumpe1.port_a, fixedBoundary.ports[1]) annotation(
    Line(points = {{-44, 18}, {-72, 18}}, color = {0, 127, 255}));
  connect(BoschPumpe2.port_a, fixedBoundary2.ports[1]) annotation(
    Line(points = {{-44, -20}, {-72, -20}}, color = {0, 127, 255}));
  connect(BoschPumpe1.port_b, DCDC_Wandler.port_a) annotation(
    Line(points = {{-24, 18}, {-2, 18}}, color = {0, 127, 255}));
  connect(DCDC_Wandler.port_b, Motor.port_a) annotation(
    Line(points = {{18, 18}, {34, 18}}, color = {0, 127, 255}));
  connect(Motor.port_b, fixedBoundary1.ports[1]) annotation(
    Line(points = {{54, 18}, {80, 18}}, color = {0, 127, 255}));
  connect(BoschPumpe2.port_b, Inverter.port_a) annotation(
    Line(points = {{-24, -20}, {-2, -20}}, color = {0, 127, 255}));
  connect(Inverter.port_b, OBC.port_a) annotation(
    Line(points = {{18, -20}, {34, -20}}, color = {0, 127, 255}));
  connect(OBC.port_b, fixedBoundary1.ports[2]) annotation(
    Line(points = {{54, -20}, {68, -20}, {68, 18}, {80, 18}}, color = {0, 127, 255}));
  connect(ELK_Waermepumpe.port_b, fixedBoundary1.ports[3]) annotation(
    Line(points = {{34, 56}, {68, 56}, {68, 18}, {80, 18}}, color = {0, 127, 255}));
  annotation(
    uses(Modelica(version = "4.0.0")),
    Diagram(coordinateSystem(extent = {{-100, -100}, {100, 100}})));
end ModelicaTest3_Kreislauf_mab;


Solution

  • Non-monotonically decreasing pump curves are, indeed, a numerical problem. Basically, they imply that there are two (or more) flow values for the same head value. That is, multiple solutions to the equation systems to be solved during simulation.

    In your case, it's problematic to use the pump curve function Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.quadraticFlow in the pump models. The function takes exactly 3 points and fits a second-order polynomial (=3 coefficients) to those values. If the values (like in your boschPumpe model) are not monotonically decreasing the fit will look like the blue part of the figure below.

    enter image description here

    Clearly, the extremum is not at zero flow but rather somewhere between 0.0001 and 0.0002. The function gives you a warning but still manages to carry the simulation through, however very slowly. The function also extrapolates linearly when the flow is higher or lower than the nominal flow points. But it uses the slope of calculated polynomial. Thus, the head calculated at e.g. 0.00001 is even lower than the provided points which doesn't make things any better. The extrapolation is shown with the orange part of the curve.

    One solution

    What you could do is to manipulate the three nominal values such that they are guaranteed to be on a parabola with maximum head at zero flow. You can do this in Excel, for instance, or you could write your own pump curve function extending Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.baseFlow. In the function you could omit the coefficient b coefficient in the polynomial head = a*V_flow^2 + b*V_flow + c which shifts the maximum head to V_flow=0. The coefficient c is then equal to the maximum head.

    The remaining coefficient a can then be calculated as a=-c/V_flow_max^2 where V_flow_max is the volume flow at head=0.

    The downside of this approach is that the slope at V_flow=0 is zero whereas in the real pump curves it could be negative (as in your real Bosch pump curve). There are probably many other ways to generate numerically 'good' pump curves.

    In my experience, the pump models in Modelica Buildings Library handle the pump curves (and zero pump speed) better than the exemplary pumps in Modelica Standard Library. You could take a look at those.