Container class for three-way valves

within Buildings.Fluid.HydronicConfigurations.Components;
model ThreeWayValve "Container class for three-way valves"
  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
    final massDynamics=energyDynamics,
    final mSenFac=1);
  extends Buildings.Fluid.Actuators.BaseClasses.ValveParameters(
    rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

  parameter Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic
    typCha=Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.EqualPercentage
    "Valve characteristic"

  parameter Buildings.Fluid.Actuators.Valves.Data.Generic flowCharacteristics1(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics for direct flow path at port_1"
  parameter Buildings.Fluid.Actuators.Valves.Data.Generic flowCharacteristics3(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics for bypass flow path at port_3"

  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal[2](
    each displayUnit="Pa")={0,0}
    "Nominal pressure drop of pipes and other equipment in flow legs at port_1 and port_3"

  parameter Real R = 50 "Rangeability, R=50...100 typically"
  parameter Real delta0 = 0.01
    "Range of significant deviation from equal percentage law"

  parameter Real fraK(min=0, max=1) = 1.0
    "Fraction Kv(port_3→port_2)/Kv(port_1→port_2)";
  parameter Real[2] l(each min=0, each max=1) = {0.0001, 0.0001}
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";
  parameter Real deltaM = 0.02
    "Fraction of nominal flow rate where linearization starts, if y=1"

  parameter Boolean[2] linearized = {false, false}
    "= true, use linear relation between m_flow and dp for any flow rate"

  parameter Modelica.Units.SI.Time tau=10
    "Time constant at nominal flow for dynamic energy and momentum balance"

  parameter Boolean from_dp = true
    "= true, use m_flow = f(dp) else dp = f(m_flow)"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_1=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_1"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_2=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_2"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_3=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_3"
  parameter Boolean verifyFlowReversal = false
    "=true, to assert that the flow does not reverse when portFlowDirection_* does not equal Bidirectional"

  parameter Boolean use_strokeTime=true
    "Set to true to continuously open and close valve"
  parameter Modelica.Units.SI.Time strokeTime=120
    "Time needed to open or close valve"
  parameter Modelica.Blocks.Types.Init init=Modelica.Blocks.Types.Init.InitialOutput
    "Type of initialization (no init/steady state/initial state/initial output)"
  parameter Real y_start=1 "Initial position of actuator"

  final parameter Modelica.Units.SI.PressureDifference dp3Valve_nominal(
    displayUnit="Pa")=dpValve_nominal/fraK^2
    "Bypass branch valve pressure drop at design conditions"
  final parameter Modelica.Units.SI.PressureDifference dp3Fixed_nominal(
    displayUnit="Pa")=dpFixed_nominal[2]
    "Bypass branch fixed pressure drop at design conditions"
  final parameter Modelica.Units.SI.PressureDifference dp3_nominal(
    displayUnit="Pa")=dp3Valve_nominal + dp3Fixed_nominal
    "Bypass branch total pressure drop at design conditions"

  // Variables
  Modelica.Units.SI.MassFlowRate m1_flow = port_1.m_flow
    "Mass flow rate in direct branch";
  Modelica.Units.SI.MassFlowRate m2_flow = -1 * port_2.m_flow
    "Mass flow rate in common branch";
  Modelica.Units.SI.MassFlowRate m3_flow = port_3.m_flow
    "Mass flow rate in bypass branch";
  Modelica.Units.SI.PressureDifference dp1(displayUnit="Pa") = port_1.p - port_2.p
    "Pressure drop across direct branch";
  Modelica.Units.SI.PressureDifference dp3(displayUnit="Pa") = port_3.p - port_2.p
    "Pressure drop across bypass branch";

  Modelica.Fluid.Interfaces.FluidPort_a port_1(
    redeclare final package Medium = Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_1 == Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_1== Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "First port, typically inlet"
  Modelica.Fluid.Interfaces.FluidPort_b port_2(
    redeclare final package Medium = Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_2 == Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_2 == Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "Second port, typically outlet"
  Modelica.Fluid.Interfaces.FluidPort_a port_3(
    redeclare final package Medium=Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "Third port, can be either inlet or outlet"

  Buildings.Controls.OBC.CDL.Interfaces.RealInput y(final unit="1")
    "Input control signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y_actual
    "Actual actuator position"

  Actuators.Valves.ThreeWayEqualPercentageLinear valEquLin(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final R=R,
    final delta0=delta0,
    final l=l,
    final fraK=fraK,
    final deltaM=deltaM,
    final tau=tau,
    final from_dp=from_dp,
    final portFlowDirection_1=portFlowDirection_1,
    final portFlowDirection_2=portFlowDirection_2,
    final portFlowDirection_3=portFlowDirection_3,
    final verifyFlowReversal=verifyFlowReversal,
    final linearized=linearized,
    final energyDynamics=energyDynamics,
    final p_start=p_start,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final C_nominal=C_nominal,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.EqualPercentage
    "Three-way valve with equal percentage and linear characteristics"

  Actuators.Valves.ThreeWayLinear valLinLin(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final l=l,
    final fraK=fraK,
    final deltaM=deltaM,
    final tau=tau,
    final from_dp=from_dp,
    final portFlowDirection_1=portFlowDirection_1,
    final portFlowDirection_2=portFlowDirection_2,
    final portFlowDirection_3=portFlowDirection_3,
    final verifyFlowReversal=verifyFlowReversal,
    final linearized=linearized,
    final energyDynamics=energyDynamics,
    final p_start=p_start,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final C_nominal=C_nominal,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.Linear
    "Three-way valve with linear characteristics"
  Actuators.Valves.ThreeWayTable valTab(
    redeclare final package Medium = Medium,
    final flowCharacteristics1=flowCharacteristics1,
    final flowCharacteristics3=flowCharacteristics3,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final fraK=fraK,
    final deltaM=deltaM,
    final tau=tau,
    final from_dp=from_dp,
    final portFlowDirection_1=portFlowDirection_1,
    final portFlowDirection_2=portFlowDirection_2,
    final portFlowDirection_3=portFlowDirection_3,
    final verifyFlowReversal=verifyFlowReversal,
    final linearized=linearized,
    final energyDynamics=energyDynamics,
    final p_start=p_start,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final C_nominal=C_nominal,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.Table
    "Three-way valve with table-specified characteristics"

initial equation
  assert(typCha<>Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.PressureIndependent,
    "In " + getInstanceName() +
    ": The pressure independent option is only available for two-way valves.");

equation

  connect(y, valEquLin.y)
  connect(valEquLin.y_actual, y_actual)
  connect(port_3, valEquLin.port_3)
  connect(valEquLin.port_2, port_2)
  connect(port_1, valEquLin.port_1)
  connect(port_1, valLinLin.port_1)
  connect(y, valLinLin.y)
  connect(valLinLin.port_3, port_3)
  connect(y, valTab.y)
  connect(valTab.port_1, port_1)
  connect(port_3, valTab.port_3)
  connect(valTab.port_2, port_2)
  connect(valTab.y_actual, y_actual)
  connect(valLinLin.y_actual, y_actual)
  connect(valLinLin.port_2, port_2)
end ThreeWayValve;


Container class for two-way valves

within Buildings.Fluid.HydronicConfigurations.Components;
model TwoWayValve "Container class for two-way valves"
  extends Buildings.Fluid.Interfaces.PartialTwoPortInterface;
  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
    final massDynamics=energyDynamics,
    final mSenFac=1);
  extends Buildings.Fluid.Actuators.BaseClasses.ValveParameters(
    rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

  parameter Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic
    typCha=Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.EqualPercentage
    "Valve characteristic"

  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal(
    displayUnit="Pa",
    min=0) = 0 "Pressure drop of pipe and other resistances that are in series"

  parameter Real l(min=1e-10, max=1) = 0.0001
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";

  parameter Buildings.Fluid.Actuators.Valves.Data.Generic flowCharacteristics(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics"

  parameter Real R=50
    "Rangeability, R=50...100 typically"
  parameter Real delta0=0.01
    "Range of significant deviation from equal percentage law"

  parameter Boolean from_dp = false
    "= true, use m_flow = f(dp) else dp = f(m_flow)"

  parameter Boolean linearized = false
    "= true, use linear relation between m_flow and dp for any flow rate"

  parameter Boolean use_strokeTime=true
    "Set to true to continuously open and close valve"
  parameter Modelica.Units.SI.Time strokeTime=120
    "Time needed to open or close valve"
  parameter Modelica.Blocks.Types.Init init=Modelica.Blocks.Types.Init.InitialOutput
    "Type of initialization (no init/steady state/initial state/initial output)"
  parameter Real y_start=1 "Initial position of actuator"

  Buildings.Controls.OBC.CDL.Interfaces.RealInput y(final unit="1")
    "Input control signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y_actual
    "Actual actuator position"

  Actuators.Valves.TwoWayEqualPercentage valEqu(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final R=R,
    final delta0=delta0,
    final l=l,
    final deltaM=deltaM,
    final from_dp=from_dp,
    final allowFlowReversal=allowFlowReversal,
    final linearized=linearized,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.EqualPercentage
    "Two-way valve with equal percentage characteristic"
  Actuators.Valves.TwoWayLinear valLin(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final l=l,
    final deltaM=deltaM,
    final from_dp=from_dp,
    final allowFlowReversal=allowFlowReversal,
    final linearized=linearized,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.Linear
    "Two-way valve with linear characteristic"
  Actuators.Valves.TwoWayTable valTab(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final deltaM=deltaM,
    final from_dp=from_dp,
    final allowFlowReversal=allowFlowReversal,
    final linearized=linearized,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start,
    final flowCharacteristics=flowCharacteristics) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.Table
    "Two-way valve with table-specified characteristic"
  Actuators.Valves.TwoWayPressureIndependent valPre(
    redeclare final package Medium = Medium,
    final CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    final m_flow_nominal=m_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final rhoStd=rhoStd,
    final dpFixed_nominal=dpFixed_nominal,
    final l=l,
    final deltaM=deltaM,
    final from_dp=from_dp,
    final allowFlowReversal=allowFlowReversal,
    final use_strokeTime=use_strokeTime,
    final strokeTime=strokeTime,
    final init=init,
    final y_start=y_start) if typCha == Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.PressureIndependent
    "Pressure-independent two-way valve"
initial equation
  assert(dpFixed_nominal > -Modelica.Constants.eps, "In " + getInstanceName() +
  ": Model requires dpFixed_nominal >= 0 but received dpFixed_nominal = "
    + String(dpFixed_nominal) + " Pa.");
equation
  connect(y, valEqu.y)
  connect(valEqu.y_actual, y_actual)
  connect(port_a, valEqu.port_a)
  connect(valEqu.port_b, port_b)
  connect(port_a, valTab.port_a)
  connect(port_a, valLin.port_a)
  connect(valLin.port_b, port_b)
  connect(valLin.y_actual, y_actual)
  connect(y, valLin.y)
  connect(y, valTab.y)
  connect(valTab.port_b, port_b)
  connect(valTab.y_actual, y_actual)
  connect(port_a, valPre.port_a)
  connect(valPre.port_b, port_b)
  connect(y, valPre.y)
  connect(valPre.y_actual, y_actual)
end TwoWayValve;


Package with operating mode definitions

within Buildings.Fluid.HydronicConfigurations.Controls;
package OperatingModes "Package with operating mode definitions"
  constant Integer disabled = 0 "Disabled";
  constant Integer enabled = 1 "Enabled (cooling mode for change-over circuits)";
  constant Integer heating = 2 "Heating mode for change-over circuits only";
end OperatingModes;


Package with control blocks

within Buildings.Fluid.HydronicConfigurations;
package Controls "Package with control blocks"
  extends Modelica.Icons.VariantsPackage;
end Controls;


PID controller with operating mode input. This block adds the following features to

Buildings.Controls.OBC.CDL.Reals.PIDWithReset.

within Buildings.Fluid.HydronicConfigurations.Controls;
block PIDWithOperatingMode "PID controller with operating mode input"

  parameter Buildings.Controls.OBC.CDL.Types.SimpleController controllerType=
    Buildings.Controls.OBC.CDL.Types.SimpleController.PI
    "Type of controller";
  parameter Real k(
    min=100*Buildings.Controls.OBC.CDL.Constants.eps)=1
    "Gain of controller"
  parameter Real Ti(unit="s")=0.5
    "Time constant of integrator block"
  parameter Real Td(unit="s")=0.1
    "Time constant of derivative block"
  parameter Real r(
    min=100*Buildings.Controls.OBC.CDL.Constants.eps)=1
    "Typical range of control error, used for scaling the control error";
  parameter Real yMax=1
    "Upper limit of output"
  parameter Real yMin=0
    "Lower limit of output"
  parameter Real Ni(
    min=100*Buildings.Controls.OBC.CDL.Constants.eps)=0.9
    "Ni*Ti is time constant of anti-windup compensation"
  parameter Real Nd(
    min=100*Buildings.Controls.OBC.CDL.Constants.eps)=10
    "The higher Nd, the more ideal the derivative block"
  parameter Boolean reverseActing=true
    "Set to true for reverse acting, or false for direct acting control action";
  parameter Real y_reset=y_neutral
    "Value to which the controller output is reset at enable time or change-over switch"
  parameter Real y_neutral=0
    "Value to which the controller output is reset at disable time";
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u_s
    "Connector of setpoint input signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput u_m
    "Connector of measurement input signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput y
    "Connector of actuator output signal"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput mode "Control mode"
  Buildings.Controls.OBC.CDL.Reals.PIDWithReset conPID(
    final controllerType=controllerType,
    final k=k,
    final Ti=Ti,
    final Td=Td,
    final r=r,
    final yMax=yMax,
    final yMin=yMin,
    final Ni=Ni,
    final Nd=Nd,
    final xi_start=y_neutral,
    final yd_start=y_neutral,
    final reverseActing=reverseActing,
    final y_reset=y_reset)
    "Controller"
  Buildings.Controls.OBC.CDL.Integers.Change cha
    "Monitor change of signal value"
  Buildings.Controls.OBC.CDL.Reals.Multiply mulSet
    "Multiply input with mapping coefficient"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor extIndSig(final nin=3)
    "Select mapping coefficient based on operating mode"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant map_ms[3](k={0,1,-1})
    "Map set point and measured values depending on actual operating mode"
  Buildings.Controls.OBC.CDL.Reals.Multiply mulMea
    "Multiply input with mapping coefficient"
  Buildings.Controls.OBC.CDL.Reals.Switch swi
  Buildings.Controls.OBC.CDL.Integers.LessEqualThreshold isDis(t=Controls.OperatingModes.disabled)
    "Returns true if disabled"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant disVal(k=y_neutral)
    "Value when disabled"
  Buildings.Controls.OBC.CDL.Integers.AddParameter addPar(final p=1)
    "Convert mode index to array index"
equation
  connect(cha.y, conPID.trigger)
  connect(u_s, mulSet.u2)
  connect(extIndSig.y, mulSet.u1)
  connect(u_m, mulMea.u2)
  connect(extIndSig.y, mulMea.u1)
  connect(mulMea.y, conPID.u_m)
  connect(mulSet.y, conPID.u_s)
  connect(mode, cha.u)
  connect(swi.y, y)
  connect(conPID.y, swi.u3)
  connect(isDis.y, swi.u2)
  connect(mode, isDis.u)
  connect(disVal.y, swi.u1)
  connect(addPar.y, extIndSig.index)
  connect(mode, addPar.u)
  connect(map_ms.y, extIndSig.u)
end PIDWithOperatingMode;


Validation models

within Buildings.Fluid.HydronicConfigurations.Controls;
package Validation "Validation models"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


Test model for PID controller with operating mode. This model validates the block

Buildings.Fluid.HydronicConfigurations.Controls.PIDWithOperatingMode
configured either as a PI or as a PID controller.

within Buildings.Fluid.HydronicConfigurations.Controls.Validation;
model PIDWithOperatingMode "Test model for PID controller with operating mode"
  Buildings.Fluid.HydronicConfigurations.Controls.PIDWithOperatingMode limPIDPar(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PID,
    k=1,
    Ti=1,
    Td=1,
    yMin=-1,
    y_reset=0.5)
    "Controller, reset to parameter value"
  Buildings.Controls.OBC.CDL.Reals.IntegratorWithReset intWitRes1
    "Integrator whose output should be brought to the set point"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant resVal(k=1)
         "Reset value"
  Buildings.Fluid.HydronicConfigurations.Controls.PIDWithOperatingMode limPIPar(
    k=1,
    Ti=1,
    Td=1,
    yMin=-1,
    y_reset=0.5)
    "Controller, reset to parameter value"
  Buildings.Controls.OBC.CDL.Reals.IntegratorWithReset intWitRes3
    "Integrator whose output should be brought to the set point"
  Buildings.Controls.OBC.CDL.Logical.Sources.Constant con(
    final k=false)
    "Constant false"

  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable mode(table=[0,0; 1,0; 1,
        1; 5,1; 5,2; 9,2; 9,0; 10,0], period=10)
                                      "Operating mode"
equation
  connect(intWitRes1.u,limPIDPar.y)
  connect(intWitRes1.y,limPIDPar.u_m)
  connect(intWitRes3.u,limPIPar.y)
  connect(intWitRes3.y,limPIPar.u_m)
  connect(resVal.y,intWitRes1.y_reset_in)
  connect(resVal.y,intWitRes3.y_reset_in)
  connect(con.y,intWitRes1.trigger)
  connect(con.y,intWitRes3.trigger)
  connect(mode.y[1], limPIDPar.mode)
  connect(mode.y[1], limPIPar.mode)
  connect(resVal.y, limPIDPar.u_s)
  connect(resVal.y, limPIPar.u_s)
end PIDWithOperatingMode;


Example models

within Buildings.Fluid.HydronicConfigurations;
package Examples "Example models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


within Buildings.Fluid.HydronicConfigurations.Examples;
model TwoWayOpenLoop
  "Model illustrating the concept of the authority for two-way valves and open loop control"
  extends Modelica.Icons.Example;

  package MediumLiq = Buildings.Media.Water
    "Medium model for hot water";
  parameter Modelica.Units.SI.MassFlowRate mLiq_flow_nominal = 1
    "Circuit mass flow rate at design conditions";
  parameter Modelica.Units.SI.Pressure p_min = 2E5
    "Circuit minimum pressure";
  parameter Modelica.Units.SI.PressureDifference dp_nominal = 1E5
    "Circuit total pressure drop at design conditions";

  Sources.Boundary_pT sup(
    redeclare final package Medium = MediumLiq,
    final p=p_min + dp_nominal,
    nPorts=5) "Pressure boundary condition at supply"
  Sources.Boundary_pT ret(
    redeclare final package Medium = MediumLiq,
    final p=p_min,
    nPorts=7)
    "Pressure boundary condition at return"
  .Buildings.Controls.OBC.CDL.Reals.Sources.Ramp ope(duration=100)
    "Valve opening signal"
  FixedResistances.PressureDrop ter50(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dp_nominal=0.5 * dp_nominal)
    "Terminal unit as a fixed resistance destroying 50% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut50(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal= 0.5 * dp_nominal,
    use_strokeTime=false) "Control valve with 50% authority"
  FixedResistances.PressureDrop ter75(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dp_nominal=0.75*dp_nominal)
    "Terminal unit as a fixed resistance destroying 75% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut25(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=0.25*dp_nominal,
    use_strokeTime=false) "Control valve with 25% authority"
  FixedResistances.PressureDrop ter25(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    dp_nominal=0.25*dp_nominal)
    "Terminal unit as a fixed resistance destroying 25% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut75(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=0.75*dp_nominal,
    use_strokeTime=false) "Control valve with 75% authority"
  Sources.Boundary_pT supOve(
    redeclare final package Medium = MediumLiq,
    final p=p_min + 1.5*dp_nominal,
    nPorts=2)
    "Pressure boundary condition at supply augmented by 50% from design value"
  Actuators.Valves.TwoWayEqualPercentage valAut50Ove(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=0.5*dp_nominal,
    use_strokeTime=false) "Control valve with 50% authority and overflow"
  FixedResistances.PressureDrop ter50Ove(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dp_nominal=0.5*dp_nominal)
    "Terminal unit as a fixed resistance destroying 50% of design pressure difference"
  FixedResistances.PressureDrop ter50Bal(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dp_nominal=0.5*dp_nominal)
    "Terminal unit as a fixed resistance destroying 50% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut33Bal(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=0.5*dp_nominal,
    use_strokeTime=false)
    "Control valve with 33% authority and balanced circuit"
  FixedResistances.PressureDrop bal50(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    dp_nominal=0.5*dp_nominal)
    "Balancing valve as a fixed resistance destroying 50% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut100(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=1*dp_nominal,
    use_strokeTime=false) "Control valve with 100% authority"
  FixedResistances.PressureDrop ter67(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dp_nominal=0.67*dp_nominal)
    "Terminal unit as a fixed resistance destroying 67% of design pressure difference"
  Actuators.Valves.TwoWayEqualPercentage valAut33(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=mLiq_flow_nominal,
    final dpValve_nominal=0.33*dp_nominal,
    use_strokeTime=false) "Control valve with 33% authority"
equation
  connect(sup.ports[2], ter50.port_a)
  connect(ter50.port_b, valAut50.port_a)
  connect(ope.y, valAut50.y)
  connect(ter75.port_b, valAut25.port_a)
  connect(ope.y, valAut25.y)
  connect(sup.ports[3], ter75.port_a)
  connect(ter25.port_a, sup.ports[4])
  connect(ter25.port_b, valAut75.port_a)
  connect(valAut75.port_b, ret.ports[2])
  connect(valAut50.port_b, ret.ports[3])
  connect(valAut25.port_b, ret.ports[4])
  connect(ope.y, valAut75.y)
  connect(supOve.ports[1], ter50Ove.port_a)
  connect(valAut50Ove.port_b, ret.ports[5])
  connect(ter50Ove.port_b, valAut50Ove.port_a)
  connect(ope.y, valAut50Ove.y)
  connect(ter50Bal.port_b, valAut33Bal.port_a)
  connect(supOve.ports[2], ter50Bal.port_a)
  connect(valAut33Bal.port_b, bal50.port_a)
  connect(bal50.port_b, ret.ports[6])
  connect(ope.y, valAut33Bal.y)
  connect(sup.ports[1], valAut100.port_a)
  connect(valAut100.port_b, ret.ports[1])
  connect(ope.y, valAut100.y)
  connect(valAut33.port_a, ter67.port_b)
  connect(valAut33.port_b, ret.ports[7])
  connect(ter67.port_a, sup.ports[5])
  connect(valAut33.y, ope.y)
end TwoWayOpenLoop;


Package with interface classes

within Buildings.Fluid.HydronicConfigurations;
package Interfaces "Package with interface classes"
  extends Modelica.Icons.InterfacesPackage;
end Interfaces;


Medium in the component

within Buildings.Fluid.HydronicConfigurations.Interfaces;
model PartialHydronicConfiguration
  replaceable package Medium =
    Buildings.Media.Water "Medium in the component"

  parameter Boolean use_siz = true
    "Set to true for built-in sizing of control valve and optional pump"

  parameter Modelica.Units.SI.MassFlowRate m1_flow_nominal(min=0)
    "Mass flow rate in primary circuit at design conditions"

  parameter Modelica.Units.SI.MassFlowRate m2_flow_nominal(min=0)
    "Mass flow rate in consumer circuit at design conditions"

  parameter Modelica.Units.SI.PressureDifference dp1_nominal(
    displayUnit="Pa",
    start=0)
    "Primary circuit pressure differential at design conditions"

  parameter Modelica.Units.SI.PressureDifference dp2_nominal(
    displayUnit="Pa",
    start=0)
    "Consumer circuit pressure differential at design conditions"

  parameter Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic typCha=
    Buildings.Fluid.HydronicConfigurations.Types.ValveCharacteristic.EqualPercentage
    "Control valve characteristic"

  parameter Buildings.Fluid.HydronicConfigurations.Types.Pump typPum=
    Buildings.Fluid.HydronicConfigurations.Types.Pump.VariableInput
    "Type of secondary pump"

  parameter Buildings.Fluid.HydronicConfigurations.Types.PumpModel typPumMod=
    Buildings.Fluid.HydronicConfigurations.Types.PumpModel.Speed
    "Type of pump model"

  parameter Buildings.Fluid.HydronicConfigurations.Types.Control typCtl=
    Buildings.Fluid.HydronicConfigurations.Types.Control.None
    "Type of built-in controls"

  parameter Buildings.Fluid.HydronicConfigurations.Types.ControlVariable
    typVar=Buildings.Fluid.HydronicConfigurations.Types.ControlVariable.SupplyTemperature
    "Controlled variable"

  parameter Boolean use_lumFloRes = true
    "Set to true to use a lumped flow resistance when possible"

  parameter Modelica.Units.SI.PressureDifference dpValve_nominal(
    displayUnit="Pa")
    "Control valve pressure drop at design conditions"

  parameter Modelica.Units.SI.PressureDifference dpBal1_nominal(
    displayUnit="Pa")=0
    "Primary balancing valve pressure drop at design conditions"
  parameter Modelica.Units.SI.PressureDifference dpBal2_nominal(
    displayUnit="Pa")=0
    "Secondary balancing valve pressure drop at design conditions"
  parameter Modelica.Units.SI.PressureDifference dpBal3_nominal(
    displayUnit="Pa")=0
    "Bypass balancing valve pressure drop at design conditions"

  parameter Actuators.Valves.Data.Generic flowCharacteristics(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics"
  parameter Actuators.Valves.Data.Generic flowCharacteristics1(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics for direct flow path at port_1"
  parameter Actuators.Valves.Data.Generic flowCharacteristics3(
    y={0,1},
    phi={0.0001,1})
    "Table with flow characteristics for bypass flow path at port_3"

  parameter Modelica.Units.SI.MassFlowRate mPum_flow_nominal=m2_flow_nominal
    "Pump head at design conditions"

  parameter Modelica.Units.SI.PressureDifference dpPum_nominal(
    displayUnit="Pa")= dp2_nominal + dpBal2_nominal
    "Pump head at design conditions"

  replaceable parameter Movers.Data.Generic perPum
    constrainedby Movers.Data.Generic(
      pressure(
        V_flow={0, 1, 2} * mPum_flow_nominal / rho_default,
        dp=if typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None then
          {1.14, 1, 0.42} * dpPum_nominal else {1.14, 1, 0.42}))
    "Pump parameters"

  parameter Buildings.Controls.OBC.CDL.Types.SimpleController controllerType=
    Buildings.Controls.OBC.CDL.Types.SimpleController.PI
    "Type of controller"
  parameter Real k(
    min=100*Buildings.Controls.OBC.CDL.Constants.eps)=0.1
    "Gain of controller"
  parameter Real Ti(unit="s")=120
    "Time constant of integrator block"

  parameter Modelica.Fluid.Types.Dynamics energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial
    "Type of energy balance: dynamic (3 initialization options) or steady state"

  parameter Boolean allowFlowReversal = true
    "= false to simplify equations, assuming, but not enforcing, no flow reversal for medium 1"

  // Diagnostics
  parameter Boolean show_T = false
    "= true, if actual temperature at port is computed"

  /* Workaround for Dymola 2022x (#SR00922000-01):
  The parameters below should be located inside the protected section.
  However, doing so yields an incorrect interpretation of the enable
  annotation attribute.
  The temporary workaround is to declare them in the public section
  and systematically assign them final values in the derived models.
  */
  parameter Boolean use_dp1
    "Set to true to enable dp1_nominal"
  parameter Boolean use_dp2
    "Set to true to enable dp2_nominal"
  parameter Buildings.Fluid.HydronicConfigurations.Types.Valve typVal
    "Type of control valve"
  parameter Boolean have_typVar = true
    "Set to true to enable the choice of the controlled variable"

  Modelica.Fluid.Interfaces.FluidPort_a port_a1(
    redeclare final package Medium = Medium,
    m_flow(min=if allowFlowReversal then -Modelica.Constants.inf else 0),
    h_outflow(start = Medium.h_default, nominal = Medium.h_default))
    "Primary supply port"
  Modelica.Fluid.Interfaces.FluidPort_b port_b1(
    redeclare final package Medium = Medium,
    m_flow(max=if allowFlowReversal then +Modelica.Constants.inf else 0),
    h_outflow(start = Medium.h_default, nominal = Medium.h_default))
    "Primary return port"
  Modelica.Fluid.Interfaces.FluidPort_a port_a2(
    redeclare final package Medium = Medium,
    m_flow(min=if allowFlowReversal then -Modelica.Constants.inf else 0),
    h_outflow(start = Medium.h_default, nominal = Medium.h_default))
    "Secondary return port"
  Modelica.Fluid.Interfaces.FluidPort_b port_b2(
    redeclare final package Medium = Medium,
    m_flow(max=if allowFlowReversal then +Modelica.Constants.inf else 0),
    h_outflow(start = Medium.h_default, nominal = Medium.h_default))
    "Secondary supply port"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput yVal(
    final unit="1", final min=0, final max=1) if have_yVal
    "Valve control signal"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput set if have_set
    "Set point"
  Buildings.Controls.OBC.CDL.Interfaces.RealInput yPum(
    final unit="1", final min=0, final max=1) if have_yPum
    "Pump control signal (variable speed)"
  Buildings.Controls.OBC.CDL.Interfaces.IntegerInput mode(final min=0, final
      max=if typCtl == Buildings.Fluid.HydronicConfigurations.Types.Control.ChangeOver
         then 2 else 1) if have_mod "Operating mode"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yVal_actual(
    final unit="1")
    if typVal <> Buildings.Fluid.HydronicConfigurations.Types.Valve.None
    "Valve position feedback"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput yPum_actual(
    final unit="1") if typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None
    "Actual pump input value that is used for computations"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput PPum(
    quantity="Power",
    final unit="W") if typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None
    "Pump electrical power"

  Medium.MassFlowRate m1_flow = port_a1.m_flow
    "Mass flow rate from port_a1 to port_b1 (m1_flow > 0 is design flow direction)";
  Modelica.Units.SI.PressureDifference dp1(displayUnit="Pa") = port_a1.p - port_b1.p
    "Pressure difference between port_a1 and port_b1";

  Medium.MassFlowRate m2_flow = port_a2.m_flow
    "Mass flow rate from port_a2 to port_b2 (m2_flow > 0 is design flow direction)";
  Modelica.Units.SI.PressureDifference dp2(displayUnit="Pa") = port_a2.p - port_b2.p
    "Pressure difference between port_a2 and port_b2";

  Medium.ThermodynamicState sta_a1=
    if allowFlowReversal then
      Medium.setState_phX(port_a1.p,
                          noEvent(actualStream(port_a1.h_outflow)),
                          noEvent(actualStream(port_a1.Xi_outflow)))
    else
      Medium.setState_phX(port_a1.p,
                          inStream(port_a1.h_outflow),
                          inStream(port_a1.Xi_outflow))
      if show_T "Medium properties in port_a1";
  Medium.ThermodynamicState sta_b1=
    if allowFlowReversal then
      Medium.setState_phX(port_b1.p,
                          noEvent(actualStream(port_b1.h_outflow)),
                          noEvent(actualStream(port_b1.Xi_outflow)))
    else
      Medium.setState_phX(port_b1.p,
                          port_b1.h_outflow,
                          port_b1.Xi_outflow)
       if show_T "Medium properties in port_b1";

  Medium.ThermodynamicState sta_a2=
    if allowFlowReversal then
      Medium.setState_phX(port_a2.p,
                          noEvent(actualStream(port_a2.h_outflow)),
                          noEvent(actualStream(port_a2.Xi_outflow)))
    else
      Medium.setState_phX(port_a2.p,
                          inStream(port_a2.h_outflow),
                          inStream(port_a2.Xi_outflow))
      if show_T "Medium properties in port_a2";
  Medium.ThermodynamicState sta_b2=
    if allowFlowReversal then
      Medium.setState_phX(port_b2.p,
                          noEvent(actualStream(port_b2.h_outflow)),
                          noEvent(actualStream(port_b2.Xi_outflow)))
    else
      Medium.setState_phX(port_b2.p,
                          port_b2.h_outflow,
                          port_b2.Xi_outflow)
       if show_T "Medium properties in port_b2";
protected
  final parameter Boolean have_yPum=
    typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None and
    typPum==Buildings.Fluid.HydronicConfigurations.Types.Pump.VariableInput
    "Set to true if an analog input is used for pump control"
  final parameter Boolean have_y1Pum=
    typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None and
    typPum==Buildings.Fluid.HydronicConfigurations.Types.Pump.NoVariableInput
    "Set to true if a digital input is used for pump control"
  final parameter Boolean have_yVal=
    typCtl==Buildings.Fluid.HydronicConfigurations.Types.Control.None and
    typVal<>Buildings.Fluid.HydronicConfigurations.Types.Valve.None
    "Set to true if an analog input is used for valve control"
  parameter Boolean have_set=
    typCtl<>Buildings.Fluid.HydronicConfigurations.Types.Control.None
    "Set to true if an analog input is used as a set point"
  final parameter Boolean have_mod=
    typCtl<>Buildings.Fluid.HydronicConfigurations.Types.Control.None
    or typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None
    "Set to true if an analog input is used as a control mode selector"

  Medium.ThermodynamicState state_a1_inflow=
    Medium.setState_phX(port_a1.p, inStream(port_a1.h_outflow), inStream(port_a1.Xi_outflow))
    "state for medium inflowing through port_a1";
  Medium.ThermodynamicState state_b1_inflow=
    Medium.setState_phX(port_b1.p, inStream(port_b1.h_outflow), inStream(port_b1.Xi_outflow))
    "state for medium inflowing through port_b1";
  Medium.ThermodynamicState state_a2_inflow=
    Medium.setState_phX(port_a2.p, inStream(port_a2.h_outflow), inStream(port_a2.Xi_outflow))
    "state for medium inflowing through port_a2";
  Medium.ThermodynamicState state_b2_inflow=
    Medium.setState_phX(port_b2.p, inStream(port_b2.h_outflow), inStream(port_b2.Xi_outflow))
    "state for medium inflowing through port_b2";

  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";
end PartialHydronicConfiguration;


Dual mixing circuit

within Buildings.Fluid.HydronicConfigurations.PassiveNetworks;
model DualMixing "Dual mixing circuit"
  extends HydronicConfigurations.Interfaces.PartialHydronicConfiguration(
    dpValve_nominal=max(dp1_nominal, 3e3),
    dpBal3_nominal=dp1_nominal+dpValve_nominal,
    dpPum_nominal=dp2_nominal + dpBal2_nominal + dpBal3_nominal,
    set(final unit="K", displayUnit="degC"),
    final dpBal1_nominal=0,
    final typVal=Buildings.Fluid.HydronicConfigurations.Types.Valve.ThreeWay,
    final typVar=Buildings.Fluid.HydronicConfigurations.Types.ControlVariable.SupplyTemperature,
    final have_typVar=false,
    final use_dp1=use_siz,
    final use_dp2=use_siz and typPum<>Buildings.Fluid.HydronicConfigurations.Types.Pump.None);

  Buildings.Fluid.HydronicConfigurations.Components.ThreeWayValve val(
    redeclare final package Medium=Medium,
    final typCha=typCha,
    final energyDynamics=energyDynamics,
    use_strokeTime=energyDynamics<>Modelica.Fluid.Types.Dynamics.SteadyState,
    final portFlowDirection_1=if allowFlowReversal then
      Modelica.Fluid.Types.PortFlowDirection.Bidirectional else
      Modelica.Fluid.Types.PortFlowDirection.Entering,
    final portFlowDirection_2=if allowFlowReversal then
      Modelica.Fluid.Types.PortFlowDirection.Bidirectional else
      Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final portFlowDirection_3=if allowFlowReversal then
      Modelica.Fluid.Types.PortFlowDirection.Bidirectional else
      Modelica.Fluid.Types.PortFlowDirection.Entering,
    final m_flow_nominal=m1_flow_nominal,
    final dpValve_nominal=dpValve_nominal,
    final dpFixed_nominal={0,0},
    final flowCharacteristics1=flowCharacteristics1,
    final flowCharacteristics3=flowCharacteristics3)
    "Control valve"
  FixedResistances.Junction jun(
    redeclare final package Medium = Medium,
    final energyDynamics=energyDynamics,
    final portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    final portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final m_flow_nominal=m1_flow_nominal .* {1,-1,-1},
    final dp_nominal=fill(0, 3))
    "Junction"
  FixedResistances.PressureDrop res2(
    redeclare final package Medium = Medium,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_nominal=m2_flow_nominal,
    final dp_nominal=dpBal2_nominal)
    "Secondary balancing valve"
  Buildings.Fluid.HydronicConfigurations.Components.Pump pum(
    redeclare final package Medium = Medium,
    final typ=typPum,
    final typMod=typPumMod,
    final m_flow_nominal=mPum_flow_nominal,
    final dp_nominal=dpPum_nominal,
    final energyDynamics=energyDynamics,
    final allowFlowReversal=allowFlowReversal,
    use_riseTime=energyDynamics<>Modelica.Fluid.Types.Dynamics.SteadyState,
    final per=perPum)
    "Pump"
  Sensors.TemperatureTwoPort T2Sup(
    redeclare final package Medium = Medium,
    final m_flow_nominal=m2_flow_nominal,
    final allowFlowReversal=allowFlowReversal,
    tau=if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then 0
      else 1)
    "Consumer circuit supply temperature sensor"
  FixedResistances.Junction junBypSup(
    redeclare final package Medium = Medium,
    final energyDynamics=energyDynamics,
    final portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    final portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final m_flow_nominal=(m2_flow_nominal - m1_flow_nominal) .* {1,-1,1},
    final dp_nominal=fill(0, 3))
    "Junction"
  FixedResistances.Junction junBypRet(
    redeclare final package Medium = Medium,
    final energyDynamics=energyDynamics,
    final portFlowDirection_1=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Entering,
    final portFlowDirection_2=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final portFlowDirection_3=if allowFlowReversal then Modelica.Fluid.Types.PortFlowDirection.Bidirectional
         else Modelica.Fluid.Types.PortFlowDirection.Leaving,
    final m_flow_nominal=(m2_flow_nominal - m1_flow_nominal) .* {1,-1,-1},
    final dp_nominal=fill(0, 3))
    "Junction"
  Sensors.TemperatureTwoPort T2Ret(
    redeclare final package Medium = Medium,
    final m_flow_nominal=m2_flow_nominal,
    final allowFlowReversal=allowFlowReversal,
    tau=if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then 0
         else 1) "Consumer circuit return temperature sensor"
  Buildings.Controls.OBC.CDL.Integers.GreaterThreshold isEna(final t=Controls.OperatingModes.disabled)
    "Returns true if enabled"
  Controls.PIDWithOperatingMode ctl(
    u_s(final unit="K", displayUnit="degC"),
    u_m(final unit="K", displayUnit="degC"),
    final reverseActing=typCtl == Buildings.Fluid.HydronicConfigurations.Types.Control.Heating,
    final yMin=0,
    final yMax=1,
    final controllerType=controllerType,
    final k=k,
    final Ti=Ti)
    if typCtl <> Buildings.Fluid.HydronicConfigurations.Types.Control.None
    "Controller"

  FixedResistances.PressureDrop res3(
    redeclare final package Medium = Medium,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_nominal=m2_flow_nominal - m1_flow_nominal,
    final dp_nominal=dpBal3_nominal)
    "Bypass balancing valve"

initial equation
  assert(
    m2_flow_nominal > m1_flow_nominal,
    "In " + getInstanceName() +
    ": Primary mass flow rate must be strictly lower than consumer circuit mass flow rate " +
    "at design conditions.");

  if dpBal3_nominal <= dpValve_nominal then
    Modelica.Utilities.Streams.print(
      "*** Warning: In " + getInstanceName() +
      ": The bypass balancing valve should generate a pressure drop higher than " +
      "the control valve at design conditions to provide sufficient primary flow.");
  end if;

equation
  connect(val.port_3, jun.port_3)
  connect(jun.port_2, port_b1)
  connect(val.port_1, port_a1)
  connect(junBypRet.port_2, jun.port_1)
  connect(pum.port_b, T2Sup.port_a)
  connect(T2Sup.port_b, port_b2)
  connect(pum.port_a, junBypSup.port_2)
  connect(val.port_2, junBypSup.port_1)
  connect(port_a2, T2Ret.port_a)
  connect(T2Ret.port_b, res2.port_a)
  connect(res2.port_b, junBypRet.port_1)
  connect(mode, isEna.u)
  connect(set, ctl.u_s)
  connect(T2Sup.T, ctl.u_m)
  connect(ctl.y, val.y)
  connect(mode, ctl.mode)
  connect(junBypSup.port_3, res3.port_b)
  connect(res3.port_a, junBypRet.port_3)
  connect(yVal, val.y)
  connect(pum.P, PPum)
  connect(pum.y_actual, yPum_actual)
  connect(val.y_actual, yVal_actual)
  connect(pum.y1, isEna.y)
  connect(yPum, pum.y)
end DualMixing;


within Buildings.Fluid.HydronicConfigurations;
package PassiveNetworks "Package of hydronic configurations for passive networks"
  extends Modelica.Icons.VariantsPackage;
end PassiveNetworks;


Single mixing circuit.

within Buildings.Fluid.HydronicConfigurations.PassiveNetworks;
model SingleMixing "Single mixing circuit"
  extends BaseClasses.SingleMixing(
    dpPum_nominal=dp2_nominal + dpBal2_nominal +
      max({val.dpValve_nominal + dp1_nominal, val.dp3Valve_nominal + dpBal3_nominal}),
    final dpBal1_nominal=0);

end SingleMixing;


within Buildings.Fluid.HydronicConfigurations.PassiveNetworks.Examples;
model DualMixing
  "Model illustrating the operation of a dual mixing circuit"
  extends BaseClasses.PartialPassivePrimary(
    typ=Buildings.Fluid.HydronicConfigurations.Types.Control.Heating,
    m1_flow_nominal=if abs(TLiqSup_nominal - TLiqEnt_nominal) > Modelica.Constants.eps
      then m2_flow_nominal*(TLiqEnt_nominal - TLiqLvg_nominal)/(TLiqSup_nominal - TLiqLvg_nominal)
      else 0.95*m2_flow_nominal,
    TLiqEnt_nominal=35 + 273.15,
    TLiqLvg_nominal=25 + 273.15,
    TLiqSup_nominal=60 + 273.15,
    del1(nPorts=2),
    ref(use_T_in=true));

  parameter Real kSizBal(final min=0) = 1.0
    "Sizing factor for bypass balancing valve (1 means balanced)"

  parameter Modelica.Units.SI.MassFlowRate m2_flow_nominal(final min=0)=
    nTer * mTer_flow_nominal
    "Mass flow rate in consumer circuit at design conditions"
  parameter Modelica.Units.SI.MassFlowRate mAir_flow_nominal=
    abs(loa.loa.Q_flow_nominal) / 3 / 1015
    "Air mass flow rate at design conditions";
  parameter Modelica.Units.SI.Temperature TAirEnt_nominal=
    24 + 273.15
    "Air entering temperature at design conditions";
  parameter Modelica.Units.SI.MassFraction phiAirEnt_nominal = 0.5
    "Air entering relative humidity at design conditions";

  Buildings.Fluid.HydronicConfigurations.PassiveNetworks.DualMixing con(
    typPum=Buildings.Fluid.HydronicConfigurations.Types.Pump.NoVariableInput,
    final typCtl=typ,
    redeclare final package Medium=MediumLiq,
    final energyDynamics=energyDynamics,
    final m2_flow_nominal=m2_flow_nominal,
    final m1_flow_nominal=m1_flow_nominal,
    dp1_nominal=res1.dp_nominal,
    final dp2_nominal=dpPip_nominal + loa1.dpTer_nominal + loa1.dpValve_nominal,
    final dpBal3_nominal=kSizBal * (con.dpValve_nominal + res1.dp_nominal))
    "Hydronic connection"
  ActiveNetworks.Examples.BaseClasses.LoadThreeWayValveControl loa(
      redeclare final package MediumLiq = MediumLiq,
      final typ=typ,
      final energyDynamics=energyDynamics,
      final mLiq_flow_nominal=mTer_flow_nominal,
      final mAir_flow_nominal=mAir_flow_nominal,
      final TAirEnt_nominal=TAirEnt_nominal,
      final phiAirEnt_nominal=phiAirEnt_nominal,
      final TLiqEnt_nominal=TLiqEnt_nominal,
      final TLiqLvg_nominal=TLiqLvg_nominal,
      final TLiqEntChg_nominal=TLiqEntChg_nominal)
    "Load"
  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable mode(
    table=[0,0; 6,0; 6,1; 15,1; 15,1; 22,1; 22,0; 24,0],
    timeScale=3600,
    period=86400) "Operating mode (time schedule)"
  Buildings.Controls.OBC.CDL.Reals.Sources.TimeTable fraLoa(table=[0,0,0; 6,
        0,0; 6.1,1,1; 8,1,1; 9,1,0; 14,0.5,0; 14.5,0,0; 16,0,0; 17,0,1; 21,0,1;
        22,0,0; 24,0,0], timeScale=3600) "Load modulating signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant T2Set(
    y(final unit="K", displayUnit="degC"),
    final k=TLiqEnt_nominal) "Consumer circuit temperature set point"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant T1Set(
    y(final unit="K", displayUnit="degC"),
    final k=TLiqSup_nominal) "Primary circuit temperature set point"
  ActiveNetworks.Examples.BaseClasses.LoadThreeWayValveControl loa1(
    redeclare final package MediumLiq = MediumLiq,
    final typ=typ,
    final energyDynamics=energyDynamics,
    final mLiq_flow_nominal=mTer_flow_nominal,
    final mAir_flow_nominal=mAir_flow_nominal,
    final TAirEnt_nominal=TAirEnt_nominal,
    final phiAirEnt_nominal=phiAirEnt_nominal,
    final TLiqEnt_nominal=TLiqEnt_nominal,
    final TLiqLvg_nominal=TLiqLvg_nominal,
    final TLiqEntChg_nominal=TLiqEntChg_nominal)
    "Load"

  FixedResistances.PressureDrop res2(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=con.pum.m_flow_nominal - loa.mLiq_flow_nominal,
    final dp_nominal=dpPip_nominal)
    "Pipe pressure drop"
  Delays.DelayFirstOrder del2(
    redeclare final package Medium = MediumLiq,
    final energyDynamics=energyDynamics,
    final m_flow_nominal=m2_flow_nominal,
    nPorts=3) "Fluid transport delay"
equation
  connect(con.port_b2, loa.port_a)
  connect(con.port_b1, del1.ports[2])
  connect(mode.y[1], loa.mode)
  connect(mode.y[1], con.mode)
  connect(fraLoa.y[1], loa.u)
  connect(res1.port_b, con.port_a1)
  connect(con.port_b2, res2.port_a)
  connect(res2.port_b, loa1.port_a)
  connect(del2.ports[1], con.port_a2)
  connect(loa.port_b, del2.ports[2])
  connect(loa1.port_b, del2.ports[3])
  connect(mode.y[1], loa1.mode)
  connect(fraLoa.y[2], loa1.u)
  connect(T1Set.y, ref.T_in)
  connect(T2Set.y, con.set)
end DualMixing;


Example models

within Buildings.Fluid.HydronicConfigurations.PassiveNetworks;
package Examples "Example models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


within Buildings.Fluid.HydronicConfigurations.PassiveNetworks.Examples;
model SingleMixing "Model illustrating the operation of single mixing circuits"
  extends BaseClasses.PartialPassivePrimary(
    typ=Buildings.Fluid.HydronicConfigurations.Types.Control.ChangeOver,
    del1(nPorts=3),
    ref(use_T_in=true));

  parameter Modelica.Units.SI.PressureDifference dp2Set(
    final min=0,
    displayUnit="Pa") = loa1.dpTer_nominal + loa1.dpValve_nominal
    "Secondary pressure differential set point"

  Buildings.Fluid.HydronicConfigurations.PassiveNetworks.SingleMixing con(
    typPum=Buildings.Fluid.HydronicConfigurations.Types.Pump.NoVariableInput,
    final typCtl=typ,
    redeclare final package Medium=MediumLiq,
    final energyDynamics=energyDynamics,
    final m2_flow_nominal=loa.m_flow_nominal,
    dp1_nominal=0,
    final dp2_nominal=loa.dpTer_nominal + loa.dpValve_nominal)
    "Hydronic connection"

  ActiveNetworks.Examples.BaseClasses.LoadThreeWayValveControl loa(
    redeclare final package MediumLiq = MediumLiq,
    final typ=typ,
    final energyDynamics=energyDynamics,
    final mLiq_flow_nominal=mTer_flow_nominal,
    final TLiqEnt_nominal=TLiqEnt_nominal,
    final TLiqLvg_nominal=TLiqLvg_nominal,
    final TLiqEntChg_nominal=TLiqEntChg_nominal)
    "Load"
  Buildings.Fluid.HydronicConfigurations.PassiveNetworks.SingleMixing con1(
    typPumMod=Buildings.Fluid.HydronicConfigurations.Types.PumpModel.Head,
    typPum=Buildings.Fluid.HydronicConfigurations.Types.Pump.VariableInput,
    final typCtl=typ,
    redeclare final package Medium = MediumLiq,
    final energyDynamics=energyDynamics,
    final m2_flow_nominal=loa1.m_flow_nominal / 0.9,
    dp1_nominal=0,
    final dp2_nominal=dp2Set)
    "Hydronic connection"
  ActiveNetworks.Examples.BaseClasses.LoadTwoWayValveControl loa1(
    redeclare final package MediumLiq = MediumLiq,
    final typ=typ,
    final energyDynamics=energyDynamics,
    final mLiq_flow_nominal=mTer_flow_nominal,
    final TLiqEnt_nominal=TLiqEnt_nominal,
    final TLiqLvg_nominal=TLiqLvg_nominal,
    final TLiqEntChg_nominal=TLiqEntChg_nominal)
    "Load"
  Sensors.RelativePressure dp2(redeclare final package Medium = MediumLiq)
    "Differential pressure"
  FixedResistances.PressureDrop resEnd2(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=0.1*con1.pum.m_flow_nominal,
    final dp_nominal=dp2Set)
    "Pipe pressure drop"
  Buildings.Controls.OBC.CDL.Integers.Sources.TimeTable mode(
    table=[0,0; 6,0; 6,2; 15,2; 15,1; 22,1; 22,0; 24,0],
    timeScale=3600,
    period=86400) "Operating mode (time schedule)"
  Buildings.Controls.OBC.CDL.Reals.Sources.TimeTable fraLoa(table=[0,0,0; 6,
        0,0; 6.1,1,1; 8,1,1; 9,1,0; 14,0.5,0; 14.5,0,0; 16,0,0; 17,0,1; 21,0,1;
        22,0,0; 24,0,0], timeScale=3600) "Load modulating signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.TimeTable setOff(table=[0,0; 10,
        0; 13,-8; 13,0; 18,0; 22,+5; 22,0; 24,0], timeScale=3600)
    "Offset applied to design supply temperature to compute set point"
  Buildings.Controls.OBC.CDL.Reals.Add          T2Set(y(final unit="K",
        displayUnit="degC"))
    "Consumer circuit temperature set point"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor T2SetMod(nin=3,
    y(final unit="K", displayUnit="degC"))
    "Select consumer circuit temperature set point based on operating mode"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant T2SetVal[3](final k={
        MediumLiq.T_default,TLiqEnt_nominal,TLiqEntChg_nominal})
    "Consumer circuit temperature set point values"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant T1SetVal[3](final k={
        MediumLiq.T_default,TLiqSup_nominal,TLiqSupChg_nominal})
    "Primary circuit temperature set point values"
  Buildings.Controls.OBC.CDL.Routing.RealExtractor T1Set(nin=3,
    y(final unit="K", displayUnit="degC"))
    "Primary circuit temperature set point"
  Modelica.Blocks.Sources.RealExpression uPum(y=(dp2Set + con1.val.dp3)/con1.pum.dp_nominal)
    "Pump control signal"
  Buildings.Controls.OBC.CDL.Integers.AddParameter addPar(final p=1)
    "Convert mode index to array index"
equation
  connect(con.port_b2, loa.port_a)
  connect(loa.port_b, con.port_a2)
  connect(con.port_b1, del1.ports[2])
  connect(con1.port_b1, del1.ports[3])
  connect(loa1.port_a, dp2.port_a)
  connect(loa1.port_b, dp2.port_b)
  connect(dp2.port_a, con1.port_b2)
  connect(dp2.port_b, con1.port_a2)
  connect(resEnd2.port_b, loa1.port_b)
  connect(resEnd2.port_a, loa1.port_a)
  connect(mode.y[1], loa.mode)
  connect(mode.y[1], loa1.mode)
  connect(mode.y[1], con.mode)
  connect(fraLoa.y[1], loa.u)
  connect(fraLoa.y[2], loa1.u)
  connect(mode.y[1], con1.mode)
  connect(T2SetVal.y, T2SetMod.u)
  connect(T2SetMod.y, T2Set.u1)
  connect(setOff.y[1], T2Set.u2)
  connect(T2Set.y, con.set)
  connect(T2Set.y, con1.set)
  connect(T1SetVal.y, T1Set.u)
  connect(T1Set.y, ref.T_in)
  connect(res1.port_b, con.port_a1)
  connect(res1.port_b, con1.port_a1)
  connect(uPum.y, con1.yPum)
  connect(mode.y[1], addPar.u)
  connect(addPar.y, T1Set.index)
  connect(addPar.y, T2SetMod.index)
end SingleMixing;


within Buildings.Fluid.HydronicConfigurations.PassiveNetworks.Examples;
model SingleMixingOpenLoop
  "Model illustrating the operation of single mixing circuits with primary back pressure"
  extends BaseClasses.PartialPassivePrimary(
    typ=Buildings.Fluid.HydronicConfigurations.Types.Control.Heating,
    TLiqEnt_nominal=60 + 273.15,
    TLiqLvg_nominal=TLiqEnt_nominal - 10,
    TLiqSup_nominal=TLiqEnt_nominal,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    del1(nPorts=3),
    res1(dp_nominal=3e4));

  parameter Boolean is_bal=false
    "Set to true for balanced bypass branch"
  parameter Modelica.Units.SI.PressureDifference dpValve_nominal(
    displayUnit="Pa") = 5e3
    "Control valve pressure drop at design conditions";

  Buildings.Fluid.HydronicConfigurations.PassiveNetworks.SingleMixing con(
    typPum=Buildings.Fluid.HydronicConfigurations.Types.Pump.NoVariableInput,
    redeclare final package Medium=MediumLiq,
    val(fraK=1.0),
    final typCtl=Buildings.Fluid.HydronicConfigurations.Types.Control.None,
    final energyDynamics=energyDynamics,
    final m2_flow_nominal=loa.m_flow_nominal,
    dp1_nominal=res1.dp_nominal,
    final dp2_nominal=loa.dpLiq_nominal,
    final dpValve_nominal=dpValve_nominal,
    final dpBal3_nominal=if is_bal then res1.dp_nominal else 0)
    "Hydronic connection"
  Buildings.Fluid.HydronicConfigurations.ActiveNetworks.Examples.BaseClasses.Load
    loa(
    redeclare final package MediumLiq = MediumLiq,
    final typ=typ,
    final energyDynamics=energyDynamics,
    final mLiq_flow_nominal=mTer_flow_nominal,
    final dpLiq_nominal=dpTer_nominal,
    final TLiqEnt_nominal=TLiqEnt_nominal,
    final TLiqLvg_nominal=TLiqLvg_nominal) "Load"
  Buildings.Fluid.HydronicConfigurations.PassiveNetworks.SingleMixing con1(
    typPum=Buildings.Fluid.HydronicConfigurations.Types.Pump.NoVariableInput,
    redeclare final package Medium = MediumLiq,
    val(fraK=1.0),
    final typCtl=Buildings.Fluid.HydronicConfigurations.Types.Control.None,
    final energyDynamics=energyDynamics,
    final m2_flow_nominal=loa1.m_flow_nominal,
    dp1_nominal=res1.dp_nominal,
    final dp2_nominal=loa1.dpLiq_nominal,
    final dpValve_nominal=dpValve_nominal,
    final dpBal3_nominal=if is_bal then res1.dp_nominal else 0)
    "Hydronic connection"
  Buildings.Fluid.HydronicConfigurations.ActiveNetworks.Examples.BaseClasses.Load
    loa1(
    redeclare final package MediumLiq = MediumLiq,
    final typ=typ,
    final energyDynamics=energyDynamics,
    final mLiq_flow_nominal=mTer_flow_nominal,
    final dpLiq_nominal=dpTer_nominal,
    final TLiqEnt_nominal=TLiqEnt_nominal,
    final TLiqLvg_nominal=TLiqLvg_nominal) "Load"
  Buildings.Controls.OBC.CDL.Integers.Sources.Constant mode(k=1)
    "Operating mode"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant fraLoa(k=1)
    "Load modulating signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.TimeTable  ope(
    table=[0,1,1; 1,0,1],
    extrapolation=Buildings.Controls.OBC.CDL.Types.Extrapolation.HoldLastPoint,
    timeScale=100) "Valve opening signal"

equation
  connect(con.port_b2, loa.port_a)
  connect(loa.port_b, con.port_a2)
  connect(con.port_b1, del1.ports[2])
  connect(con1.port_b1, del1.ports[3])
  connect(mode.y, loa.mode)
  connect(mode.y, loa1.mode)
  connect(mode.y, con.mode)
  connect(fraLoa.y, loa.u)
  connect(fraLoa.y, loa1.u)
  connect(mode.y, con1.mode)
  connect(res1.port_b, con.port_a1)
  connect(res1.port_b, con1.port_a1)
  connect(con1.port_b2, loa1.port_a)
  connect(loa1.port_b, con1.port_a2)
  connect(ope.y[1], con.yVal)
  connect(ope.y[2], con1.yVal)
end SingleMixingOpenLoop;


Package with base classes

within Buildings.Fluid.HydronicConfigurations.PassiveNetworks.Examples;
package BaseClasses "Package with base classes"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings.Fluid.HydronicConfigurations.PassiveNetworks.Examples.BaseClasses;
model PartialPassivePrimary
  "Partial model of passive primary network"
  extends Modelica.Icons.Example;

  package MediumLiq = Buildings.Media.Water
    "Medium model for hot water";

  parameter Buildings.Fluid.HydronicConfigurations.Types.Control typ
    "Load type"

  parameter Integer nTer = 2
    "Number of terminal units";
  parameter Modelica.Units.SI.MassFlowRate mTer_flow_nominal = 1
    "Terminal unit mass flow rate at design conditions"
  parameter Modelica.Units.SI.MassFlowRate m1_flow_nominal(final min=0)=
    nTer * mTer_flow_nominal
    "Mass flow rate in primary circuit at design conditions"
  parameter Modelica.Units.SI.PressureDifference dpTer_nominal(displayUnit="Pa")=
    3E4
    "Terminal unit pressure drop at design conditions"
  parameter Modelica.Units.SI.PressureDifference dpPip_nominal(displayUnit="Pa")=
    0.5E4
    "Pipe section pressure drop at design conditions"

  parameter Modelica.Units.SI.Pressure p_min=200000
    "Circuit minimum pressure";

  parameter Modelica.Units.SI.Temperature TLiqEnt_nominal=
    if typ==Buildings.Fluid.HydronicConfigurations.Types.Control.Heating
    then 60+273.15 else 7+273.15
    "Liquid entering temperature at design conditions";
  parameter Modelica.Units.SI.Temperature TLiqLvg_nominal=TLiqEnt_nominal+(
    if typ==Buildings.Fluid.HydronicConfigurations.Types.Control.Heating
    then -10 else +5)
    "Liquid leaving temperature at design conditions";
  parameter Modelica.Units.SI.Temperature TLiqEntChg_nominal=
    60+273.15
    "Liquid entering temperature in change-over mode"

  parameter Modelica.Units.SI.Temperature TLiqSup_nominal=TLiqEnt_nominal
    "Liquid primary supply temperature at design conditions";
  parameter Modelica.Units.SI.Temperature TLiqSupChg_nominal=TLiqEntChg_nominal
    "Liquid primary supply temperature in change-over mode"

  parameter Modelica.Fluid.Types.Dynamics energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial
    "Type of energy balance: dynamic (3 initialization options) or steady state"

  Sources.Boundary_pT ref(
    redeclare final package Medium = MediumLiq,
    final p=p_min,
    T=TLiqSup_nominal,
    nPorts=2)
    "Pressure and temperature boundary condition"
  Sensors.TemperatureTwoPort T1Ret(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=m1_flow_nominal,
    final tau=if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState
         then 0 else 1,
    T_start=TLiqSup_nominal) "Return temperature sensor"
  Sensors.TemperatureTwoPort T1Sup(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=m1_flow_nominal,
    final tau=if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState
         then 0 else 1,
    T_start=TLiqSup_nominal) "Supply temperature sensor"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT1(y(final unit="K"))
    "Primary Delta-T"
  Delays.DelayFirstOrder del1(
    redeclare final package Medium = MediumLiq,
    final energyDynamics=energyDynamics,
    final m_flow_nominal=m1_flow_nominal,
    tau=10,
    nPorts=1)
    "Fluid transport delay"
  FixedResistances.PressureDrop res1(
    redeclare final package Medium = MediumLiq,
    final m_flow_nominal=m1_flow_nominal,
    dp_nominal=0)
    "Pipe pressure drop"
  Sensors.MassFlowRate m1_flow(
    redeclare final package Medium = MediumLiq)
    "Mass flow rate in primary circuit"
equation
  connect(T1Ret.port_b, ref.ports[1])
  connect(T1Ret.T, dT1.u1)
  connect(T1Sup.T, dT1.u2)

  connect(ref.ports[2], T1Sup.port_a)
  connect(T1Sup.port_b, res1.port_a)
  connect(m1_flow.port_b, T1Ret.port_a)
  connect(m1_flow.port_a, del1.ports[1])
end PartialPassivePrimary;


within Buildings.Fluid.Interfaces;
model ConservationEquation "Lumped volume with mass and energy balance"

  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations;

  // Constants
  parameter Boolean initialize_p = not Medium.singleState
    "= true to set up initial equations for pressure"

  constant Boolean simplify_mWat_flow = true
    "Set to true to cause port_a.m_flow + port_b.m_flow = 0 even if mWat_flow is non-zero. Used only if Medium.nX > 1";

  // Port definitions
  parameter Integer nPorts=0 "Number of ports"

  parameter Boolean use_mWat_flow = false
    "Set to true to enable input connector for moisture mass flow rate"
  parameter Boolean use_C_flow = false
    "Set to true to enable input connector for trace substance"

  Modelica.Blocks.Interfaces.RealInput Q_flow(unit="W")
    "Sensible plus latent heat flow rate transferred into the medium"
  Modelica.Blocks.Interfaces.RealInput mWat_flow(final quantity="MassFlowRate",
                                                 unit="kg/s")
    if use_mWat_flow "Moisture mass flow rate added to the medium"
  Modelica.Blocks.Interfaces.RealInput[Medium.nC] C_flow
    if use_C_flow "Trace substance mass flow rate added to the medium"

  // Outputs that are needed in models that use this model
  Modelica.Blocks.Interfaces.RealOutput hOut(unit="J/kg",
                                             start=hStart,
                                             nominal=Medium.h_default)
    "Leaving specific enthalpy of the component"
  Modelica.Blocks.Interfaces.RealOutput XiOut[Medium.nXi](each unit="1",
                                                          each min=0,
                                                          each max=1)
    "Leaving species concentration of the component"
  Modelica.Blocks.Interfaces.RealOutput COut[Medium.nC](each min=0)
    "Leaving trace substances of the component"
  Modelica.Blocks.Interfaces.RealOutput UOut(unit="J")
    "Internal energy of the component"
  Modelica.Blocks.Interfaces.RealOutput mXiOut[Medium.nXi](each min=0, each unit="kg")
    "Species mass of the component"
  Modelica.Blocks.Interfaces.RealOutput mOut(min=0, unit="kg")
    "Mass of the component"
  Modelica.Blocks.Interfaces.RealOutput mCOut[Medium.nC](each min=0, each unit="kg")
    "Trace substance mass of the component"

  Modelica.Fluid.Vessels.BaseClasses.VesselFluidPorts_b ports[nPorts](
      redeclare each final package Medium = Medium,
      each h_outflow(nominal=Medium.h_default),
      each Xi_outflow(each nominal=0.01)) "Fluid inlets and outlets"

  // Set nominal attributes where literal values can be used.
  Medium.BaseProperties medium(
    p(start=p_start),
    h(start=hStart),
    T(start=T_start),
    Xi(
      each stateSelect=if medium.preferredMediumStates then StateSelect.prefer else StateSelect.default,
      start=X_start[1:Medium.nXi]),
    X(start=X_start),
    d(start=rho_start)) "Medium properties";

  Modelica.Units.SI.Energy U(
    start=fluidVolume*rho_start*
        Medium.specificInternalEnergy(Medium.setState_pTX(
        T=T_start,
        p=p_start,
        X=X_start[1:Medium.nXi])) + (T_start - Medium.reference_T)*CSen,
      nominal=1E5) "Internal energy of fluid";

  Modelica.Units.SI.Mass m(start=fluidVolume*rho_start, stateSelect=if
        massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
        StateSelect.default else StateSelect.prefer) "Mass of fluid";

  Modelica.Units.SI.Mass[Medium.nXi] mXi(
    each stateSelect=StateSelect.never,
    start=fluidVolume*rho_start*X_start[1:Medium.nXi])
    "Masses of independent components in the fluid";
  Modelica.Units.SI.Mass[Medium.nC] mC(start=fluidVolume*rho_start*C_start)
    "Masses of trace substances in the fluid";
  // C need to be added here because unlike for Xi, which has medium.Xi,
  // there is no variable medium.C
  Medium.ExtraProperty C[Medium.nC](nominal=C_nominal)
    "Trace substance mixture content";

  Modelica.Units.SI.MassFlowRate mb_flow "Mass flows across boundaries";
  Modelica.Units.SI.MassFlowRate[Medium.nXi] mbXi_flow
    "Substance mass flows across boundaries";
  Medium.ExtraPropertyFlowRate[Medium.nC] mbC_flow
    "Trace substance mass flows across boundaries";
  Modelica.Units.SI.EnthalpyFlowRate Hb_flow
    "Enthalpy flow across boundaries or energy source/sink";

  // Parameters that need to be defined by an extending class
  parameter Modelica.Units.SI.Volume fluidVolume "Volume";
  final parameter Modelica.Units.SI.HeatCapacity CSen=(mSenFac - 1)*rho_default
      *cp_default*fluidVolume
    "Aditional heat capacity for implementing mFactor";
protected
  Medium.EnthalpyFlowRate ports_H_flow[nPorts];
  Modelica.Units.SI.MassFlowRate ports_mXi_flow[nPorts,Medium.nXi];
  Medium.ExtraPropertyFlowRate ports_mC_flow[nPorts,Medium.nC];
  parameter Modelica.Units.SI.SpecificHeatCapacity cp_default=
      Medium.specificHeatCapacityCp(state=state_default)
    "Heat capacity, to compute additional dry mass";
  parameter Modelica.Units.SI.Density rho_start=Medium.density(
      Medium.setState_pTX(
      T=T_start,
      p=p_start,
      X=X_start[1:Medium.nXi])) "Density, used to compute fluid mass";

  // Parameter for avoiding extra overhead calculations when CSen==0
  final parameter Boolean computeCSen = abs(mSenFac-1) > Modelica.Constants.eps
  final parameter Medium.ThermodynamicState state_default = Medium.setState_pTX(
      T=Medium.T_default,
      p=Medium.p_default,
      X=Medium.X_default[1:Medium.nXi]) "Medium state at default values";
  // Density at medium default values, used to compute the size of control volumes
  final parameter Modelica.Units.SI.Density rho_default=Medium.density(state=
      state_default) "Density, used to compute fluid mass";
  // Parameter that is used to construct the vector mXi_flow
  final parameter Real s[Medium.nXi] = {if Modelica.Utilities.Strings.isEqual(
                                            string1=Medium.substanceNames[i],
                                            string2="Water",
                                            caseSensitive=false)
                                            then 1 else 0 for i in 1:Medium.nXi}
    "Vector with zero everywhere except where species is";
  parameter Modelica.Units.SI.SpecificEnthalpy hStart=
      Medium.specificEnthalpy_pTX(
      p_start,
      T_start,
      X_start) "Start value for specific enthalpy";

  // Set _simplify_mWat_flow == false for Glycol47; otherwise Dymola 2018FD01
  // cannot differentiate the equations.
  constant Boolean _simplify_mWat_flow = simplify_mWat_flow and Medium.nX > 1
   "If true, then port_a.m_flow + port_b.m_flow = 0 even if mWat_flow is non-zero, and equations are simplified";

  // Conditional connectors
  Modelica.Blocks.Interfaces.RealInput mWat_flow_internal(unit="kg/s")
    "Needed to connect to conditional connector";
  Modelica.Blocks.Interfaces.RealInput C_flow_internal[Medium.nC]
    "Needed to connect to conditional connector";

initial equation
  // Assert that the substance with name 'water' has been found.
  if use_mWat_flow then
    assert(Medium.nXi == 0 or abs(sum(s) - 1) < 1e-5, "In " + getInstanceName()
       + ":
         If Medium.nXi > 1, then substance 'water' must be present for one component of '"
       + Medium.mediumName + "'.
         Check medium model.");
  end if;

  // Make sure that if energyDynamics is SteadyState, then
  // massDynamics is also SteadyState.
  // Otherwise, the system of ordinary differential equations may be inconsistent.
  if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    assert(massDynamics == energyDynamics, "In " + getInstanceName() + ":
         If 'massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState', then it is
         required that 'energyDynamics==Modelica.Fluid.Types.Dynamics.SteadyState'.
         Otherwise, the system of equations may not be consistent.
         You need to select other parameter values.");
  end if;

  // initialization of balances
  if energyDynamics == Modelica.Fluid.Types.Dynamics.FixedInitial then
      medium.T = T_start;
  else
    if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
        der(medium.T) = 0;
    end if;
  end if;

  if massDynamics == Modelica.Fluid.Types.Dynamics.FixedInitial then
    if initialize_p then
      medium.p = p_start;
    end if;
  else
    if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
      if initialize_p then
        der(medium.p) = 0;
      end if;
    end if;
  end if;

  if substanceDynamics == Modelica.Fluid.Types.Dynamics.FixedInitial then
    medium.Xi = X_start[1:Medium.nXi];
  else
    if substanceDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
      der(medium.Xi) = zeros(Medium.nXi);
    end if;
  end if;

  if traceDynamics == Modelica.Fluid.Types.Dynamics.FixedInitial then
    C = C_start[1:Medium.nC];
  else
    if traceDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
      der(C) = zeros(Medium.nC);
    end if;
  end if;

equation
  // Conditional connectors
  connect(mWat_flow, mWat_flow_internal);
  if not use_mWat_flow then
    mWat_flow_internal = 0;
  end if;

  connect(C_flow, C_flow_internal);
  if not use_C_flow then
    C_flow_internal = zeros(Medium.nC);
  end if;

  // Total quantities
  if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    m = fluidVolume*rho_start;
  else
    if _simplify_mWat_flow then
      // If moisture is neglected in mass balance, assume for computation
      // of the mass of air that the air is at Medium.X_default.
      m = fluidVolume*Medium.density(Medium.setState_phX(
        p = medium.p,
        h = hOut,
        X = Medium.X_default));
    else
      // Use actual density
      m = fluidVolume*medium.d;
    end if;
  end if;
  mXi = m*medium.Xi;
  if computeCSen then
    U = m*medium.u + CSen*(medium.T-Medium.reference_T);
  else
    U = m*medium.u;
  end if;
  mC = m*C;

  hOut = medium.h;
  XiOut = medium.Xi;
  COut = C;

  for i in 1:nPorts loop
    //The semiLinear function should be used for the equations below
    //for allowing min/max simplifications.
    //See https://github.com/ibpsa/modelica-ibpsa/issues/216 for a discussion and motivation
    ports_H_flow[i]     = semiLinear(ports[i].m_flow, inStream(ports[i].h_outflow), ports[i].h_outflow)
      "Enthalpy flow";
    for j in 1:Medium.nXi loop
      ports_mXi_flow[i,j] = semiLinear(ports[i].m_flow, inStream(ports[i].Xi_outflow[j]), ports[i].Xi_outflow[j])
        "Component mass flow";
    end for;
    for j in 1:Medium.nC loop
      ports_mC_flow[i,j]  = semiLinear(ports[i].m_flow, inStream(ports[i].C_outflow[j]),  ports[i].C_outflow[j])
        "Trace substance mass flow";
    end for;
  end for;

  for i in 1:Medium.nXi loop
    mbXi_flow[i] = sum(ports_mXi_flow[:,i]);
  end for;

  for i in 1:Medium.nC loop
    mbC_flow[i]  = sum(ports_mC_flow[:,i]);
  end for;

  mb_flow = sum(ports.m_flow);
  Hb_flow = sum(ports_H_flow);

  // Energy and mass balances
  if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    0 = Hb_flow + Q_flow;
  else
    der(U) = Hb_flow + Q_flow;
  end if;

  if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    0 = mb_flow + (if simplify_mWat_flow then 0 else mWat_flow_internal);
  else
    der(m) = mb_flow + (if simplify_mWat_flow then 0 else mWat_flow_internal);
  end if;

  if substanceDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    zeros(Medium.nXi) = mbXi_flow + mWat_flow_internal * s;
  else
    der(medium.Xi) = (mbXi_flow + mWat_flow_internal * s)/m;
  end if;

  if traceDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    zeros(Medium.nC)  = mbC_flow + C_flow_internal;
  else
    der(mC)  = mbC_flow + C_flow_internal;
  end if;

  // Properties of outgoing flows
  for i in 1:nPorts loop
      ports[i].p          = medium.p;
      ports[i].h_outflow  = medium.h;
      ports[i].Xi_outflow = medium.Xi;
      ports[i].C_outflow  = C;
  end for;
  UOut=U;
  mXiOut=mXi;
  mOut=m;
  mCOut=mC;
end ConservationEquation;


Partial model with eight ports

within Buildings.Fluid.Interfaces;
partial model EightPort "Partial model with eight ports"

  replaceable package Medium1 =
    Modelica.Media.Interfaces.PartialMedium "Medium 1 in the component"
  replaceable package Medium2 =
    Modelica.Media.Interfaces.PartialMedium "Medium 2 in the component"
  replaceable package Medium3 =
    Modelica.Media.Interfaces.PartialMedium "Medium 3 in the component"
  replaceable package Medium4 =
    Modelica.Media.Interfaces.PartialMedium "Medium 4 in the component"

  parameter Boolean allowFlowReversal1 = true
    "= true to allow flow reversal in medium 1, false restricts to design direction (port_a -> port_b)"
  parameter Boolean allowFlowReversal2 = true
    "= true to allow flow reversal in medium 2, false restricts to design direction (port_a -> port_b)"
  parameter Boolean allowFlowReversal3 = true
    "= true to allow flow reversal in medium 3, false restricts to design direction (port_a -> port_b)"
  parameter Boolean allowFlowReversal4 = true
    "= true to allow flow reversal in medium 4, false restricts to design direction (port_a -> port_b)"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_a1_start=Medium1.h_default
    "Start value for enthalpy flowing out of port a1"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_b1_start=Medium1.h_default
    "Start value for enthalpy flowing out of port b1"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_a2_start=Medium2.h_default
    "Start value for enthalpy flowing out of port a2"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_b2_start=Medium2.h_default
    "Start value for enthalpy flowing out of port b2"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_a3_start=Medium3.h_default
    "Start value for enthalpy flowing out of port a1"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_b3_start=Medium3.h_default
    "Start value for enthalpy flowing out of port b1"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_a4_start=Medium4.h_default
    "Start value for enthalpy flowing out of port a1"

  parameter Modelica.Units.SI.SpecificEnthalpy h_outflow_b4_start=Medium4.h_default
    "Start value for enthalpy flowing out of port b1"

  Modelica.Fluid.Interfaces.FluidPort_a port_a1(
                     redeclare final package Medium = Medium1,
                     m_flow(min=if allowFlowReversal1 then -Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_a1_start))
    "Fluid connector a1 (positive design flow direction is from port_a1 to port_b1)"
  Modelica.Fluid.Interfaces.FluidPort_b port_b1(
                     redeclare final package Medium = Medium1,
                     m_flow(max=if allowFlowReversal1 then +Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_b1_start))
    "Fluid connector b1 (positive design flow direction is from port_a1 to port_b1)"

  Modelica.Fluid.Interfaces.FluidPort_a port_a2(
                     redeclare final package Medium = Medium2,
                     m_flow(min=if allowFlowReversal2 then -Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_a2_start))
    "Fluid connector a2 (positive design flow direction is from port_a2 to port_b2)"
  Modelica.Fluid.Interfaces.FluidPort_b port_b2(
                     redeclare final package Medium = Medium2,
                     m_flow(max=if allowFlowReversal2 then +Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_b2_start))
    "Fluid connector b2 (positive design flow direction is from port_a2 to port_b2)"

  Modelica.Fluid.Interfaces.FluidPort_a port_a3(
                     redeclare final package Medium = Medium3,
                     m_flow(min=if allowFlowReversal3 then -Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_a3_start))
    "Fluid connector a1 (positive design flow direction is from port_a3 to port_b3)"
  Modelica.Fluid.Interfaces.FluidPort_b port_b3(
                     redeclare final package Medium = Medium3,
                     m_flow(max=if allowFlowReversal3 then +Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_b3_start))
    "Fluid connector b2 (positive design flow direction is from port_a3 to port_b3)"
  Modelica.Fluid.Interfaces.FluidPort_a port_a4(
                     redeclare final package Medium = Medium4,
                     m_flow(min=if allowFlowReversal4 then -Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_a4_start))
    "Fluid connector a1 (positive design flow direction is from port_a4 to port_b4)"
  Modelica.Fluid.Interfaces.FluidPort_b port_b4(
                     redeclare final package Medium = Medium4,
                     m_flow(max=if allowFlowReversal4 then +Modelica.Constants.inf else 0),
                     h_outflow(start=h_outflow_b4_start))
    "Fluid connector b2 (positive design flow direction is from port_a4 to port_b4)"
end EightPort;


within Buildings.Fluid.Interfaces;
model EightPortHeatMassExchanger
  "Model transporting four fluid streams between eight ports with storing mass or energy"
  extends Buildings.Fluid.Interfaces.PartialEightPortInterface(
    final h_outflow_a1_start = h1_outflow_start,
    final h_outflow_b1_start = h1_outflow_start,
    final h_outflow_a2_start = h2_outflow_start,
    final h_outflow_b2_start = h2_outflow_start,
    final h_outflow_a3_start = h3_outflow_start,
    final h_outflow_b3_start = h3_outflow_start,
    final h_outflow_a4_start = h4_outflow_start,
    final h_outflow_b4_start = h4_outflow_start);

  extends Buildings.Fluid.Interfaces.EightPortFlowResistanceParameters(
     final computeFlowResistance1=true, final computeFlowResistance2=true,final computeFlowResistance3=true, final computeFlowResistance4=true);

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

  parameter Modelica.Units.SI.Time tau1=30 "Time constant at nominal flow"
  parameter Modelica.Units.SI.Time tau2=30 "Time constant at nominal flow"
  parameter Modelica.Units.SI.Time tau3=30 "Time constant at nominal flow"
  parameter Modelica.Units.SI.Time tau4=30 "Time constant at nominal flow"

  // Assumptions
  parameter Modelica.Fluid.Types.Dynamics energyDynamics=Modelica.Fluid.Types.Dynamics.DynamicFreeInitial
    "Formulation of energy balance"

  // Initialization
  parameter Medium1.AbsolutePressure p1_start = Medium1.p_default
    "Start value of pressure"
  parameter Medium1.Temperature T1_start = Medium1.T_default
    "Start value of temperature"
  parameter Medium1.MassFraction X1_start[Medium1.nX] = Medium1.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium1.ExtraProperty C1_start[Medium1.nC](
       quantity=Medium1.extraPropertiesNames)=fill(0, Medium1.nC)
    "Start value of trace substances"
  parameter Medium1.ExtraProperty C1_nominal[Medium1.nC](
       quantity=Medium1.extraPropertiesNames) = fill(1E-2, Medium1.nC)
    "Nominal value of trace substances. (Set to typical order of magnitude.)"

  parameter Medium2.AbsolutePressure p2_start = Medium2.p_default
    "Start value of pressure"
  parameter Medium2.Temperature T2_start = Medium2.T_default
    "Start value of temperature"
  parameter Medium2.MassFraction X2_start[Medium2.nX] = Medium2.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium2.ExtraProperty C2_start[Medium2.nC](
       quantity=Medium2.extraPropertiesNames)=fill(0, Medium2.nC)
    "Start value of trace substances"
  parameter Medium2.ExtraProperty C2_nominal[Medium2.nC](
       quantity=Medium2.extraPropertiesNames) = fill(1E-2, Medium2.nC)
    "Nominal value of trace substances. (Set to typical order of magnitude.)"

  parameter Medium3.AbsolutePressure p3_start = Medium3.p_default
    "Start value of pressure"
  parameter Medium3.Temperature T3_start = Medium3.T_default
    "Start value of temperature"
  parameter Medium3.MassFraction X3_start[Medium3.nX] = Medium3.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium3.ExtraProperty C3_start[Medium3.nC](
       quantity=Medium3.extraPropertiesNames)=fill(0, Medium3.nC)
    "Start value of trace substances"
  parameter Medium3.ExtraProperty C3_nominal[Medium3.nC](
       quantity=Medium3.extraPropertiesNames) = fill(1E-2, Medium3.nC)
    "Nominal value of trace substances. (Set to typical order of magnitude.)"

  parameter Medium4.AbsolutePressure p4_start = Medium4.p_default
    "Start value of pressure"
  parameter Medium4.Temperature T4_start = Medium4.T_default
    "Start value of temperature"
  parameter Medium4.MassFraction X4_start[Medium4.nX] = Medium4.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium4.ExtraProperty C4_start[Medium4.nC](
       quantity=Medium4.extraPropertiesNames)=fill(0, Medium4.nC)
    "Start value of trace substances"
  parameter Medium4.ExtraProperty C4_nominal[Medium4.nC](
       quantity=Medium4.extraPropertiesNames) = fill(1E-2, Medium4.nC)
    "Nominal value of trace substances. (Set to typical order of magnitude.)"

  Modelica.Units.SI.HeatFlowRate Q1_flow=vol1.heatPort.Q_flow
    "Heat flow rate into medium 1";
  Modelica.Units.SI.HeatFlowRate Q2_flow=vol2.heatPort.Q_flow
    "Heat flow rate into medium 2";
  Modelica.Units.SI.HeatFlowRate Q3_flow=vol3.heatPort.Q_flow
    "Heat flow rate into medium 1";
  Modelica.Units.SI.HeatFlowRate Q4_flow=vol4.heatPort.Q_flow
    "Heat flow rate into medium 2";

  Buildings.Fluid.MixingVolumes.MixingVolume vol1(
    redeclare final package Medium = Medium1,
    nPorts = 2,
    V=m1_flow_nominal*tau1/rho1_nominal,
    final m_flow_nominal=m1_flow_nominal,
    energyDynamics=if tau1 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    massDynamics=if tau1 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    final p_start=p1_start,
    final T_start=T1_start,
    final X_start=X1_start,
    final C_start=C1_start,
    final C_nominal=C1_nominal) "Volume for fluid 1"

  Buildings.Fluid.MixingVolumes.MixingVolume vol2(
    redeclare final package Medium = Medium2,
    nPorts=2,
    energyDynamics=if tau2 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    massDynamics=if tau2 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    final p_start=p2_start,
    final T_start=T2_start,
    final X_start=X2_start,
    final C_start=C2_start,
    final C_nominal=C2_nominal,
    final m_flow_nominal=m2_flow_nominal,
    V=m2_flow_nominal*tau2/rho2_nominal) "Volume for fluid 2"

  Buildings.Fluid.MixingVolumes.MixingVolume vol3(
    redeclare final package Medium = Medium3,
    energyDynamics=if tau3 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    massDynamics=if tau3 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    final p_start=p3_start,
    final T_start=T3_start,
    final X_start=X3_start,
    final C_start=C3_start,
    final C_nominal=C3_nominal,
    final m_flow_nominal=m3_flow_nominal,
    V=m3_flow_nominal*tau3/rho3_nominal,
    nPorts=2) "Volume for fluid 3"
  Buildings.Fluid.MixingVolumes.MixingVolume vol4(
    redeclare final package Medium = Medium4,
    nPorts=2,
    energyDynamics=if tau4 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    massDynamics=if tau4 > Modelica.Constants.eps
                         then energyDynamics else
                         Modelica.Fluid.Types.Dynamics.SteadyState,
    final p_start=p4_start,
    final T_start=T4_start,
    final X_start=X4_start,
    final C_start=C4_start,
    final C_nominal=C4_nominal,
    final m_flow_nominal=m4_flow_nominal,
    V=m4_flow_nominal*tau4/rho4_nominal) "Volume for fluid 4"

  Buildings.Fluid.FixedResistances.PressureDrop preDro1(
    redeclare final package Medium = Medium1,
    final m_flow_nominal=m1_flow_nominal,
    final deltaM=deltaM1,
    final allowFlowReversal=allowFlowReversal1,
    final show_T=false,
    final from_dp=from_dp1,
    final linearized=linearizeFlowResistance1,
    final homotopyInitialization=homotopyInitialization,
    final dp_nominal=dp1_nominal) "Pressure drop model for fluid 1"

  Buildings.Fluid.FixedResistances.PressureDrop preDro2(
    redeclare final package Medium = Medium2,
    final m_flow_nominal=m2_flow_nominal,
    final deltaM=deltaM2,
    final allowFlowReversal=allowFlowReversal2,
    final show_T=false,
    final from_dp=from_dp2,
    final linearized=linearizeFlowResistance2,
    final homotopyInitialization=homotopyInitialization,
    final dp_nominal=dp2_nominal) "Pressure drop model for fluid 2"
  Buildings.Fluid.FixedResistances.PressureDrop preDro3(
    redeclare final package Medium = Medium3,
    final m_flow_nominal=m3_flow_nominal,
    final deltaM=deltaM3,
    final allowFlowReversal=allowFlowReversal3,
    final show_T=false,
    final from_dp=from_dp3,
    final linearized=linearizeFlowResistance3,
    final homotopyInitialization=homotopyInitialization,
    final dp_nominal=dp3_nominal) "Pressure drop model for fluid 3"

  Buildings.Fluid.FixedResistances.PressureDrop preDro4(
    redeclare final package Medium = Medium4,
    final m_flow_nominal=m4_flow_nominal,
    final deltaM=deltaM4,
    final allowFlowReversal=allowFlowReversal4,
    final show_T=false,
    final from_dp=from_dp4,
    final linearized=linearizeFlowResistance4,
    final homotopyInitialization=homotopyInitialization,
    final dp_nominal=dp4_nominal) "Pressure drop model for fluid 4"
protected
  parameter Medium1.ThermodynamicState sta1_nominal=Medium1.setState_pTX(
      T=Medium1.T_default, p=Medium1.p_default, X=Medium1.X_default);
  parameter Modelica.Units.SI.Density rho1_nominal=Medium1.density(sta1_nominal)
    "Density, used to compute fluid volume";
  parameter Medium2.ThermodynamicState sta2_nominal=Medium2.setState_pTX(
      T=Medium2.T_default, p=Medium2.p_default, X=Medium2.X_default);
  parameter Modelica.Units.SI.Density rho2_nominal=Medium2.density(sta2_nominal)
    "Density, used to compute fluid volume";
  parameter Medium1.ThermodynamicState sta3_nominal=Medium3.setState_pTX(
      T=Medium3.T_default, p=Medium3.p_default, X=Medium3.X_default);
  parameter Modelica.Units.SI.Density rho3_nominal=Medium3.density(sta3_nominal)
    "Density, used to compute fluid volume";
  parameter Medium4.ThermodynamicState sta4_nominal=Medium4.setState_pTX(
      T=Medium4.T_default, p=Medium4.p_default, X=Medium4.X_default);
  parameter Modelica.Units.SI.Density rho4_nominal=Medium4.density(sta4_nominal)
    "Density, used to compute fluid volume";

  parameter Medium1.ThermodynamicState sta1_start=Medium1.setState_pTX(
      T=T1_start, p=p1_start, X=X1_start);
  parameter Modelica.Units.SI.SpecificEnthalpy h1_outflow_start=
      Medium1.specificEnthalpy(sta1_start)
    "Start value for outflowing enthalpy";
  parameter Medium2.ThermodynamicState sta2_start=Medium2.setState_pTX(
      T=T2_start, p=p2_start, X=X2_start);
  parameter Modelica.Units.SI.SpecificEnthalpy h2_outflow_start=
      Medium2.specificEnthalpy(sta2_start)
    "Start value for outflowing enthalpy";
  parameter Medium3.ThermodynamicState sta3_start=Medium3.setState_pTX(
      T=T3_start, p=p3_start, X=X3_start);
  parameter Modelica.Units.SI.SpecificEnthalpy h3_outflow_start=
      Medium3.specificEnthalpy(sta3_start)
    "Start value for outflowing enthalpy";
  parameter Medium4.ThermodynamicState sta4_start=Medium4.setState_pTX(
      T=T4_start, p=p4_start, X=X4_start);
  parameter Modelica.Units.SI.SpecificEnthalpy h4_outflow_start=
      Medium4.specificEnthalpy(sta4_start)
    "Start value for outflowing enthalpy";

initial algorithm
  // Check for tau1
  assert((energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState) or
          tau1 > Modelica.Constants.eps,
"The parameter tau1, or the volume of the model from which tau may be derived, is unreasonably small.
 You need to set energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState to model steady-state.
 Received tau1 = " + String(tau1) + "\n");

 // Check for tau2
  assert((energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState) or
          tau2 > Modelica.Constants.eps,
"The parameter tau2, or the volume of the model from which tau may be derived, is unreasonably small.
 You need to set energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState to model steady-state.
 Received tau2 = " + String(tau2) + "\n");

  // Check for tau1
  assert((energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState) or
          tau3 > Modelica.Constants.eps,
"The parameter tau3, or the volume of the model from which tau may be derived, is unreasonably small.
 You need to set energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState to model steady-state.
 Received tau3 = " + String(tau3) + "\n");

 // Check for tau2
  assert((energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState) or
          tau4 > Modelica.Constants.eps,
"The parameter tau4, or the volume of the model from which tau may be derived, is unreasonably small.
 You need to set energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState to model steady-state.
 Received tau4 = " + String(tau4) + "\n");

  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
  connect(vol1.ports[2], port_b1)
  connect(port_a1, preDro1.port_a)
  connect(preDro1.port_b, vol1.ports[1])
  connect(port_a2, preDro2.port_a)
  connect(preDro4.port_a, port_a4)
  connect(preDro4.port_b, vol4.ports[1])
  connect(port_b4, vol4.ports[2])
  connect(port_a3, preDro3.port_a)
  connect(preDro3.port_b, vol3.ports[1])
  connect(port_b3, vol3.ports[2])
  connect(port_b2, vol2.ports[1])
  connect(preDro2.port_b, vol2.ports[2])
end EightPortHeatMassExchanger;