This test checks if mass and energy is conserved when mixing fluid streams using dynamic balances. This model verifies if mass and energy are conserved,
using a dynamic balance.
Two air streams with different mass flow rate are humidified
by a mixing volume with two different vapor mass flow rates.
These flows are then mixed.
Boundary integrals are used to verify if air mass,
vapour mass and internal energy are conserved.

within Buildings.Fluid.MixingVolumes.Validation;
model MoistureMixingConservationDynamicBalance
  "This test checks if mass and energy is conserved when mixing fluid streams using dynamic balances"
  extends
    Buildings.Fluid.MixingVolumes.Validation.BaseClasses.MoistureMixingConservation(
    mWatFloSol(k={vol.X_start[1],vol1.X_start[1],vol2.X_start[1]}*m_start),
    vol(
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial),
    vol1(
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial),
    vol2(
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial),
    mFloSol(k=sum(m_start)),
    hSol(k=sum(U_start)),
    mWatFlo3(k=0));

  parameter Modelica.Units.SI.Mass[3] m_start(each fixed=false)
    "Initial mass of the mixing volumes";
  parameter Modelica.Units.SI.InternalEnergy[3] U_start(each fixed=false)
    "Initial energy of the mixing volumes";
  Modelica.Blocks.Continuous.Integrator intMasFloVapIn(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of added vapour"
  Modelica.Blocks.Math.Add3 add "Adder for injected water"
  Modelica.Blocks.Sources.RealExpression masVapVol(y=vol.mXi[1] + vol1.mXi[1] +
        vol2.mXi[1]) "Vapour mass stored in mixing volumes"
  Modelica.Blocks.Math.Add3 add3Vap(k3=-1)
    "Sum of vapour mass should be conserved"
  Modelica.Blocks.Sources.RealExpression masVol(y=vol1.m + vol.m + vol2.m)
    "Mass stored in mixing volumes"
  Modelica.Blocks.Continuous.Integrator intMasFloOut(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of leaving mass"
  Modelica.Blocks.Math.Add3 add3Mass(k3=-1) "Adding 3 mass streams"
  Modelica.Blocks.Continuous.Integrator intMasFloIn(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of added mass"
  Modelica.Blocks.Sources.Constant masFloIn(k=sou1.m_flow + sou2.m_flow)
    "Added air mass flow rate"
  Modelica.Blocks.Continuous.Integrator intEntOut(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of leaving enthalpy"
  Modelica.Blocks.Continuous.Integrator intEntIn(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of added enthalpy"
  Modelica.Blocks.Sources.RealExpression entVol(y=vol1.U + vol.U + vol2.U)
    "Energy stored in mixing volumes"
  Modelica.Blocks.Math.Add3 add3Ent(k3=-1) "Adding 3 enthalpy streams"
  Modelica.Blocks.Sources.Constant entIn(
    k=sou1.m_flow*sou1.h + sou2.m_flow*sou2.h)
    "Added enthalpy"
  Modelica.Blocks.Math.Product pro "Water vapor flow rate"
  Modelica.Blocks.Continuous.Integrator intMasVapOut(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integral of leaving vapor mass"
initial equation
  m_start = {vol.m, vol1.m,vol2.m};
  U_start = {vol.U, vol1.U,vol2.U};

equation
  connect(add.u1, mWatFlo2.y)

  connect(add.u2, mWatFlo1.y)

  connect(add3Vap.y,cheMasFra.u2)
  connect(masVapVol.y,add3Vap. u2)
  connect(intMasFloVapIn.u, add.y)
  connect(intMasFloVapIn.y,add3Vap. u3)
  connect(senMasFlo.m_flow, intMasFloOut.u)
  connect(add3Mass.y,cheMasFlo.u2)
  connect(masVol.y, add3Mass.u2)
  connect(intMasFloIn.y, add3Mass.u3)
  connect(intMasFloIn.u, masFloIn.y)
  connect(add3Ent.y,cheSpeEnt.u2)
  connect(entVol.y, add3Ent.u2)
  connect(intEntIn.y, add3Ent.u3)
  connect(entIn.y, intEntIn.u)
  connect(add3Ent.u1, intEntOut.y)
  connect(intEntOut.u, senSpeEnt.H_flow)
  connect(add.u3, mWatFlo3.y)
  connect(pro.u1, senMasFra.X)
  connect(pro.u2, senMasFlo.m_flow)
  connect(intMasVapOut.y, add3Vap.u1)
  connect(intMasVapOut.u, pro.y)
  connect(add3Mass.u1, intMasFloOut.y)
end MoistureMixingConservationDynamicBalance;


This test checks if mass and energy is conserved when mixing fluid streams using steady state balances. This test checks if water vapour mass is conserved.
Two air streams with different mass flow rate are humidified
by a mixing volume with two different vapor mass flow rates.
These flows are then mixed.
Afterwards the added water is removed again.
The final water concentration, mass flow rate and enthalpy
flow rate should then be equal to the initial values.

within Buildings.Fluid.MixingVolumes.Validation;
model MoistureMixingConservationSteadyState
  "This test checks if mass and energy is conserved when mixing fluid streams using steady state balances"
  extends
    Buildings.Fluid.MixingVolumes.Validation.BaseClasses.MoistureMixingConservation(
    mWatFloSol(k=0),
    mFloSol(k=sou1.m_flow + sou2.m_flow),
    hSol(k=Medium.h_default*(sou1.m_flow + sou2.m_flow)));

equation
  connect(cheMasFra.u2, senMasFra.X)
  connect(cheMasFlo.u2, senMasFlo.m_flow)
  connect(cheSpeEnt.u2, senSpeEnt.H_flow)
end MoistureMixingConservationSteadyState;


within Buildings.Fluid.MixingVolumes.Validation;
model MSLCombustionAir
  "Test model with medium for combustion air from the Modelica Standard Library"
    extends Modelica.Icons.Example;

  replaceable package Medium =
      Modelica.Media.IdealGases.MixtureGases.CombustionAir;

  parameter Modelica.Units.SI.PressureDifference dp_nominal=10
    "Nominal pressure drop";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=10
    "Nominal mass flow rate";

  Buildings.Fluid.FixedResistances.PressureDrop res(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal)
    "Pressure drop"
  Buildings.Fluid.Sources.MassFlowSource_T masFloSou(
    redeclare package Medium = Medium,
    m_flow=m_flow_nominal,
    nPorts=1) "Mass flow source"
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = Medium,
    p=Medium.p_default + dp_nominal,
    nPorts=1)
    "Pressure boundary condition"
  Buildings.Fluid.MixingVolumes.MixingVolume vol(
    redeclare package Medium = Medium,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    m_flow_nominal=m_flow_nominal,
    V=1,
    nPorts=2)
    "Control volume"
equation
  connect(res.port_b, bou.ports[1])
  connect(masFloSou.ports[1], vol.ports[1])
  connect(vol.ports[2], res.port_a)
end MSLCombustionAir;


within Buildings.Fluid.MixingVolumes.Validation;
model MSLFlueGasSixComponents
  "Test model with medium for flue gas from the Modelica Standard Library"
    extends Buildings.Fluid.MixingVolumes.Validation.MSLCombustionAir(
      redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.FlueGasSixComponents);
end MSLFlueGasSixComponents;


Collection of validation models

within Buildings.Fluid.MixingVolumes;
package Validation "Collection of validation models"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


This test checks if trace substance mass flow rates are conserved when a dynamic balance is used. This test checks if the trace substance flow rate is
conserved when adding moisture to a mixing volume that is configured to steady state.<br/>
The trace substance flow rate at the inlet and outlet should be equal
since the trace substance concentration should not
be affected by the independent mass fraction concentration.

within Buildings.Fluid.MixingVolumes.Validation;
model TraceSubstanceConservationDynamicBalance
  "This test checks if trace substance mass flow rates are conserved when a dynamic balance is used"
  extends
    Buildings.Fluid.MixingVolumes.Validation.BaseClasses.TraceSubstanceConservation(
     vol(massDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial));

  Modelica.Blocks.Continuous.Integrator intTraSubIn(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integrator for trace substance inlet"
  Modelica.Blocks.Continuous.Integrator intTraSubOut(
    k=1,
    initType=Modelica.Blocks.Types.Init.InitialState,
    y_start=0) "Integrator for trace substance outlet"
  Modelica.Blocks.Sources.RealExpression reaExp(y=vol.m*vol.C[1])
    "Mixing volume total species mass"
  Modelica.Blocks.Math.Add cheConMas(k2=-1) "Check for conservation of mass"
  Modelica.Blocks.Math.Add3 add3(
    k1=-1,
    k2=+1,
    k3=-1) "Conservation of mass"
  Modelica.Blocks.Sources.Constant zero(k=0) "Zero input"
equation
  connect(reaExp.y, add3.u3)
  connect(intTraSubIn.y, add3.u2)
  connect(intTraSubOut.y, add3.u1)
  connect(add3.y, cheConMas.u1)
  connect(zero.y, cheConMas.u2)
  connect(intTraSubIn.u, CfloIn.y)
  connect(CfloOut.y, intTraSubOut.u)
end TraceSubstanceConservationDynamicBalance;


This test checks if trace substance mass flow rates are conserved when steady state. This test checks if the trace substance flow rate is conserved
when adding moisture to a mixing volume that is configured to steady state.<br/>
The trace substance flow rate at the inlet and outlet should be equal
since the trace substance concentration should not
be affected by the independent mass fraction concentration.

within Buildings.Fluid.MixingVolumes.Validation;
model TraceSubstanceConservationSteadyState
  "This test checks if trace substance mass flow rates are conserved when steady state"
  extends
    Buildings.Fluid.MixingVolumes.Validation.BaseClasses.TraceSubstanceConservation(
     sou(X={0,1}));
  Modelica.Blocks.Math.Add cheEquTra2(k2=-1)
    "Check for equality of trace substances"
  Modelica.Blocks.Math.Add cheEquTra1(k2=-1)
    "Check for equality of trace substances"
  Modelica.Blocks.Sources.Constant const(k=sou.m_flow*sou.C[1])
    "Set point of trace substance concentration"
equation
  connect(const.y,cheEquTra1. u1)
  connect(cheEquTra1.u2, CfloIn.y)
  connect(cheEquTra2.u2, CfloIn.y)
  connect(cheEquTra2.u1, CfloOut.y)
end TraceSubstanceConservationSteadyState;


within Buildings.Fluid.MixingVolumes.Validation.BaseClasses;
partial model MixingVolumeReverseFlow
  "Test model for mixing volume with flow reversal"
 extends Modelica.Icons.Example;
 package Medium = Buildings.Media.Air "Medium model";

  constant Boolean prescribedHeatFlowRate = false
    "Flag that affects what steady state balance is used in the volume";
  parameter Modelica.Units.SI.Pressure dp_nominal=10 "Nominal pressure drop";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=2.0
    "Nominal mass flow rate";

  Modelica.Fluid.Sources.MassFlowSource_T sou(
    redeclare package Medium = Medium,
    nPorts=1,
    m_flow=m_flow_nominal,
    use_m_flow_in=true,
    T=313.15) "Flow source and sink"
  Sources.Boundary_pT bou(
    redeclare package Medium = Medium,
    T=303.15,
    nPorts=2) "Boundary condition"
  replaceable Buildings.Fluid.MixingVolumes.MixingVolume volDyn
    constrainedby Buildings.Fluid.MixingVolumes.MixingVolume(
        redeclare package Medium = Medium,
        V=1,
        nPorts=2,
        m_flow_nominal=m_flow_nominal,
        energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Volume with dynamic balance"

  replaceable Buildings.Fluid.MixingVolumes.MixingVolume volSte
    constrainedby Buildings.Fluid.MixingVolumes.MixingVolume(
        redeclare package Medium = Medium,
        final prescribedHeatFlowRate = prescribedHeatFlowRate,
        V=1,
        nPorts=2,
        m_flow_nominal=m_flow_nominal,
        energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Volume with steady-state balance"

  Modelica.Blocks.Math.Gain gain
    "Gain to add heat, moisture or trace substance flow rate"
  Modelica.Fluid.Sources.MassFlowSource_T sou1(
    redeclare package Medium = Medium,
    nPorts=1,
    m_flow=m_flow_nominal,
    use_m_flow_in=true,
    T=313.15) "Flow source and sink"
  Modelica.Blocks.Sources.Ramp m_flow(
    height=-2*m_flow_nominal,
    duration=10,
    offset=m_flow_nominal) "Mass flow rate"
equation
  connect(sou.ports[1], volDyn.ports[1])
  connect(sou1.ports[1], volSte.ports[1])
  connect(volSte.ports[2], bou.ports[1])
  connect(volDyn.ports[2], bou.ports[2])
  connect(sou.m_flow_in, m_flow.y)
  connect(m_flow.y, sou1.m_flow_in)
  connect(m_flow.y, gain.u)
end MixingVolumeReverseFlow;


within Buildings.Fluid.MixingVolumes.Validation.BaseClasses;
partial model MoistureMixingConservation
  "Partial for checking conservation of mass for independent mass fraction"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Air "Medium model";
  Buildings.Fluid.Sources.MassFlowSource_h sou1(
    redeclare package Medium = Medium,
    nPorts=1,
    m_flow=2,
    X={0,1}) "Air source"
  Buildings.Fluid.Sources.MassFlowSource_h sou2(
    redeclare package Medium = Medium,
    nPorts=1,
    m_flow=1,
    X={0,1}) "Air source"
  Buildings.Fluid.MixingVolumes.MixingVolumeMoistAir vol(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    V=1,
    nPorts=2,
    allowFlowReversal=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Mixing volume for adding water"
  Buildings.Fluid.MixingVolumes.MixingVolumeMoistAir vol1(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    V=1,
    nPorts=2,
    allowFlowReversal=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Mixing volume for adding water"
  Buildings.Fluid.Sources.Boundary_pT sin(redeclare package Medium = Medium,
      nPorts=1) "Air sink"
  Buildings.Fluid.MixingVolumes.MixingVolumeMoistAir vol2(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    V=1,
    nPorts=2,
    allowFlowReversal=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Mixing volume for removing water"
  Modelica.Blocks.Sources.Constant mWatFlo1(k=0.001) "Water mass flow rate 1"
  Modelica.Blocks.Sources.Constant mWatFlo3(k=-(mWatFlo1.k + mWatFlo2.k))
    "Withdrawn water rate"
  Modelica.Blocks.Sources.Constant mWatFlo2(k=0.003) "Water mass flow rate 2"
  Modelica.Blocks.Math.Add cheMasFra(k2=-1)
    "Check for water conservation"
  Buildings.Fluid.Sensors.MassFractionTwoPort senMasFra(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0,
    allowFlowReversal=false) "Sensor for measuring water content"

  Modelica.Blocks.Sources.Constant mWatFloSol "Solution mass fraction water"
  Buildings.Fluid.Sensors.MassFlowRate senMasFlo(redeclare package Medium =
        Medium, allowFlowReversal=false) "Mass flow rate sensor"
  Modelica.Blocks.Math.Add cheMasFlo(k2=-1)
    "Check for conservation of mass"
  Modelica.Blocks.Sources.Constant mFloSol "Solution mass flow rate"
  Sensors.EnthalpyFlowRate senSpeEnt(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    m_flow_nominal=1,
    tau=0) "Specific enthalpy flow rate sensor"
  Modelica.Blocks.Math.Add cheSpeEnt(k2=-1)
    "Check for conservation of energy"
  Modelica.Blocks.Sources.Constant hSol "Solution mass flow rate"

protected
  Modelica.Fluid.Interfaces.FluidPort_a port_a(
  redeclare package Medium = Medium)
    "Fluid port for using fluid stream mixing implementation"
equation
  connect(sou1.ports[1], vol.ports[1])
  connect(sou2.ports[1], vol1.ports[1])
  connect(mWatFlo1.y, vol.mWat_flow)
  connect(mWatFlo3.y, vol2.mWat_flow)
  connect(mWatFlo2.y, vol1.mWat_flow)
  connect(vol1.ports[2], port_a)
  connect(vol.ports[2], port_a)
  connect(port_a, vol2.ports[1])
  connect(mWatFloSol.y,cheMasFra. u1)
  connect(senMasFlo.port_b, senMasFra.port_a)
  connect(vol2.ports[2], senSpeEnt.port_a)
  connect(senSpeEnt.port_b, senMasFlo.port_a)
  connect(hSol.y,cheSpeEnt. u1)
  connect(mFloSol.y,cheMasFlo. u1)
  connect(senMasFra.port_b, sin.ports[1])
end MoistureMixingConservation;


Baseclasses package

within Buildings.Fluid.MixingVolumes.Validation;
package BaseClasses "Baseclasses package"
extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings.Fluid.MixingVolumes.Validation.BaseClasses;
model TraceSubstanceConservation
  "This test checks if trace substance mass flow rates are conserved"
  extends Modelica.Icons.Example;
  constant String substanceName="CO2";
  package Medium = Buildings.Media.Air(extraPropertiesNames={substanceName});
  Buildings.Fluid.Sources.MassFlowSource_h sou(
    redeclare package Medium = Medium,
    nPorts=1,
    m_flow=1,
    X={0.01,0.99},
    C=fill(0.001, Medium.nC)) "Air source with moisture and trace substances"

  Buildings.Fluid.MixingVolumes.MixingVolumeMoistAir vol(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    V=1,
    nPorts=2,
    allowFlowReversal=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Mixing volume for adding moisture"
  Modelica.Blocks.Sources.Constant mWatFlo(k=0.001) "Water mass flow rate "
  Buildings.Fluid.Sensors.TraceSubstancesTwoPort senTraSubIn(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0,
    allowFlowReversal=false,
    substanceName=substanceName) "Measured inlet trace substance concentration"
  Buildings.Fluid.Sensors.TraceSubstancesTwoPort senTraSubOut(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0,
    allowFlowReversal=false,
    substanceName=substanceName)
    "Measured outlet trace substance concentration"
  Buildings.Fluid.Sources.Boundary_pT sin(redeclare package Medium = Medium,
      nPorts=1) "Air sink"
  Sensors.MassFlowRate senMasFloIn(redeclare package Medium = Medium,
      allowFlowReversal=false) "Fluid mass flow rate at inlet"
  Sensors.MassFlowRate senMasFloOut(redeclare package Medium = Medium,
      allowFlowReversal=false) "Fluid mass flow rate at outlet"
  Modelica.Blocks.Math.Product CfloIn "Trace substance mass flow rate at inlet"
  Modelica.Blocks.Math.Product CfloOut
    "Trace substance mass flow rate at outlet"
equation
  connect(mWatFlo.y, vol.mWat_flow)
  connect(sou.ports[1], senTraSubIn.port_a)
  connect(senTraSubIn.port_b, senMasFloIn.port_a)
  connect(senMasFloIn.port_b, vol.ports[1])
  connect(sin.ports[1], senMasFloOut.port_b)
  connect(senMasFloOut.port_a, senTraSubOut.port_b)
  connect(CfloIn.u2, senTraSubIn.C)
  connect(CfloIn.u1, senMasFloIn.m_flow)
  connect(CfloOut.u1, senMasFloOut.m_flow)
  connect(CfloOut.u2, senTraSubOut.C)
  connect(vol.ports[2], senTraSubOut.port_a)
end TraceSubstanceConservation;


Fan or pump with ideally controlled head dp as input signal. This model describes a fan or pump with prescribed head.
The input connector provides the pressure rise from the inlet to the outlet.

within Buildings.Fluid.Movers;
model FlowControlled_dp
  "Fan or pump with ideally controlled head dp as input signal"
  extends Buildings.Fluid.Movers.BaseClasses.PartialFlowMachine(
    final preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.PressureDifference,
    final computePowerUsingSimilarityLaws=per.havePressureCurve,
    preSou(dp_start=dp_start, control_dp=not prescribeSystemPressure),
    final stageInputs(each final unit="Pa") = heads,
    final constInput(final unit="Pa") = constantHead,
    final _m_flow_nominal=m_flow_nominal,
    motSpe(
      Rising=dp_nominal/riseTime,
      Falling=-dp_nominal/riseTime,
      final y_start=dp_start,
      u(final unit="Pa", nominal=dp_nominal),
      y(final unit="Pa", nominal=dp_nominal)),
    eff(per(
        final pressure=if per.havePressureCurve then per.pressure else
            Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParameters(
            V_flow={i/(nOri - 1)*2.0*m_flow_nominal/rho_default for i in 0:(
              nOri - 1)},
            dp={i/(nOri - 1)*2.0*dp_nominal for i in (nOri - 1):-1:0}),
        final etaHydMet=if (per.etaHydMet == Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate
             or per.etaHydMet == Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber)
             and not per.havePressureCurve then Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.NotProvided
             else per.etaHydMet,
        final etaMotMet=if (per.etaMotMet == Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
             or per.etaMotMet == Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve)
             and (not per.haveWMot_nominal and not per.havePressureCurve) then
            Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided
             else per.etaMotMet), r_N(start=if abs(dp_nominal) > 1E-8 then
            dp_start/dp_nominal else 0)));

  parameter Modelica.Units.SI.PressureDifference dp_start(
    min=0,
    displayUnit="Pa") = 0 "Initial value of pressure raise"

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal(
    final min=Modelica.Constants.small)
    "Nominal mass flow rate"

  // For air, we set dp_nominal = 600 as default, for water we set 10000
  parameter Modelica.Units.SI.PressureDifference dp_nominal(
    final min=Modelica.Constants.small,
    displayUnit="Pa") = if rho_default < 500 then 500 else 10000 "Nominal pressure raise, used to normalized the filter if use_riseTime=true,
        to set default values of constantHead and heads, and
        and for default pressure curve if not specified in record per"

  parameter Modelica.Units.SI.PressureDifference constantHead(
    min=0,
    displayUnit="Pa") = dp_nominal
    "Constant pump head, used when inputType=Constant"

  // By default, set heads proportional to sqrt(speed/speed_nominal)
  parameter Modelica.Units.SI.PressureDifference[:] heads(
    each min=0,
    each displayUnit="Pa") = dp_nominal*{(per.speeds[i]/per.speeds[end])^2 for
    i in 1:size(per.speeds, 1)}
    "Vector of head set points, used when inputType=Stages"
  parameter Boolean prescribeSystemPressure = false
    "=true, to control mover such that pressure difference is obtained across two remote points in system"

  Modelica.Blocks.Interfaces.RealInput dpMea(
    final quantity="PressureDifference",
    displayUnit="Pa",
    final unit="Pa")=gain.u if prescribeSystemPressure
    "Measurement of pressure difference between two points where the set point should be obtained"

  Modelica.Blocks.Interfaces.RealInput dp_in(final unit="Pa")
    if inputType == Buildings.Fluid.Types.InputType.Continuous
    "Prescribed pressure rise"

  Modelica.Blocks.Interfaces.RealOutput dp_actual(final unit="Pa")
    "Pressure difference between the mover inlet and outlet"

protected
  Modelica.Blocks.Math.Gain gain(final k=-1)
equation
  assert(inputSwitch.u >= -1E-3,
    "Pressure set point for mover cannot be negative. Obtained dp = " + String(inputSwitch.u));

  if use_riseTime then
    connect(motSpe.y, gain.u)
  else
    connect(inputSwitch.y, gain.u)
  end if;

  connect(inputSwitch.u, dp_in)
  connect(preSou.dp_in, gain.y)
  connect(senRelPre.p_rel, dp_actual)
end FlowControlled_dp;


Fan or pump with ideally controlled mass flow rate as input signal. This model describes a fan or pump with prescribed mass flow rate.

within Buildings.Fluid.Movers;
model FlowControlled_m_flow
  "Fan or pump with ideally controlled mass flow rate as input signal"
  extends Buildings.Fluid.Movers.BaseClasses.PartialFlowMachine(
    final preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.FlowRate,
    final computePowerUsingSimilarityLaws=per.havePressureCurve,
    final stageInputs(each final unit="kg/s") = massFlowRates,
    final constInput(final unit="kg/s") = constantMassFlowRate,
    final _m_flow_nominal=m_flow_nominal,
    motSpe(
      Rising=m_flow_nominal/riseTime,
      Falling=-m_flow_nominal/riseTime,
      final y_start=m_flow_start,
      u(final unit="kg/s", nominal=m_flow_nominal),
      y(final unit="kg/s", nominal=m_flow_nominal)),
    eff(per(
        final pressure=if per.havePressureCurve then per.pressure else
            Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParameters(
            V_flow={i/(nOri - 1)*2.0*m_flow_nominal/rho_default for i in 0:(
              nOri - 1)},
            dp={i/(nOri - 1)*2.0*dp_nominal for i in (nOri - 1):-1:0}),
        final etaHydMet=if (per.etaHydMet == Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate
             or per.etaHydMet == Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber)
             and not per.havePressureCurve then Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.NotProvided
             else per.etaHydMet,
        final etaMotMet=if (per.etaMotMet == Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
             or per.etaMotMet == Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve)
             and (not per.haveWMot_nominal and not per.havePressureCurve) then
            Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided
             else per.etaMotMet), r_N(start=if abs(m_flow_nominal) > 1E-8 then
            m_flow_start/m_flow_nominal else 0)),
    preSou(m_flow_start=m_flow_start));

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal(
    final min=Modelica.Constants.small)
    "Nominal mass flow rate"

  // For air, we set dp_nominal = 600 as default, for water we set 10000
  parameter Modelica.Units.SI.PressureDifference dp_nominal(
    final min=Modelica.Constants.small,
    displayUnit="Pa") = if rho_default < 500 then 500 else 10000
    "Nominal pressure raise, used for default pressure curve if not specified in record per"

  parameter Modelica.Units.SI.MassFlowRate m_flow_start(min=0) = 0
    "Initial value of mass flow rate"

  parameter Modelica.Units.SI.MassFlowRate constantMassFlowRate=m_flow_nominal
    "Constant pump mass flow rate, used when inputType=Constant"

  // By default, set massFlowRates proportional to (speed/speed_nominal)
  parameter Modelica.Units.SI.MassFlowRate[:] massFlowRates=m_flow_nominal*{per.speeds[
      i]/per.speeds[end] for i in 1:size(per.speeds, 1)}
    "Vector of mass flow rate set points, used when inputType=Stage"

  parameter Modelica.Units.SI.Pressure dpMax(
    min=0,
    displayUnit="Pa") = 2*max(eff.per.pressure.dp)
   "Maximum pressure allowed to operate the model, if exceeded, the simulation stops with an error"

  Modelica.Blocks.Interfaces.RealInput m_flow_in(
    final unit="kg/s",
    nominal=m_flow_nominal)
    if inputType == Buildings.Fluid.Types.InputType.Continuous
    "Prescribed mass flow rate"
  Modelica.Blocks.Interfaces.RealOutput m_flow_actual(
    final unit="kg/s",
    nominal=m_flow_nominal) "Actual mass flow rate"

equation
  assert(-dp <= dpMax,
    "In " + getInstanceName() + ": Model operates with head -dp = " + String(-dp) + " Pa,
    exceeding the pressure allowed by the parameter " + getInstanceName() + ".dpMax.
    This can happen if the model forces a high mass flow rate through a closed actuator,
    or if the performance record is unreasonable. Please verify your model, and
    consider using one of the other pump or fan models.");

  if use_riseTime then
    connect(motSpe.y, preSou.m_flow_in)
  else
  connect(inputSwitch.y, preSou.m_flow_in)
  end if;

  connect(inputSwitch.u, m_flow_in)

  connect(eff.m_flow, senMasFlo.m_flow)
  connect(senMasFlo.m_flow, m_flow_actual)
end FlowControlled_m_flow;


Package with fan and pump models

within Buildings.Fluid;
package Movers "Package with fan and pump models"
  extends Modelica.Icons.Package;
end Movers;


Fan or pump with ideally controlled normalized speed y as input signal. This model describes a fan or pump with prescribed normalized speed.
The input connector provides the normalized rotational speed (between 0 and 1).
The head is computed based on the performance curve that take as an argument
the actual volume flow rate divided by the maximum flow rate and the relative
speed of the fan.

within Buildings.Fluid.Movers;
model SpeedControlled_y
  "Fan or pump with ideally controlled normalized speed y as input signal"
  extends Buildings.Fluid.Movers.BaseClasses.PartialFlowMachine(
    final preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    final nominalValuesDefineDefaultPressureCurve=false,
    final computePowerUsingSimilarityLaws=true,
    final stageInputs(each final unit="1") = per.speeds,
    final constInput(final unit="1") = per.constantSpeed,
    motSpe(
      final y_start=y_start,
      u(final unit="1"),
      y(final unit="1")),
    eff(per(
        final pressure=per.pressure,
        final etaHydMet=per.etaHydMet,
        final etaMotMet=per.etaMotMet), r_N(start=y_start)));

  parameter Real y_start(min=0, max=1, unit="1")=0 "Initial value of speed"

  Modelica.Blocks.Interfaces.RealInput y(
    unit="1")
 if inputType == Buildings.Fluid.Types.InputType.Continuous
    "Constant normalized rotational speed"

protected
  Modelica.Blocks.Math.Gain gain(final k=-1) "Pressure gain"
initial equation
  assert(per.havePressureCurve,
   "SpeedControlled_y requires to set the pressure vs. flow rate curve in record 'per'.");

equation
  connect(inputSwitch.u, y)
  connect(eff.dp, gain.u)
  connect(gain.y, preSou.dp_in)

  if use_riseTime then
    connect(motSpe.y, eff.y_in)
  else
    connect(inputSwitch.y, eff.y_in)
  end if;
end SpeedControlled_y;


User's Guide

within Buildings.Fluid.Movers;
package UsersGuide "User's Guide"
  extends Modelica.Icons.Information;

end UsersGuide;


Partial model with performance curves for fans or pumps. This is an interface that implements the functions to compute the head, power draw
and efficiency of fans and pumps.

within Buildings.Fluid.Movers.BaseClasses;
model FlowMachineInterface
  "Partial model with performance curves for fans or pumps"
  extends Modelica.Blocks.Icons.Block;

  import cha = Buildings.Fluid.Movers.BaseClasses.Characteristics;

  constant Boolean homotopyInitialization = true "= true, use homotopy method"

  parameter Buildings.Fluid.Movers.Data.Generic per
    "Record with performance data"

  parameter Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable preVar=
    Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed "Type of prescribed variable";
  parameter Boolean computePowerUsingSimilarityLaws
    "= true, compute power exactly, using similarity laws. Otherwise approximate.";

  final parameter Modelica.Units.SI.VolumeFlowRate V_flow_nominal=per.pressure.V_flow[
      nOri] "Nominal volume flow rate, used for homotopy";

  parameter Modelica.Units.SI.Density rho_default
    "Fluid density at medium default state";

  final parameter Boolean haveVMax = (abs(per.pressure.dp[nOri]) < Modelica.Constants.eps)
    "Flag, true if user specified data that contain V_flow_max";

  final parameter Modelica.Units.SI.VolumeFlowRate V_flow_max=
    if per.V_flow_max>Modelica.Constants.eps
      then per.V_flow_max
    else
      V_flow_nominal
    "Maximum volume flow rate, used for smoothing";

  parameter Integer nOri(min=1) "Number of data points for pressure curve"

 // Normalized speed
  Modelica.Blocks.Interfaces.RealInput y_in(final unit="1") if preSpe
    "Prescribed mover speed"

  Modelica.Blocks.Interfaces.RealOutput y_out(
    final unit="1") "Mover speed (prescribed or computed)"

  Modelica.Blocks.Interfaces.RealInput m_flow(
    final quantity="MassFlowRate",
    final unit="kg/s") "Mass flow rate"

  Modelica.Blocks.Interfaces.RealInput rho(
    final quantity="Density",
    final unit="kg/m3",
    min=0.0) "Medium density"

  Modelica.Blocks.Interfaces.RealOutput V_flow(
    quantity="VolumeFlowRate",
    final unit="m3/s") "Volume flow rate"

  Modelica.Blocks.Interfaces.RealInput dp_in(
    quantity="PressureDifference",
    final unit="Pa") if prePre "Prescribed pressure increase"

  Modelica.Blocks.Interfaces.RealOutput dp(
    quantity="Pressure",
    final unit="Pa") if not prePre "Pressure increase (computed or prescribed)"

  Modelica.Blocks.Interfaces.RealOutput WFlo(
    quantity="Power",
    final unit="W") "Flow work"

  Modelica.Blocks.Interfaces.RealOutput WHyd(
    quantity="Power",
    final unit="W") "Hydraulic work (shaft work, brake horsepower)"

  Modelica.Blocks.Interfaces.RealOutput PEle(
    quantity="Power",
    final unit="W") "Electrical power consumed"

  Modelica.Blocks.Interfaces.RealOutput eta(
    final quantity="Efficiency",
    final unit="1",
    start = 0.49) "Overall efficiency"
  // A start value is given to suppress the following translation warning:
  //   "Some variables are iteration variables of the initialization problem:
  //   but they are not given any explicit start values. Zero will be used."

  Modelica.Blocks.Interfaces.RealOutput etaHyd(
    final quantity="Efficiency",
    final unit="1",
    start = 0.7) "Hydraulic efficiency"

  Modelica.Blocks.Interfaces.RealOutput etaMot(
    final quantity="Efficiency",
    final unit="1",
    start = 0.7) "Motor efficiency"

  // "Shaft rotational speed";
  Modelica.Blocks.Interfaces.RealOutput r_N(unit="1")
    "Ratio N_actual/N_nominal";
  Real r_V(start=1, unit="1") "Ratio V_flow/V_flow_max";

protected
  final parameter Boolean preSpe=
    preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed
    "True if speed is a prescribed variable of this block";
  final parameter Boolean prePre=
    preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.PressureDifference or
    preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.FlowRate
    "True if pressure head is a prescribed variable of this block";

  // Derivatives for cubic spline
  final parameter Real etaDer[size(per.efficiency.V_flow,1)]=
    if not per.etaHydMet==Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Efficiency_VolumeFlowRate
      then zeros(size(per.efficiency.V_flow,1))
    elseif (size(per.efficiency.V_flow, 1) == 1)
      then {0}
    else
      Buildings.Utilities.Math.Functions.splineDerivatives(
        x=per.efficiency.V_flow,
        y=per.efficiency.eta,
        ensureMonotonicity=Buildings.Utilities.Math.Functions.isMonotonic(
          x=per.efficiency.eta,
          strict=false))
    "Coefficients for cubic spline of total or hydraulic efficiency vs. volume flow rate";
  final parameter Real motDer[size(per.motorEfficiency.V_flow, 1)]=
    if not per.etaMotMet==Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_VolumeFlowRate
      then zeros(size(per.motorEfficiency.V_flow,1))
    elseif (size(per.motorEfficiency.V_flow, 1) == 1)
      then {0}
    else
      Buildings.Utilities.Math.Functions.splineDerivatives(
        x=per.motorEfficiency.V_flow,
        y=per.motorEfficiency.eta,
        ensureMonotonicity=Buildings.Utilities.Math.Functions.isMonotonic(
          x=per.motorEfficiency.eta,
          strict=false))
    "Coefficients for cubic spline of motor efficiency vs. volume flow rate";
  final parameter Real motDer_yMot[size(per.motorEfficiency_yMot.y,1)]=
    if not per.etaMotMet==
      Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
      then zeros(size(per.motorEfficiency_yMot.y,1))
    elseif (size(per.motorEfficiency_yMot.y,1) == 1)
      then {0}
    else
      Buildings.Utilities.Math.Functions.splineDerivatives(
        x=per.motorEfficiency_yMot.y,
        y=per.motorEfficiency_yMot.eta,
        ensureMonotonicity=Buildings.Utilities.Math.Functions.isMonotonic(
          x=per.motorEfficiency_yMot.eta,
          strict=false))
    "Coefficients for cubic spline of motor efficiency vs. motor PLR";
  final parameter Real motDer_yMot_generic[9]=
    if per.etaMotMet==
      Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve
      or  (per.etaMotMet==
      Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided
           and per.haveWMot_nominal)
      then Buildings.Utilities.Math.Functions.splineDerivatives(
             x=per.motorEfficiency_yMot_generic.y,
             y=per.motorEfficiency_yMot_generic.eta,
             ensureMonotonicity=true)
    else zeros(9)
    "Coefficients for cubic spline of motor efficiency vs. motor PLR with generic curves";

  final parameter Modelica.Units.SI.PressureDifference dpMax(
    displayUnit="Pa")=
    per.dpMax "Maximum head";

  parameter Real delta = 0.05
    "Small value used to for regularization and to approximate an internal flow resistance of the fan";

  parameter Real kRes(min=0, unit="kg/(s.m4)") =  dpMax/V_flow_max*delta^2/10
    "Coefficient for internal pressure drop of the fan or pump";

  parameter Integer curve=
     if (haveVMax and haveDPMax) or (nOri == 2) then 1
     elseif haveVMax or haveDPMax then 2
     else 3
    "Flag, used to pick the right representation of the fan or pump's pressure curve";

  final parameter
    Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParametersInternal pCur1(
    final n = nOri,
    final V_flow = if (haveVMax and haveDPMax) or (nOri == 2) then
             {per.pressure.V_flow[i] for i in 1:nOri}
             else
             zeros(nOri),
    final dp = if (haveVMax and haveDPMax) or (nOri == 2) then
             {(per.pressure.dp[i] + per.pressure.V_flow[i] * kRes) for i in 1:nOri}
             else
             zeros(nOri))
    "Volume flow rate vs. total pressure rise with correction for fan or pump's resistance added";

  parameter
    Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParametersInternal pCur2(
    final n = nOri + 1,
    V_flow = if (haveVMax and haveDPMax) or (nOri == 2) then
                zeros(nOri + 1)
             elseif haveVMax then
              cat(1, {0}, {per.pressure.V_flow[i] for i in 1:nOri})
             elseif haveDPMax then
              cat(1, { per.pressure.V_flow[i] for i in 1:nOri}, {V_flow_max})
             else
              zeros(nOri + 1),
    dp = if (haveVMax and haveDPMax) or (nOri == 2) then
                zeros(nOri + 1)
             elseif haveVMax then
              cat(1, {dpMax}, {per.pressure.dp[i] + per.pressure.V_flow[i] * kRes for i in 1:nOri})
             elseif haveDPMax then
              cat(1, {per.pressure.dp[i] + per.pressure.V_flow[i] * kRes for i in 1:nOri}, {0})
             else
               zeros(nOri+1))
    "Volume flow rate vs. total pressure rise with correction for fan or pump's resistance added";
  parameter
    Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParametersInternal pCur3(
    final n = nOri + 2,
    V_flow = if (haveVMax and haveDPMax) or (nOri == 2) then
               zeros(nOri + 2)
             elseif haveVMax or haveDPMax then
               zeros(nOri + 2)
             else
               cat(1, {0}, {per.pressure.V_flow[i] for i in 1:nOri}, {V_flow_max}),
    dp =     if (haveVMax and haveDPMax) or (nOri == 2) then
               zeros(nOri + 2)
             elseif haveVMax or haveDPMax then
               zeros(nOri + 2)
             else
               cat(1, {dpMax}, {per.pressure.dp[i] + per.pressure.V_flow[i] * kRes for i in 1:nOri}, {0}))
    "Volume flow rate vs. total pressure rise with correction for fan or pump's resistance added";

  parameter Real preDer1[nOri](each fixed=false)
    "Derivatives of flow rate vs. pressure at the support points";
  parameter Real preDer2[nOri+1](each fixed=false)
    "Derivatives of flow rate vs. pressure at the support points";
  parameter Real preDer3[nOri+2](each fixed=false)
    "Derivatives of flow rate vs. pressure at the support points";
  parameter Real powDer[size(per.power.V_flow,1)]=
   if per.etaHydMet==
      Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate then
     Buildings.Utilities.Math.Functions.splineDerivatives(
                   x=per.power.V_flow,
                   y=per.power.P,
                   ensureMonotonicity=Buildings.Utilities.Math.Functions.isMonotonic(x=per.power.P,
                                                                                strict=false))
   else
     zeros(size(per.power.V_flow,1))
    "Coefficients for polynomial of power vs. flow rate";

  final parameter Buildings.Fluid.Movers.BaseClasses.Euler.powerWithDerivative powEu_internal=
    if (curve == 1) then
      Buildings.Fluid.Movers.BaseClasses.Euler.power(peak=per.peak,pressure=pCur1)
    elseif (curve == 2) then
      Buildings.Fluid.Movers.BaseClasses.Euler.power(peak=per.peak,pressure=pCur2)
    else
      Buildings.Fluid.Movers.BaseClasses.Euler.power(peak=per.peak,pressure=pCur3)
    "Intermediate parameter";
  final parameter Buildings.Fluid.Movers.BaseClasses.Characteristics.powerParameters powEu(
    V_flow = powEu_internal.V_flow,
    P = powEu_internal.P)
    "Power vs. volumetric flow rate computed from Euler number";
  final parameter Real powEuDer[:] = powEu_internal.d
    "Power derivative with respect to volumetric flow rate computed from Euler number";

  parameter Boolean haveMinimumDecrease=
    if nOri<2 then false
    else
      Modelica.Math.BooleanVectors.allTrue({(per.pressure.dp[i + 1] -
      per.pressure.dp[i])/(per.pressure.V_flow[i + 1] - per.pressure.V_flow[
      i]) < -kRes for i in 1:nOri - 1}) "Flag used for reporting";

  parameter Boolean haveDPMax = (abs(per.pressure.V_flow[1])  < Modelica.Constants.eps)
    "Flag, true if user specified data that contain dpMax";

  Modelica.Blocks.Interfaces.RealOutput dp_internal
    "If dp is prescribed, use dp_in and solve for r_N, otherwise compute dp using r_N";

  Modelica.Units.SI.Efficiency eta_internal
    "Either eta or etaHyd";

  Modelica.Units.SI.Power P_internal
    "Either PEle or WHyd";

  parameter Real deltaP = 1E-4 * V_flow_max * dpMax
    "Small value for regularisation of power";

  Real yMot(final min=0, final start=0.833)=
    if per.haveWMot_nominal
      then WHyd/per.WMot_nominal
    else 1
    "Motor part load ratio";

function getPerformanceDataAsString
  input Buildings.Fluid.Movers.BaseClasses.Characteristics.flowParameters pressure
      "Performance data";
  input Real derivative[:](unit="kg/(s.m4)") "Derivative";
  input Integer minimumLength =  6 "Minimum width of result";
  input Integer significantDigits = 6 "Number of significant digits";
  output String str "String representation";
algorithm
  str :="";
  for i in 1:size(derivative, 1) loop
    str :=str + "  V_flow[" + String(i) + "]=" + String(
        pressure.V_flow[i],
        minimumLength=minimumLength,
        significantDigits=significantDigits) + "\t" + "dp[" + String(i) + "]=" +
        String(
        pressure.dp[i],
        minimumLength=minimumLength,
        significantDigits=significantDigits) + "\tResulting derivative dp/dV_flow = "
         + String(
        derivative[i],
        minimumLength=minimumLength,
        significantDigits=significantDigits) + "\n";
  end for;
end getPerformanceDataAsString;

function getArrayAsString
  input Real array[:] "Array to be printed";
  input String varName "Variable name";
  input Integer minimumLength =  6 "Minimum width of result";
  input Integer significantDigits = 6 "Number of significant digits";
  output String str "String representation";
algorithm
  str :="";
  for i in 1:size(array, 1) loop
    str :=str + "  " + varName + "[" + String(i) + "]=" + String(
        array[i],
        minimumLength=minimumLength,
        significantDigits=significantDigits) + "\n";
  end for;
end getArrayAsString;

initial equation
  // Check validity of data
  assert(nOri > 1, "Must have at least two data points for pressure.V_flow.");
  assert(Buildings.Utilities.Math.Functions.isMonotonic(x=per.pressure.V_flow, strict=true) and
  per.pressure.V_flow[1] > -Modelica.Constants.eps,
  "The fan pressure rise must be a strictly decreasing sequence with respect to the volume flow rate,
  with the first element for the fan pressure raise being non-zero.
The following performance data have been entered:
" + getArrayAsString(per.pressure.V_flow, "pressure.V_flow"));

  if not haveVMax then
    assert(nOri>=2,
      "When the maximum flow is not specified,
      at least two points are needed for the power curve.");
    if nOri>=2 then
      assert((per.pressure.V_flow[nOri]-per.pressure.V_flow[nOri-1])
           /((per.pressure.dp[nOri]-per.pressure.dp[nOri-1]))<0,
    "The last two pressure points for the fan or pump's performance curve must be decreasing.
Received
" + getArrayAsString(per.pressure.dp, "dp"));
    end if;
  end if;

  // Write warning if the volumetric flow rate versus pressure curve does not satisfy
  // the minimum decrease condition
  if (not haveMinimumDecrease) then
    Modelica.Utilities.Streams.print("
Warning:
========
It is recommended that the volume flow rate versus pressure relation
of the fan or pump satisfy the minimum decrease condition

        (per.pressure.dp[i+1]-per.pressure.dp[i])
d[i] = ------------------------------------------------- < " + String(-kRes) + "
       (per.pressure.V_flow[i+1]-per.pressure.V_flow[i])

 is
" + getArrayAsString({(per.pressure.dp[i+1]-per.pressure.dp[i])
        /(per.pressure.V_flow[i+1]-per.pressure.V_flow[i]) for i in 1:nOri-1}, "d") + "
Otherwise, a solution to the equations may not exist if the fan or pump's speed is reduced.
In this situation, the solver will fail due to non-convergence and
the simulation stops.");
  end if;

  // Correction for flow resistance of the fan or pump
  if curve == 1 then  // ----- Curve 1
    // V_flow_max and dpMax are provided by the user, or we only have two data points
    preDer1= Buildings.Utilities.Math.Functions.splineDerivatives(x=pCur1.V_flow,
      y=pCur1.dp);
    preDer2= zeros(nOri + 1);
    preDer3= zeros(nOri + 2);
  elseif curve == 2 then  // ----- Curve 2
    // V_flow_max or dpMax is provided by the user, but not both
    preDer1= zeros(nOri);
    preDer2= Buildings.Utilities.Math.Functions.splineDerivatives(x=pCur2.V_flow,
      y=pCur2.dp);
    preDer3= zeros(nOri + 2);
  else  // ----- Curve 3
    // Neither V_flow_max nor dpMax are provided by the user
    preDer1= zeros(nOri);
    preDer2= zeros(nOri + 1);
    preDer3= Buildings.Utilities.Math.Functions.splineDerivatives(x=pCur3.V_flow,
      y=pCur3.dp);
  end if;

  assert(not ((per.etaMotMet==
           Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
           or  per.etaMotMet==
           Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve)
         and not per.haveWMot_nominal),
         "In " + getInstanceName() + ": etaMotMet is set to
         .Efficiency_MotorPartLoadRatio or .GenericCurve which requires
         the motor's rated power, but per.WMot_nominal is not assigned or
         cannot be estimated because no power curve is provided.");

  assert(max(per.power.P)<1E-6 or per.WMot_nominal>max(per.power.P)*0.99,
         "In " + getInstanceName() + ": The rated motor power provided in
         per.WMot_nominal is smaller than the maximum power provided in per.power.
         Use a larger value for per.WMot_nominal or leave it blank to allow the
         model to assume a default value.");

  assert(homotopyInitialization, "In " + getInstanceName() +
         ": The constant homotopyInitialization has been modified from its default
         value. This constant will be removed in future releases.",
         level = AssertionLevel.warning);

equation
  // Assign values of dp and r_N, depending on which variable exists and is prescribed
  connect(dp_internal,dp);
  connect(dp_internal,dp_in);
  connect(r_N, y_in);
  y_out=r_N;

  // Flow rate conversion
  V_flow = m_flow / rho;

  // Hydraulic equations
  r_V = V_flow/V_flow_max;

  // If the speed is not prescribed and we do not require exact power computations, we set r_N = 1.
  // Similarity laws are then not used, meaning the power computation is less accurate.
  // This however has the advantage that no non-linear algebraic loop is formed and
  // it allows an implementation when the pressure curve is unknown.
  if (computePowerUsingSimilarityLaws == false) and preVar <> Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed then
    r_N=1;
  else
  // For the homotopy method, we approximate dp by an equation
  // that is linear in V_flow, and that goes linearly to 0 as r_N goes to 0.
  // The three branches below are identical, except that we pass either
  // pCur1, pCur2 or pCur3, and preDer1, preDer2 or preDer3
  if (curve == 1) then
    if homotopyInitialization then
       V_flow*kRes + dp_internal = homotopy(actual=cha.pressure(
                                                     V_flow=V_flow,
                                                     r_N=r_N,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer1,
                                                     per=pCur1),
                                           simplified=r_N * (cha.pressure(
                                                     V_flow=V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer1,
                                                     per=pCur1)
                               +(V_flow-V_flow_nominal) * (cha.pressure(
                                                     V_flow=(1+delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer1,
                                                     per=pCur1)
                                       -cha.pressure(V_flow=(1-delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer1,
                                                     per=pCur1))
                                                    /(2*delta*V_flow_nominal)));

    else
       V_flow*kRes + dp_internal= cha.pressure(V_flow=V_flow,
                                               r_N=r_N,
                                               dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                               d=preDer1,
                                               per=pCur1);
    end if;     // end of computation for this branch
   elseif (curve == 2) then
    if homotopyInitialization then
       V_flow*kRes + dp_internal = homotopy(actual=cha.pressure(
                                                     V_flow=V_flow,
                                                     r_N=r_N,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer2,
                                                     per=pCur2),
                                            simplified=r_N * (cha.pressure(
                                                     V_flow=V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer2,
                                                     per=pCur2)
                               +(V_flow-V_flow_nominal) * (cha.pressure(
                                                     V_flow=(1+delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer2,
                                                     per=pCur2)
                                       -cha.pressure(V_flow=(1-delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer2,
                                                     per=pCur2))
                                                    /(2*delta*V_flow_nominal)));

    else
       V_flow*kRes + dp_internal= cha.pressure(V_flow=V_flow,
                                                      r_N=r_N,
                                                      dpMax=dpMax,
                                                      V_flow_max=V_flow_max,
                                                      d=preDer2,
                                                      per=pCur2);
    end if;     // end of computation for this branch
  else
    if homotopyInitialization then
       V_flow*kRes + dp_internal = homotopy(actual=cha.pressure(
                                                     V_flow=V_flow,
                                                     r_N=r_N,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer3,
                                                     per=pCur3),
                          simplified=r_N * (cha.pressure(
                                                     V_flow=V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer3,
                                                     per=pCur3)
                               +(V_flow-V_flow_nominal)*
                                       (cha.pressure(V_flow=(1+delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer3,
                                                     per=pCur3)
                                       -cha.pressure(V_flow=(1-delta)*V_flow_nominal,
                                                     r_N=1,
                                                     dpMax=dpMax,
                                                     V_flow_max=V_flow_max,
                                                     d=preDer3,
                                                     per=pCur3))
                                                    /(2*delta*V_flow_nominal)));

    else
       V_flow*kRes + dp_internal= cha.pressure(V_flow=V_flow,
                                                      r_N=r_N,
                                                      dpMax=dpMax,
                                                      V_flow_max=V_flow_max,
                                                      d=preDer3,
                                                      per=pCur3);
    end if;
    // end of computation for this branch
  end if;
    // end of if/else choosing between exact/simplified power computation
  end if;

  // Power and efficiency
  WFlo = Buildings.Utilities.Math.Functions.smoothMax(
           x1=dp_internal*V_flow,
           x2=0,
           deltaX=deltaP/2);
  if per.powerOrEfficiencyIsHydraulic then
    eta = etaHyd * etaMot;
  else
    etaHyd = Buildings.Utilities.Math.Functions.smoothMin(
               x1=eta/etaMot, x2=1, deltaX=1E-3);
  end if;

  // Hydraulic efficiency etaHyd and hydraulic work WHyd
  //   or total efficiency eta and total electric power PEle
  //   depending on the information provided
  if per.powerOrEfficiencyIsHydraulic then
    P_internal=WHyd;
    eta_internal=etaHyd;
    PEle = WFlo / Buildings.Utilities.Math.Functions.smoothMax(
                    x1=eta, x2=1E-2, deltaX=1E-3);
  else
    P_internal=PEle;
    eta_internal=eta;
    WHyd = WFlo / Buildings.Utilities.Math.Functions.smoothMax(
                    x1=etaHyd, x2=1E-2, deltaX=1E-3);
  end if;
  if per.etaHydMet==
       Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate then
    if homotopyInitialization then
      P_internal = homotopy(actual=cha.power(per=per.power, V_flow=V_flow, r_N=r_N, d=powDer, delta=delta),
                      simplified=V_flow/V_flow_nominal*
                            cha.power(per=per.power, V_flow=V_flow_nominal, r_N=1, d=powDer, delta=delta));
    else
      P_internal = (rho/rho_default)*cha.power(per=per.power, V_flow=V_flow, r_N=r_N, d=powDer, delta=delta);
    end if;
    eta_internal = WFlo/Buildings.Utilities.Math.Functions.smoothMax(
                     x1=P_internal, x2=deltaP, deltaX=deltaP/2);
  elseif per.etaHydMet==
       Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber then
    if homotopyInitialization then
      P_internal = homotopy(actual=cha.power(per=powEu, V_flow=V_flow, r_N=r_N, d=powEuDer, delta=delta),
                      simplified=V_flow/V_flow_nominal*
                            cha.power(per=powEu, V_flow=V_flow_nominal, r_N=1, d=powEuDer, delta=delta));
    else
      P_internal = (rho/rho_default)*cha.power(per=powEu, V_flow=V_flow, r_N=r_N, d=powEuDer, delta=delta);
    end if;
    eta_internal = WFlo / Buildings.Utilities.Math.Functions.smoothMax(
                            x1=P_internal, x2=deltaP, deltaX=deltaP/2);
  elseif per.etaHydMet == Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Efficiency_VolumeFlowRate then
    if homotopyInitialization then
      eta_internal = homotopy(actual=cha.efficiency(per=per.efficiency,     V_flow=V_flow, d=etaDer, r_N=r_N, delta=delta),
                        simplified=cha.efficiency(per=per.efficiency, V_flow=V_flow_max,   d=etaDer, r_N=r_N, delta=delta));
    else
      eta_internal = cha.efficiency(per=per.efficiency, V_flow=V_flow, d=etaDer, r_N=r_N, delta=delta);
    end if;
    P_internal=WFlo/Buildings.Utilities.Math.Functions.smoothMax(
                      x1=eta_internal, x2=1E-2, deltaX=1E-3);
  else // Not provided
    if per.powerOrEfficiencyIsHydraulic then
      eta_internal=0.7;
    else
      eta_internal=0.49;
    end if;
    P_internal=WFlo/eta_internal;
  end if;

  // Motor efficiency etaMot
  if per.etaMotMet == Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_VolumeFlowRate then
    if homotopyInitialization then
      etaMot = homotopy(actual=cha.efficiency(per=per.motorEfficiency,     V_flow=V_flow, d=motDer, r_N=r_N, delta=delta),
                        simplified=cha.efficiency(per=per.motorEfficiency, V_flow=V_flow_max,   d=motDer, r_N=r_N, delta=delta));
    else
      etaMot = cha.efficiency(per=per.motorEfficiency,     V_flow=V_flow, d=motDer, r_N=r_N, delta=delta);
    end if;
  elseif per.etaMotMet==
       Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio then
    if homotopyInitialization then
      etaMot =homotopy(actual=cha.efficiency_yMot(
        per=per.motorEfficiency_yMot,
        y=yMot,
        d=motDer_yMot), simplified=cha.efficiency_yMot(
        per=per.motorEfficiency_yMot,
        y=1,
        d=motDer_yMot));
    else
      etaMot =cha.efficiency_yMot(
        per=per.motorEfficiency_yMot,
        y=yMot,
        d=motDer_yMot);
    end if;
  elseif per.etaMotMet==
       Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve then
      if homotopyInitialization then
        etaMot =homotopy(actual=cha.efficiency_yMot(
          per=per.motorEfficiency_yMot_generic,
          y=yMot,
          d=motDer_yMot_generic), simplified=cha.efficiency_yMot(
          per=per.motorEfficiency_yMot_generic,
          y=1,
          d=motDer_yMot_generic));
      else
        etaMot =cha.efficiency_yMot(
          per=per.motorEfficiency_yMot_generic,
          y=yMot,
          d=motDer_yMot_generic);
      end if;
  else // Not provided
    etaMot = 0.7;
  end if;
end FlowMachineInterface;


Base class for pressure and mass flow source with optional power input. Model of a fictitious pipe that is used as a base class
for a pressure source or to prescribe a mass flow rate.

within Buildings.Fluid.Movers.BaseClasses;
model IdealSource
  "Base class for pressure and mass flow source with optional power input"
  extends Buildings.Fluid.Interfaces.PartialTwoPortTransport(show_T=false);

  // Quantity to control
  parameter Boolean control_m_flow
    "if true, then the mass flow rate is equal to the value of m_flow_in"
  parameter Boolean control_dp = not control_m_flow
    "if true, then the head is equal to the value of dp_in"

  Modelica.Blocks.Interfaces.RealInput m_flow_in(unit="kg/s") if control_m_flow
    "Prescribed mass flow rate"
  Modelica.Blocks.Interfaces.RealInput dp_in(unit="Pa") if control_dp
    "Prescribed pressure difference port_a.p-port_b.p"

protected
  Modelica.Blocks.Interfaces.RealInput m_flow_internal(unit="kg/s")
    "Needed to connect to conditional connector";
  Modelica.Blocks.Interfaces.RealInput dp_internal(unit="Pa")
    "Needed to connect to conditional connector";

equation
  // Ideal control
  if control_m_flow then
    m_flow = m_flow_internal;
  else
    m_flow_internal = 0;
  end if;
  if control_dp then
    dp = dp_internal;
  else
    dp_internal = 0;
  end if;

  connect(dp_internal, dp_in);
  connect(m_flow_internal, m_flow_in);

  // Energy balance (no storage)
  port_a.h_outflow = if allowFlowReversal then inStream(port_b.h_outflow) else Medium.h_default;
  port_b.h_outflow = inStream(port_a.h_outflow);
end IdealSource;


within Buildings.Fluid.Movers;
package BaseClasses "Package with base classes for Buildings.Fluid.Movers"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings.Fluid.Movers.BaseClasses;
partial model PartialFlowMachine
  "Partial model to interface fan or pump models with the medium"
  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
    final massDynamics=energyDynamics,
    final mSenFac=1);
  extends Buildings.Fluid.Interfaces.PartialTwoPort(
    port_a(
      p(start=Medium.p_default),
      h_outflow(start=h_outflow_start)),
    port_b(
      p(start=p_start),
      h_outflow(start=h_outflow_start)));

  replaceable parameter Buildings.Fluid.Movers.Data.Generic per
    constrainedby Buildings.Fluid.Movers.Data.Generic
    "Record with performance data"

  parameter Buildings.Fluid.Types.InputType inputType = Buildings.Fluid.Types.InputType.Continuous
    "Control input type"
  parameter Real constInput = 0 "Constant input set point"
  parameter Real stageInputs[:]
    "Vector of input set points corresponding to stages"

  parameter Boolean computePowerUsingSimilarityLaws
    "= true, compute power exactly, using similarity laws. Otherwise approximate.";

  parameter Boolean addPowerToMedium=true
    "Set to false to avoid any power (=heat and flow work) being added to medium (may give simpler equations)";

  parameter Boolean nominalValuesDefineDefaultPressureCurve = false
    "Set to true to avoid warning if m_flow_nominal and dp_nominal are used to construct the default pressure curve";

  parameter Modelica.Units.SI.Time tau=1
    "Time constant of fluid volume for nominal flow, used if energy or mass balance is dynamic"

  // Classes used to implement the filtered speed
  parameter Boolean use_riseTime=true
    "Set to true to continuously change motor speed"

  parameter Modelica.Units.SI.Time riseTime=30
    "Time needed to change motor speed between zero and full speed"
  parameter Modelica.Blocks.Types.Init init=Modelica.Blocks.Types.Init.InitialOutput
    "Type of initialization (no init/steady state/initial state/initial output)"

  // Connectors and ports
  Modelica.Blocks.Interfaces.IntegerInput stage
    if inputType == Buildings.Fluid.Types.InputType.Stages
    "Stage input signal for the pressure head"

  Modelica.Blocks.Interfaces.RealOutput y_actual(
    final unit="1")
    "Actual normalised fan or pump speed that is used for computations"

  Modelica.Blocks.Interfaces.RealOutput P(
    quantity="Power",
    final unit="W") "Electrical power consumed"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPort(
    T(start=293.15))
    "Heat dissipation to environment"

  // Variables
  Modelica.Units.SI.VolumeFlowRate VMachine_flow(start=_VMachine_flow) = eff.V_flow
    "Volume flow rate";
  Modelica.Units.SI.PressureDifference dpMachine(displayUnit="Pa") = -preSou.dp
    "Pressure difference";

  Real eta(unit="1", final quantity="Efficiency") =    eff.eta "Global efficiency";
  Real etaHyd(unit="1", final quantity="Efficiency") = eff.etaHyd "Hydraulic efficiency";
  Real etaMot(unit="1", final quantity="Efficiency") = eff.etaMot "Motor efficiency";

  // Quantity to control

  // Copied from Fluid.Interfaces.PartialTwoPortInterface
  parameter Modelica.Units.SI.MassFlowRate m_flow_small(min=0) = 1E-4*abs(
    _m_flow_nominal) "Small mass flow rate for regularization of zero flow"
  // Diagnostics
   parameter Boolean show_T = false
    "= true, if actual temperature at port is computed"

  Modelica.Units.SI.MassFlowRate m_flow(start=_m_flow_start) = port_a.m_flow
    "Mass flow rate from port_a to port_b (m_flow > 0 is design flow direction)";

  Modelica.Units.SI.PressureDifference dp(
    start=_dp_start,
    displayUnit="Pa") = port_a.p - port_b.p
    "Pressure difference between port_a and port_b";

  Medium.ThermodynamicState sta_a=
    if allowFlowReversal then
      Medium.setState_phX(port_a.p,
                          noEvent(actualStream(port_a.h_outflow)),
                          noEvent(actualStream(port_a.Xi_outflow)))
    else
      Medium.setState_phX(port_a.p,
                          noEvent(inStream(port_a.h_outflow)),
                          noEvent(inStream(port_a.Xi_outflow)))
      if show_T "Medium properties in port_a";

  Medium.ThermodynamicState sta_b=
    if allowFlowReversal then
      Medium.setState_phX(port_b.p,
                          noEvent(actualStream(port_b.h_outflow)),
                          noEvent(actualStream(port_b.Xi_outflow)))
    else
      Medium.setState_phX(port_b.p,
                          noEvent(port_b.h_outflow),
                          noEvent(port_b.Xi_outflow))
       if show_T "Medium properties in port_b";
  // - Copy continues in protected section

protected
  parameter Modelica.Units.SI.MassFlowRate _m_flow_nominal=
    max(eff.per.pressure.V_flow)*rho_default
    "Nominal mass flow rate";

  // Copied from Fluid.Interfaces.PartialTwoPortInterface
  final parameter Modelica.Units.SI.MassFlowRate _m_flow_start=0
    "Start value for m_flow, used to avoid a warning if not set in m_flow, and to avoid m_flow.start in parameter window";
  final parameter Modelica.Units.SI.PressureDifference _dp_start(displayUnit=
        "Pa") = 0
    "Start value for dp, used to avoid a warning if not set in dp, and to avoid dp.start in parameter window";
  // - End of copy

  final parameter Modelica.Units.SI.VolumeFlowRate _VMachine_flow=0
    "Start value for VMachine_flow, used to avoid a warning if not specified";

  parameter Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable preVar "Type of prescribed variable";

  // The parameter speedIsInput is required to conditionally remove the instance gain.
  // If the conditional removal of this instance where to use the test
  // preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
  // then OpenModelica fails to translate the model with the message
  // .../PartialFlowMachine.mo:185:3-189:70:writable]
  // Error: Variable Types.PrescribedVariable.Speed not found in scope
  // Buildings.Fluid.Movers.SpeedControlled_y$floMac1.
  final parameter Boolean speedIsInput=
    (preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed)
    "Parameter that is true if speed is the controlled variables";

  final parameter Integer nOri = size(per.pressure.V_flow, 1)
    "Number of data points for pressure curve"

  final parameter Boolean haveVMax = eff.haveVMax
    "Flag, true if user specified data that contain V_flow_max";

  final parameter Modelica.Units.SI.VolumeFlowRate V_flow_max=eff.V_flow_max;
  final parameter Modelica.Units.SI.Density rho_default=
    Medium.density_pTX(
      p=Medium.p_default,
      T=Medium.T_default,
      X=Medium.X_default) "Default medium density";

  final parameter Medium.ThermodynamicState sta_start=Medium.setState_pTX(
    T=T_start,
    p=p_start,
    X=X_start) "Medium state at start values";

  final parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_start=
      Medium.specificEnthalpy(sta_start) "Start value for outflowing enthalpy";

  Modelica.Blocks.Sources.Constant[size(stageInputs, 1)] stageValues(
    final k=stageInputs)
   if inputType == Buildings.Fluid.Types.InputType.Stages "Stage input values"
  Modelica.Blocks.Sources.Constant setConst(
    final k=constInput)
   if inputType == Buildings.Fluid.Types.InputType.Constant
    "Constant input set point"

  Extractor extractor(final nin=size(stageInputs,1))
   if inputType == Buildings.Fluid.Types.InputType.Stages "Stage input extractor"

  Modelica.Blocks.Routing.RealPassThrough inputSwitch
    "Dummy connection for easy connection of input options"

  Buildings.Fluid.Delays.DelayFirstOrder vol(
    redeclare final package Medium = Medium,
    final tau=tau,
    final energyDynamics=energyDynamics,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final m_flow_nominal=_m_flow_nominal,
    final m_flow_small=m_flow_small,
    final p_start=p_start,
    final prescribedHeatFlowRate=true,
    final allowFlowReversal=allowFlowReversal,
    nPorts=2) "Fluid volume for dynamic model"

  Modelica.Blocks.Nonlinear.SlewRateLimiter motSpe(
    Rising=1/riseTime,
    Falling=-1/riseTime,
    Td=0.001*riseTime,
    initType=init,
    strict=true)
    if use_riseTime
      "Dynamics of engine speed"

  Buildings.Fluid.Movers.BaseClasses.IdealSource preSou(
    redeclare final package Medium = Medium,
    final m_flow_small=m_flow_small,
    final allowFlowReversal=allowFlowReversal,
    final control_m_flow= (preVar ==  Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.FlowRate))
    "Pressure source"

  Buildings.Fluid.Movers.BaseClasses.PowerInterface heaDis(
    final motorCooledByFluid=per.motorCooledByFluid,
    final delta_V_flow=1E-3*V_flow_max)
   if addPowerToMedium "Heat dissipation into medium"

  Modelica.Blocks.Math.Add PToMed(final k1=1, final k2=1)
 if addPowerToMedium "Heat and work input into medium"

  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow prePow(
    final alpha=0,
    port(T(start=293.15)))
 if addPowerToMedium
    "Prescribed power (=heat and flow work) flow for dynamic model"

  Modelica.Blocks.Sources.RealExpression rho_inlet(y=
    Medium.density(
      Medium.setState_phX(port_a.p,
                          inStream(port_a.h_outflow),
                          inStream(port_a.Xi_outflow))))
    "Density of the inflowing fluid"

  Buildings.Fluid.Sensors.MassFlowRate senMasFlo(
    redeclare final package Medium = Medium) "Mass flow rate sensor"

  Buildings.Fluid.Sensors.RelativePressure senRelPre(
    redeclare final package Medium = Medium) "Head of mover"

  // Because the speed data are not used by FlowMachineInterface, we set them
  // to zero.
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff(
    per(
      final powerOrEfficiencyIsHydraulic = per.powerOrEfficiencyIsHydraulic,
      final efficiency =           per.efficiency,
      final motorEfficiency =      per.motorEfficiency,
      final motorEfficiency_yMot = per.motorEfficiency_yMot,
      final motorCooledByFluid =   per.motorCooledByFluid,
      final speed_nominal =        0,
      final constantSpeed =        0,
      final speeds =               {0},
      final power =                per.power,
      final peak =                 per.peak),
    final nOri = nOri,
    final rho_default=rho_default,
    final computePowerUsingSimilarityLaws=computePowerUsingSimilarityLaws,
    r_V(start=_m_flow_nominal/rho_default),
    final preVar=preVar) "Flow machine"

protected
  block Extractor
    "Extract scalar signal out of signal vector dependent on IntegerRealInput index"
    extends Modelica.Blocks.Interfaces.MISO;

    Modelica.Blocks.Interfaces.IntegerInput index "Integer input for control input"
  equation
  y = sum({if index == i then u[i] else 0 for i in 1:nin});
  end Extractor;

initial algorithm
  // The control signal is dp or m_flow but the user did not provide a fan or pump curve.
  // Hence, the speed is computed using default values, which likely are wrong.
  // Therefore, scaling the power using the speed is inaccurate.
  assert(nominalValuesDefineDefaultPressureCurve or
         per.havePressureCurve or
         (preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed),
"*** Warning in " + getInstanceName() +
": Mover is flow or pressure controlled and uses default pressure curve.
This leads to an approximate power consumption.
Set nominalValuesDefineDefaultPressureCurve=true to suppress this warning.",
         level=AssertionLevel.warning);

  // The control signal is dp or m_flow but the user did not provide a fan or pump curve.
  // Hence, the speed is computed using default values, which likely are wrong.
  // In addition, the user wants to use (V_flow, P) to compute the power.
  // This can lead to using a power that is less than the flow work. We avoid
  // this by ignoring the setting of per.etaHydMet.
  // The comment is split into two parts since otherwise the JModelica C-compiler
  // throws warnings.
  assert(nominalValuesDefineDefaultPressureCurve or
         (per.havePressureCurve or
           (preVar == Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed)) or
         per.etaHydMet<>
      Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate,
"*** Warning in " + getInstanceName() +
": Mover is flow or pressure controlled, uses default pressure curve and
has per.etaHydMet=.Power_VolumeFlowRate.
As this can cause wrong power consumption, the model overrides this setting by using per.etaHydMet=.NotProvided.
Set nominalValuesDefineDefaultPressureCurve=true to suppress this warning.",
         level=AssertionLevel.warning);

  assert(per.havePressureCurve or
          not (per.etaHydMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate
            or per.etaHydMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber),
"*** Warning in " + getInstanceName() +
": Mover has per.etaHydMet=.Power_VolumeFlowRate or per.etaHydMet=.EulerNumber.
This requires per.pressure to be provided.
Because it is not, the model overrides this setting by using per.etaHydMet=.NotProvided.
Also consider using models under Movers.Preconfigured which autopopulate a pressure curve.",
         level=AssertionLevel.warning);

  assert(per.havePressureCurve or per.haveWMot_nominal or
          not (per.etaMotMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
            or per.etaMotMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve),
"*** Warning in " + getInstanceName() +
": Mover has per.etaMotMet=.Efficiency_MotorPartLoadRatio or per.etaMotMet=.GenericCurve.
This requires per.WMot_nominal or per.pressure to be provided. Because neither is provided,
the model overrides this setting and by using per.etaMotMet=.NotProvided.
Also consider using models under Movers.Preconfigured which autopopulate a pressure curve.",
         level=AssertionLevel.warning);

  assert(per.powerOrEfficiencyIsHydraulic or
          not (per.etaMotMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio
            or per.etaMotMet ==
               Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve),
"*** Warning in " + getInstanceName() +
": Mover has per.etaMotMet=.Efficiency_MotorPartLoadRatio or per.etaMotMet=.GenericCurve
and provides information for total electric power instead of hydraulic power.
This forms an algebraic loop. If simulation fails to converge,
see the \"Motor efficiency\" section in the users guide for how to correct it.",
         level=AssertionLevel.warning);

equation
  connect(prePow.port, vol.heatPort)

  connect(vol.heatPort, heatPort)
  connect(preSou.port_b, port_b)
  connect(stageValues.y, extractor.u)
  connect(extractor.y, inputSwitch.u)
  connect(setConst.y, inputSwitch.u)
  connect(extractor.index, stage)

  connect(PToMed.y, prePow.Q_flow)
  connect(PToMed.u1, heaDis.Q_flow)

  connect(senRelPre.port_b, preSou.port_a)
  connect(senRelPre.port_a, preSou.port_b)
  connect(heaDis.V_flow,eff. V_flow)
  connect(eff.PEle, heaDis.PEle)
  connect(eff.WFlo, heaDis.WFlo)
  connect(rho_inlet.y,eff. rho)
  connect(eff.m_flow, senMasFlo.m_flow)
  connect(eff.WFlo, PToMed.u2)
  connect(inputSwitch.y, motSpe.u)
  connect(senRelPre.p_rel, eff.dp_in)
  connect(eff.y_out, y_actual)
  connect(port_a, vol.ports[1])
  connect(vol.ports[2], senMasFlo.port_a)
  connect(senMasFlo.port_b, preSou.port_a)
  connect(eff.WHyd, heaDis.WHyd)
  connect(eff.PEle, P)
end PartialFlowMachine;


Partial model to compute power draw and heat dissipation of fans and pumps. Block that implements the functions to compute the
heat dissipation of fans and pumps. It is used by the model

Buildings.Fluid.Movers.BaseClasses.PartialFlowMachine.

within Buildings.Fluid.Movers.BaseClasses;
model PowerInterface
  "Partial model to compute power draw and heat dissipation of fans and pumps"
  extends Modelica.Blocks.Icons.Block;

  constant Boolean homotopyInitialization = true "= true, use homotopy method"

  parameter Boolean motorCooledByFluid
    "Flag, true if the motor is cooled by the fluid stream";

  parameter Modelica.Units.SI.VolumeFlowRate delta_V_flow
    "Factor used for setting heat input into medium to zero at very small flows";

  Modelica.Blocks.Interfaces.RealInput V_flow(
    final quantity="VolumeFlowRate",
    final unit="m3/s") "Volume flow rate"

  Modelica.Blocks.Interfaces.RealInput WFlo(
    final quantity="Power",
    final unit="W") "Flow work"

  Modelica.Blocks.Interfaces.RealInput WHyd(final quantity="Power", final unit="W")
    "Hydraulic work (converted to flow work and heat)"

  Modelica.Blocks.Interfaces.RealInput PEle(
    final quantity="Power",
    final unit="W") "Electrical power consumed"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(
    quantity="Power",
    final unit="W") "Heat input from fan or pump to medium"

protected
  Modelica.Units.SI.HeatFlowRate QThe_flow
    "Heat input from fan or pump to medium";

  Modelica.Units.SI.Efficiency etaHyd
    "Hydraulic efficiency";

initial equation
  assert(homotopyInitialization, "In " + getInstanceName() +
    ": The constant homotopyInitialization has been modified from its default value. This constant will be removed in future releases.",
    level = AssertionLevel.warning);

equation
  // Hydraulic power (transmitted by shaft), etaHyd = WFlo/WHyd
  etaHyd = WFlo / Buildings.Utilities.Math.Functions.smoothMax(
                     x1=WHyd, x2=1E-5, deltaX=1E-6);
  // Heat input into medium
  QThe_flow +  WFlo = if motorCooledByFluid then PEle else WHyd;
  // At m_flow = 0, the solver may still obtain positive values for QThe_flow.
  // The next statement sets the heat input into the medium to zero for very small flow rates.
  Q_flow = if homotopyInitialization then
    homotopy(actual=Buildings.Utilities.Math.Functions.regStep(
                      y1=QThe_flow,
                      y2=0,
                      x=noEvent(abs(V_flow))-2*delta_V_flow,
                      x_small=delta_V_flow),
            simplified=0)
    else
      Buildings.Utilities.Math.Functions.regStep(
                      y1=QThe_flow,
                      y2=0,
                      x=noEvent(abs(V_flow))-2*delta_V_flow,
                      x_small=delta_V_flow);
end PowerInterface;


Package with type definitions

within Buildings.Fluid.Movers.BaseClasses;
package Types "Package with type definitions"
  extends Modelica.Icons.TypesPackage;

  type PrescribedVariable = enumeration(
      Speed "Speed is prescribed",
      FlowRate "Flow rate is prescribed",
      PressureDifference "Pressure difference is prescribed")
    "Enumeration to choose what variable is prescribed";
  type HydraulicEfficiencyMethod = enumeration(
      NotProvided
        "Not provided, computed from other efficiency terms",
      Efficiency_VolumeFlowRate "Array of efficiency vs. volumetric flow rate",
      Power_VolumeFlowRate
        "Array of power vs. volumetric flow rate",
      EulerNumber
        "One peak point to be used for the Euler number")
    "Enumeration to choose the computation method for total efficiency and hydraulic efficiency"

  type MotorEfficiencyMethod = enumeration(
      NotProvided
        "Not provided, computed from other efficiency terms",
      Efficiency_VolumeFlowRate "Array of efficiency vs. volumetric flow rate",
      Efficiency_MotorPartLoadRatio
        "Rated input and array of efficiency vs. motor part load ratio yMot=WHyd/WMot_nominal",
      GenericCurve
        "Rated input and maximum efficiency to be used for generic curves")
    "Enumeration to choose the computation method for motor efficiency"
end Types;


within Buildings.Fluid.Movers.BaseClasses;
package Characteristics "Functions for fan or pump characteristics"
end Characteristics;


within Buildings.Fluid.Movers.BaseClasses;
package Euler "Functions and data record templates for Euler number"
end Euler;


Validates the Euler number method at nominal mover speed. This model validates the power and efficiency computation using the Euler number
and its correlation as implemented in

Buildings.Fluid.Movers.BaseClasses.Euler.
Its results of calculated efficiency and power are compared with those obtained
using power curves.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model EulerComparison
  "Validates the Euler number method at nominal mover speed"
  extends Modelica.Icons.Example;

  parameter Integer nOri(min=1)=size(per1.power.V_flow,1)
    "Number of data points for pressure curve"
  parameter Modelica.Units.SI.Density rhoCon=1.2
    "Constant density";

  parameter Buildings.Fluid.Movers.Data.Fans.Greenheck.BIDW13 per1
    "Mover performance curves with flow rate, pressure rise, and power";
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff1(
    per=per1,
    rho_default=rhoCon,
    nOri=nOri,
    computePowerUsingSimilarityLaws=false)
    "Flow machine interface using power characteristic"
  parameter Buildings.Fluid.Movers.Data.Generic per2(
    final powerOrEfficiencyIsHydraulic=per1.powerOrEfficiencyIsHydraulic,
    final etaHydMet=
           Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber,
    final etaMotMet=
           Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided,
    pressure=per1.pressure,
    power=per1.power)
    "Peak condition";
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff2(
    per=per2,
    rho_default=rhoCon,
    nOri=nOri,
    computePowerUsingSimilarityLaws=false)
    "Flow machine interface using power Euler correlation"

  Modelica.Blocks.Sources.Constant y(k=1) "Control signal"
  Modelica.Blocks.Sources.Constant rho(k=rhoCon) "Density"

  Modelica.Units.SI.VolumeFlowRate V_flow = m_flow.y/rhoCon "Volumetric flow rate";
  Modelica.Units.SI.Power P1(displayUnit="W")=
    if per1.powerOrEfficiencyIsHydraulic
    then eff1.WHyd
    else eff1.PEle
    "Power from interpolation (total or hydraulic depending on data)";
  Modelica.Units.SI.Power P2(displayUnit="W")=
    if per1.powerOrEfficiencyIsHydraulic
    then eff2.WHyd
    else eff2.PEle
    "Power from Euler number (total or hydraulic depending on data)";
  Modelica.Units.SI.Efficiency eta1=
    if per1.powerOrEfficiencyIsHydraulic
    then eff1.etaHyd
    else eff1.eta
    "Efficiency from interpolation (total or hydraulic depending on data)";
  Modelica.Units.SI.Efficiency eta2=
    if per1.powerOrEfficiencyIsHydraulic
    then eff2.etaHyd
    else eff2.eta
    "Efficiency from Euler number (total or hydraulic depending on data)";

  Modelica.Blocks.Sources.Ramp m_flow(
    height=eff1.V_flow_max*rhoCon,
    duration=1)
    "Mass flow rate"
equation
  connect(eff1.rho, rho.y)
  connect(y.y, eff1.y_in)
  connect(m_flow.y, eff1.m_flow)
  connect(y.y, eff2.y_in)
  connect(rho.y, eff2.rho)
  connect(m_flow.y, eff2.m_flow)
end EulerComparison;


Displays the curve of the Euler number's correlation. This validation model plots and compares out the original correlation function
of Euler number and its polynomial approximation.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model EulerCurve "Displays the curve of the Euler number's correlation"
  extends Modelica.Icons.Example;
  Real x "log10(Eu/Eu_peak)";
  Real etaApp "eta/eta_peak from polynomial approximation";
  Real etaOri "eta/eta_peak from original correlation";

function correlation
  input Real x "log10(Eu/Eu_peak)";
  output Real y "eta/eta_peak";
  protected
  constant Real a=-2.732094, b=2.273014, c=0.196344, d=5.267518;
  Real Z1, Z2, Z3;
algorithm
  Z1:=(x-a)/b;
  Z2:=((exp(c*x)*d*x)-a)/b;
  Z3:=-a/b;
  y:=(exp(-0.5*Z1^2)*(1+sign(Z2)*Modelica.Math.Special.erf(u=abs(Z2)/sqrt(2))))
    /(exp(-0.5*Z3^2)*(1+Modelica.Math.Special.erf(u=Z3/sqrt(2))));
end correlation;

initial equation
  x = -4;
equation
  der(x)=1;
  etaApp = Buildings.Fluid.Movers.BaseClasses.Euler.correlation(x);
  etaOri = correlation(x);
end EulerCurve;


Validates the Euler number method at reduced mover speed. Similar to

Buildings.Fluid.Movers.BaseClasses.Validation.EulerComparison,
this model compares the power computed from the Euler number method with measured
values. The power data at speeds N=4100, 3400, and 2500 RPM from the power map of

Buildings.Fluid.Movers.Data.Fans.Greenheck.BIDW13
(shown below) was used.
Note that the pressure curves in the figure do not seem to be independently
measured but rather simply scaled from the nominal curve.
However, because the Euler number method also does not account for the efficiency
degradation along any curve Δp=kV̇2,
using the Euler number method has equivalent accuracy to using such power maps.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model EulerReducedSpeed
  "Validates the Euler number method at reduced mover speed"
  extends Modelica.Icons.Example;

  parameter Integer nOri(min=1)=size(per1.power.V_flow,1)
    "Number of data points for pressure curve"
  parameter Modelica.Units.SI.Density rhoCon=1.2
    "Constant density";

  parameter Buildings.Fluid.Movers.Data.Fans.Greenheck.BIDW13 per1(
    final etaHydMet=
            Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber,
    final etaMotMet=
            Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided)
    "Mover performance curves with flow rate, pressure rise, and power";
  parameter Buildings.Fluid.Movers.Data.Generic per2(
    power(V_flow={0.939939939939939,1.88588588588588,2.83183183183181,
         3.77177177177175,4.46546546546544}, P={4332.517,5801.546,6651.644,
         6800.784,6502.504}),
    pressure(V_flow={0.939939939939939,1.88588588588588,2.83183183183181,
            3.77177177177175,4.46546546546544}, dp={2081.78137651821,
            2008.90688259109,1474.4939271255,704.453441295546,0}))
    "Mover performance curves at reduced speed N=3400";
  parameter Buildings.Fluid.Movers.Data.Generic per3(
    power(V_flow={0.939939939939939,1.88588588588588,2.83183183183181,
         3.28228228228228}, P={1998.476,2535.38,2714.348,2550.294}),
    pressure(V_flow={0.939939939939939,1.88588588588588,2.83183183183181,
            3.28228228228228}, dp={1127.12550607287,903.643724696356,
            340.080971659919,0}))
    "Mover performance curves at reduced speed N=2500";

  Modelica.Units.SI.Power pow1=
    Buildings.Utilities.Math.Functions.smoothInterpolation(
      x=m1_flow.y/rhoCon,
      xSup=per1.power.V_flow,
      ySup=per1.power.P,
      ensureMonotonicity=false) "Measured power";
  Modelica.Units.SI.Power pow2=
    Buildings.Utilities.Math.Functions.smoothInterpolation(
      x=m2_flow.y/rhoCon,
      xSup=per2.power.V_flow,
      ySup=per2.power.P,
      ensureMonotonicity=false) "Measured power";
  Modelica.Units.SI.Power pow3=
    Buildings.Utilities.Math.Functions.smoothInterpolation(
      x=m3_flow.y/rhoCon,
      xSup=per3.power.V_flow,
      ySup=per3.power.P,
      ensureMonotonicity=false) "Measured power";

  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff1(
    per=per1,
    rho_default=rhoCon,
    nOri=nOri,
    computePowerUsingSimilarityLaws=false)
    "Flow machine interface using power characteristic"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff2(
    per=per1,
    rho_default=rhoCon,
    nOri=nOri,
    computePowerUsingSimilarityLaws=false)
    "Flow machine interface using power Euler correlation"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff3(
    per=per1,
    rho_default=rhoCon,
    nOri=nOri,
    computePowerUsingSimilarityLaws=false)
    "Flow machine interface using power Euler correlation"

  Modelica.Blocks.Sources.Constant y1(k=1) "Control signal"
  Modelica.Blocks.Sources.Constant rho(k=rhoCon) "Density"
  Modelica.Blocks.Sources.Constant y2(k=3400/4100)
    "Reduced speed y = 3400/4100 = 0.829"
  Modelica.Blocks.Sources.Constant y3(k=2500/4100)
    "Reduced speed y = 2500/4100 = 0.610"

  Modelica.Blocks.Sources.Ramp m1_flow(
    height=0.98*max(per1.pressure.V_flow)*rhoCon,
    duration=1,
    offset=0.01*max(per1.pressure.V_flow)*rhoCon)
    "Mass flow rate"
  Modelica.Blocks.Sources.Ramp m2_flow(
    height=0.98*max(per2.pressure.V_flow)*rhoCon,
    duration=1,
    offset=0.01*max(per2.pressure.V_flow)*rhoCon) "Mass flow rate"
  Modelica.Blocks.Sources.Ramp m3_flow(
    height=0.98*max(per3.pressure.V_flow)*rhoCon,
    duration=1,
    offset=0.01*max(per3.pressure.V_flow)*rhoCon) "Mass flow rate"

equation
  connect(eff1.rho, rho.y)
  connect(y1.y, eff1.y_in)
  connect(m1_flow.y, eff1.m_flow)
  connect(rho.y, eff2.rho)
  connect(y2.y, eff2.y_in)
  connect(m2_flow.y, eff2.m_flow)
  connect(y3.y, eff3.y_in)
  connect(m3_flow.y, eff3.m_flow)
  connect(rho.y, eff3.rho)
end EulerReducedSpeed;


Simple model to validate FlowMachineInterface. Simple validation model for the flow machine interface model.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model FlowMachineInterface "Simple model to validate FlowMachineInterface"
  extends Modelica.Icons.Example;
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff(
    per(pressure(V_flow={0,1}, dp={1000,0})),
    rho_default=1.2,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true) "Flow machine interface model"
  Modelica.Blocks.Sources.Constant y(k=1) "Control signal"
  Modelica.Blocks.Sources.Constant rho(k=1.2) "Density"
  Modelica.Blocks.Sources.Ramp m_flow(height=1.2, duration=1) "Mass flow rate"
equation
  connect(eff.rho, rho.y)
  connect(m_flow.y, eff.m_flow)
  connect(y.y, eff.y_in)
end FlowMachineInterface;


Validation model for different hydraulic efficiency options. This is a simple validation model for

Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface
where its hydraulic efficiency ηhyd is specified with
different methods defined in

Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model HydraulicEfficiencyMethods
  "Validation model for different hydraulic efficiency options"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Movers.Data.Generic per(
    powerOrEfficiencyIsHydraulic=true,
    final pressure(V_flow={0,1}, dp={1000,0}),
    final etaMotMet=Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided)
    "Performance record";
  parameter Modelica.Units.SI.Density rhoFlu=1.2 "Fluid density";

  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff1(
    per(
      powerOrEfficiencyIsHydraulic=per.powerOrEfficiencyIsHydraulic,
      pressure=per.pressure,
      etaMotMet=per.etaMotMet,
      etaHydMet=Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.NotProvided),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaHydMet=.NotProvided"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff2(
    per(
      powerOrEfficiencyIsHydraulic=per.powerOrEfficiencyIsHydraulic,
      pressure=per.pressure,
      etaMotMet=per.etaMotMet,
      etaHydMet=Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Efficiency_VolumeFlowRate,
      efficiency(V_flow={0,0.3,0.6,0.8,1}, eta={0,0.5,0.7,0.5,0})),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaHydMet=.Efficiency_VolumeFlowRate"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff3(
    per(
      powerOrEfficiencyIsHydraulic=per.powerOrEfficiencyIsHydraulic,
      pressure=per.pressure,
      etaMotMet=per.etaMotMet,
      etaHydMet=Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.Power_VolumeFlowRate,
      power(V_flow={0,0.25,0.5,0.75,1}, P={480,540,510,550,710})),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaHydMet=.Power_VolumeFlowRate"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff4(
    per(
      powerOrEfficiencyIsHydraulic=per.powerOrEfficiencyIsHydraulic,
      pressure=per.pressure,
      etaMotMet=per.etaMotMet,
      etaHydMet=Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.EulerNumber,
      peak(
        V_flow=0.5,
        dp=500,
        eta=0.7)),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaHydMet=.EulerNumber"

  Modelica.Blocks.Sources.Constant y(k=1) "Relative speed"
  Modelica.Blocks.Sources.Ramp m_flow(height=rho.k, duration=1) "Mass flow rate"
  Modelica.Blocks.Sources.Constant rho(k=rhoFlu) "Density"

equation
  connect(eff3.rho, rho.y)
  connect(rho.y,eff4. rho)
  connect(m_flow.y,eff3. m_flow)
  connect(m_flow.y,eff4. m_flow)
  connect(m_flow.y, eff1.m_flow)
  connect(m_flow.y, eff2.m_flow)
  connect(rho.y, eff2.rho)
  connect(rho.y, eff1.rho)
  connect(y.y, eff1.y_in)
  connect(y.y, eff2.y_in)
  connect(y.y, eff3.y_in)
  connect(y.y, eff4.y_in)
end HydraulicEfficiencyMethods;


Validation model for different motor efficiency options. This is a simple validation model for

Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface
where its motor efficiency ηmot is specified with
different methods defined in

Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.
The instance eff4 specifies ηmot as
a function of motor PLR by finding a generic curve with the maximum motor
efficiency and rated motor input power. The generic curve is generated by

Buildings.Fluid.Movers.BaseClasses.Characteristics.motorEfficiencyCurve.

within Buildings.Fluid.Movers.BaseClasses.Validation;
model MotorEfficiencyMethods
  "Validation model for different motor efficiency options"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Movers.Data.Generic per(
    final pressure(V_flow={0,1}, dp={1000,0}),
    final etaHydMet=Buildings.Fluid.Movers.BaseClasses.Types.HydraulicEfficiencyMethod.NotProvided)
    "Performance record";
  parameter Modelica.Units.SI.Density rhoFlu=1.2 "Fluid density";

  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff1(
    per(
      pressure=per.pressure,
      etaHydMet=per.etaHydMet,
      etaMotMet=Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.NotProvided),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaMotMet=.NotProvided"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff2(
    per(
      pressure=per.pressure,
      etaHydMet=per.etaHydMet,
      etaMotMet=Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_VolumeFlowRate,
      motorEfficiency(V_flow={0,0.3,0.6,1}, eta={0,0.4,0.6,0.7})),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaMotMet=.Efficiency_VolumeFlowRate"

  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff3(
    per(
      pressure=per.pressure,
      etaHydMet=per.etaHydMet,
      etaMotMet=Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.Efficiency_MotorPartLoadRatio,
      motorEfficiency_yMot(y={0,0.25,0.5,1}, eta={0,0.56,0.7,0.7}),
      WMot_nominal=500),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaMotMet=.Efficiency_MotorPartLoadRatio"
  Buildings.Fluid.Movers.BaseClasses.FlowMachineInterface eff4(
    per(
      pressure=per.pressure,
      etaHydMet=per.etaHydMet,
      etaMotMet=Buildings.Fluid.Movers.BaseClasses.Types.MotorEfficiencyMethod.GenericCurve,
      etaMot_max=0.7,
      WMot_nominal=500),
    rho_default=rhoFlu,
    nOri=2,
    preVar=Buildings.Fluid.Movers.BaseClasses.Types.PrescribedVariable.Speed,
    computePowerUsingSimilarityLaws=true)
    "FlowMachineInterface with per.etaMotMet=.GenericCurve"

  Modelica.Blocks.Sources.Constant y(k=1) "Relative speed"
  Modelica.Blocks.Sources.Ramp m_flow(height=1*rhoFlu, duration=1) "Mass flow rate"
  Modelica.Blocks.Sources.Constant rho(k=rhoFlu) "Density"

equation
  connect(eff3.rho, rho.y)
  connect(rho.y,eff4. rho)
  connect(m_flow.y,eff3. m_flow)
  connect(m_flow.y,eff4. m_flow)
  connect(m_flow.y, eff1.m_flow)
  connect(m_flow.y, eff2.m_flow)
  connect(rho.y, eff2.rho)
  connect(rho.y, eff1.rho)
  connect(y.y, eff1.y_in)
  connect(y.y, eff2.y_in)
  connect(y.y, eff3.y_in)
  connect(y.y, eff4.y_in)
end MotorEfficiencyMethods;