within Modelica.Fluid;
package Machines
  "Devices for converting between energy held in a fluid and mechanical energy"
  extends Modelica.Icons.VariantsPackage;
  model SweptVolume
    "Varying cylindric volume depending on the position of the piston"
    import Modelica.Constants.pi;

    parameter SI.Area pistonCrossArea "Cross sectional area of piston";
    parameter SI.Volume clearance "Remaining volume at zero piston stroke";

    SI.Volume V "Fluid volume";

    // Mass and energy balance, ports
    extends Modelica.Fluid.Vessels.BaseClasses.PartialLumpedVessel(
      final fluidVolume = V,
      heatTransfer(surfaceAreas={pistonCrossArea+2*sqrt(pistonCrossArea*pi)*(flange.s+clearance/pistonCrossArea)}));

    Modelica.Mechanics.Translational.Interfaces.Flange_b flange
      "Translation flange for piston"

  equation
    assert(flange.s >= 0, "Piston stroke (given by flange.s) must not be smaller than zero!");

    // volume size
    V = clearance + flange.s * pistonCrossArea;

    0 = flange.f + (medium.p - system.p_ambient) * pistonCrossArea;

    // energy balances
    Wb_flow = medium.p * pistonCrossArea * (-der(flange.s));

    // definition of port pressures
    for i in 1:nPorts loop
      vessel_ps_static[i] = medium.p;
    end for;
  end SweptVolume;

  model Pump "Centrifugal pump with mechanical connector for the shaft"
    extends Modelica.Fluid.Machines.BaseClasses.PartialPump;
    SI.Angle phi "Shaft angle";
    SI.AngularVelocity omega "Shaft angular velocity";
    Modelica.Mechanics.Rotational.Interfaces.Flange_a shaft
  equation
    phi = shaft.phi;
    omega = der(phi);
    N = Modelica.Units.Conversions.to_rpm(omega);
    W_single = omega*shaft.tau;
  end Pump;

  model ControlledPump
    "Centrifugal pump with ideally controlled mass flow rate"
    import Modelica.Units.NonSI.AngularVelocity_rpm;
    extends Modelica.Fluid.Machines.BaseClasses.PartialPump(
      N_nominal=1500,
      N(start=N_nominal),
      redeclare replaceable function flowCharacteristic =
          Modelica.Fluid.Machines.BaseClasses.PumpCharacteristics.quadraticFlow
          ( V_flow_nominal={0, V_flow_op, 1.5*V_flow_op},
            head_nominal={2*head_op, head_op, 0}));

    // nominal values
    parameter Medium.AbsolutePressure p_a_nominal
      "Nominal inlet pressure for predefined pump characteristics";
    parameter Medium.AbsolutePressure p_b_nominal
      "Nominal outlet pressure, fixed if not control_m_flow and not use_p_set";
    parameter Medium.MassFlowRate m_flow_nominal
      "Nominal mass flow rate, fixed if control_m_flow and not use_m_flow_set";

    // what to control
    parameter Boolean control_m_flow = true
      "= false to control outlet pressure port_b.p instead of m_flow"
    parameter Boolean use_m_flow_set = false
      "= true to use input signal m_flow_set instead of m_flow_nominal"
    parameter Boolean use_p_set = false
      "= true to use input signal p_set instead of p_b_nominal"

    // exemplary characteristics
    final parameter SI.VolumeFlowRate V_flow_op = m_flow_nominal/rho_nominal
      "Operational volume flow rate according to nominal values";
    final parameter SI.Position head_op = (p_b_nominal-p_a_nominal)/(rho_nominal*g)
      "Operational pump head according to nominal values";

    Modelica.Blocks.Interfaces.RealInput m_flow_set(unit="kg/s") if use_m_flow_set
      "Prescribed mass flow rate"
    Modelica.Blocks.Interfaces.RealInput p_set(unit="Pa") if use_p_set
      "Prescribed outlet pressure"

  protected
    Modelica.Blocks.Interfaces.RealInput m_flow_set_internal(unit="kg/s")
      "Needed to connect to conditional connector";
    Modelica.Blocks.Interfaces.RealInput p_set_internal(unit="Pa")
      "Needed to connect to conditional connector";
  equation
    // Ideal control
    if control_m_flow then
      m_flow = m_flow_set_internal;
    else
      dp_pump = p_set_internal - port_a.p;
    end if;

    // Internal connector value when use_m_flow_set = false
    if not use_m_flow_set then
      m_flow_set_internal = m_flow_nominal;
    end if;
    if not use_p_set then
      p_set_internal = p_b_nominal;
    end if;
    connect(m_flow_set, m_flow_set_internal);
    connect(p_set, p_set_internal);
  end ControlledPump;

  model PrescribedPump "Centrifugal pump with ideally controlled speed"
    extends Modelica.Fluid.Machines.BaseClasses.PartialPump;
    parameter Boolean use_N_in = false
      "Get the rotational speed from the input connector";
    parameter Modelica.Units.NonSI.AngularVelocity_rpm
      N_const =                                                                     N_nominal
      "Constant rotational speed"
    Modelica.Blocks.Interfaces.RealInput N_in(unit="rev/min") if use_N_in
      "Prescribed rotational speed"

  protected
    Modelica.Blocks.Interfaces.RealInput N_in_internal(unit="rev/min")
      "Needed to connect to conditional connector";
  equation
    // Connect statement active only if use_p_in = true
    connect(N_in, N_in_internal);
    // Internal connector value when use_p_in = false
    if not use_N_in then
      N_in_internal = N_const;
    end if;
    // Set N with a lower limit to avoid singularities at zero speed
    N = max(N_in_internal,1e-3) "Rotational speed"; otherwise, a constant rotational speed equal to n_const (which can be different from N_nominal) is assumed.

",
        revisions="

31 Oct 2005
    by Francesco Casella:

       Model added to the Fluid library

"));
  end PrescribedPump;

  package BaseClasses
    "Base classes used in the Machines package (only of interest to build new component models)"
    extends Modelica.Icons.BasesPackage;

  partial model PartialPump "Base model for centrifugal pumps"
      import Modelica.Units.NonSI;
      import Modelica.Constants;

    extends Modelica.Fluid.Interfaces.PartialTwoPort(
      port_b_exposesState = energyDynamics<>Types.Dynamics.SteadyState or massDynamics<>Types.Dynamics.SteadyState,
      port_a(
        p(start=p_a_start),
        m_flow(start = m_flow_start,
               min = if allowFlowReversal and not checkValve then -Constants.inf else 0)),
      port_b(
        p(start=p_b_start),
        m_flow(start = -m_flow_start,
               max = if allowFlowReversal and not checkValve then +Constants.inf else 0)));

    // Initialization
    parameter Medium.AbsolutePressure p_a_start=system.p_start
        "Guess value for inlet pressure"
    parameter Medium.AbsolutePressure p_b_start=p_a_start
        "Guess value for outlet pressure"
    parameter Medium.MassFlowRate m_flow_start = system.m_flow_start
        "Guess value of m_flow = port_a.m_flow"
    parameter Types.CheckValveHomotopyType checkValveHomotopy = Types.CheckValveHomotopyType.NoHomotopy "= whether the valve is Closed, Open, or unknown at initialization"
    final parameter SI.VolumeFlowRate V_flow_single_init = m_flow_start/rho_nominal/nParallel
        "Used for simplified initialization model";
    final parameter SI.Position delta_head_init = flowCharacteristic(V_flow_single_init*1.1)-flowCharacteristic(V_flow_single_init)
        "Delta head for a 10% increase of flow at the initialization point";

    // Characteristic curves
    parameter Integer nParallel(min=1) = 1 "Number of pumps in parallel"
    replaceable function flowCharacteristic =
        PumpCharacteristics.baseFlow
        "Head vs. V_flow characteristic at nominal speed and density"
    parameter NonSI.AngularVelocity_rpm N_nominal
        "Nominal rotational speed for flow characteristic"
    parameter Medium.Density rho_nominal = Medium.density_pTX(Medium.p_default, Medium.T_default, Medium.X_default)
        "Nominal fluid density for characteristic"
    parameter Boolean use_powerCharacteristic = false
        "Use powerCharacteristic (vs. efficiencyCharacteristic)"
    replaceable function powerCharacteristic =
          PumpCharacteristics.quadraticPower (
         V_flow_nominal={0,0,0},W_nominal={0,0,0})
        "Power consumption vs. V_flow at nominal speed and density"
    replaceable function efficiencyCharacteristic =
      PumpCharacteristics.constantEfficiency(eta_nominal = 0.8) constrainedby
        PumpCharacteristics.baseEfficiency
        "Efficiency vs. V_flow at nominal speed and density"

    // Assumptions
    parameter Boolean checkValve=false "= true to prevent reverse flow"

    parameter SI.Volume V = 0 "Volume inside the pump"

    // Energy and mass balance
    extends Modelica.Fluid.Interfaces.PartialLumpedVolume(
        final fluidVolume = V,
        energyDynamics = Types.Dynamics.SteadyState,
        massDynamics = Types.Dynamics.SteadyState,
        final p_start = p_b_start);

    // Heat transfer through boundary, e.g., to add a housing
    parameter Boolean use_HeatTransfer = false
        "= true to use a HeatTransfer model, e.g., for a housing"
    replaceable model HeatTransfer =
        Modelica.Fluid.Vessels.BaseClasses.HeatTransfer.IdealHeatTransfer
      constrainedby
        Modelica.Fluid.Vessels.BaseClasses.HeatTransfer.PartialVesselHeatTransfer
        "Wall heat transfer"
    HeatTransfer heatTransfer(
      redeclare package Medium = Medium,
      final n=1,
      surfaceAreas={4*Modelica.Constants.pi*(3/4*V/Modelica.Constants.pi)^(2/3)},
      final states = {medium.state},
      final use_k = use_HeatTransfer)
    Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPort if use_HeatTransfer

    // Variables
    final parameter SI.Acceleration g=system.g "Constant gravity acceleration";
    Medium.Density rho = medium.d;
    SI.Pressure dp_pump = port_b.p - port_a.p "Pressure change";
    SI.Position head = dp_pump/(rho*g) "Pump head";
    SI.MassFlowRate m_flow = port_a.m_flow "Mass flow rate (total)";
    SI.MassFlowRate m_flow_single = m_flow/nParallel
        "Mass flow rate (single pump)";
    SI.VolumeFlowRate V_flow "Volume flow rate (total)";
    SI.VolumeFlowRate V_flow_single(start = m_flow_start/rho_nominal/nParallel)
        "Volume flow rate (single pump)";
    NonSI.AngularVelocity_rpm N(start = N_nominal) "Shaft rotational speed";
    SI.Power W_single "Power Consumption (single pump)";
    SI.Power W_total = W_single*nParallel "Power Consumption (total)";
    Real eta "Global Efficiency";
    final constant Medium.MassFlowRate unit_m_flow=1
    Real s(start = m_flow_start/unit_m_flow)
        "Curvilinear abscissa for the flow curve in parametric form (either mass flow rate or head)";

    // Diagnostics
    replaceable model Monitoring =
      Modelica.Fluid.Machines.BaseClasses.PumpMonitoring.PumpMonitoringBase
      constrainedby
        Modelica.Fluid.Machines.BaseClasses.PumpMonitoring.PumpMonitoringBase
        "Optional pump monitoring"
    Monitoring monitoring(
            redeclare package Medium = Medium,
            final state_in = Medium.setState_phX(port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow)),
            final state = medium.state) "Monitoring model"
    protected
    constant SI.Position unitHead = 1;
    constant SI.MassFlowRate unitMassFlowRate = 1;

  equation
    // Flow equations
     V_flow = homotopy(m_flow/rho,
                       m_flow/rho_nominal);
     V_flow_single = V_flow/nParallel;
    if not checkValve then
      // Regular flow characteristics without check valve
      // The simplified model uses an approximation of the tangent to the head curve in the initialization point
      head = homotopy((N/N_nominal)^2*flowCharacteristic(V_flow_single*N_nominal/N),
                       N/N_nominal*(flowCharacteristic(V_flow_single_init)+(V_flow_single-V_flow_single_init)*noEvent(if abs(V_flow_single_init)>0 then delta_head_init/(0.1*V_flow_single_init) else 0)));
      s = 0;
    else
      // Flow characteristics when check valve is open
      // The simplified model uses an approximation of the tangent to the head curve in the initialization point
      // or the zero-flow vertical axis in case the system is initialized with the check valve closed
      if checkValveHomotopy == Types.CheckValveHomotopyType.NoHomotopy then
        head = if s > 0 then (N/N_nominal)^2*flowCharacteristic(V_flow_single*N_nominal/N)
                               else (N/N_nominal)^2*flowCharacteristic(0) - s*unitHead;
        V_flow_single = if s > 0 then s*unitMassFlowRate/rho else 0;
      else
        head = homotopy(if s > 0 then (N/N_nominal)^2*flowCharacteristic(V_flow_single*N_nominal/N)
                               else (N/N_nominal)^2*flowCharacteristic(0) - s*unitHead,
                      if checkValveHomotopy == Types.CheckValveHomotopyType.Open then
                        N/N_nominal*(flowCharacteristic(V_flow_single_init)+(V_flow_single-V_flow_single_init)*noEvent(if abs(V_flow_single_init)>0 then delta_head_init/(0.1*V_flow_single_init) else 0))
                      else
                        N/N_nominal*flowCharacteristic(0) - s*unitHead);
        V_flow_single = homotopy(if s > 0 then s*unitMassFlowRate/rho else 0,
                               if checkValveHomotopy == Types.CheckValveHomotopyType.Open then s*unitMassFlowRate/rho_nominal else 0);
      end if;
    end if;
    // Power consumption
    if use_powerCharacteristic then
      W_single = homotopy((N/N_nominal)^3*(rho/rho_nominal)*powerCharacteristic(V_flow_single*N_nominal/N),
                          N/N_nominal*V_flow_single/V_flow_single_init*powerCharacteristic(V_flow_single_init));
      eta = dp_pump*V_flow_single/W_single;
    else
      eta = homotopy(efficiencyCharacteristic(V_flow_single*(N_nominal/N)),
                     efficiencyCharacteristic(V_flow_single_init));
      W_single = homotopy(dp_pump*V_flow_single/eta,
                          dp_pump*V_flow_single_init/eta);
    end if;

    // Energy balance
    Wb_flow = W_total;
    Qb_flow = heatTransfer.Q_flows[1];
    Hb_flow = port_a.m_flow*actualStream(port_a.h_outflow) +
              port_b.m_flow*actualStream(port_b.h_outflow);

    // Ports
    port_a.h_outflow = medium.h;
    port_b.h_outflow = medium.h;
    port_b.p = medium.p
        "Outlet pressure is equal to medium pressure, which includes Wb_flow";

    // Mass balance
    mb_flow = port_a.m_flow + port_b.m_flow;

    mbXi_flow = port_a.m_flow*actualStream(port_a.Xi_outflow) +
                port_b.m_flow*actualStream(port_b.Xi_outflow);
    port_a.Xi_outflow = medium.Xi;
    port_b.Xi_outflow = medium.Xi;

    mbC_flow = port_a.m_flow*actualStream(port_a.C_outflow) +
               port_b.m_flow*actualStream(port_b.C_outflow);
    port_a.C_outflow = C;
    port_b.C_outflow = C;

    connect(heatTransfer.heatPorts[1], heatPort)
this is recommended to avoid singularities in the computation of the outlet enthalpy in case of zero flow rate.
If zero flow rate conditions are always avoided, this dynamic effect can be neglected by leaving the default value V = 0, thus avoiding fast state variables in the model.

Dynamics options

Steady-state mass and energy balances are assumed per default, neglecting the holdup of fluid in the pump; this configuration works well if the flow rate is always positive.
Dynamic mass and energy balance can be used by setting the corresponding dynamic parameters. This is recommended to avoid singularities at zero or reversing mass flow rate. If the initial conditions imply non-zero mass flow rate, it is possible to use the SteadyStateInitial condition, otherwise it is recommended to use FixedInitial in order to avoid undetermined initial conditions.

Heat transfer

The Boolean parameter use_HeatTransfer can be set to true if heat exchanged with the environment
should be taken into account or to model a housing. This might be desirable if a pump with realistic
powerCharacteristic for zero flow operates while a valve prevents fluid flow.

Diagnostics of Cavitation

The replaceable Monitoring submodel can be configured to PumpMonitoringNPSH,
in order to compute the Net Positive Suction Head available and check for cavitation,
provided a two-phase medium model is used (see Advanced tab).

",
        revisions="

8 Jan 2013
    by Rüdiger Franke:

    moved NPSH diagnostics from PartialPump to replaceable sub-model PumpMonitoring.PumpMonitoringNPSH (see ticket #646)

Dec 2008
    by Rüdiger Franke:

Replaced simplified mass and energy balances with rigorous formulation (base class PartialLumpedVolume)

Introduced optional HeatTransfer model defining Qb_flow

Enabled events when the checkValve is operating to support the opening of a discrete valve before port_a

31 Oct 2005
    by Francesco Casella:

       Model added to the Fluid library

"));
  end PartialPump;

  package PumpCharacteristics "Functions for pump characteristics"
    extends Modelica.Icons.Package;
    import Modelica.Units.NonSI;

    partial function baseFlow "Base class for pump flow characteristics"
      extends Modelica.Icons.Function;
      input SI.VolumeFlowRate V_flow "Volumetric flow rate";
      output SI.Position head "Pump head";
    end baseFlow;

    partial function basePower
        "Base class for pump power consumption characteristics"
      extends Modelica.Icons.Function;
      input SI.VolumeFlowRate V_flow "Volumetric flow rate";
      output SI.Power consumption "Power consumption";
    end basePower;

    partial function baseEfficiency "Base class for efficiency characteristics"
      extends Modelica.Icons.Function;
      input SI.VolumeFlowRate V_flow "Volumetric flow rate";
      output Real eta "Efficiency";
    end baseEfficiency;

    function linearFlow "Linear flow characteristic"
      extends baseFlow;
      input SI.VolumeFlowRate V_flow_nominal[2]
          "Volume flow rate for two operating points (single pump)"
      input SI.Position head_nominal[2] "Pump head for two operating points"
      /* Linear system to determine the coefficients:
  head_nominal[1] = c[1] + V_flow_nominal[1]*c[2];
  head_nominal[2] = c[1] + V_flow_nominal[2]*c[2];
  */
      protected
      Real c[2] = Modelica.Math.Matrices.solve([ones(2),V_flow_nominal],head_nominal)
          "Coefficients of linear head curve";
    algorithm
      assert(c[2] <= -Modelica.Constants.small,
             "Wrong pump curve -- head_nominal must be monotonically decreasing with increasing V_flow_nominal",
             level=AssertionLevel.warning);
      // Flow equation: head = q*c[1] + c[2];
      head := c[1] + V_flow*c[2];
    end linearFlow;

    function quadraticFlow
        "Quadratic flow characteristic, including linear extrapolation"
      extends baseFlow;
      input SI.VolumeFlowRate V_flow_nominal[3]
          "Volume flow rate for three operating points (single pump)"
      input SI.Position head_nominal[3] "Pump head for three operating points"
      protected
      Real V_flow_nominal2[3] = {V_flow_nominal[1]^2,V_flow_nominal[2]^2, V_flow_nominal[3]^2}
          "Squared nominal flow rates";
      /* Linear system to determine the coefficients:
  head_nominal[1] = c[1] + V_flow_nominal[1]*c[2] + V_flow_nominal[1]^2*c[3];
  head_nominal[2] = c[1] + V_flow_nominal[2]*c[2] + V_flow_nominal[2]^2*c[3];
  head_nominal[3] = c[1] + V_flow_nominal[3]*c[2] + V_flow_nominal[3]^2*c[3];
  */
      Real c[3] = Modelica.Math.Matrices.solve([ones(3), V_flow_nominal, V_flow_nominal2],head_nominal)
          "Coefficients of quadratic head curve";
      SI.VolumeFlowRate V_flow_min = min(V_flow_nominal);
      SI.VolumeFlowRate V_flow_max = max(V_flow_nominal);
    algorithm
      assert(max(c[2].+2*c[3]*V_flow_nominal) <= -Modelica.Constants.small,
             "Wrong pump curve -- head_nominal must be monotonically decreasing with increasing V_flow_nominal",
             level=AssertionLevel.warning);
      if V_flow < V_flow_min then
        head := max(head_nominal) + (V_flow-V_flow_min)*(c[2]+2*c[3]*V_flow_min);
      elseif V_flow > V_flow_max then
        head := min(head_nominal) + (V_flow-V_flow_max)*(c[2]+2*c[3]*V_flow_max);
      else
        // Flow equation: head  = c[1] + V_flow*c[2] + V_flow^2*c[3];
        head := c[1] + V_flow*(c[2] + V_flow*c[3]);
      end if;
    end quadraticFlow;

    function polynomialFlow
        "Polynomial flow characteristic, including linear extrapolation"
      extends baseFlow;
      input SI.VolumeFlowRate V_flow_nominal[:]
          "Volume flow rate for N operating points (single pump)"
      input SI.Position head_nominal[:] "Pump head for N operating points"
      protected
      Integer N = size(V_flow_nominal,1) "Number of nominal operating points";
      Real V_flow_nominal_pow[N,N] = {{if j > 1 then V_flow_nominal[i]^(j-1) else 1 for j in 1:N} for i in 1:N}
          "Rows: different operating points; columns: increasing powers";
      /* Linear system to determine the coefficients (example N=3):
  head_nominal[1] = c[1] + V_flow_nominal[1]*c[2] + V_flow_nominal[1]^2*c[3];
  head_nominal[2] = c[1] + V_flow_nominal[2]*c[2] + V_flow_nominal[2]^2*c[3];
  head_nominal[3] = c[1] + V_flow_nominal[3]*c[2] + V_flow_nominal[3]^2*c[3];
  */
      Real c[size(V_flow_nominal,1)] = Modelica.Math.Matrices.solve(V_flow_nominal_pow,head_nominal)
          "Coefficients of polynomial head curve";
      SI.VolumeFlowRate V_flow_min = min(V_flow_nominal);
      SI.VolumeFlowRate V_flow_max = max(V_flow_nominal);
      Real max_dhdV = c[2] + max(sum((i-1)*V_flow_nominal.^(i-2)*c[i] for i in 3:N));
      Real poly;
    algorithm
      assert(max_dhdV <= -Modelica.Constants.small,
             "Wrong pump curve -- head_nominal must be monotonically decreasing with increasing V_flow_nominal",
             level=AssertionLevel.warning);
      if V_flow < V_flow_min then
        //head := max(head_nominal) + (V_flow-V_flow_min)*(c[2]+sum((i-1)*V_flow_min^(i-2)*c[i] for i in 3:N));
        poly := c[N]*(N-1);
        for i in 1:N-2 loop
          poly := V_flow_min*poly + c[N-i]*(N-i-1);
        end for;
        head := max(head_nominal) + (V_flow-V_flow_min)*poly;
      elseif V_flow > V_flow_max then
        //head := min(head_nominal) + (V_flow-V_flow_max)*(c[2]+sum((i-1)*V_flow_max^(i-2)*c[i] for i in 3:N));
        poly := c[N]*(N-1);
        for i in 1:N-2 loop
          poly := V_flow_max*poly + c[N-i]*(N-i-1);
        end for;
        head := min(head_nominal) + (V_flow-V_flow_max)*poly;
      else
        // Flow equation (example N=3): head  = c[1] + V_flow*c[2] + V_flow^2*c[3];
        // Note: the implementation is numerically efficient only for low values of Na
        //head := sum(V_flow^(i-1)*c[i] for i in 1:N);
        poly := c[N];
        for i in 1:N-1 loop
          poly := V_flow*poly + c[N-i];
         end for;
        head := poly;
      end if;
    end polynomialFlow;

    function constantEfficiency "Constant efficiency characteristic"
       extends baseEfficiency;
       input Real eta_nominal "Nominal efficiency"
    algorithm
      eta := eta_nominal;
    end constantEfficiency;

    function linearPower "Linear power consumption characteristic"
      extends basePower;
      input SI.VolumeFlowRate V_flow_nominal[2]
          "Volume flow rate for two operating points (single pump)"
      input SI.Power W_nominal[2] "Power consumption for two operating points"
      /* Linear system to determine the coefficients:
  W_nominal[1] = c[1] + V_flow_nominal[1]*c[2];
  W_nominal[2] = c[1] + V_flow_nominal[2]*c[2];
  */
      protected
      Real c[2] = Modelica.Math.Matrices.solve([ones(2),V_flow_nominal],W_nominal)
          "Coefficients of linear power consumption curve";
    algorithm
      consumption := c[1] + V_flow*c[2];
    end linearPower;

    function quadraticPower "Quadratic power consumption characteristic"
      extends basePower;
      input SI.VolumeFlowRate V_flow_nominal[3]
          "Volume flow rate for three operating points (single pump)"
      input SI.Power W_nominal[3]
          "Power consumption for three operating points"
      protected
      Real V_flow_nominal2[3] = {V_flow_nominal[1]^2,V_flow_nominal[2]^2, V_flow_nominal[3]^2}
          "Squared nominal flow rates";
      /* Linear system to determine the coefficients:
  W_nominal[1] = c[1] + V_flow_nominal[1]*c[2] + V_flow_nominal[1]^2*c[3];
  W_nominal[2] = c[1] + V_flow_nominal[2]*c[2] + V_flow_nominal[2]^2*c[3];
  W_nominal[3] = c[1] + V_flow_nominal[3]*c[2] + V_flow_nominal[3]^2*c[3];
  */
      Real c[3] = Modelica.Math.Matrices.solve([ones(3),V_flow_nominal,V_flow_nominal2],W_nominal)
          "Coefficients of quadratic power consumption curve";
    algorithm
      consumption := c[1] + V_flow*c[2] + V_flow^2*c[3];
    end quadraticPower;

  end PumpCharacteristics;

    package PumpMonitoring "Monitoring of pump operation"
      extends Modelica.Icons.Package;
      model PumpMonitoringBase "Interface for pump monitoring"
        outer Modelica.Fluid.System system "System wide properties";
        //
        // Internal interface
        // (not exposed to GUI; needs to be hard coded when using this model
        //
        replaceable package Medium =
          Modelica.Media.Interfaces.PartialMedium "Medium in the component"

        // Inputs
        input Medium.ThermodynamicState state_in
          "Thermodynamic state of inflow";
        input Medium.ThermodynamicState state "Thermodynamic state in the pump";

      end PumpMonitoringBase;

      model PumpMonitoringNPSH "Monitor Net Positive Suction Head (NPSH)"
        extends PumpMonitoringBase(redeclare replaceable package Medium =
          Modelica.Media.Interfaces.PartialTwoPhaseMedium);
        Medium.Density rho_in = Medium.density(state_in)
          "Liquid density at the inlet port_a";
        SI.Position NPSHa=NPSPa/(rho_in*system.g)
          "Net Positive Suction Head available";
        SI.Pressure NPSPa=assertPositiveDifference(Medium.pressure(state_in), Medium.saturationPressure(Medium.temperature(state_in)),
                                                   "Cavitation occurs at the pump inlet")
          "Net Positive Suction Pressure available";
        SI.Pressure NPDPa=assertPositiveDifference(Medium.pressure(state), Medium.saturationPressure(Medium.temperature(state)),
                                                   "Cavitation occurs in the pump")
          "Net Positive Discharge Pressure available";
      end PumpMonitoringNPSH;

      function assertPositiveDifference
        extends Modelica.Icons.Function;
        input SI.Pressure p;
        input SI.Pressure p_sat;
        input String message;
        output SI.Pressure dp;
      algorithm
        dp := p - p_sat;
        assert(p >= p_sat, message);
      end assertPositiveDifference;
    end PumpMonitoring;
  end BaseClasses;
end Machines;


within Modelica;
package Fluid "Library of 1-dim. thermo-fluid flow models using the Modelica.Media media description"
  extends Modelica.Icons.Package;
  import Modelica.Units.SI;
  import Cv = Modelica.Units.Conversions;

package UsersGuide "User's Guide"
  extends Modelica.Icons.Information;

  class Overview "Overview"
    extends Modelica.Icons.Information;
  end Overview;

  class GettingStarted "Getting started"
    extends Modelica.Icons.Information;
  end GettingStarted;

  package ComponentDefinition "Component definition"
    extends Modelica.Icons.Information;

  class FluidConnectors "Fluid connectors"
    extends Modelica.Icons.Information;

Generally, with the current library design, it is necessary to explicitly select the medium
model for each component in a circuit. This model is then propagated to the ports,
and a Modelica translator will check that the quantity and unit attributes
of connected interfaces are identical. Therefore, an error occurs,
if connected FluidPorts do not have a medium with the same medium name.

Automatic propagation of fluid models through the ports is not directly possible with the
Modelica 3.6 specification, but might be supported by the Modelica tool. For example,
in Dymola the option Advanced.MediaPropagation=1 can be set
to apply automatic propagation of media models in a circuit.

The thermodynamic pressure is an effort variable, which means that the connection
of two or more ports states that the port pressures are the same.

The mass flow rate is a flow variable, which means that the connection of two or
more ports states that the sum of all flow rates is zero.

The last variable is a stream variable, i.e., a specific quantity carried by the
flow variable. The quantity on the connector always
corresponds to the value close to the connection point, assuming that the fluid is
flowing out of the connector, regardless of the actual direction of the flow. This helps
avoiding singularities when the mass flow goes through zero. The stream properties for the
other flow direction can be inquired with the built-in operator inStream(..), while the
value of the stream variable corresponding to the actual flow direction can be inquired
through the built-in operator actualStream(…).

The actual equations corresponding to these operators are introduced and solved automatically
by the tool. In principle, they correspond to the balance equation
sum(flow_variable) = 0 and sum(flow_variable*stream_variable_at_connection) = 0 applied to the set of connected ports. In this case the first equation is the mass balance sum(m_flow) = 0, and the second is the energy balance at the connection point sum(m_flow*h_connection) = 0.

In the simpler case of a one-to-one connections between port_a and port_b,
inStream(port_a.h_outflow) just returns port_b.h_outflow. For multiple-way connections,
mixing equations are generated, and special care is taken in order to avoid discontinuities
around zero flow rates. For more details, see this
presentation
which illustrates the stream concept rationale and the underlying technicalities.

A connector should have only the minimal number of variables to
describe the interface, otherwise there will be connection
restrictions in certain cases. Therefore, in the connector
no redundant variables are present, e.g., the temperature T
is not present because it can be computed from the connector
variables pressure p and specific enthalpy h.

Here are two simple examples to illustrate modeling with stream connectors. The first
one is a rigid adiabatic volume mixing two flows, where the kinetic and gravitational
terms in the energy balance are neglected for simplicity.

model MixingVolume \"Volume that mixes two flows\"
  import Modelica.Units.SI;
  replaceable package Medium = Modelica.Media.Interfaces.PartialPureSubstance;
  FluidPort port_a(redeclare package Medium = Medium) \"Fluid connector a\";
  FluidPort port_b(redeclare package Medium = Medium) \"Fluid connector b\";
  parameter SI.Volume V \"Volume of device\";
  SI.Mass             m \"Mass in device\";
  SI.Energy           U \"Inner energy in device\";
  Medium.BaseProperties medium(preferredMediumStates=true) \"Medium in the device\";
equation
  // Definition of port variables
  port_a.p         = medium.p;
  port_b.p         = medium.p;
  port_a.h_outflow = medium.h;  // The stream variable always corresponds to the
  port_b.h_outflow = medium.h;  // properties of the fluid holdup (outgoing flow)

  // Total quantities
  m = V*medium.d;
  U = m*medium.u;
  // Mass and energy balance (actualStream(..) is a built-in operator for streams to
  // compute the right h, depending on the flow direction)
  der(m) = port_a.m_flow + port_b.m_flow;
  der(U) = port_a.m_flow*actualStream(port_a.h_outflow) +
           port_b.m_flow*actualStream(port_b.h_outflow);
end MixingVolume;

The second example is the model of a component describing a lumped pressure loss
between two ports, with no energy storage and no heat transfer. An isenthalpic
transformation is assumed (changes in kinetic and potential energy between
inlet and outlet are neglected).

model PressureLoss \"Pressure loss component\"
  replaceable package Medium = Modelica.Media.Interfaces.PartialPureSubstance;
  FluidPort port_a(redeclare package Medium = Medium) \"Fluid connector a\";
  FluidPort port_b(redeclare package Medium = Medium) \"Fluid connector b\";
  Medium.ThermodynamicState port_a_state_inflow \"State at port_a if inflowing\";
  Medium.ThermodynamicState port_b_state_inflow \"State at port_b if inflowing\";
  Medium.Density d_a \"Density at port a if inflowing\";
  Medium.Density d_b \"Density at port b if inflowing\";
  replaceable function f = SomeSpecificMassFlowFunction
    \"Function to compute the mass flow rate\";
equation
  // Medium states for inflowing fluid
  port_a_state_inflow = Medium.setState_phX(port_a.p, inStream(port_a.h_outflow));
  port_b_state_inflow = Medium.setState_phX(port_b.p, inStream(port_b.h_outflow));
  // Mass balance
  0 = port_a.m_flow + port_b.m_flow;
  // Instantaneous propagation of enthalpy flow between the ports with
  // isenthalpic state transformation (no storage and no loss of energy)
  port_a.h_outflow = inStream(port_b.h_outflow);
  port_b.h_outflow = inStream(port_a.h_outflow);
  // (Regularized) Momentum balance
  port_a.m_flow = f(port_a.p - port_b.p, d_a, d_b);
end PressureLoss;

If many such components are connected in series between two models with storage, the
specific enthalpies are propagated in both directions and available to all pressure
loss components, without problems when the mass flow goes through zero. The function f
then uses either d_a or d_b depending on the sign of port_a.p-port_b.p, with a
suitable regularization around zero to avoid discontinuities.

Please note that these models are highly idealized in order to explain the stream connector
concept. Device models in the library are much more complete, handling issues such as
initialization, steady vs. dynamic modelling, heat transfer from the outside, etc.

Multiple-substance media

Modelica.Fluid can handle models where the fluid contains multiple substances, so that its
composition can be characterized by mass fraction vectors.

connector FluidPort
  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    \"Medium model of the fluid\";
  flow Medium.MassFlowRate m_flow
    \"Mass flow rate from the connection point into the component\";
  Medium.AbsolutePressure p
    \"Thermodynamic pressure in the connection point\";
  stream Medium.SpecificEnthalpy h_outflow
    \"Specific thermodynamic enthalpy close to the connection point if m_flow < 0\";
  stream Medium.MassFraction Xi_outflow[Medium.nXi]
    \"Independent mixture mass fractions m_i/m close to the connection point if m_flow < 0\";
  stream Medium.ExtraProperty C_outflow[Medium.nC]
    \"Properties c_i/m close to the connection point if m_flow < 0\";
end FluidPort;

The mass fraction vectors Xi and C are also stream quantities, as they are carried by the mass
flow rate. The corresponding connection equations are sum(m_flow*Xi) and sum(m_flow*C), which correspond to mass balances for the single substances. The vector Xi contains the mass fractions
of the main components of the fluid, and is used together with p and h to determine the
thermodynamic state of the fluid. The vector C contains the mass fraction of the trace components,
which are accounted for in mass balances, but is ignored when computing the fluid properties. This
allows to easily declare and use medium models with trace components starting from existing medium
models (e.g., adding CO2 traces to Moist Air for air conditioning models).

Approximations in balance equations at connection point

Summing up, when two or more ports of the type FluidPort are connected, the following
equations are generated by the tool:

sum(port_j.m_flow) = 0;               // Total Mass balance
port_j = port_k;                      // Momentum balance
sum(port_j.m_flow*h_connection) = 0;  // Energy balance
sum(port_j.m_flow*Xi_connection) = 0; // Single component mass balances
sum(port_j.m_flow*C_connection) = 0;  // Trace components mass balances

It is very important to bear in mind that

 the mass balances are always exact;

 the momentum and energy balance are only exact when two port with the same
diameter are connected, because there is no friction and no change in fluid velocity.

In all other cases, i.e., different port diameters and/or multiple port connections:

 The momentum balance does not consider friction effects and changes of pressure due to changes
in velocity.

 There might thus be errors in the momentum balance of the order of magnitude
of the dynamic pressure ρv2/2.

 The energy balance does not consider the kinetic terms (gravity terms cancel out due
to the infinitesimal size of the connection volume). There might thus be errors in the momentum balance of the order of magnitude of the kinetic energy v^2/2.

In many applications, where fluid speeds are low and thermal phenomena are mainly of interest,
these approximations are commonly made and lead to acceptable results.
In all other cases, explicit fitting and junction models should be used, that model explicitly
all the kinetic phenomena with the appropriate level of detail.

"));
  end FluidConnectors;

  class BalanceEquations "Balance equations"
    extends Modelica.Icons.Information;
  end BalanceEquations;

  class UpstreamDiscretization "Upstream discretization"
    extends Modelica.Icons.Information;

  Interfaces.FluidPort_a port_a(redeclare package Medium = Medium);
  Interfaces.FluidPort_b port_b(redeclare package Medium = Medium);

  Medium.ThermodynamicState port_a_state_inflow
    \"Medium state close to port_a for inflowing mass flow\";
  Medium.ThermodynamicState port_b_state_inflow
    \"Medium state close to port_b for inflowing mass flow\";

equation
  // Isenthalpic state transformation (no storage and no loss of energy)
  port_a.h_outflow  = inStream(port_b.h_outflow);
  port_b.h_outflow  = inStream(port_a.h_outflow);

  port_a.Xi_outflow = inStream(port_b.Xi_outflow);
  port_b.Xi_outflow = inStream(port_a.Xi_outflow);

  // Mass balance
  port_a.m_flow + port_b.m_flow = 0;

  // Medium states for inflowing medium
  port_a_state_inflow = Medium.setState_phX(port_a.p, port_b.h_outflow, port_b.Xi_outflow);
  port_b_state_inflow = Medium.setState_phX(port_b.p, port_a.h_outflow, port_a.Xi_outflow);

  // Densities close to the parts when mass flows in to the respective port
  port_a_rho_inflow = Medium.density(port_a_state_inflow);
  port_b_rho_inflow = Medium.density(port_b_state_inflow);

  // Pressure drop correlation (k_ab, k_ba are the loss factors for the two flow
  // directions; e.g., for a circular device: k = 8*zeta/(pi*diameter)^2)^2)
  m_flow = Utilities.regRoot2(port_a.p - port_b.p, dp_small,
                              port_a_rho_inflow/k1, port_b_rho_inflow/k2);

The medium states for inflowing media can be used to compute density and dynamic
viscosity which in turn can be use to formulate the pressure drop equation.
The standard pressure drop equation

dp = port_a - port_b;
m_flow = sqrt(2/(zeta*diameter))*if dp >= 0 then  sqrt(dp)
                                            else -sqrt(-dp)

cannot be used, since the function has an infinite derivative at dp=0.
Instead the region around zero mass flow rate must be regularized using
one of the regularization functions of Modelica.Fluid.Utilities.
This requires to have density and/or other medium properties for both
flow directions at the same time. These media properties can be computed
from the medium states of the inflowing fluid at the two ports.

If the above component is connected between two volumes, i.e.,
the independent medium variables in port_a and port_b are
usually states, then port_a.h and port_b.h are either states
(i.e., known quantities in the model) or are computed from
states. In either case they are \"known\". In such a situation,
all equations can be directly evaluated without any problems.
Zero or reversed mass flow rate does not pose any problems because
the medium properties are always computed for both flow directions
and are then used in the regularization function.

If 3 or more components are connected together, it can be shown
that a system of non-linear algebraic equations appear.
The equations are written by purpose in such a form, that
a tool can select mass flow rates and pressures as iteration
variables of this system. The advantage is that these iteration
variables are continuous and even often differentiable. The
alternative to use the medium states as iteration variables
is not good, because T,h,d are discontinuous for reversing flow
direction.

"));
  end UpstreamDiscretization;

  class RegularizingCharacteristics "Regularizing characteristics"
    extends Modelica.Icons.Information;
  end RegularizingCharacteristics;

  class WallFriction "Wall friction"
    extends Modelica.Icons.Information;

with
   Re     = |v|*D*ρ/μ
          = |m_flow|*4/(π*D*μ)
   m_flow = A*v*ρ
   A      = π*(D/2)^2
   λ2     = λ*Re^2
   k2     = L*μ^2/(2*D^3*ρ)

where

 L is the length of the pipe.

 D is the diameter of the pipe. If the pipe has not a
     circular cross section, D = 4*A/P, where A is the cross section
     area and P is the wetted perimeter.

 λ = λ(Re,Δ) is the \"usual\" wall friction coefficient.

 λ2 = λ*Re^2 is the used friction coefficient to get a numerically
     well-posed formulation.

 Re = |v|*D*ρ/μ is the Reynolds number.

 Δ = δ/D is the relative roughness where
     \"δ\" is
     the absolute \"roughness\", i.e., the averaged height of asperities in the pipe
     (δ may change over time due to growth of surface asperities during
      service, see [Idelchik 1994, p. 85, Tables 2-1, 2-2]).

 ρ is the upstream density.

 μ is the upstream dynamic viscosity.

 v is the mean velocity.

The first form with λ is used and presented in textbooks,
see \"blue\" curve in the next figure:

This form is not suited for a simulation program since
λ = 64/Re if Re < 2000, i.e., a division by zero occurs for
zero mass flow rate because Re = 0 in this case.
More useful for a simulation model is the friction coefficient
λ2 = λ*Re^2, because λ2 = 64*Re if Re < 2000 and
therefore no problems for zero mass flow rate occur.
The characteristic of λ2 is shown in the next figure and is
used in Modelica.Fluid:

The pressure loss characteristic is divided into three regions:

 Region 1:
     For Re ≤ 2000, the flow is laminar and the exact solution of the
     3-dim. Navier-Stokes equations (momentum and mass balance) is used under the
     assumptions of steady flow, constant pressure gradient and constant
     density and viscosity (= Hagen-Poiseuille flow) leading to λ2 = 64*Re.
     Therefore:

dp = 128*μ*L/(π*D^4*ρ)*m_flow

 Region 3:
     For Re ≥ 4000, the flow is turbulent.
     Depending on the calculation direction (see \"inverse formulation\"
     below) either of two explicit equations are used. If the pressure drop dp
     is assumed to be known, λ2 = |dp|/k2. The
     Colebrook-White equation
     [Colebrook 1939; Idelchik 1994, p. 83, eq. (2-9)]:

1/sqrt(λ) = -2*lg( 2.51/(Re*sqrt(λ)) + 0.27*Δ)

     gives an implicit relationship between Re and λ.
     Inserting λ2 = λ*Re^2 allows to solve this equation analytically
     for Re:

Re = -2*sqrt(λ2)*lg(2.51/sqrt(λ2) + 0.27*Δ)

     Finally, the mass flow rate m_flow is computed from Re via
     m_flow = Re*π*D*μ/4*sign(dp).
     These are the red curves in the diagrams above.

     If the mass flow rate is assumed known (and therefore implicitly
     also the Reynolds number), then λ2 is computed by an
     approximation of the inverse of the Colebrook-White equation
     [Swamee and Jain 1976;
     Miller 1990, p. 191, eq.(8.4)] adapted to λ2:

λ2 = 0.25*(Re/lg(Δ/3.7 + 5.74/Re^0.9))^2

     The pressure drop is then computed as dp = k2*λ2*sign(m_flow).
     These are the blue curves in the diagrams above.

 Region 2:
     For 2000 ≤ Re ≤ 4000 there is a transition region between laminar
     and turbulent flow. The value of λ2 depends on more factors as just
     the Reynolds number and the relative roughness, therefore only crude
     approximations are possible in this area.

     The deviation from the laminar region depends on the
     relative roughness. A laminar flow at Re=2000 is only reached for smooth pipes.
     The deviation Reynolds number Re1 is computed according to
     [Samoilenko 1968; Idelchik 1994, p. 81, sect. 2.1.21] as:

Re1 = 745*e^(if Δ ≤ 0.0065 then 1 else 0.0065/Δ)

     These are the blue curves in the diagrams above.

     Between Re1=Re1(δ/D) and Re2=4000,
     λ2 is approximated by a cubic
     polynomial in the \"lg(λ2) - lg(Re)\" chart (see figures above) such that the
     first derivative is continuous at these two points. In order to avoid
     the solution of non-linear equations, two different cubic polynomials are used
     for the direct and the inverse formulation. This leads to some discrepancies
     in λ2 (= differences between the red and the blue curves).
     This is acceptable, because the transition region is anyway not
     precisely known since the actual friction coefficient depends on
     additional factors and since the operating points are usually
     not in this region.

The absolute roughness δ has usually to
be estimated. In [Idelchik 1994, pp. 105-109,
Table 2-5; Miller 1990, p. 190, Table 8-1] many examples are given.
As a short summary:

Smooth pipes
      Drawn brass, copper, aluminium, glass, etc.
      δ = 0.0025 mm

Steel pipes
      New smooth pipes
      δ = 0.025 mm

Mortar lined, average finish
      δ = 0.1 mm

Heavy rust
      δ = 1 mm

Concrete pipes
      Steel forms, first class workmanship
      δ = 0.025 mm

Steel forms, average workmanship
      δ = 0.1 mm

Block linings
      δ = 1 mm

The equations above are valid for incompressible flow.
They can also be applied for compressible flow up to about Ma = 0.6
(Ma is the Mach number) with a maximum error in λ of about 3 %.
The effect of gas compressibility in a wide region can be taken into
account by the following formula derived by Voronin
[Voronin 1959; Idelchik 1994, p. 97, sect. 2.1.81]:

λ_comp = λ*(1 + (κ-1)/2 * Ma^2)^(-0.47)

where κ is the isentropic coefficient
(for ideal gases, κ is the ratio of specific heat capacities cp/cv).
An appreciable decrease in the coefficient \"λ_comp\" is observed
only in a narrow transonic region and also at supersonic flow velocities
by about 15% [Idelchik 1994, p. 97, sect. 2.1.81].
This effect is not yet included in Modelica.Fluid.
Another restriction is that the pressure drop model is valid
only for steady state or slowly changing mass flow rate.
For large fluid acceleration, the pressure drop depends additionally
on the frequency of the changing mass flow rate.

Inverse formulation

In the \"Advanced menu\" it is possible via parameter
\"from_dp\" to define in which form the
pressure drop equation is actually evaluated (default is from_dp = true):

from_dp = true:   m_flow = f1(dp)
        = false:  dp     = f2(m_flow)

\"from_dp\" can be useful to avoid nonlinear systems of equations
in cases where the inverse pressure loss function is needed.

Summary

A detailed pressure drop model for pipe wall friction is
provided in the form m_flow = f1(dp, Δ) or
dp = f2(m_flow, Δ).
These functions are continuous and differentiable,
are provided in an explicit form without solving non-linear equations,
and do behave well also at small mass flow rates. This pressure drop
model can be used stand-alone in a static momentum balance and in
a dynamic momentum balance as the friction pressure drop term.
It is valid for incompressible and compressible flow up to a Mach number of 0.6.

References

Colebrook F. (1939):

Turbulent flow in pipes with particular reference to the transition
         region between the smooth and rough pipe laws.
         J. Inst. Civ. Eng. no. 4, 14-25.

Idelchik I.E. (1994):

Handbook of Hydraulic Resistance. 3rd edition, Begell House, ISBN 0-8493-9908-4

Miller D. S. (1990):

Internal flow systems.
    2nd edition. Cranfield:BHRA(Information Services).

Samoilenko L.A. (1968):

Investigation of the Hydraulic Resistance of Pipelines in the
        Zone of Transition from Laminar into Turbulent Motion.
        Thesis (Cand. of Technical Science), Leningrad.

Swamee P.K. and Jain A.K. (1976):

Explicit equations for pipe-flow problems.
         Proc. ASCE, J.Hydraul. Div., 102 (HY5), pp. 657-664.

Voronin F.S. (1959):

Effect of contraction on the friction coefficient in a
           turbulent gas flow.
           Inzh. Fiz. Zh., vol. 2, no. 11, pp. 81-85.

"));
  end WallFriction;

  class ValveCharacteristics "Valve characteristics"
    extends Modelica.Icons.Information;
  end ValveCharacteristics;
  end ComponentDefinition;

  package BuildingSystemModels "Building system models"
    extends Modelica.Icons.Information;
    class SystemComponent "System component"
      extends Modelica.Icons.Information;
    end SystemComponent;

    class MediumDefinition "Definition of the medium models"
      extends Modelica.Icons.Information;
    end MediumDefinition;

    class CustomizingModel "Customizing a system model"
      extends Modelica.Icons.Information;
    end CustomizingModel;
  end BuildingSystemModels;

  class ReleaseNotes "Release notes"
    extends Modelica.Icons.ReleaseNotes;
  end ReleaseNotes;

class Contact "Contact"
  extends Modelica.Icons.Contact;
end Contact;
end UsersGuide;
end Fluid;