diff --git a/Buildings/Applications/BaseClasses/Controls/VariableSpeedPumpStage.mo b/Buildings/Applications/BaseClasses/Controls/VariableSpeedPumpStage.mo
index 754b67d33ec..f18b7e4788b 100644
--- a/Buildings/Applications/BaseClasses/Controls/VariableSpeedPumpStage.mo
+++ b/Buildings/Applications/BaseClasses/Controls/VariableSpeedPumpStage.mo
@@ -99,8 +99,10 @@ model VariableSpeedPumpStage "Staging control for variable speed pumps"
final integerFalse=0, final integerTrue=2)
annotation (Placement(transformation(extent={{20,-80},{40,-60}})));
Buildings.Controls.OBC.CDL.Integers.Add addInt
+ "Outputs how many pumps are to be commanded on"
annotation (Placement(transformation(extent={{60,-56},{80,-36}})));
Buildings.Controls.OBC.CDL.Conversions.IntegerToReal intToRea
+ "Comparison from integer to real signal for number of pumps"
annotation (Placement(transformation(extent={{40,-10},{60,10}})));
Modelica.Blocks.Interfaces.BooleanInput on
@@ -115,12 +117,12 @@ equation
pattern=LinePattern.Dash));
connect(con1.outPort, oneOn.inPort[1])
annotation (Line(
- points={{-50,38.5},{-50,26},{-50.5,26},{-50.5,21}},
+ points={{-50,38.5},{-50,26},{-49.75,26},{-49.75,21}},
color={0,0,0},
pattern=LinePattern.Dash));
connect(con2.inPort, oneOn.outPort[1])
annotation (Line(
- points={{-50,-26},{-50,-10},{-50.25,-10},{-50.25,-0.5}},
+ points={{-50,-26},{-50,-10},{-49.875,-10},{-49.875,-0.5}},
color={0,0,0},
pattern=LinePattern.Dash));
connect(con2.outPort, twoOn.inPort[1])
@@ -140,12 +142,12 @@ equation
pattern=LinePattern.Dash));
connect(con3.outPort, oneOn.inPort[2])
annotation (Line(
- points={{-10,-38.5},{-10,26},{-49.5,26},{-49.5,21}},
+ points={{-10,-38.5},{-10,26},{-50.25,26},{-50.25,21}},
color={0,0,0},
pattern=LinePattern.Dash));
connect(con4.inPort, oneOn.outPort[2])
annotation (Line(
- points={{-22,48},{-22,-10},{-49.75,-10},{-49.75,-0.5}},
+ points={{-22,48},{-22,-10},{-50.125,-10},{-50.125,-0.5}},
color={0,0,0},
pattern=LinePattern.Dash));
connect(combiTable1Ds.y, y)
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/IdealUser.mo b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/IdealUser.mo
new file mode 100644
index 00000000000..96e1e39cfec
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/IdealUser.mo
@@ -0,0 +1,194 @@
+within Buildings.Experimental.DHC.Plants.Cooling.BaseClasses;
+model IdealUser "Ideal user model"
+ replaceable package Medium =
+ Modelica.Media.Interfaces.PartialMedium "Medium package";
+
+ parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
+ "Nominal mass flow rate";
+
+ parameter Modelica.Units.SI.Temperature T_CHWR_nominal
+ "Nominal temperature of CHW return";
+ parameter Modelica.Units.SI.PressureDifference dp_nominal(
+ final displayUnit="Pa")
+ "Nominal pressure drop when valve is fully open";
+
+ Buildings.Fluid.Actuators.Valves.TwoWayEqualPercentage val(
+ redeclare final package Medium = Medium,
+ final use_inputFilter=true,
+ final dpValve_nominal=dp_nominal/2,
+ final init=Modelica.Blocks.Types.Init.InitialState,
+ final dpFixed_nominal=dp_nominal/2,
+ final m_flow_nominal=m_flow_nominal,
+ y_start=0) "User control valve"
+ annotation (Placement(transformation(extent={{0,50},{20,70}})));
+ Buildings.Controls.Continuous.LimPID conPI(
+ controllerType=Modelica.Blocks.Types.SimpleController.PI,
+ k=0.5,
+ Ti=20,
+ final reverseActing=true) "PI controller" annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-30,80})));
+ Modelica.Blocks.Interfaces.RealInput mPre_flow(
+ final quantity = "MassFlowRate",
+ final unit = "kg/s")
+ "Load in terms of flow rate prescription" annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,80}), iconTransformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,80})));
+ Modelica.Blocks.Interfaces.RealOutput yVal_actual(
+ final unit = "1")
+ "Consumer control valve actuator position" annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,80}), iconTransformation(
+ extent={{10,-10},{-10,10}},
+ rotation=180,
+ origin={110,80})));
+ Buildings.Fluid.Sensors.RelativePressure senRelPre(
+ redeclare final package Medium = Medium)
+ "Differential pressure sensor" annotation (Placement(
+ transformation(
+ extent={{-10,10},{10,-10}},
+ rotation=-90,
+ origin={-72,20})));
+ Modelica.Blocks.Interfaces.RealOutput dp(
+ final quantity="PressureDifference",
+ final unit="Pa",
+ displayUnit="Pa")
+ "Differential pressure from the sensor" annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,20}), iconTransformation(
+ extent={{10,-10},{-10,10}},
+ rotation=180,
+ origin={110,40})));
+ Modelica.Fluid.Interfaces.FluidPort_a port_a(
+ p(start=Medium.p_default),
+ redeclare final package Medium = Medium,
+ h_outflow(start=Medium.h_default, nominal=Medium.h_default))
+ "Fluid connector a (positive design flow direction is from port_a to port_b)"
+ annotation (Placement(transformation(extent={{-110,50},{-90,70}}),
+ iconTransformation(extent={{-110,50},{-90,70}})));
+ Modelica.Fluid.Interfaces.FluidPort_b port_b(
+ p(start=Medium.p_default),
+ redeclare final package Medium = Medium,
+ h_outflow(start=Medium.h_default, nominal=Medium.h_default))
+ "Fluid connector b (positive design flow direction is from port_a to port_b)"
+ annotation (Placement(transformation(extent={{-90,-70},{-110,-50}}),
+ iconTransformation(extent={{-90,-70},{-110,-50}})));
+ Buildings.Fluid.Sensors.MassFlowRate senMasFlo(
+ redeclare final package Medium = Medium)
+ "Mass flow rate sensor"
+ annotation (Placement(transformation(extent={{-20,-70},{-40,-50}})));
+ Buildings.Fluid.Sources.PropertySource_T proSou(
+ redeclare final package Medium = Medium,
+ final use_T_in=true) "Ideal temperature source" annotation (Placement(
+ transformation(
+ extent={{10,-10},{-10,10}},
+ rotation=0,
+ origin={10,-60})));
+ Modelica.Blocks.Sources.Constant TRet(final k=T_CHWR_nominal)
+ "Constant CHW return temperature"
+ annotation (Placement(transformation(extent={{-20,-40},{0,-20}})));
+equation
+ connect(senRelPre.p_rel, dp) annotation (Line(points={{-63,20},{110,20}},
+ color={0,0,127}));
+ connect(val.y_actual, yVal_actual)
+ annotation (Line(points={{15,67},{40,67},{40,80},{110,80}},
+ color={0,0,127}));
+ connect(senRelPre.port_a, port_a) annotation (Line(
+ points={{-72,30},{-72,60},{-100,60}},
+ color={0,127,255},
+ pattern=LinePattern.Dash));
+ connect(senRelPre.port_b, port_b) annotation (Line(
+ points={{-72,10},{-72,-60},{-100,-60}},
+ color={0,127,255},
+ pattern=LinePattern.Dash));
+ connect(conPI.y, val.y)
+ annotation (Line(points={{-19,80},{10,80},{10,72}}, color={0,0,127}));
+ connect(senMasFlo.port_b, port_b)
+ annotation (Line(points={{-40,-60},{-100,-60}}, color={0,127,255}));
+ connect(senMasFlo.m_flow, conPI.u_m)
+ annotation (Line(points={{-30,-49},{-30,68}}, color={0,0,127}));
+ connect(port_a, val.port_a)
+ annotation (Line(points={{-100,60},{0,60}}, color={0,127,255}));
+ connect(conPI.u_s, mPre_flow)
+ annotation (Line(points={{-42,80},{-110,80}}, color={0,0,127}));
+ connect(senMasFlo.port_a, proSou.port_b)
+ annotation (Line(points={{-20,-60},{0,-60}}, color={0,127,255}));
+ connect(proSou.port_a, val.port_b) annotation (Line(points={{20,-60},{40,-60},
+ {40,60},{20,60}}, color={0,127,255}));
+ connect(TRet.y, proSou.T_in)
+ annotation (Line(points={{1,-30},{14,-30},{14,-48}}, color={0,0,127}));
+ annotation (
+ defaultComponentName = "ideUse",
+ Documentation(info="
+
+This is a simple ideal user model used by
+
+Buildings.Experimental.DHC.Plants.Cooling.Examples.StoragePlantDualSource.
+The load of the user is described by a varying mass flow rate setpoint.
+The valve is controlled to maintain the requested mass flow rate.
+The model sets its leaving water temperature to the user-specified temperature
+T_CHWR_nominal.
+
+", revisions="
+
+
+July 31, 2023, by Michael Wetter:
+Revised implementation, removed unused parameter.
+
+
+February 18, 2022 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+This model provides junction models in parallel on the distribution pipe lines.
+This model is for breaking algebraic loops only and has no pressure drop.
+
+", revisions="
+
+
+July 31, 2023, by Michael Wetter:
+Propagated parameters, and introduced design flow rate m_flow_nominal.
+
+
+October 31, 2022 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+This model contains two pipes in parallel that represent the supply and return
+pipes of a district CHW network.
+Only pressure drop is considered. This model does not consider heat loss.
+
+", revisions="
+
+
+July 27, 2022 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+"));
+end ParallelPipes;
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/ReversibleConnection.mo b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/ReversibleConnection.mo
new file mode 100644
index 00000000000..65fca950256
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/ReversibleConnection.mo
@@ -0,0 +1,166 @@
+within Buildings.Experimental.DHC.Plants.Cooling.BaseClasses;
+model ReversibleConnection
+ "A connection that supports reversible flow with a pump and a valve"
+ extends Buildings.Fluid.Interfaces.PartialTwoPortInterface(
+ final allowFlowReversal=true);
+
+ parameter Modelica.Units.SI.MassFlowRate mTan_flow_nominal(min=0)
+ "Nominal mass flow rate for CHW tank branch";
+ parameter Modelica.Units.SI.PressureDifference dpPum_nominal
+ "Nominal pressure difference for supply pump sizing";
+ parameter Modelica.Units.SI.PressureDifference dpVal_nominal
+ "Nominal pressure difference for return valve sizing";
+ parameter Modelica.Units.SI.ThermodynamicTemperature T_start
+ "Start temperature"
+ annotation(Dialog(tab = "Initialization"));
+
+ Modelica.Blocks.Interfaces.RealOutput PEle(
+ final quantity="Power",
+ final unit="W")
+ "Estimated power consumption" annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,50}), iconTransformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,60})));
+ Modelica.Blocks.Interfaces.RealInput yPum(final unit="1")
+ "Normalized speed signal for pump" annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,70}), iconTransformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,60})));
+ Buildings.Fluid.Movers.Preconfigured.SpeedControlled_y pum(
+ redeclare final package Medium = Medium,
+ final m_flow_nominal=m_flow_nominal,
+ final addPowerToMedium=false,
+ final dp_nominal=dpPum_nominal) "Supply pump"
+ annotation (Placement(transformation(extent={{0,40},{20,60}})));
+ Buildings.Fluid.FixedResistances.CheckValve cheVal(
+ redeclare final package Medium = Medium,
+ final m_flow_nominal=m_flow_nominal,
+ final dpValve_nominal=0.1*dpPum_nominal,
+ final dpFixed_nominal=0) "Check valve"
+ annotation (Placement(transformation(extent={{-40,40},{-20,60}})));
+ Buildings.Fluid.Actuators.Valves.TwoWayPressureIndependent val(
+ redeclare final package Medium = Medium,
+ final m_flow_nominal=mTan_flow_nominal,
+ final dpValve_nominal=dpVal_nominal,
+ y_start=0)
+ "Valve that throttles CHW from the supply line to the tank"
+ annotation (Placement(transformation(extent={{20,-40},{0,-20}})));
+ Modelica.Blocks.Interfaces.RealInput yVal(final unit="1")
+ "Normalized flow signal for valve" annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,-70}), iconTransformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,-60})));
+
+protected
+ Buildings.Fluid.FixedResistances.Junction jun1(
+ redeclare final package Medium = Medium,
+ energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
+ tau=30,
+ T_start=T_start,
+ m_flow_nominal={-mTan_flow_nominal,m_flow_nominal,-m_flow_nominal},
+ dp_nominal={0,0,0}) "Junction" annotation (Placement(transformation(
+ extent={{-10,10},{10,-10}},
+ rotation=90,
+ origin={-70,0})));
+ Buildings.Fluid.FixedResistances.Junction jun2(
+ redeclare final package Medium = Medium,
+ energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
+ tau=30,
+ T_start=T_start,
+ m_flow_nominal={-m_flow_nominal,-mTan_flow_nominal,m_flow_nominal},
+ dp_nominal={0,0,0}) "Junction" annotation (Placement(transformation(
+ extent={{-10,10},{10,-10}},
+ rotation=270,
+ origin={70,0})));
+
+equation
+ connect(pum.port_b, jun2.port_1)
+ annotation (Line(points={{20,50},{70,50},{70,10}}, color={0,127,255}));
+ connect(jun2.port_3, port_b) annotation (Line(points={{80,-1.77636e-15},{90,-1.77636e-15},
+ {90,0},{100,0}}, color={0,127,255}));
+ connect(val.port_a, jun2.port_2)
+ annotation (Line(points={{20,-30},{70,-30},{70,-10}}, color={0,127,255}));
+ connect(jun1.port_3, port_a) annotation (Line(points={{-80,5.55112e-16},{-90,5.55112e-16},
+ {-90,0},{-100,0}}, color={0,127,255}));
+ connect(jun1.port_1, val.port_b)
+ annotation (Line(points={{-70,-10},{-70,-30},{0,-30}}, color={0,127,255}));
+ connect(pum.y, yPum)
+ annotation (Line(points={{10,62},{10,70},{-110,70}}, color={0,0,127}));
+ connect(yVal, val.y) annotation (Line(points={{-110,-70},{-20,-70},{-20,-10},{
+ 10,-10},{10,-18}}, color={0,0,127}));
+ connect(pum.P, PEle) annotation (Line(points={{21,59},{90,59},{90,50},{110,50}},
+ color={0,0,127}));
+ connect(jun1.port_2, cheVal.port_a)
+ annotation (Line(points={{-70,10},{-70,50},{-40,50}}, color={0,127,255}));
+ connect(cheVal.port_b, pum.port_a)
+ annotation (Line(points={{-20,50},{0,50}}, color={0,127,255}));
+ annotation (Documentation(info="
+
+This model implements a piping connection for reversible mass flow rate between the storage plant
+and the district network.
+
+", revisions="
+
+
+March 22, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+"), defaultComponentName = "revCon", Icon(graphics={ Rectangle(
+ extent={{-100,100},{100,-100}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Rectangle(extent={{-80,40},{80,-40}}, lineColor={28,108,200}),
+ Line(points={{-100,0},{-80,0}}, color={28,108,200}),
+ Ellipse(
+ extent={{20,60},{60,20}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Polygon(
+ points={{40,-40},{24,-30},{24,-50},{40,-40}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Polygon(
+ points={{40,-40},{56,-30},{56,-50},{40,-40}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Polygon(
+ points={{60,40},{40,60},{40,20},{60,40}},
+ lineColor={0,0,0},
+ fillColor={0,0,0},
+ fillPattern=FillPattern.Solid),
+ Line(points={{80,0},{100,0}}, color={28,108,200}),
+ Polygon(
+ points={{-6,2},{2.74617e-16,-16},{-12,-16},{-6,2}},
+ lineColor={28,108,200},
+ lineThickness=1,
+ fillColor={28,108,200},
+ fillPattern=FillPattern.Solid,
+ origin={2,44},
+ rotation=-90),
+ Line(points={{-14,50},{-54,50}}, color={0,127,255}),
+ Line(points={{4,-30},{-36,-30}}, color={0,127,255}),
+ Polygon(
+ points={{-2,6},{16,0},{16,12},{-2,6}},
+ lineColor={28,108,200},
+ lineThickness=1,
+ fillColor={28,108,200},
+ fillPattern=FillPattern.Solid,
+ origin={-52,-36},
+ rotation=360)}));
+end ReversibleConnection;
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/TankBranch.mo b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/TankBranch.mo
new file mode 100644
index 00000000000..ac3c611f23a
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/TankBranch.mo
@@ -0,0 +1,235 @@
+within Buildings.Experimental.DHC.Plants.Cooling.BaseClasses;
+model TankBranch "Model of the tank branch of a storage plant"
+
+ extends Buildings.Fluid.Interfaces.PartialFourPort(
+ redeclare final package Medium1 = Medium,
+ redeclare final package Medium2 = Medium);
+
+ replaceable package Medium =
+ Modelica.Media.Interfaces.PartialMedium "Medium package";
+
+ parameter Modelica.Units.SI.MassFlowRate m_flow_nominal(min=0)
+ "Nominal mass flow rate"
+ annotation(Dialog(group="Nominal values"));
+ parameter Modelica.Units.SI.MassFlowRate mTan_flow_nominal(min=0)
+ "Nominal mass flow rate for CHW tank branch"
+ annotation(Dialog(group="Nominal values"));
+ parameter Modelica.Units.SI.MassFlowRate mChi_flow_nominal(min=0)
+ "Nominal mass flow rate for CHW chiller branch"
+ annotation(Dialog(group="Nominal values"));
+ parameter Modelica.Units.SI.Temperature T_CHWS_nominal(displayUnit="degC")=
+ 7+273.15 "Nominal temperature of CHW supply"
+ annotation(Dialog(group="Nominal values"));
+ parameter Modelica.Units.SI.Temperature T_CHWR_nominal(displayUnit="degC")=
+ 12+273.15
+ "Nominal temperature of CHW return"
+ annotation(Dialog(group="Nominal values"));
+
+ // Storage tank parameters
+ parameter Modelica.Units.SI.Volume VTan "Tank volume"
+ annotation(Dialog(group="Tank"));
+ parameter Modelica.Units.SI.Length hTan
+ "Height of tank (without insulation)"
+ annotation(Dialog(group="Tank"));
+ parameter Modelica.Units.SI.Length dIns "Thickness of insulation"
+ annotation(Dialog(group="Tank"));
+ parameter Modelica.Units.SI.ThermalConductivity kIns=0.04
+ "Specific heat conductivity of insulation"
+ annotation(Dialog(group="Tank"));
+ parameter Integer nSeg(min=2) = 5 "Number of volume segments"
+ annotation(Dialog(group="Tank"));
+ parameter Modelica.Fluid.Types.Dynamics energyDynamics=
+ Modelica.Fluid.Types.Dynamics.FixedInitial
+ "Formulation of energy balance"
+ annotation(Evaluate=true, Dialog(tab = "Dynamics", group="Conservation equations"));
+ parameter Medium.AbsolutePressure p_start = Medium.p_default
+ "Start value of pressure"
+ annotation(Dialog(tab = "Initialization"));
+ parameter Medium.Temperature T_start=T_CHWR_nominal
+ "Start value of temperature"
+ annotation(Dialog(tab = "Initialization"));
+ parameter Modelica.Units.SI.Temperature TFlu_start[nSeg]=T_start*ones(nSeg)
+ "Initial temperature of the tank segments, with TFlu_start[1] being the top segment"
+ annotation (Dialog(tab="Initialization"));
+ parameter Modelica.Units.SI.Time tau=1 "Time constant for mixing"
+ annotation(Dialog(group="Tank"));
+
+ Buildings.Fluid.Storage.Stratified tan(
+ redeclare final package Medium = Medium,
+ final allowFlowReversal=true,
+ final VTan=VTan,
+ final hTan=hTan,
+ final dIns=dIns,
+ final kIns=kIns,
+ final nSeg=nSeg,
+ final energyDynamics=energyDynamics,
+ final p_start=p_start,
+ final T_start=T_start,
+ final TFlu_start=TFlu_start,
+ final tau=tau,
+ final m_flow_nominal=mTan_flow_nominal,
+ show_T=true) "Tank"
+ annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
+ Buildings.Fluid.Sensors.MassFlowRate senFlo(
+ redeclare final package Medium = Medium,
+ final allowFlowReversal=true) "Flow rate sensor for the tank,"
+ annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=90,
+ origin={-50,-30})));
+ Modelica.Blocks.Interfaces.RealOutput mTan_flow(
+ final quantity="MassFlowRate",
+ final unit="kg/s") "Mass flow rate of the tank"
+ annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,90}), iconTransformation(
+ extent={{10,-10},{-10,10}},
+ rotation=180,
+ origin={110,100})));
+ Modelica.Blocks.Interfaces.RealOutput Ql_flow(
+ final quantity="HeatFlowRate",
+ final unit="W")
+ "Heat loss of tank (positive if heat flows from tank to ambient)"
+ annotation (Placement(transformation(extent={{100,0},{120,20}}),
+ iconTransformation(extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,0})));
+ Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorTop
+ "Heat port tank top (outside insulation)"
+ annotation (Placement(transformation(extent={{-16,34},{-4,46}}),
+ iconTransformation(extent={{14,34},{26,46}})));
+ Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorSid
+ "Heat port tank side (outside insulation)"
+ annotation (Placement(transformation(extent={{4,-26},{16,-14}}),
+ iconTransformation(extent={{26,-6},{38,6}})));
+ Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorBot
+ "Heat port tank bottom (outside insulation). Leave unconnected for adiabatic condition"
+ annotation (Placement(transformation(extent={{-16,-46},{-4,-34}}),
+ iconTransformation(extent={{14,-46},{26,-34}})));
+ Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a[tan.nSeg] heaPorVol
+ "Heat port that connects to the control volumes of the tank"
+ annotation (Placement(transformation(extent={{-26,-26},{-14,-14}}),
+ iconTransformation(extent={{-6,-6},{6,6}})));
+ Modelica.Blocks.Interfaces.RealOutput TTan[2](
+ each final quantity="Temperature",
+ each displayUnit="C") "Temperatures at the tank 1: top and 2: bottom"
+ annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={110,-90}), iconTransformation(
+ extent={{10,-10},{-10,10}},
+ rotation=180,
+ origin={110,-100})));
+ Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTemTop
+ "Temperature sensor for tank top"
+ annotation (Placement(transformation(extent={{20,20},{40,40}})));
+ Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTemBot
+ "Temperature sensor for tank bottom"
+ annotation (Placement(transformation(extent={{20,-40},{40,-20}})));
+
+protected
+ Buildings.Fluid.FixedResistances.Junction junSup(
+ redeclare final package Medium = Medium,
+ energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
+ T_start=T_CHWS_nominal,
+ tau=30,
+ m_flow_nominal={-mChi_flow_nominal,mTan_flow_nominal,m_flow_nominal},
+ dp_nominal={0,0,0}) "Junction on the supply side"
+ annotation (Placement(transformation(extent={{40,50},{60,70}})));
+ Buildings.Fluid.FixedResistances.Junction junRet(
+ redeclare final package Medium = Medium,
+ energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
+ T_start=T_CHWR_nominal,
+ tau=30,
+ m_flow_nominal={-m_flow_nominal,mChi_flow_nominal,mTan_flow_nominal},
+ dp_nominal={0,0,0}) "Junction on the return side" annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=180,
+ origin={-50,-60})));
+
+equation
+ connect(senFlo.m_flow, mTan_flow) annotation (Line(points={{-61,-30},{-66,-30},
+ {-66,90},{110,90}}, color={0,0,127}));
+ connect(tan.Ql_flow, Ql_flow)
+ annotation (Line(points={{11,7.2},{11,10},{110,10}},
+ color={0,0,127}));
+ connect(tan.heaPorTop, heaPorTop) annotation (Line(points={{2,7.4},{2,30},{-10,
+ 30},{-10,40}},color={191,0,0}));
+ connect(tan.heaPorSid, heaPorSid) annotation (Line(points={{5.6,0},{5.6,-4},{10,
+ -4},{10,-20}}, color={191,0,0}));
+ connect(tan.heaPorBot, heaPorBot)
+ annotation (Line(points={{2,-7.4},{2,-30},{-10,-30},{-10,-40}},
+ color={191,0,0}));
+ connect(heaPorVol, tan.heaPorVol) annotation (Line(points={{-20,-20},{-20,-4},
+ {0,-4},{0,0}}, color={191,0,0}));
+ connect(junRet.port_3, senFlo.port_a)
+ annotation (Line(points={{-50,-50},{-50,-40}}, color={0,127,255}));
+ connect(senFlo.port_b, tan.port_a)
+ annotation (Line(points={{-50,-20},{-50,0},{-10,0}}, color={0,127,255}));
+ connect(tan.port_b, junSup.port_3)
+ annotation (Line(points={{10,0},{50,0},{50,50}}, color={0,127,255}));
+ connect(tan.heaPorTop, senTemTop.port)
+ annotation (Line(points={{2,7.4},{2,30},{20,30}}, color={191,0,0}));
+ connect(tan.heaPorBot, senTemBot.port)
+ annotation (Line(points={{2,-7.4},{2,-30},{20,-30}}, color={191,0,0}));
+ connect(senTemTop.T, TTan[1]) annotation (Line(points={{41,30},{70,30},{70,-90},
+ {110,-90},{110,-92.5}}, color={0,0,127}));
+ connect(senTemBot.T, TTan[2]) annotation (Line(points={{41,-30},{70,-30},{70,-87.5},
+ {110,-87.5}}, color={0,0,127}));
+ connect(port_b2, junRet.port_2)
+ annotation (Line(points={{-100,-60},{-60,-60}}, color={0,127,255}));
+ connect(junRet.port_1, port_a2)
+ annotation (Line(points={{-40,-60},{100,-60}}, color={0,127,255}));
+ connect(junSup.port_2, port_b1)
+ annotation (Line(points={{60,60},{100,60}}, color={0,127,255}));
+ connect(junSup.port_1, port_a1)
+ annotation (Line(points={{40,60},{-100,60}}, color={0,127,255}));
+ annotation (Icon(coordinateSystem(preserveAspectRatio=false, extent={{-100,-100},
+ {100,100}}), graphics={
+ Rectangle(
+ extent={{-100,100},{100,-100}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Line(points={{-100,-60},{100,-60}}, color={28,108,200}),
+ Line(points={{-100,60},{100,60}}, color={28,108,200}),
+ Line(points={{-42,-60}}, color={28,108,200}),
+ Line(points={{-60,-58},{-60,50},{0,50},{0,-52},{60,-52},{60,60}}, color
+ ={28,108,200}),
+ Rectangle(
+ extent={{-28,40},{32,-40}},
+ lineColor={0,0,0},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Line(
+ points={{38,0},{80,0},{100,0}},
+ color={127,0,0},
+ pattern=LinePattern.Dot),
+ Line(
+ points={{26,-44},{52,-44},{52,0}},
+ color={127,0,0},
+ pattern=LinePattern.Dot),
+ Line(
+ points={{26,44},{52,44},{52,-2}},
+ color={127,0,0},
+ pattern=LinePattern.Dot)}), Diagram(
+ coordinateSystem(preserveAspectRatio=false, extent={{-100,-100},{100,100}})),
+ defaultComponentName = "tanBra",
+ Documentation(info="
+
+This model is part of a storage plant. This branch has a stratified tank.
+This tank can potentially be charged remotely by a chiller from the district
+CHW network, or by a chiller that is local to the energy transfer station that contains this tank.
+
+", revisions="
+
+
+October 4, 2022 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+"));
+end TankBranch;
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/Validation/IdealUser.mo b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/Validation/IdealUser.mo
new file mode 100644
index 00000000000..156d6b8207c
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/BaseClasses/Validation/IdealUser.mo
@@ -0,0 +1,79 @@
+within Buildings.Experimental.DHC.Plants.Cooling.BaseClasses.Validation;
+model IdealUser "Test model for the dummy user"
+ extends Modelica.Icons.Example;
+
+ package Medium = Buildings.Media.Water "Medium model";
+
+ parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=1
+ "Nominal mass flow rate";
+ parameter Modelica.Units.SI.PressureDifference dp_nominal=500000
+ "Nominal pressure difference";
+ parameter Modelica.Units.SI.AbsolutePressure p_Pressurisation=300000
+ "Pressurisation point";
+ parameter Modelica.Units.SI.Temperature T_CHWR_nominal(
+ final displayUnit="degC")=12+273.15
+ "Nominal temperature of CHW return";
+ parameter Modelica.Units.SI.Temperature T_CHWS_nominal(
+ final displayUnit="degC")=7+273.15
+ "Nominal temperature of CHW supply";
+ parameter Boolean allowFlowReversal=false
+ "Flow reversal setting";
+ parameter Modelica.Units.SI.Power QCooLoa_flow_nominal=5*4200*0.9
+ "Nominal cooling load of one consumer";
+
+ Buildings.Experimental.DHC.Plants.Cooling.BaseClasses.IdealUser ideUse(
+ redeclare final package Medium = Medium,
+ final m_flow_nominal=m_flow_nominal,
+ T_CHWR_nominal=T_CHWR_nominal,
+ final dp_nominal=dp_nominal) "Ideal user" annotation (Placement(
+ transformation(extent={{-10,-10},{10,10}}, rotation=0)));
+ Buildings.Fluid.Sources.Boundary_pT sin(
+ redeclare final package Medium = Medium,
+ final p=p_Pressurisation,
+ final T=T_CHWR_nominal,
+ nPorts=1) "Sink representing CHW return line"
+ annotation (Placement(transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-70,-30})));
+ Buildings.Fluid.Sources.Boundary_pT sou(
+ redeclare final package Medium = Medium,
+ final p=p_Pressurisation+dp_nominal,
+ final T=T_CHWS_nominal,
+ nPorts=1) "Source representing CHW supply line" annotation (Placement(
+ transformation(
+ extent={{10,10},{-10,-10}},
+ rotation=180,
+ origin={-70,30})));
+ Modelica.Blocks.Sources.TimeTable mPre_flow(table=[0*3600,0; 0.5*3600,0; 0.5*
+ 3600,m_flow_nominal; 0.75*3600,m_flow_nominal; 0.75*3600,0; 1*3600,0])
+ "Prescribed flow rate representing cooling load"
+ annotation (Placement(transformation(extent={{-80,60},{-60,80}})));
+equation
+ connect(sou.ports[1],ideUse. port_a) annotation (Line(points={{-60,30},{-40,
+ 30},{-40,6},{-10,6}}, color={0,127,255}));
+ connect(ideUse.port_b, sin.ports[1])
+ annotation (Line(points={{-10,-6},{-40,-6},{-40,-30},{-60,-30}},
+ color={0,127,255}));
+ connect(mPre_flow.y, ideUse.mPre_flow) annotation (Line(points={{-59,70},{-20,
+ 70},{-20,8},{-11,8}}, color={0,0,127}));
+annotation(__Dymola_Commands(file="modelica://Buildings/Resources/Scripts/Dymola/Experimental/DHC/Plants/Cooling/BaseClasses/Validation/IdealUser.mos"
+ "Simulate and plot"),
+experiment(Tolerance=1e-06, StopTime=3600), Documentation(info="
+
+This is a simple test model for the ideal user.
+
+", revisions="
+
+
+July 31, 2023, by Michael Wetter:
+Revised implementation, removed unused parameter.
+
+
+February 18, 2022 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+This block implements a state graph to control the flows of the storage plant.
+It receives two tank status Boolean signals indicating that the tank
+is charged or empty. These two signals can be both false indicating an
+in-between state. The block can receive one of the following commands:
+
+
+
+Charge tank,
+
+
+No command, and
+
+
+Discharge tank.
+
+
+
+The command to tank may be disregarded. For example, if the
+tank is receiving a discharge command but it is already empty, it will not
+discharge which would let warm return water directly into the supply side.
+
+
+The system transitions among the following states:
+
+
+
+
+
Step
+
Description
+
Transition in
+
Transition out
+
+
+
+
+
All Off (Initial Step)
+
All off. This is the initial step.
+
-
+
-
+
+
+
Local Charging
+
Charge the tank with the local chiller.
+
\"Charge tank\" command AND tank is not charged yet AND chiller is enabled. This transition takes priority over the one below.1
+
The in-transition condition becomes false.
+
+
+
Remote Charging
+
Charge the tank with the remote chiller.
+
Same as above except that the chiller is not enabled.
+
The in-transition condition becomes false.
+
+
+
Secondary Pump On
+
Turn on the secondary pump. This step is in parallel with the two below.2
+
The district has load AND the additional conditions of either step below become true.
+
Both steps below are no longer active (implicit).
+
+
+
Tank Producing
+
The tank produces CHW to the district. This step is in parallel with \"secondary pump on\".
+
The district has load AND \"Discharge tank\" command AND tank not empty. This transition takes priority over the one below.
+
To \"chiller producing\": The in-transition condition becomes false AND The chiller is enabled. This transition takes priority over the one below.
+
+
+
To initial step: No load OR the in-transition conditions of \"tank producing\" and \"chiller producing\" are both false (i.e. neither tank or chiller is available).
+
+
+
Chiller Producing
+
The chiller produces CHW to the district. This step is in parallel with \"secondary pump on\".
+
The district has load AND the chiller is enabled.
+
To \"tank producing\": The condition for in-transition of \"tank producing\" becomes true. This transition takes priority over the one below.
+
+
+
To initial step: No load OR the in-transition conditions of \"tank producing\" and \"chiller producing\" are both false.
+
+
+
+
+Notes:
+
+
+
+Out-transitions from the same step have priorities. When the conditions of
+more than one of them become true, the transition connected by a connector
+with the lowest index in the array fires.
+For example, even when the in-transition condition of \"chiller producing\"
+becomes true, as long as the in-transition condition of \"tank producing\"
+is also true, the latter fires because of priority.
+
+
+Steps that are in parallel are and must be active at the same time.
+When \"secondary pump on\" is active, either \"tank producing\"
+or \"chiller producing\" is also active.
+
+
+", revisions="
+
+
+February 23, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+"));
+end FlowControl;
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/Controls/SelectMin.mo b/Buildings/Experimental/DHC/Plants/Cooling/Controls/SelectMin.mo
new file mode 100644
index 00000000000..25f67a144d9
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/Controls/SelectMin.mo
@@ -0,0 +1,51 @@
+within Buildings.Experimental.DHC.Plants.Cooling.Controls;
+block SelectMin
+ "Block that includes or excludes storage plant pressure signal for min"
+ extends Modelica.Blocks.Icons.Block;
+ parameter Integer nin
+ "Number of input connections"
+ annotation (Dialog(connectorSizing=true),HideResult=true);
+ Buildings.Controls.OBC.CDL.Interfaces.RealInput dpUse[nin]
+ "Connector of Real input signals" annotation (Placement(transformation(
+ extent={{-120,60},{-100,40}}), iconTransformation(extent={{-140,80},{-100,
+ 40}})));
+ Buildings.Controls.OBC.CDL.Interfaces.RealInput dpStoPla
+ "Connector of Real input signals" annotation (Placement(transformation(
+ extent={{-120,10},{-100,-10}}), iconTransformation(extent={{-140,20},{
+ -100,-20}})));
+ Modelica.Blocks.Interfaces.BooleanInput isChaRem
+ "The storage plant is in remote charging mode" annotation (Placement(
+ transformation(extent={{-120,-60},{-100,-40}}), iconTransformation(
+ extent={{-120,-70},{-100,-50}})));
+ Modelica.Blocks.Interfaces.RealOutput y annotation (Placement(transformation(
+ extent={{100,-10},{120,10}}), iconTransformation(extent={{100,-10},{120,
+ 10}})));
+equation
+ y = if isChaRem then
+ min(min(dpUse),dpStoPla)
+ else
+ min(dpUse);
+annotation(defaultComponentName="selMin",
+ Icon(graphics={Line(
+ points={{-80,60},{-60,40},{-20,80}},
+ color={0,140,72},
+ thickness=5), Text(
+ extent={{-78,2},{-20,-78}},
+ textColor={28,108,200},
+ textString="?")}),
+ Documentation(info="
+
+This block finds the minimum value from pressure head signals.
+The signal from the storage plant is included
+only when the plant is in remote charging mode.
+
+", revisions="
+
+
+Jun 23, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+"));
+end SelectMin;
diff --git a/Buildings/Experimental/DHC/Plants/Cooling/Controls/TankStatus.mo b/Buildings/Experimental/DHC/Plants/Cooling/Controls/TankStatus.mo
new file mode 100644
index 00000000000..9a1521c2b8d
--- /dev/null
+++ b/Buildings/Experimental/DHC/Plants/Cooling/Controls/TankStatus.mo
@@ -0,0 +1,120 @@
+within Buildings.Experimental.DHC.Plants.Cooling.Controls;
+block TankStatus "Returns the tank status from its temperature sensors"
+
+ parameter Modelica.Units.SI.Temperature THig
+ "Higher threshold to consider the tank empty";
+ parameter Modelica.Units.SI.Temperature TLow
+ "Lower threshold to consider the tank full";
+
+ parameter Modelica.Units.SI.TemperatureDifference dTHys(min=0.1) = 0.5
+ "Deadband for hysteresis";
+
+ Buildings.Controls.OBC.CDL.Interfaces.RealInput TTan[2](
+ each final quantity="Temperature",
+ each final unit="K",
+ each displayUnit="degC") "Temperatures at the tank 1: top; and 2: bottom"
+ annotation (Placement(
+ transformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,0}), iconTransformation(
+ extent={{-10,-10},{10,10}},
+ rotation=0,
+ origin={-110,0})));
+ Buildings.Controls.OBC.CDL.Continuous.Hysteresis hysCha(
+ final uLow=TLow,
+ final uHigh=TLow + dTHys) "Hysteresis, tank charged"
+ annotation (Placement(visible = true, transformation(origin = {0, 50}, extent = {{-40, -60}, {-20, -40}}, rotation = 0)));
+ Buildings.Controls.OBC.CDL.Continuous.Hysteresis hysEmp(
+ final uHigh=THig,
+ final uLow=THig - dTHys) "Hysteresis, tank empty"
+ annotation (Placement(transformation(extent={{-40,40},{-20,60}})));
+ Buildings.Controls.OBC.CDL.Logical.Not not1 "Not block"
+ annotation (Placement(visible = true, transformation(origin = {0, 50}, extent = {{20, -60}, {40, -40}}, rotation = 0)));
+ Buildings.Controls.OBC.CDL.Interfaces.BooleanOutput y[2]
+ "Tank status - y[1]=true is empty; y[2] = true is charged; both false means partially charged"
+ annotation (Placement(transformation(extent={{100,-10},{120,10}}),
+ iconTransformation(extent={{100,-10},{120,10}})));
+ Buildings.Controls.OBC.CDL.Continuous.Sources.Constant TTanTopChe(
+ final k(final unit="K", displayUnit="degC") = THig) "Set point for top temperatuer of tank"
+ annotation(
+ Placement(visible = true, transformation(origin = {-70, -30}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
+ Buildings.Controls.OBC.CDL.Continuous.Sources.Constant TTanBotChe(
+ final k(final unit="K", displayUnit="degC") = TLow) "Set point for bottom temperature of tank"
+ annotation(
+ Placement(visible = true, transformation(origin = {-70, -70}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
+ Buildings.Controls.OBC.CDL.Continuous.Greater gre
+ "Test for temperature set points"
+ annotation(
+ Placement(visible = true, transformation(origin = {-30, -50}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
+ Buildings.Controls.OBC.CDL.Utilities.Assert assMes(
+ message = "THig must be greater than TLow.")
+ "Assertion if temperature set points are not correct"
+ annotation(
+ Placement(visible = true, transformation(origin = {10, -50}, extent = {{-10, -10}, {10, 10}}, rotation = 0)));
+equation
+ connect(hysCha.y, not1.u)
+ annotation (Line(points={{-18,0},{18,0}},
+ color={255,0,255}));
+ connect(TTan[1],hysCha. u) annotation (Line(points={{-110,-2.5},{-52,-2.5},{-52,
+ 0},{-42,0}}, color={0,0,127}));
+ connect(TTan[2],hysEmp. u) annotation (Line(points={{-110,2.5},{-52,2.5},{-52,
+ 50},{-42,50}},
+ color={0,0,127}));
+ connect(hysEmp.y, y[1]) annotation (Line(points={{-18,50},{60,50},{60,0},{110,
+ 0},{110,-2.5}}, color={255,0,255}));
+ connect(not1.y, y[2]) annotation (Line(points={{42,0},{60,0},{60,2.5},{110,
+ 2.5}}, color={255,0,255}));
+ connect(TTanTopChe.y, gre.u1) annotation(
+ Line(points = {{-58, -30}, {-50, -30}, {-50, -50}, {-42, -50}}, color = {0, 0, 127}));
+ connect(TTanBotChe.y, gre.u2) annotation(
+ Line(points = {{-58, -70}, {-52, -70}, {-52, -58}, {-42, -58}}, color = {0, 0, 127}));
+ connect(gre.y, assMes.u) annotation(
+ Line(points = {{-18, -50}, {-2, -50}}, color = {255, 0, 255}));
+ annotation (Icon(coordinateSystem(preserveAspectRatio=false), graphics={
+ Rectangle(
+ extent={{-100,-100},{100,100}},
+ lineColor={0,0,127},
+ fillColor={255,255,255},
+ fillPattern=FillPattern.Solid),
+ Rectangle(
+ extent={{-42,72},{40,-72}},
+ fillColor={135,135,135},
+ fillPattern=FillPattern.Solid,
+ pattern=LinePattern.None),
+ Rectangle(
+ extent={{-30,-64},{28,-28}},
+ fillColor={28,108,200},
+ fillPattern=FillPattern.Solid,
+ pattern=LinePattern.None),
+ Rectangle(
+ extent={{-30,-20},{28,16}},
+ fillColor={28,108,200},
+ fillPattern=FillPattern.Solid,
+ pattern=LinePattern.None),
+ Rectangle(
+ extent={{-24,54},{22,30}},
+ fillColor={135,135,135},
+ fillPattern=FillPattern.Solid,
+ pattern=LinePattern.None)}),
+ Diagram(coordinateSystem(preserveAspectRatio=false)),
+ defaultComponentName="tanSta",
+ Documentation(info="
+
+This model outputs tank status signals using the temperatures
+at the CHW tank top and the tank bottom as input.
+The status has two separate boolean signals indicating whether the tank is
+charged or empty (of cooling). The two output signals can be both false,
+indicating an in-between state, but they can never both be true.
+
+",
+revisions="
+
+
+August 10, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+August 11, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+
+",
+ info="
+
+This model validates
+
+Buildings.Experimental.DHC.Plants.Cooling.Controls.TankStatus.
+Note that the output signals turn true as soon as their respective temperature
+input crosses the threshold, but there is a delay for it to turn back to
+false due to the hysteresis.
+
+The source blocks give the system the following operation schedule during
+simulation:
+
+
+
+At time = 0, the system is all off.
+
+
+At time = 200, the system is commanded to charge the tank.
+The chiller is available and charges the tank locally.
+After some time, the charging stops when the tank status signal returns
+that the tank is charged.
+
+
+At time = 1800, load appears at the district network.
+The storage plant starts producing CHW to the system.
+Currently the tank is not commanded to charge or discharge, therefore it
+functions like a common pipe and the CHW is supplied by the chiller.
+
+
+At time = 3500, the load increases and there is no more overflow
+through the tank.
+
+
+At time = 4000, the system is commanded to have the tank take
+priority for CHW production. After some time, the cooling in the tank is
+empty and the tank stops producing. Now the chiller takes over.
+
+
+At time = 7000, there is no longer load in the district system.
+The system is back to the all-off state.
+
+
+At time = 7500, the system is once again commanded to charge
+the tank, but the chiller in the storage plant is not enabled.
+The tank is therefore charged remotely by the district.
+This stops once the tank is charged again.
+
+
+
Implementation
+
+The chiller is implemented as an ideal temperature source using
+
+Buildings.Fluid.Sources.PropertySource_T.
+Its outlet temperature is always at the prescribed value.
+
+", revisions="
+
+
+July 31, 2023, by Michael Wetter:
+Revised implementation, removed unused parameter.
+
+
+January 11, 2023 by Hongxiang Fu:
+First implementation. This is for
+#2859.
+
+This model encompasses the components of a chilled water storage plant.
+It includes a flow-controlled primary pump, a stratefied
+storage tank, a reversible connection with the district network, and related controls
+to coordinate charging and discharging of the tank.
+The chiller is intentionally excluded in this component so that it can be
+otherwise chosen and configured.
+The tank in this plant can be charged by its local chiller or by a remote
+chiller on the same CHW district network.
+
+Plant 1 only has a chiller. The supply pump, P1, is controlled to ensure that
+all users have enough pressure head. This represents a remote chiller plant,
+referenced above.
+
+
+Plant 2 has a chiller and a stratified CHW tank and is represented by this model.
+The storage plant has a reversible connection to the district network
+that can either pump water to the network from the plant using the
+pump Psec,
+or throttle water from the pressurised network to charge the tank.
+
+
+
+
+
+
Control Signals
+
+The plants are controlled as follows:
+
+
+
+In plant 1, for the sake of this discussion, assume the chiller is always on.
+The speed-controlled pump
+ensures that the users have enough pressure head at all times.
+This includes plant 2 when its tank is charged remotely by plant 1 and
+it acts like an energy consumer.
+
+
+For plant 2:
+
+
+In the chiller loop, chiller 2 and its primary pump Ppri
+are on whenever needed (for charging the tank or producing CHW to the
+network). Otherwise, they are commanded off.
+
+
+The system receives one of the following three commands regarding the tank:
+charge, discharge, or no action.
+The tank controller returns status signals. It can be empty, charged, or in-between.
+The command to tank may be disregarded. For example, if the
+tank is receiving a discharge command but it is already empty, it will not
+discharge which would let warm return water directly into the supply side.
+See the Implementation section for details.
+
+
+The reversible connection between plant 2 and the district network
+modulates the flow rate needed by plant 2.
+
+
+When the storage plant produces CHW, Psec receives a speed control
+signal from the same PI controller as P1 in plant 1.
+
+
+When the storage plant is charged remotely, the pressure-independent valve
+is controlled to maintain a constant flow from the pressurised network
+to the storage tank.
+
+
+Otherwise, the connection cuts off flow to isolate plant 2 from
+the district network.
+
Added models for a district CHW system with two plants,
+ where one of them has a storage tank that can be charged remotely by the other plant.
+ This is for
+ issue 2859.