Test model for the heating curve with different exponent. Example that demonstrates the use of the hot water temperature reset
for a heating system.
Both instances are identical except that heaCurM sets m=1.3.

within Buildings.Controls.OBC.Utilities.SetPoints.Validation;
model SupplyReturnTemperatureResetExponent
  "Test model for the heating curve with different exponent"
  Buildings.Controls.OBC.Utilities.SetPoints.SupplyReturnTemperatureReset heaCur(
    m=1,
    TSup_nominal=313.15,
    TRet_nominal=308.15,
    TOut_nominal=263.15)
    "Compute the supply and return set point of heating systems with varying outdoor temperature and m=1"
  Buildings.Controls.OBC.CDL.Reals.Sources.Ramp TOut(
    height=30,
    duration=1,
    offset=258.15,
    y(unit="K"))
    "Outdoor temperature varying from -10 degC to 30 degC"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TRoo(
    k=273.15+20,
    y(unit="K"))
    "Room temperature 20 degC"
  Buildings.Controls.OBC.Utilities.SetPoints.SupplyReturnTemperatureReset heaCurM(
    m=1.3,
    TSup_nominal=313.15,
    TRet_nominal=308.15,
    TOut_nominal=263.15)
    "Compute the supply and return set point of heating systems with varying outdoor temperature and m=1.3"

equation
  connect(TOut.y,heaCur.TOut)
  connect(TRoo.y,heaCur.TSetZon)
  connect(heaCurM.TOut,TOut.y)
  connect(TRoo.y,heaCurM.TSetZon)
end SupplyReturnTemperatureResetExponent;


Validation model for the block OptimalStart for cooling system with a negative start time. This model is to validate the block

Buildings.Controls.OBC.Utilities.OptimalStart for space cooling system with
a negative simulation start time.

within Buildings.Controls.OBC.Utilities.Validation;
model OptimalStartCoolingNegativeStartTime
  "Validation model for the block OptimalStart for cooling system with a negative start time"
  Buildings.Controls.OBC.Utilities.OptimalStart optStaCoo(
    computeHeating=false,
    computeCooling=true)
    "Optimal start for cooling system"
  Modelica.Blocks.Continuous.Integrator TRoo(
    k=0.0000005,
    y_start=24+273.15)
    "Room air temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetCooOcc(
    k=24+273.15)
    "Zone cooling setpoint during occupancy"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TOut(
    amplitude=5,
    freqHz=1/86400,
    offset=28+273.15,
    startTime(
      displayUnit="s")=-691200)
    "Outdoor dry bulb temperature to test cooling system"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter UA(k=100)
    "Overall heat loss coefficient"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT
    "Temperature difference between zone and outdoor"
  Buildings.Controls.OBC.CDL.Reals.Add dTdt
    "Temperature derivative"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QCoo(k=-2000)
    "Heat extraction in the zone"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea(
    realTrue=-6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add
    "Reset setpoint from unoccupied to occupied during optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=1,
    reverseActing=false)
    "PI control signal for the cooling power"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(
    occupancy=3600*{7,19},
    period=24*3600)
    "Daily schedule"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetCoo(
    realTrue=273.15+24,
    realFalse=273.15+30,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for cooling"

equation
  connect(dT.y,UA.u)
  connect(dTdt.y,TRoo.u)
  connect(QCoo.y,dTdt.u2)
  connect(TRoo.y,optStaCoo.TZon)
  connect(TSetCooOcc.y,optStaCoo.TSetZonCoo)
  connect(UA.y,dTdt.u1)
  connect(optStaCoo.optOn,booToRea.u)
  connect(add.y,conPID.u_s)
  connect(TRoo.y,conPID.u_m)
  connect(occSch.tNexOcc,optStaCoo.tNexOcc)
  connect(conPID.y,QCoo.u)
  connect(occSch.occupied,TSetCoo.u)
  connect(TSetCoo.y,add.u2)
  connect(booToRea.y,add.u1)
  connect(TRoo.y, dT.u2)
  connect(TOut.y, dT.u1)
end OptimalStartCoolingNegativeStartTime;


Validation model for the block OptimalStart for cooling system with a positive start time. This model is to validate the block

Buildings.Controls.OBC.Utilities.OptimalStart for space cooling system
with a positive simulation start time.

within Buildings.Controls.OBC.Utilities.Validation;
model OptimalStartCoolingPositiveStartTime
  "Validation model for the block OptimalStart for cooling system with a positive start time"
  Buildings.Controls.OBC.Utilities.OptimalStart optStaCoo(
    computeHeating=false,
    computeCooling=true)
    "Optimal start for cooling system"
  Modelica.Blocks.Continuous.Integrator TRoo(
    k=0.0000005,
    y_start=24+273.15)
    "Room air temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetCooOcc(
    k=24+273.15)
    "Zone cooling setpoint during occupancy"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TOut(
    amplitude=5,
    freqHz=1/86400,
    offset=28+273.15,
    startTime(
      displayUnit="s")=0)
    "Outdoor dry bulb temperature to test cooling system"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter UA(k=100)
    "Overall heat loss coefficient"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT
    "Temperature difference between zone and outdoor"
  Buildings.Controls.OBC.CDL.Reals.Add dTdt
    "Temperature derivative"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QCoo(k=-2000)
    "Heat extraction in the zone"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea(
    realTrue=-6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add
    "Reset setpoint from unoccupied to occupied during optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=1,
    reverseActing=false)
    "PI control signal for the cooling power"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(
    occupancy=3600*{7,19},
    period=24*3600)
    "Daily schedule"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetCoo(
    realTrue=273.15+24,
    realFalse=273.15+30,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for cooling"

equation
  connect(dT.y,UA.u)
  connect(dTdt.y,TRoo.u)
  connect(QCoo.y,dTdt.u2)
  connect(TRoo.y,optStaCoo.TZon)
  connect(TSetCooOcc.y,optStaCoo.TSetZonCoo)
  connect(UA.y,dTdt.u1)
  connect(optStaCoo.optOn,booToRea.u)
  connect(add.y,conPID.u_s)
  connect(TRoo.y,conPID.u_m)
  connect(occSch.tNexOcc,optStaCoo.tNexOcc)
  connect(conPID.y,QCoo.u)
  connect(occSch.occupied,TSetCoo.u)
  connect(TSetCoo.y,add.u2)
  connect(booToRea.y,add.u1)
  connect(TRoo.y, dT.u2)
  connect(TOut.y, dT.u1)
end OptimalStartCoolingPositiveStartTime;


Validation model for the block OptimalStart for heating system. This model is to validate the block

Buildings.Controls.OBC.Utilities.OptimalStart for space heating system.

within Buildings.Controls.OBC.Utilities.Validation;
model OptimalStartHeating
  "Validation model for the block OptimalStart for heating system"
  Buildings.Controls.OBC.Utilities.OptimalStart optStaHea(
    computeHeating=true,
    computeCooling=false)
    "Optimal start for heating system"
  Modelica.Blocks.Continuous.Integrator TRoo(
    k=0.000005,
    y_start=21+273.15)
    "Room air temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetHeaOcc(
    k=21+273.15)
    "Zone heating setpoint during occupancy"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(
    occupancy=3600*{7,19},
    period=24*3600)
    "Daily schedule"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter UA(k=10)
    "Overall heat loss coefficient"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT
    "Temperature difference between zone and outdoor"
  Buildings.Controls.OBC.CDL.Reals.Add dTdt
    "Temperature derivative"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QHea(k=500)
    "Heat injection in the zone"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea(
    realTrue=6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TOut(
    amplitude=10,
    freqHz=1/86400,
    phase=3.1415926535898,
    offset=10+273.15,
    startTime(
      displayUnit="d")=-172800)
    "Outdoor dry bulb temperature to test heating system"
  Buildings.Controls.OBC.CDL.Reals.PID conPID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=1.5)
    "PI control for space heating"
  Buildings.Controls.OBC.CDL.Reals.Add add
    "Reset temperature from unoccupied to occupied for optimal start period"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetHea(
    realTrue=273.15+21,
    realFalse=273.15+15,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for heating"

equation
  connect(dT.y,UA.u)
  connect(dTdt.y,TRoo.u)
  connect(QHea.y,dTdt.u2)
  connect(TRoo.y,optStaHea.TZon)
  connect(occSch.tNexOcc,optStaHea.tNexOcc)
  connect(TSetHeaOcc.y,optStaHea.TSetZonHea)
  connect(UA.y,dTdt.u1)
  connect(add.y,conPID.u_s)
  connect(conPID.y,QHea.u)
  connect(TRoo.y,conPID.u_m)
  connect(optStaHea.optOn,booToRea.u)
  connect(TSetHea.u,occSch.occupied)
  connect(TSetHea.y,add.u2)
  connect(booToRea.y,add.u1)
  connect(TOut.y, dT.u1)
  connect(TRoo.y, dT.u2)
end OptimalStartHeating;


Validation model for the block OptimalStart for both heating and cooling system. This models validates both space heating and cooling for the block

Buildings.Controls.OBC.Utilities.OptimalStart.

within Buildings.Controls.OBC.Utilities.Validation;
model OptimalStartHeatingCooling
  "Validation model for the block OptimalStart for both heating and cooling system"
  Buildings.Controls.OBC.Utilities.OptimalStart optSta(
    computeHeating=true,
    computeCooling=true)
    "Optimal start for both heating and cooling system"
  Modelica.Blocks.Continuous.Integrator TRoo(
    k=0.0000004,
    y_start=19+273.15)
    "Room air temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetCooOcc(
    k=24+273.15)
    "Zone cooling setpoint during occupancy"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TOutBase(
    amplitude=5,
    freqHz=1/86400,
    offset=15+273.15,
    startTime(
      displayUnit="h")=0)
    "Outdoor dry bulb temperature, base component"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter UA(k=25)
    "Overall heat loss coefficient"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT
    "Temperature difference between zone and outdoor"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QCoo(k=-4000)
    "Heat extraction in the zone"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea1(
    realTrue=-6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add1
    "Reset temperature from unoccupied to occupied for optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID1(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=3,
    reverseActing=false)
    "PI control for space cooling"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(
    occupancy=3600*{7,19},
    period=24*3600)
    "Daily schedule"
  Buildings.Controls.OBC.CDL.Reals.MultiSum mulSum(
    nin=3)
    "Sum heat gains"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant TSetHeaOcc(
    k=21+273.15)
    "Zone heating setpoint during occupancy"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea2(
    realTrue=6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add2
    "Reset temperature from unoccupied to occupied for optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=3)
    "PI control for space heating"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QHea(k=2000)
    "Heat injection in the zone"
  Buildings.Controls.OBC.CDL.Reals.Add TOut
    "Outdoor dry bulb temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse pul(
    shift(
      displayUnit="d")=604800,
    amplitude=15,
    period(
      displayUnit="d")=1209600)
    "Range of outdoor dry bulb temperature"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetHea(
    realTrue=273.15+21,
    realFalse=273.15+15,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for heating"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetCoo(
    realTrue=273.15+24,
    realFalse=273.15+30,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for cooling"

equation
  connect(dT.y,UA.u)
  connect(TRoo.y,optSta.TZon)
  connect(TSetCooOcc.y,optSta.TSetZonCoo)
  connect(optSta.optOn,booToRea1.u)
  connect(add1.y,conPID1.u_s)
  connect(TRoo.y,conPID1.u_m)
  connect(occSch.tNexOcc,optSta.tNexOcc)
  connect(UA.y,mulSum.u[1])
  connect(TRoo.u,mulSum.y)
  connect(TSetHeaOcc.y,optSta.TSetZonHea)
  connect(optSta.optOn,booToRea2.u)
  connect(add2.y,conPID.u_s)
  connect(conPID1.y,QCoo.u)
  connect(conPID.y,QHea.u)
  connect(QCoo.y,mulSum.u[2])
  connect(QHea.y,mulSum.u[3])
  connect(TOutBase.y,TOut.u1)
  connect(pul.y,TOut.u2)
  connect(TSetCoo.y,add1.u2)
  connect(occSch.occupied,TSetCoo.u)
  connect(TSetHea.u,occSch.occupied)
  connect(booToRea1.y,add1.u1)
  connect(booToRea2.y,add2.u1)
  connect(TSetHea.y,add2.u2)
  connect(TRoo.y, conPID.u_m)
  connect(TOut.y, dT.u1)
  connect(TRoo.y, dT.u2)
end OptimalStartHeatingCooling;


Validation model for the block OptimalStart for the case with no preheating nor precooling. This models validates the block

Buildings.Controls.OBC.Utilities.OptimalStart for the case when the optimal start
is not turned on for preheating or precooling before the scheduled occupancy.

within Buildings.Controls.OBC.Utilities.Validation;
model OptimalStartNoHeatingNoCooling
  "Validation model for the block OptimalStart for the case with no preheating nor precooling"
  Buildings.Controls.OBC.Utilities.OptimalStart optSta(
    computeHeating=false,
    computeCooling=false)
    "Optimal start for both heating and cooling system"
  Modelica.Blocks.Continuous.Integrator TRoo(
    k=0.0000004,
    y_start=19+273.15)
    "Room air temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Sin TOutBase(
    amplitude=5,
    freqHz=1/86400,
    offset=15+273.15,
    startTime(
      displayUnit="h")=0)
    "Outdoor dry bulb temperature, base component"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter UA(k=25)
    "Overall heat loss coefficient"
  Buildings.Controls.OBC.CDL.Reals.Subtract dT
    "Temperature difference between zone and outdoor"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QCoo(k=-4000)
    "Heat extraction in the zone"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea1(
    realTrue=-6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add1
    "Reset temperature from unoccupied to occupied for optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID1(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=3,
    reverseActing=false)
    "PI control for space cooling"
  Buildings.Controls.SetPoints.OccupancySchedule occSch(
    occupancy=3600*{7,19},
    period=24*3600)
    "Daily schedule"
  Buildings.Controls.OBC.CDL.Reals.MultiSum mulSum(
    nin=3)
    "Sum heat gains"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal booToRea2(
    realTrue=6)
    "Convert Boolean to Real signal"
  Buildings.Controls.OBC.CDL.Reals.Add add2
    "Reset temperature from unoccupied to occupied for optimal start period"
  Buildings.Controls.OBC.CDL.Reals.PID conPID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PI,
    Ti=3)
    "PI control for space heating"
  Buildings.Controls.OBC.CDL.Reals.MultiplyByParameter QHea(k=2000)
    "Heat injection in the zone"
  Buildings.Controls.OBC.CDL.Reals.Add TOut
    "Outdoor dry bulb temperature"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse pul(
    shift(
      displayUnit="d")=604800,
    amplitude=15,
    period(
      displayUnit="d")=1209600)
    "Range of outdoor dry bulb temperature"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetHea(
    realTrue=273.15+21,
    realFalse=273.15+15,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for heating"
  Buildings.Controls.OBC.CDL.Conversions.BooleanToReal TSetCoo(
    realTrue=273.15+24,
    realFalse=273.15+30,
    y(final unit="K",
      displayUnit="degC"))
    "Room temperature set point for cooling"

equation
  connect(dT.y,UA.u)
  connect(TRoo.y,optSta.TZon)
  connect(optSta.optOn,booToRea1.u)
  connect(add1.y,conPID1.u_s)
  connect(TRoo.y,conPID1.u_m)
  connect(occSch.tNexOcc,optSta.tNexOcc)
  connect(UA.y,mulSum.u[1])
  connect(TRoo.u,mulSum.y)
  connect(optSta.optOn,booToRea2.u)
  connect(add2.y,conPID.u_s)
  connect(conPID1.y,QCoo.u)
  connect(conPID.y,QHea.u)
  connect(QCoo.y,mulSum.u[2])
  connect(QHea.y,mulSum.u[3])
  connect(TOutBase.y,TOut.u1)
  connect(pul.y,TOut.u2)
  connect(TSetCoo.y,add1.u2)
  connect(occSch.occupied,TSetCoo.u)
  connect(TSetHea.u,occSch.occupied)
  connect(TSetHea.y,add2.u2)
  connect(booToRea2.y,add2.u1)
  connect(booToRea1.y,add1.u1)
  connect(TRoo.y, conPID.u_m)
  connect(TOut.y, dT.u1)
  connect(TRoo.y, dT.u2)
end OptimalStartNoHeatingNoCooling;


Collection of validation models

within Buildings.Controls.OBC.Utilities;
package Validation "Collection of validation models"
end Validation;


Test model for PIDWithInputGains. Validation test for the block

Buildings.Controls.OBC.Utilities.PIDWithInputGains.

within Buildings.Controls.OBC.Utilities.Validation;
model PIDWithInputGains
  "Test model for PIDWithInputGains"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse pulse(
    period=0.25)
    "Setpoint"
  Buildings.Controls.OBC.CDL.Reals.Sources.Constant const(
    k=0.5)
    "Measured value"
  Buildings.Controls.OBC.Utilities.PIDWithInputGains PIDWitInpGai(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PID)
    "PID controller with input gains"
  Buildings.Controls.OBC.CDL.Reals.PIDWithReset PID(
    controllerType=Buildings.Controls.OBC.CDL.Types.SimpleController.PID,
    k=1,
    Ti=0.5,
    Td=0.1)
    "PID controller with constant gains"
  Buildings.Controls.OBC.CDL.Logical.Sources.Pulse resSig(period=1)
    "Reset signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse k(
    amplitude=0.2,
    width=0.4,
    period=1,
    shift=0.6,
    offset=1)
    "Control gain signal"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse Ti(
    amplitude=0.2,
    width=0.4,
    period=1,
    shift=0.6,
    offset=0.5)
    "Time constant signal for the integral term"
  Buildings.Controls.OBC.CDL.Reals.Sources.Pulse Td(
    amplitude=0.1,
    width=0.4,
    period=1,
    shift=0.6,
    offset=0.1)
    "Time constant signal for the derivative term"
  Buildings.Controls.OBC.CDL.Reals.Abs abs1
    "Absolute value of controller output"
  Buildings.Controls.OBC.CDL.Reals.Subtract sub
    "Difference in controller output"
  Buildings.Controls.OBC.CDL.Reals.LessThreshold lesThr(t=1E-5, h=1E-4)
    "Output true if outputs are bigger than threshold"
  Buildings.Controls.OBC.CDL.Utilities.Assert assMes(
    message="Control outputs differ more than expected")
    "Make sure outputs are within expected tolerance"
  Buildings.Controls.OBC.CDL.Reals.Sources.CivilTime modTim
    "Civil time"
  Buildings.Controls.OBC.CDL.Reals.GreaterThreshold greThr(t=0.59)
    "Output true if model time is below 0.6"
  Buildings.Controls.OBC.CDL.Logical.Or or2
    "Output true either if time is bigger than 0.59, or if tolerance is maintained"
equation
  connect(resSig.y, PID.trigger)
  connect(PIDWitInpGai.trigger, PID.trigger)
  connect(pulse.y, PID.u_s)
  connect(PIDWitInpGai.u_s, PID.u_s)
  connect(const.y, PID.u_m)
  connect(PIDWitInpGai.u_m, PID.u_m)
  connect(k.y, PIDWitInpGai.k)
  connect(PIDWitInpGai.Ti, Ti.y)
  connect(PIDWitInpGai.Td, Td.y)
  connect(PID.y, sub.u1)
  connect(PIDWitInpGai.y, sub.u2)
  connect(sub.y, abs1.u)
  connect(abs1.y, lesThr.u)
  connect(modTim.y, greThr.u)
  connect(greThr.y, or2.u1)
  connect(lesThr.y, or2.u2)
  connect(or2.y, assMes.u)
end PIDWithInputGains;


Block that predicts an electrical load. Data-driven model that predicts the electrical load.
This load prediction can for example be used in a demand response client.

within Buildings.Controls.Predictors;
block ElectricalLoad "Block that predicts an electrical load"
  extends Modelica.Blocks.Icons.DiscreteBlock;

  parameter Integer nSam(min=1) = 24
    "Number of intervals in a day for which baseline is computed";

  parameter Integer nPre(min=1) = 1
    "Number of intervals for which future load need to be predicted (set to one to only predict current time, or to nSam to predict one day)";

  parameter Integer nHis(min=1) = 10 "Number of history terms to be stored";

  parameter Buildings.Controls.Predictors.Types.PredictionModel
    predictionModel = Types.PredictionModel.WeatherRegression
    "Load prediction model";

  parameter Boolean use_dayOfAdj=true "if true, use the day of adjustment"

  parameter Modelica.Units.SI.Time dayOfAdj_start(
    max=0,
    displayUnit="h") = -14400
    "Number of hours prior to current time when day of adjustment starts"

  parameter Modelica.Units.SI.Time dayOfAdj_end(
    max=0,
    displayUnit="h") = -3600
    "Number of hours prior to current time when day of adjustment ends"

  parameter Real minAdjFac(min=0) = 0.8 "Minimum adjustment factor"

  parameter Real maxAdjFac(min=0) = 1.2 "Maximum adjustment factor"

  Modelica.Blocks.Interfaces.RealInput TOut(unit="K")
  if (predictionModel == Buildings.Controls.Predictors.Types.PredictionModel.WeatherRegression)
    "Outside air temperature"

  Modelica.Blocks.Interfaces.RealInput TOutFut[nPre-1](each unit="K")
    if (predictionModel == Buildings.Controls.Predictors.Types.PredictionModel.WeatherRegression)
    "Future outside air temperatures"

  Modelica.Blocks.Interfaces.RealInput ECon(unit="J", nominal=1E5)
    "Consumed electrical energy"

  discrete Modelica.Blocks.Interfaces.RealOutput PPre[nPre](each unit="W")
    "Predicted power consumptions (first element is for current time"

  Buildings.Controls.Interfaces.DayTypeInput typeOfDay[integer((nPre-1)/nSam)+2] "Type of day for the current and the future days for which a prediction is to be made.
    Typically, this has dimension 2 for predictions up to and including 24 hours, and 2+n for any additional day"

  Modelica.Blocks.Interfaces.BooleanInput storeHistory
    "If false, history terms are no longer stored for the remainder of the day"

  discrete Real adj(unit="1") "Load adjustment factor";
protected
  parameter Modelica.Units.SI.Time samplePeriod=86400/nSam
    "Sample period of the component";
  parameter Modelica.Units.SI.Time samStart(fixed=false)
    "Time when the first sampling starts";
  parameter Integer iDayOf_start = integer((nSam*dayOfAdj_start/86400+1E-8))
    "Counter where day of look up begins";
  parameter Integer iDayOf_end = integer((nSam*dayOfAdj_end  /86400+1E-8))
    "Counter where day of look up ends";

  parameter Integer nDayOf = iDayOf_end-iDayOf_start
    "Number of samples used for the day of adjustment";
  parameter Modelica.Units.SI.Time dt=86400/nSam
    "Length of one sampling interval";
  discrete Modelica.Units.SI.Power PAve "Average power over the past interval";
  Boolean sampleTrigger "True, if sample time instant";

  discrete output Modelica.Units.SI.Energy ELast "Energy at the last sample";
  discrete output Modelica.Units.SI.Time tLast
    "Time at which last sample occurred";
  output Integer[nPre] iSam "Index for power of the current sampling interval";

  discrete output Modelica.Units.SI.Power P[Buildings.Controls.Types.nDayTypes,
    nSam,nHis] "Baseline power consumption";
  // The temperature history is set to a zero array if it is not needed.
  // This significantly reduces the size of the code that needs to be compiled.

  discrete output Modelica.Units.SI.Temperature T[if predictionModel == Types.PredictionModel.WeatherRegression
     then Buildings.Controls.Types.nDayTypes else 0,if predictionModel == Types.PredictionModel.WeatherRegression
     then nSam else 0,if predictionModel == Types.PredictionModel.WeatherRegression
     then nHis else 0] "Temperature history";

  Integer _typeOfDay[nPre]
    "Type of day for each time interval for which prediction is to be made";

  Integer iHis[Buildings.Controls.Types.nDayTypes,nSam]
    "Index for power of the current sampling history, for the currrent time interval";
  Boolean historyComplete[Buildings.Controls.Types.nDayTypes,nSam](each start=false, each fixed=true)
    "Flage, set to true when all history terms are built up for the given day type and given time interval";

  Boolean _storeHistory
    "Flag, switched to false when block gets an storeHistory=false signal, and remaining false until midnight";

  discrete Modelica.Units.SI.Energy EActAve
    "Actual energy over the day off period";
  discrete Modelica.Units.SI.Energy EHisAve
    "Actual load over the day off period, summed over all time intervals";

  discrete Modelica.Units.SI.Power PPreHis[Buildings.Controls.Types.nDayTypes,
    nSam] "Predicted power consumptions for all day off time intervals";
  Boolean PPreHisSet[Buildings.Controls.Types.nDayTypes, nSam](each start=false, each fixed=true)
    "Flag, true if a value in PPreHis has been set for that element";

  // If predictionModel <> Types.PredictionModel.WeatherRegression,
  // then intTOut = 0 and hence we set fixed=false.
  // This is required to avoid a warning if model is translated in pedantic mode
  // in Dymola 2016.
  Real intTOut(unit="K.s",
               start=0,
               fixed = (predictionModel == Types.PredictionModel.WeatherRegression))
    "Time integral of outside temperature";
  discrete Real intTOutLast(unit="K.s")
    "Last sampled value of time integral of outside temperature";

  Integer idxSam "Index to access iSam[1]";

  // Conditional connectors
  Modelica.Blocks.Interfaces.RealInput TOut_in_internal(unit="K")
    "Needed to connect to conditional connector";

  Modelica.Blocks.Interfaces.RealInput TOutFut_in_internal[nPre-1](each unit="K")
    "Needed to connect to conditional connector";

  // Functions
  function isMidNight
    input Modelica.Units.SI.Time t "Simulation time";
    output Boolean r "True if time is midnight, false otherwise";
  algorithm
    r := rem(t, 86400.0) < 1;
  end isMidNight;

  function getTypeOfDays
    input Modelica.Units.SI.Time t "Simulation time";
    input Buildings.Controls.Types.Day[:] typeOfDay
      "Type of day as received from input connector";
    input Modelica.Units.SI.Time dt "Length of one sampling interval";
    input Integer nPre "Number of predictions to be made";
    output Integer[nPre] tod "Type of day for each prediction interval";
  protected
    Integer itod "Pointer to the type of day";
  algorithm
    tod[1] :=Integer(typeOfDay[1]);
    if nPre > 1 then
      itod :=1;
      for i in 2:nPre loop
        // We reached mid-night. Hence, we need to take the next type of day.
        if isMidNight(t + (i-1)*dt) then
          itod := itod + 1;
        end if;
        tod[i] :=Integer(typeOfDay[itod]);
      end for;
    end if;
  end getTypeOfDays;

  function incrementIndex
    input Integer i "Counter";
    input Integer n "Maximum value of counter";
    output Integer iNew "New value of counter";
  algorithm
    iNew :=if i == n then 1 else i + 1;
  end incrementIndex;

  function getIndex
    input Integer i "Counter";
    input Integer n "Maximum value of counter";
    output Integer iNew "New value of counter";
  algorithm
    iNew := mod(i, n);
    if iNew == 0 then
      iNew := n;
    end if;
  end getIndex;

initial equation
  for i in 1:nPre loop
    iSam[i] = i;
  end for;

  P = zeros(
    Buildings.Controls.Types.nDayTypes,
    nSam,
    nHis);
  PPre = zeros(nPre);

  T = zeros(size(T,1), size(T,2), size(T,3));

  EActAve    = 0;
  EHisAve    = 0;
  PPreHis    = zeros(Buildings.Controls.Types.nDayTypes, nSam);

  ELast = 0;
  intTOutLast = 0;
  tLast = time;
  iHis = zeros(Buildings.Controls.Types.nDayTypes, nSam);

   _storeHistory = true;
   samStart = Buildings.Controls.Predictors.BaseClasses.sampleStart(
     t=time,
     samplePeriod=samplePeriod);

   for i in 1:nPre loop
      _typeOfDay[i] =  Integer(Buildings.Controls.Types.Day.WorkingDay);
   end for;

   // Compute the offset of the index that is used to look up the data for
   // the dayOfAdj
   if use_dayOfAdj then
     assert(iDayOf_start < iDayOf_end, "
Wrong values for parameters.
  Require dayOfAdjustement_start < dayOfAdjustement_end + 86400/nSam.
  Received dayOfAdj_start = " + String(dayOfAdj_start) + "
           dayOfAdj_end   = " + String(dayOfAdj_end));
   end if;
   idxSam = 0;
   adj = 1;
   PAve = 0;
equation
  // Conditional connector
  connect(TOut,    TOut_in_internal);
  connect(TOutFut, TOutFut_in_internal);
  if predictionModel <> Types.PredictionModel.WeatherRegression then
    TOutFut_in_internal = zeros(nPre-1);
    TOut_in_internal = 0;
  end if;

  // Sample trigger
  sampleTrigger = sample(samStart, samplePeriod);

  // Averaging of outside temperature
  if predictionModel == Types.PredictionModel.WeatherRegression then
    der(intTOut) = TOut_in_internal;
  else
    intTOut = 0;
  end if;

algorithm

  when sampleTrigger then
    // Set flag for event day.
    // isMidnight is true if time is within 1 second of midnight.
    _storeHistory :=if not pre(_storeHistory) and (not isMidNight(t=time)) then false else storeHistory;

    _typeOfDay := getTypeOfDays(t=time, typeOfDay=typeOfDay, dt=dt, nPre=nPre);

    // Index to access iSam[1]
    idxSam :=getIndex(iSam[1] - 1, nSam);

    // Update the history terms with the average power of the time interval,
    // unless we have an event day.
    if (_storeHistory) or (pre(_storeHistory)) then
      if (time - tLast) > 1E-5 then
        // Update iHis, which points to where the last interval's power
        // consumption will be stored.
        iHis[pre(_typeOfDay[1]), idxSam] :=
          incrementIndex(iHis[pre(_typeOfDay[1]), idxSam], nHis);
        if iHis[pre(_typeOfDay[1]), idxSam] == nHis then
          historyComplete[pre(_typeOfDay[1]), idxSam] :=true;
        end if;
        PAve :=(ECon - ELast)/(time - tLast);
        P[pre(_typeOfDay[1]), idxSam, iHis[pre(_typeOfDay[1]), idxSam]] := PAve;
        if predictionModel == Types.PredictionModel.WeatherRegression then
          T[pre(_typeOfDay[1]), idxSam, iHis[pre(_typeOfDay[1]), idxSam]] := (intTOut-intTOutLast)/(time - tLast);
        end if;
      end if;
    end if;
    // Initialize the energy consumed since the last sampling.
    ELast := ECon;
    intTOutLast :=intTOut;
    tLast := time;

    // Compute the baseline prediction for the current hour,
    // with k being equal to the number of stored history terms.
    // If in a later implementation, we want more terms into the future, then
    // a loop should be added over for i = iSam[1]...upper_bound, whereas
    // the loop needs to wrap around nSam.
    if predictionModel == Types.PredictionModel.Average then
      for m in 1:nPre loop
        // Note that as this branch does not use TOutFut, at every time step, computations
        // are repeated because PPre[m] = pre(PPre[m+1]). This could be improved
        // in future versions.
        PPre[m] :=Buildings.Controls.Predictors.BaseClasses.average(
                    P={P[_typeOfDay[m], iSam[m], i] for i in 1:nHis},
                    k=if historyComplete[_typeOfDay[m], iSam[m]] then nHis else iHis[_typeOfDay[m], iSam[m]]);
      end for;
    elseif predictionModel == Types.PredictionModel.WeatherRegression then
      for m in 1:nPre loop
        PPre[m] :=Buildings.Controls.Predictors.BaseClasses.weatherRegression(
                    TCur=if m == 1 then TOut_in_internal else TOutFut_in_internal[m-1],
                    T={T[_typeOfDay[m], iSam[m], i] for i in 1:nHis},
                    P={P[_typeOfDay[m], iSam[m], i] for i in 1:nHis},
                    k=if historyComplete[_typeOfDay[m], iSam[m]] then nHis else iHis[_typeOfDay[m], iSam[m]]);
      end for;
    else
      PPre:= zeros(nPre);
      assert(false, "Wrong value for prediction model.");
    end if;

    if use_dayOfAdj then
      if _storeHistory or pre(_storeHistory) then

        // Store the predicted power consumption. This variable is stored
        // to avoid having to compute the average or weather regression multiple times.
        PPreHis[_typeOfDay[1],    getIndex(idxSam+1, nSam)] :=  PPre[1];

        // If iHis == 0, then there is no history yet and hence PPre[1] is 0.
        PPreHisSet[_typeOfDay[1], getIndex(idxSam+1, nSam)] :=  (iHis[_typeOfDay[1], iSam[1]] > 0);
      end if;
      // Compute average historical load.
      // This is a running sum over the past nHis days for the time window from iDayOf_start to iDayOf_end.
      EHisAve := 0;
      EActAve := 0;
      for i in iDayOf_start:iDayOf_end-1 loop
        if Modelica.Math.BooleanVectors.allTrue(
           {PPreHisSet[_typeOfDay[1], getIndex(iSam[1]+i, nSam)] for i in iDayOf_start:iDayOf_end-1}) then
           EHisAve := EHisAve + dt*PPreHis[_typeOfDay[1],
                        getIndex(idxSam+i+1, nSam)];
           EActAve := EActAve + dt*P[_typeOfDay[1],
                        getIndex(idxSam+i+1, nSam), iHis[_typeOfDay[1], getIndex(idxSam+i+1, nSam)]];
        else
          EHisAve := 0;
          EActAve := 0;
        end if;
      end for;

      // Compute the load adjustment factor.
      if (EHisAve > Modelica.Constants.eps or EHisAve < -Modelica.Constants.eps) then
        adj := min(maxAdjFac, max(minAdjFac, EActAve/EHisAve));
      else
        adj := 1;
      end if;
      // Apply the load adjustment factor to all predicted loads, not only to the
      // predicted load of the current time step.
      PPre[:] :=PPre[:]*adj;
    else
      EActAve := 0;
      EHisAve := 0;
      PPreHis[_typeOfDay[1], getIndex(iSam[1], nSam)] := 0;
      PPreHisSet[_typeOfDay[1], getIndex(iSam[1], nSam)] :=  false;
      adj := 1;
    end if;

    // Update iSam
    for i in 1:nPre loop
      iSam[i]   := incrementIndex(iSam[i], nSam);
    end for;

  end when;
end ElectricalLoad;


Package with models for load prediction

within Buildings.Controls;
package Predictors "Package with models for load prediction"
  extends Modelica.Icons.Package;
end Predictors;


Types for prediction models

within Buildings.Controls.Predictors;
package Types "Types for prediction models"
  type PredictionModel = enumeration(
      Average "Average load of previous days",
      WeatherRegression "Regression model based on outside temperature")
    "Enumeration for the prediction models"
end Types;


Package with base classes

within Buildings.Controls.Predictors;
package BaseClasses "Package with base classes"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings.Controls.Predictors.BaseClasses;
package Examples "Collection of models that illustrate model use and test models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Test model for sampleStart function. This model tests the function

Buildings.Controls.Predictors.BaseClasses.sampleStart.
The function produces the following sequence of points

within Buildings.Controls.Predictors.BaseClasses.Examples;
model SampleStart "Test model for sampleStart function"
  extends Modelica.Icons.Example;
  parameter Modelica.Units.SI.Time tSimSta[21]={i for i in -10:10}
    "Simulation start times";
  parameter Modelica.Units.SI.Time tSample=5 "Sample time";
  parameter Modelica.Units.SI.Time samSta[21](each fixed=false)
    "Start of sampling time";
initial equation
 samSta = sampleStart(t =            tSimSta,
                      samplePeriod = tSample);
 /*
 for i in 1:size(tSimSta,1) loop
   Modelica.Utilities.Streams.print("t = " + String(tSimSta[i]) + " samSta = " + String(samSta[i]));
 end for;
 */
end SampleStart;


This example applies the load prediction to a BESTEST model. This example applies the load prediction to the heating load of
BESTEST case 600.
On the right hand side of the model is the load prediction.
It is configured to compute a regression based on the outdoor temperature.
The output of the load prediction is compared with the actual
heating power. This comparison is done in the block
preErr which outputs the prediction error,
normalized by the roughly the maximum heating power.

within Buildings.Controls.Predictors.Examples;
model BESTEST "This example applies the load prediction to a BESTEST model"
  extends Buildings.ThermalZones.Detailed.Validation.BESTEST.Cases6xx.Case600;
  ElectricalLoad preReg(predictionModel=Buildings.Controls.Predictors.Types.PredictionModel.WeatherRegression)
    "Load prediction based on regression"
  Sources.DayType dayType(days={Buildings.Controls.Types.Day.WorkingDay})
    "Day type, set to use always the same day as BESTEST case 600 has no weekly schedule"
  Modelica.Blocks.Sources.BooleanConstant storeResults
    "Boolean signal to store the results for the regression"
  Modelica.Blocks.Math.Add preErr(k1=1/4000, k2=-1/4000)
    "Prediction error, normalized by peak power"
equation
  connect(dayType.y, preReg.typeOfDay)
  connect(storeResults.y, preReg.storeHistory)
  connect(EHea.y, preReg.ECon)
  connect(weaBus.TDryBul, preReg.TOut)
  connect(preErr.u2, gaiHea.y)
  connect(preErr.u1, preReg.PPre[1])
end BESTEST;


within Buildings.Controls.Predictors;
package Examples "Collection of models that illustrate model use and test models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Demand response client with constant input for actual power consumption. This model is identical to

Buildings.Controls.Predictors.Validation.ConstantInputDayOfAdjustment,
except that this model does not use any day-of adjustment for the predicted load.

within Buildings.Controls.Predictors.Validation;
model ConstantInput
  "Demand response client with constant input for actual power consumption"
  extends
    Buildings.Controls.Predictors.Validation.BaseClasses.PartialSimpleTestCase;
  Modelica.Blocks.Sources.Constant PCon(k=1) "Measured power consumption"
  Modelica.Blocks.Continuous.Integrator integrator
    "Integrator to compute energy from power"
equation
  connect(PCon.y, integrator.u)
  connect(integrator.y, baseLoad.ECon)
end ConstantInput;


Demand response client with constant input for actual power consumption. This model is identical to

Buildings.Controls.Predictors.Validation.ConstantInput,
except that the demand respond client is configured to use the day-of adjustment.

within Buildings.Controls.Predictors.Validation;
model ConstantInputDayOfAdjustment
  "Demand response client with constant input for actual power consumption"
  extends Buildings.Controls.Predictors.Validation.ConstantInput(
     baseLoad(use_dayOfAdj=true));
end ConstantInputDayOfAdjustment;


Demand response client with actual power consumption being linear in the temperature. This model is identical to

Buildings.Controls.Predictors.Validation.SineInput,
except that the input client.PCon is linear in the temperature.

within Buildings.Controls.Predictors.Validation;
model LinearInput
  "Demand response client with actual power consumption being linear in the temperature"
  extends
    Buildings.Controls.Predictors.Validation.BaseClasses.PartialSimpleTestCase(
     baseLoad(predictionModel=Buildings.Controls.Predictors.Types.PredictionModel.WeatherRegression));
  Modelica.Blocks.Sources.Ramp   TOut(
    y(unit="K", displayUnit="degC"),
    offset=283.15,
    height=10,
    duration(displayUnit="h") = 61*24*3600) "Outside temperature"
  Modelica.Blocks.Sources.Constant POffSet(k=1) "Offset for power"
  Modelica.Blocks.Math.Add PCon(k2=0.2) "Consumed power"
  Modelica.Blocks.Math.UnitConversions.To_degC to_degC
  Modelica.Blocks.Math.Add err(k2=-1) "Prediction error"
  Sampler TSam(samplePeriod=tSample)
    "Sampler to turn TOut into a piece-wise constant signal. This makes it easier to verify the results"
  Modelica.Blocks.Continuous.Integrator integrator
    "Integrator to compute energy from power"
  Modelica.Blocks.Sources.RealExpression TOutFut[nPre - 1](each y=293.15)
    if nPre > 1 "Prediction of future outside temperatures"
equation
  connect(POffSet.y, PCon.u1)
  connect(TOut.y, TSam.u)
  connect(TSam.y, baseLoad.TOut)
  connect(PCon.y, integrator.u)
  connect(integrator.y, baseLoad.ECon)
  connect(err.u2, PCon.y)
  connect(to_degC.u, TSam.y)
  connect(to_degC.y, PCon.u2)
  connect(baseLoad.PPre[1], err.u1)
  connect(baseLoad.TOutFut, TOutFut.y)
end LinearInput;


Demand response client with constant input for actual power consumption. This model is identical to

Buildings.Controls.Predictors.Validation.LinearInput,
except that the demand respond client is configured to use the day-of adjustment.

within Buildings.Controls.Predictors.Validation;
model LinearInputDayOfAdjustment
  "Demand response client with constant input for actual power consumption"
  extends Buildings.Controls.Predictors.Validation.LinearInput(
     baseLoad(use_dayOfAdj=true));
end LinearInputDayOfAdjustment;


within Buildings.Controls.Predictors;
package Validation "Collection of models that validate the load predictors"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


Demand response client with sinusoidal input for actual power consumption. Model that demonstrates and tests the demand response model.
Input to the model is a sinusoidal consumed electrical power
which has been discretized using a sampler.
Because of this discretization and because of the periodicity
of the input signal, the baseline prediction model will be able to
predict the load exactly.
The baseline prediction model also takes as an input signal
the day type, and a demand response signal.
Every seventh day, there is a demand response signal.

within Buildings.Controls.Predictors.Validation;
model SineInput
  "Demand response client with sinusoidal input for actual power consumption"
  extends
    Buildings.Controls.Predictors.Validation.BaseClasses.PartialSimpleTestCase;

  Modelica.Blocks.Sources.Cosine PBas(
    amplitude=0.5,
    offset=0.5,
    f=1/tPeriod,
    phase=3.1415926535898) "Measured power consumption"
  Sampler P(samplePeriod=tSample)
    "Sampler to turn PCon into a piece-wise constant signal. This makes it easier to verify the results"
  Modelica.Blocks.Continuous.Integrator integrator
    "Integrator to compute energy from power"
  Modelica.Blocks.Sources.RealExpression realExpression(
    y=if (dayType.y[1] == Buildings.Controls.Types.Day.WorkingDay) then 0 else 1)
  Modelica.Blocks.Math.Add PCon "Consumed power"
equation
  connect(P.y, integrator.u)
  connect(integrator.y, baseLoad.ECon)
  connect(PCon.u2, PBas.y)
  connect(realExpression.y, PCon.u1)
  connect(PCon.y, P.u)
end SineInput;


Demand response client with constant input for actual power consumption. This model is identical to

Buildings.Controls.Predictors.Validation.SineInput,
except that the demand respond client is configured to use the day-of adjustment.

within Buildings.Controls.Predictors.Validation;
model SineInputDayOfAdjustment
  "Demand response client with constant input for actual power consumption"
  extends Buildings.Controls.Predictors.Validation.SineInput(
     baseLoad(use_dayOfAdj=true));
end SineInputDayOfAdjustment;


within Buildings.Controls.Predictors.Validation;
package BaseClasses "Package with base classes for Buildings.Controls.Predictors.Examples"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


Partial base class for simple test case of base load prediction. Partial base class to build test for the load prediction.

within Buildings.Controls.Predictors.Validation.BaseClasses;
partial model PartialSimpleTestCase
  "Partial base class for simple test case of base load prediction"
  extends Modelica.Icons.Example;

  parameter Modelica.Units.SI.Time tPeriod=24*3600 "Period";
  parameter Modelica.Units.SI.Time tSample=3600 "Sampling period";
  parameter Integer nPre(min=1) = 12 "Number of time steps to predict";
  ElectricalLoad baseLoad(
      final nPre=nPre,
      use_dayOfAdj=false,
      predictionModel=Buildings.Controls.Predictors.Types.PredictionModel.Average)
    "Baseload prediction"
  Modelica.Blocks.Sources.BooleanPulse tri(
    width=4/24*100/7,
    period=7*tPeriod,
    startTime=1.5*tPeriod) "Sample trigger"
  Sources.DayType dayType(nout=2)
    "Outputs the type of the day for each hour where load is to be predicted"
  Modelica.Blocks.Logical.Not notEventDay
    "Output true if it is not an event day"
  // The Sampler is reimplemented to avoid in Dymola 2016 the translation warning
  // for the models in the parent package:
  //   The initial conditions for variables of type Boolean are not fully specified.
  //   Dymola has selected default initial conditions.
  //   Assuming fixed default start value for the discrete non-states:
  //     ...firstTrigger(start = false)
  //     ...
protected
  block Sampler
    extends Modelica.Blocks.Discrete.Sampler(
      firstTrigger(start=false, fixed=true));
  end Sampler;
equation
  connect(tri.y, notEventDay.u)
  connect(notEventDay.y, baseLoad.storeHistory)
  connect(dayType.y, baseLoad.typeOfDay)
end PartialSimpleTestCase;


Occupancy schedule with look-ahead. This model outputs whether the building is currently occupied,
and how long it will take until the next time when the building
will be occupied or non-occupied.
The latter may be used, for example, to start a ventilation system
half an hour before occupancy starts in order to ventilate the room.

within Buildings.Controls.SetPoints;
block OccupancySchedule "Occupancy schedule with look-ahead"
  extends Modelica.Blocks.Icons.Block;

  parameter Real occupancy[:]=3600*{7, 19}
    "Occupancy table, each entry switching occupancy on or off";
  parameter Boolean firstEntryOccupied = true
    "Set to true if first entry in occupancy denotes a changed from unoccupied to occupied";
  parameter Modelica.Units.SI.Time period=86400 "End time of periodicity";

  Modelica.Blocks.Interfaces.RealOutput tNexNonOcc
    "Time until next non-occupancy"
  Modelica.Blocks.Interfaces.RealOutput tNexOcc "Time until next occupancy"
  Modelica.Blocks.Interfaces.BooleanOutput occupied
    "Outputs true if occupied at current time"

protected
  final parameter Integer nRow = size(occupancy,1)
    "Number of rows in the schedule";

  discrete Modelica.Units.SI.Time tOcc "Time when next occupancy starts";
  discrete Modelica.Units.SI.Time tNonOcc "Time when next non-occupancy starts";
  discrete Modelica.Units.SI.Time tNext "Time of next switch in schedule";

  function getOutput "Get the next occupancy or non-occupancy outputs"
    extends Modelica.Icons.Function;

    input Modelica.Units.SI.Time t "Current model time";
    input Modelica.Units.SI.Time period "Periodicity";
    input Real occupancy[nRow]
      "Occupancy table, each entry switching occupancy on or off";
    input Boolean firstEntryOccupied
      "Set to true if first entry in occupancy denotes a changed from unoccupied to occupied";
    input Integer nRow
      "Number of rows in the schedule";

    output Modelica.Units.SI.Time tOcc "Time when next occupancy starts";
    output Modelica.Units.SI.Time tNonOcc "Time when next non-occupancy starts";
    output Boolean occupied
    "Outputs true if occupied at current time";
    output Modelica.Units.SI.Time tNext "Time of next switch in schedule";

  protected
    Integer iPerSta
      "Counter for the period in which the next occupancy starts";
    Integer iPerSto
      "Counter for the period in which the next occupancy stops";
    Integer nexStaInd "Next index when occupancy starts";
    Integer nexStoInd "Next index when occupancy stops";
  algorithm
    // Initialize variables
    iPerSta := integer(t/period);
    iPerSto := iPerSta;

    // First, assume that the first entry is occupied
    nexStaInd := 1;
    nexStoInd := 2;
    // nRow is an even number
    for
     i in 1:2:nRow-1 loop
   if t >= occupancy[i] + iPerSta*period then
     nexStaInd := i+2;
   end if;
    end for;
    for
     i in 2:2:nRow loop
   if t >= occupancy[i] + iPerSto*period then
     nexStoInd := i+2;
   end if;
    end for;
    if nexStaInd > nRow then
    nexStaInd := 1;
    iPerSta :=iPerSta + 1;
    end if;
    if nexStoInd > nRow then
    nexStoInd := 2;
    iPerSto :=iPerSto + 1;
    end if;
    tOcc := occupancy[nexStaInd] + iPerSta*period;
    tNonOcc := occupancy[nexStoInd] + iPerSto*period;

    occupied := tNonOcc < tOcc;
    // Now, correct if the first entry is vaccant instead of occupied
    if not firstEntryOccupied then
     (nexStaInd, nexStoInd) := switchInteger(nexStaInd, nexStoInd);
     (iPerSta, iPerSto)     := switchInteger(iPerSta,   iPerSto);
     (tOcc, tNonOcc)        := switchReal(tOcc,      tNonOcc);
   occupied := not occupied;
    end if;

   tNext :=min(tOcc, tNonOcc);
  end getOutput;

encapsulated function switchInteger "Switch two Integer arguments"
  import Modelica;
  extends Modelica.Icons.Function;
  input Integer x1 "First argument";
  input Integer x2 "Second argument";
  output Integer y1 "Output = x2";
  output Integer y2 "Output = x1";
algorithm
  y1:=x2;
  y2:=x1;
end switchInteger;

encapsulated function switchReal "Switch two Real arguments"
  import Modelica;
  extends Modelica.Icons.Function;
  input Real x1 "First argument";
  input Real x2 "Second argument";
  output Real y1 "Output = x2";
  output Real y2 "Output = x1";
algorithm
  y1:=x2;
  y2:=x1;
end switchReal;

initial equation
  // Check parameters for correctness
 assert(mod(nRow, 2) < 0.1,
   "The parameter \"occupancy\" must have an even number of elements.\n");
 assert(0 <= occupancy[1],
   "The first element of \"occupancy\" must be bigger than or equal to zero."
   + "\n   Received occupancy[1] = " + String(occupancy[1]));
 assert(period >= occupancy[nRow],
   "The parameter \"period\" must be greater than or equal to the last element of \"occupancy\"."
   + "\n   Received period      = " + String(period)
   + "\n            occupancy[" + String(nRow) +
     "] = " + String(occupancy[nRow]));
  for i in 1:nRow-1 loop
    assert(occupancy[i] < occupancy[i+1],
      "The elements of the parameter \"occupancy\" must be strictly increasing.");
  end for;

  (tOcc, tNonOcc, occupied, tNext) = getOutput(t = time, period = period, occupancy= occupancy, firstEntryOccupied = firstEntryOccupied, nRow=nRow);

equation
  when time >= pre(tNext) then
    (tOcc, tNonOcc, occupied, tNext) = getOutput(t = time, period = period, occupancy= occupancy, firstEntryOccupied = firstEntryOccupied, nRow=nRow);
  end when;

 tNexOcc    =  tOcc-time;
 tNexNonOcc =  tNonOcc-time;
end OccupancySchedule;


within Buildings.Controls;
package SetPoints "Package with models for control set points"
  extends Modelica.Icons.VariantsPackage;
end SetPoints;


Block to compute the supply and return set point. This block computes the set point temperatures for the
supply and return water temperature.
The set point for the room air temperature can either be specified
by a parameter, or it can be an input to the model. The latter allows
to use this model with systems that have night set back.

within Buildings.Controls.SetPoints;
block SupplyReturnTemperatureReset
  "Block to compute the supply and return set point"
  extends Modelica.Blocks.Icons.Block;

  parameter Real m = 1.3 "Exponent for heat transfer";
  parameter Modelica.Units.SI.Temperature TSup_nominal "Supply temperature"
  parameter Modelica.Units.SI.Temperature TRet_nominal "Return temperature"
  parameter Modelica.Units.SI.Temperature TRoo_nominal=293.15
    "Room temperature"
  parameter Modelica.Units.SI.Temperature TOut_nominal "Outside temperature"

  parameter Boolean use_TRoo_in = false
    "Get the room temperature set point from the input connector"
  parameter Modelica.Units.SI.Temperature TRoo=293.15
    "Fixed value of room temperature set point"
  parameter Modelica.Units.SI.TemperatureDifference dTOutHeaBal(displayUnit="K")
     = 8 "Offset for heating curve";
  Modelica.Blocks.Interfaces.RealInput TRoo_in(final quantity="ThermodynamicTemperature",
                                               final unit = "K", displayUnit = "degC", min=0)
       if use_TRoo_in "Room air temperature set point"

  Modelica.Blocks.Interfaces.RealInput TOut(final quantity="ThermodynamicTemperature",
                                            final unit = "K", displayUnit = "degC", min=0)
    "Outside temperature"
  Modelica.Blocks.Interfaces.RealOutput TSup(final quantity="ThermodynamicTemperature",
                                            final unit = "K", displayUnit = "degC", min=0)
    "Setpoint for supply temperature"
  Modelica.Blocks.Interfaces.RealOutput TRet(final quantity="ThermodynamicTemperature",
                                            final unit = "K", displayUnit = "degC", min=0)
    "Setpoint for return temperature"

protected
  Modelica.Blocks.Interfaces.RealInput TRoo_in_internal(final quantity="ThermodynamicTemperature",
                                                        final unit = "K", displayUnit = "degC", min=0)
    "Needed to connect to conditional connector";
  Real qRel "Relative heating load = Q_flow/Q_flow_nominal";
  Modelica.Units.SI.Temperature TOutOffSet
    "Effective outside temperature for heat transfer (takes into account room heat gains)";
  parameter Modelica.Units.SI.Temperature TOutOffSet_nominal=TOut_nominal +
      dTOutHeaBal
    "Effective outside temperature for heat transfer at nominal conditions (takes into account room heat gains)";

equation
  connect(TRoo_in, TRoo_in_internal);
  if not use_TRoo_in then
    TRoo_in_internal = TRoo;
  end if;
 TOutOffSet = TOut + dTOutHeaBal;
 // Relative heating load, compared to nominal conditions
 qRel = max(0, (TRoo_in_internal-TOutOffSet)/(TRoo_nominal-TOutOffSet_nominal));
 TSup = TRoo_in_internal
          + ((TSup_nominal+TRet_nominal)/2-TRoo_nominal) * qRel^(1/m)
          + (TSup_nominal-TRet_nominal)/2 * qRel;
 TRet = TSup - qRel * (TSup_nominal-TRet_nominal);
end SupplyReturnTemperatureReset;


Model for a set point that is interpolated based on a user-specified table. This block can be used to schedule a set-point by using piecewise linear functions.
For example, the instances

within Buildings.Controls.SetPoints;
model Table
  "Model for a set point that is interpolated based on a user-specified table"
  extends Modelica.Blocks.Interfaces.SISO;
  parameter Real table[:,2]=fill(0.0, 1, 2)
    "Table matrix ( e.g., table=[u1, y1; u2, y2; u3, y3])";

  parameter Real offset=0 "Offset of output signal";

  parameter Boolean constantExtrapolation = true
    "If true, then y=y1 for u<u1, and y=yMax for u>uMax";

protected
  final parameter Integer nRow = if constantExtrapolation then
                        size(table,1)+2 else
                        size(table,1) "Number of rows";
  final parameter Real[nRow,2] offsetVector = [zeros(nRow), offset*ones(nRow)]
    "Vector to take offset of output signal into account";
  Modelica.Blocks.Tables.CombiTable1Dv tab(tableOnFile=false, final table=(if
        constantExtrapolation then cat(
        1,
        [table[1, 1] - 1,table[1, 2]],
        table,
        [table[end, 1] + 1,table[end, 2]]) else table) + offsetVector)
    "Table used for interpolation"
equation
  connect(u, tab.u[1])

  connect(tab.y[1], y)
Buildings.Controls.SetPoints.Table tabConExt(constantExtrapolation=true,
                                             table=[20, 0.0;
                                                    22, 0.5;
                                                    25, 0.5;
                                                    26, 1.0]);

will cause the following output:

For the default setting constantExtrapolation=true, the
block outputs
y=y1+offset for u ≤ u1, and
y=yMax+offset for u ≥ uMax.
Otherwise, the table is linearly extrapolated with a constant derivative.

Note that the first column must be strictly increasing.

",
revisions="

August 30, 2016, by Michael Wetter:

Changed protected final parameter nCol to nRow.

This is for
issue 555.

April 5, 2011, by Michael Wetter:

Fixed wrong table declaration.

July 13, 2010, by Michael Wetter:

First implementation.

"),
    Icon(graphics={
    Text(
      extent={{-78,-45},{-40,-56}},
      textColor={0,0,0},
      textString="offset"),
    Line(
      points={{-42,-24},{-42,-74}},
      color={95,95,95},
      thickness=0.25,
      arrow={Arrow.None,Arrow.None}),
    Line(
      points={{-82,-24},{-22,-24},{26,24}},
      color={0,0,255},
      thickness=0.5),
    Line(points={{-82,64},{-82,-84}}, color={95,95,95}),
    Text(
      extent={{-80,88},{-39,68}},
      textColor={0,0,0},
      textString="y"),
    Polygon(
      points={{-82,86},{-88,64},{-76,64},{-82,86}},
      lineColor={95,95,95},
      fillColor={95,95,95},
      fillPattern=FillPattern.Solid),
    Polygon(
      points={{88,-74},{66,-68},{66,-80},{88,-74}},
      lineColor={95,95,95},
      fillColor={95,95,95},
      fillPattern=FillPattern.Solid),
    Line(points={{-92,-74},{80,-74}}, color={95,95,95}),
    Text(
      extent={{68,-84},{92,-95}},
      textColor={0,0,0},
          textString="u"),
    Polygon(
      points={{-42,-24},{-44,-34},{-39,-34},{-42,-24}},
      lineColor={95,95,95},
      fillColor={95,95,95},
      fillPattern=FillPattern.Solid),
    Polygon(
      points={{-42,-74},{-45,-64},{-40,-64},{-42,-74},{-42,-74}},
      lineColor={95,95,95},
      fillColor={95,95,95},
      fillPattern=FillPattern.Solid),
    Line(
      points={{26,24},{77,24}},
      color={0,0,255},
      thickness=0.5)}));
end Table;


Test model for occupancy schedule with look-ahead. Example that demonstrates the use of the occupancy schedule.
The figure below shows how the time until the next occupancy starts or ends
is decreased. The red line hits zero when the schedule indicates an occupied time,
and the blue line hits zero when the schedule indicates a non-occupied time.

within Buildings.Controls.SetPoints.Examples;
model OccupancySchedule "Test model for occupancy schedule with look-ahead"
  extends Modelica.Icons.Example;
  Buildings.Controls.SetPoints.OccupancySchedule occSchDay "Day schedule"
  Buildings.Controls.SetPoints.OccupancySchedule occSchWee(occupancy=3600*{7,19,
        31,43,55,67,79,91,103,115,127,139}, period=7*24*3600) "Week schedule"
end OccupancySchedule;


within Buildings.Controls.SetPoints;
package Examples "Collection of models that illustrate model use and test models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Test model for the heating curve. Example that demonstrates the use of the hot water temperature reset
for a heating system.
The parameters of the block heaCur
are for a heating system with
60°C supply water temperature and
40°C return water temperature at
an outside temperature of
-10°C and a room temperature of
20°C. The offset for the temperature reset is
8 Kelvin, i.e., above
12°C outside temperature, there is no heating load.
The figure below shows the computed supply and return water temperatures.

within Buildings.Controls.SetPoints.Examples;
model SupplyReturnTemperatureReset "Test model for the heating curve"
  extends Modelica.Icons.Example;
  Buildings.Controls.SetPoints.SupplyReturnTemperatureReset heaCur(
    m=1,
    TSup_nominal=333.15,
    TRet_nominal=313.15,
    TOut_nominal=263.15)
    "Temperature reset"
  Modelica.Blocks.Sources.Ramp TOut(
    height=40,
    duration=1,
    offset=263.15)
    "Signal for outside air temperature"
  Buildings.Controls.SetPoints.SupplyReturnTemperatureReset heaCur1(
    m=1,
    use_TRoo_in=true,
    TSup_nominal=333.15,
    TRet_nominal=313.15,
    TOut_nominal=263.15,
    dTOutHeaBal=15)
    "Temperature reset that uses room temperature as input"
  Modelica.Blocks.Sources.Step TRoo(
    offset=273.15 + 20,
    startTime=0.5,
    height=-5) "Night set back from 20 to 15 deg C"
  Modelica.Blocks.Sources.Constant TOut2(k=273.15 - 10)
   "Constant signal for outdoor air temperature"
equation
  connect(TOut.y, heaCur.TOut)
  connect(TOut2.y, heaCur1.TOut)
  connect(TRoo.y, heaCur1.TRoo_in)
end SupplyReturnTemperatureReset;


Test model for table that determines set points. Example that demonstrates the use of the block

Buildings.Controls.SetPoints.Table.

within Buildings.Controls.SetPoints.Examples;
model Table "Test model for table that determines set points"
  extends Modelica.Icons.Example;
  Buildings.Controls.SetPoints.Table tabConExt(table=[20,0; 22,0.5; 25,0.5; 26,1])
  Modelica.Blocks.Sources.Ramp TRoo(
    duration=1,
    offset=15,
    height=15)
  Buildings.Controls.SetPoints.Table tabLinExt(constantExtrapolation=false, table=[
        20,0; 22,0.5; 25,0.5; 26,1])
equation
  connect(TRoo.y, tabLinExt.u)
  connect(TRoo.y, tabConExt.u)
end Table;


Test model for occupancy schedule with look-ahead. Example that validates the occupancy schedule
for a negative start time.

within Buildings.Controls.SetPoints.Validation;
model OccupancyScheduleNegativeStartTime
  "Test model for occupancy schedule with look-ahead"
  extends Modelica.Icons.Example;
  Buildings.Controls.SetPoints.OccupancySchedule occSchDay(occupancy=3600*{7,24})
                                                       "Day schedule"
  OccupancySchedule occSchDay1(occupancy=3600*{0,2})   "Day schedule"
  OccupancySchedule occSchDay2(
    occupancy=3600*{7,10,17,20},
    firstEntryOccupied=false)                 "Day schedule"
end OccupancyScheduleNegativeStartTime;


Validation model for occupancy schedule with positive start time. Example that validates the occupancy schedule
for a positive start time.

within Buildings.Controls.SetPoints.Validation;
model OccupancySchedulePositiveStartTime
  "Validation model for occupancy schedule with positive start time"
  extends OccupancyScheduleNegativeStartTime;

end OccupancySchedulePositiveStartTime;


Collection of validation models

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


Block that outputs a signal that indicates week-day or week-end. This block outputs a periodic signal that indicates the type of the day.
It can for example be used to generate a signal that indicates whether
the current time is a work day or a non-working day.
The output signal is of type

Buildings.Controls.Types.Day.

within Buildings.Controls.Sources;
model DayType "Block that outputs a signal that indicates week-day or week-end"
  extends Modelica.Blocks.Icons.DiscreteBlock;
  parameter Integer nout = 2
    "Number of days to output. Set to two for one day predictions";
  parameter Buildings.Controls.Types.Day[:] days={
    Buildings.Controls.Types.Day.WorkingDay,
    Buildings.Controls.Types.Day.WorkingDay,
    Buildings.Controls.Types.Day.WorkingDay,
    Buildings.Controls.Types.Day.WorkingDay,
    Buildings.Controls.Types.Day.WorkingDay,
    Buildings.Controls.Types.Day.NonWorkingDay,
    Buildings.Controls.Types.Day.NonWorkingDay}
    "Array where each element is a day indicator";
   parameter Integer iStart(min=1, max=size(days, 1)) = 1
    "Index of element in days at simulation start";

  Interfaces.DayTypeOutput y[nout]
    "Type of the day for the current and the next (nout-1) days"

protected
  parameter Modelica.Units.SI.Time samplePeriod=86400
    "Sample period of the component";
  output Integer iDay(min=1, max=size(days, 1))
    "Pointer to days that determines what day type is sent to the output";
  parameter Modelica.Units.SI.Time firstSample(fixed=false)
    "Time when the sampling starts";
  output Boolean sampleTrigger "True, if sample time instant";
  output Boolean skipIDayIncrement
    "If true, don't increment iDay in first sample";

initial equation
  iDay = iStart;
  firstSample = ceil(time/86400)*86400;
  // skipIDayIncrement is true if the simulation starts at midnight.
  skipIDayIncrement = abs(firstSample-time) < 1E-8;
equation
  for i in 1:nout loop
    y[i] = days[ mod(iDay+i-2, size(days, 1))+1];
  end for;
  sampleTrigger = sample(firstSample, samplePeriod);
  when sampleTrigger then
    skipIDayIncrement = false;
    if pre(skipIDayIncrement) then
      iDay = pre(iDay);
    else
      iDay = mod(pre(iDay), size(days, 1))+1;
    end if;
  end when;
end DayType;


within Buildings.Controls;
package Sources "Package with models for source signals that are used with controllers"
  extends Modelica.Icons.SourcesPackage;
end Sources;


Example model for the source that outputs the type of the day. This example generates signals for three different work weeks.
The instance dayTypMon outputs a signal with five working
days, followed by two non-working days.
The instance dayTypSat does the same, except that the first
days is a non-working day.
The instance dayTypTwoWeeks outputs six working days, followed
by 8 non-working days.
The instance dayTypMonThr is configured the same as
dayTypMon, except that it outputs the type of the day
for three days, starting with the current day, then the next day and
the day after.

within Buildings.Controls.Sources.Examples;
model DayType "Example model for the source that outputs the type of the day"
  extends Modelica.Icons.Example;
  Buildings.Controls.Sources.DayType dayTypMon
    "Model that outputs the type of the day, starting with Monday"
  Buildings.Controls.Sources.DayType dayTypSat(iStart=6)
    "Model that outputs the type of the day, starting with Saturday"
  Buildings.Controls.Sources.DayType dayTypTwoWeeks(
    days={
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.WorkingDay,
        Buildings.Controls.Types.Day.NonWorkingDay,Buildings.Controls.Types.Day.NonWorkingDay,
        Buildings.Controls.Types.Day.NonWorkingDay,Buildings.Controls.Types.Day.NonWorkingDay,
        Buildings.Controls.Types.Day.NonWorkingDay,Buildings.Controls.Types.Day.NonWorkingDay,
        Buildings.Controls.Types.Day.NonWorkingDay,Buildings.Controls.Types.Day.NonWorkingDay},
    nout=14)
    "Model that outputs the type of the day, starting with 6 workdays, then 8 non-working days"
  Buildings.Controls.Sources.DayType dayTypMonThr(nout=3)
    "Model that outputs the type of the day for 3 days, starting with Monday"
end DayType;


within Buildings.Controls.Sources;
package Examples "Collection of models that illustrate model use and test models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


within Buildings;
package DHC "Models for district heating and cooling systems"
  extends Modelica.Icons.VariantsPackage;
end DHC;


Package with type definitions

within Buildings.DHC;
package Types  "Package with type definitions"
  extends Modelica.Icons.TypesPackage;

  type DistrictSystemType = enumeration(
      CombinedGeneration1
    "First generation district heating and cooling system",
      CombinedGeneration2to4
    "Second to fourth generation district heating and cooling system",
      CombinedGeneration5
    "Fifth generation district heating and cooling system",
      Cooling
    "District cooling system",
      HeatingGeneration1
    "First generation district heating system",
      HeatingGeneration2to4
    "Second to fourth generation district heating system")
    "Enumeration to define the type of district system"
end Types;


info

within Buildings.DHC;
package UsersGuide
  extends Modelica.Icons.Information;
end UsersGuide;


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


within Buildings.DHC.BaseClasses;
package Steam "Package for steam systems using the split-medium approach."
  extends Modelica.Icons.VariantsPackage;
end Steam;


within Buildings.DHC.BaseClasses.Steam;
partial model PartialSaturatedControlVolume
  "Partial control volume for evaporation/condensation processes"
  extends Buildings.BaseClasses.BaseIcon;
  extends Buildings.DHC.BaseClasses.Steam.PartialTwoPortTwoMedium(
    p_start=MediumSte.p_default,
    final T_start=MediumSte.saturationTemperature(p_start));

  // Medium declarations
  replaceable package MediumWat =
    Buildings.Media.Specialized.Water.TemperatureDependentDensity
    "Liquid water medium";
  replaceable package MediumSte = Buildings.Media.Steam
     "Steam medium";

  // Parameters
  parameter Modelica.Units.SI.Volume V "Total volume";
  // Initialization
  parameter Modelica.Units.SI.Volume VWat_start=V/2
    "Start value of liquid volume"

  // Variables
  MediumWat.ThermodynamicState stateWat(p=p, T=T)
    "Saturated state, liquid water";
  MediumSte.ThermodynamicState stateSte(p=p, T=T)
    "Saturated state, steam";
  MediumSte.AbsolutePressure p(
    final start=p_start,
    stateSelect=if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState
    then StateSelect.default else StateSelect.prefer)
    "Pressure inside volume";
  MediumSte.Temperature T(final start=T_start) "Temperature inside volume";
  Modelica.Units.SI.Volume VSte "Volume of steam vapor";
  Modelica.Units.SI.Volume VWat(
    final start=VWat_start,
    fixed=true,
    stateSelect=if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState
      then StateSelect.default else StateSelect.prefer)
    "Volume of liquid water phase";
  Modelica.Units.SI.VolumeFlowRate VWat_flow(start=0)
    "Volumetric flow rate of liquid water";
  MediumSte.SpecificEnthalpy hSte "Specific enthalpy of steam vapor";
  MediumWat.SpecificEnthalpy hWat "Specific enthalpy of liquid water";
  MediumSte.Density rhoSte "Density of steam vapor";
  MediumWat.Density rhoWat "Density of liquid water";
  Modelica.Units.SI.Mass m "Total mass of volume";
  Modelica.Units.SI.Energy U "Internal energy";
  Modelica.Units.SI.MassFlowRate mWat_flow "Water mass flow rate";
  Modelica.Units.SI.MassFlowRate mSte_flow "Steam mass flow rate";

  // Input
  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heatPort(
    T(final start=T_start)) if not steadyDynamics "Heat port"
  //Output
  Modelica.Blocks.Interfaces.RealOutput VLiq(final unit="m3") "Liquid volume"

protected
  final parameter Boolean steadyDynamics=
    energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState
    "= true, if steady state formulation";

  Buildings.HeatTransfer.Sources.PrescribedTemperature preTem if not steadyDynamics
    "Prescribed temperature"
  Modelica.Blocks.Sources.RealExpression portT(y=T)  if not steadyDynamics
    "Port temperature"
  Modelica.Thermal.HeatTransfer.Sensors.HeatFlowSensor heaFloSen if not steadyDynamics
    "Heat flow sensor"
  Modelica.Blocks.Interfaces.RealInput Q_flow_internal
    "Needed to use conditional connector Q_flow";

initial equation
  // 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 'energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState', then it is
         required that 'massDynamics==Modelica.Fluid.Types.Dynamics.SteadyState'.
         Otherwise, the system of equations may not be consistent.
         You need to select other parameter values.");
  end if;

// Initial conditions
  if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
    der(T) = 0;
  end if;
  if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial then
    der(p)=0;
  end if;

equation
 // Total quantities
  m = rhoSte*VSte + rhoWat*VWat "Total mass";
  U = rhoSte*VSte*hSte + rhoWat*VWat*hWat - p*V "Total energy";
  V = VWat + VSte "Total volume";

  // Mass balance
  if massDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    0 = mWat_flow + mSte_flow "Steady state mass balance";
  else
    der(m) = mWat_flow + mSte_flow "Dynamic mass balance";
  end if;
  der(VWat) = VWat_flow;

  // Energy balance
  if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
    der(U) = 0;
    Q_flow_internal = 0;
  else
    connect(heaFloSen.Q_flow, Q_flow_internal) "Needed because of conditional input";
    if allowFlowReversal then
      der(U) = Q_flow_internal
            + port_a.m_flow*actualStream(port_a.h_outflow)
            + port_b.m_flow*actualStream(port_b.h_outflow);
    else
      der(U) = Q_flow_internal
            + port_a.m_flow*inStream(port_a.h_outflow)
            + port_b.m_flow*port_b.h_outflow;
    end if;
  end if;

  // Properties of saturated liquid and steam
  T = MediumSte.saturationTemperature(p);
  hSte=MediumSte.specificEnthalpy(stateSte);
  hWat=MediumWat.specificEnthalpy(stateWat);
  rhoSte=MediumSte.density(stateSte);
  rhoWat=MediumWat.density(stateWat);

  // boundary conditions at the ports
  port_a.p = p;
  port_b.p = p;

// outputs
  VLiq = VWat;

  // Check that evaporation is actually possible
  assert(VSte >= 0, "There is no more steam vapor in the volume.");
  assert(VWat >= 0, "There is no more liquid water in the volume.");

  connect(portT.y,preTem. T)
  connect(heaFloSen.port_b,preTem. port)
  connect(heaFloSen.port_a, heatPort)

The metal drum is excluded from the mass and energy balances;

 Implementation

This model is configured to allow both steady state and dynamic mass
and energy balances. The heat transfer through the
heatPort is disabled in steady state balance.
This is required because the fluid is restricted to a saturated state;
thus, the heat transfer rate is a function of mass flow rate only
if the volume is steady. The fluid mass m in the volume is
calculated as

m = ρsVs + ρwVw

where ρ is density,V is volume, and subscripts represent
the steam and liquid water components, respectively.
The total internal energy U is

U = ρsVshs + ρwVw − pV

where h is specific enthalpy, p is pressure, and the
total volume of fluid V=Vs+Vw.

The steady state mass balance is given as

ṁs + ṁw = 0,

while no additional equation is given for the steady state energy
balance, since the heat flow rate into the water must be removed
from the system in which the control volume is used.

The dynamic mass and energy balances are given as

dm/dt = ṁs + ṁw

dU/dt = Q̇ + ṁs hs + ṁ
w hw

where ̇ṁs and ṁw
are the mass flow rates of steam and liquid water
respectively; Q̇ is the heat flow rate
into the control volume;
hs and hw are the specific
enthalpies of steam and liquid water, respectively.
Note that with an evaporation process, the liquid
phase (water) is always assigned at the port_a (inlet),
while the vapor phase (steam) is always at the port_b (outlet).
The opposite holds for a condensation process.

Assumptions

Three principal assumptions are made with this model:

The fluid within the volume is wet steam.

Liquid and vapor subcomponents are at equilibrium; and

Fluid is discharged from the volume as ei
ther saturated
liquid or saturated vapor.

Models that extend this base class include

Buildings.DHC.Plants.Steam.BaseClasses.ControlVolumeEvaporation and

Buildings.DHC.Loads.Steam.BaseClasses.ControlVolumeCondensation.

Reference

Hinkelman, Kathryn, Saranya Anbarasu, Michael Wetter,
Antoine Gautier, and Wangda Zuo. 2022. “A Fast and Accurate Modeling
Approach for Water and Steam Thermodynamics with Practical
Applications in District Heating System Simulation.” Preprint. February 24.
doi:10.13140/RG.2.2.20710.29762.

"));
end PartialSaturatedControlVolume;


within Buildings.DHC.BaseClasses.Steam;
partial model PartialTwoPortTwoMedium
  "Partial model with two ports with two separate medium models without storing mass or energy"

  replaceable package Medium_a =
      Modelica.Media.Interfaces.PartialMedium
    "Medium model for port_a (inlet)";
  replaceable package Medium_b =
      Modelica.Media.Interfaces.PartialMedium
    "Medium model for port_b (outlet)";

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Nominal mass flow rate"
  parameter Modelica.Units.SI.MassFlowRate m_flow_small(min=0) = 1E-4*abs(m_flow_nominal)
    "Small mass flow rate for regularization of zero flow";

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

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

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

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

  Medium_b.ThermodynamicState sta_b=
      Medium_b.setState_phX(port_b.p,
                          noEvent(actualStream(port_b.h_outflow)),
                          noEvent(actualStream(port_b.Xi_outflow))) if show_T
    "Medium properties in port_b";

  Modelica.Fluid.Interfaces.FluidPort_a port_a(
    redeclare final package Medium = Medium_a,
     m_flow(min=if allowFlowReversal then -Modelica.Constants.inf else 0),
     h_outflow(start = Medium_a.h_default, nominal = Medium_a.h_default))
    "Fluid connector a (positive design flow direction is from port_a to port_b)"
  Modelica.Fluid.Interfaces.FluidPort_b port_b(
    redeclare final package Medium = Medium_b,
     m_flow(max=if allowFlowReversal then +Modelica.Constants.inf else 0),
     h_outflow(start = Medium_b.h_default, nominal = Medium_b.h_default))
    "Fluid connector b (positive design flow direction is from port_a to port_b)"

  // Assumptions
  parameter Boolean allowFlowReversal = true
    "= false to simplify equations, assuming, but not enforcing, no flow reversal. Used only if model has two ports."

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

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

  // Initialization
  parameter Medium_b.AbsolutePressure p_start = Medium_b.p_default
    "Start value of pressure"
  parameter Medium_b.Temperature T_start=Medium_b.T_default
    "Start value of temperature"

protected
  final parameter Modelica.Units.SI.MassFlowRate _m_flow_start = 0
  "Start value for m_flow, used to avoid a warning if not set in m_flow, and to avoid m_flow.start in parameter window";
  final parameter Modelica.Units.SI.PressureDifference _dp_start(displayUnit="Pa") = 0
  "Start value for dp, used to avoid a warning if not set in dp, and to avoid dp.start in parameter window";
end PartialTwoPortTwoMedium;


within Buildings.DHC;
package ETS "Package of models for energy transfer stations"
  extends Modelica.Icons.VariantsPackage;
end ETS;


Package with type definitions

within Buildings.DHC.ETS;
package Types
  "Package with type definitions"
  extends Modelica.Icons.TypesPackage;
  type ConnectionConfiguration = enumeration(
      TwoWayValve
    "Two-way valve on district side",
      Pump
    "Pump on district side")
    "Enumeration for the type of connection with the district network"
end Types;


Model of a collector/distributor with zero pressure drop between connections. This model represents a collector/distributor which connects
nCon hydronic circuits in parallel.
The pressure drop between each connection is assumed negligible
compared to the pressure drop in each circuit, and is set to zero
in the model.
By default,

within Buildings.DHC.ETS.BaseClasses;
model CollectorDistributor
  "Model of a collector/distributor with zero pressure drop between connections"
  extends Buildings.DHC.Networks.BaseClasses.PartialDistribution2Pipe(
    mDis_flow_nominal=sum(
      mCon_flow_nominal),
    final mDisCon_flow_nominal=fill(
      mDis_flow_nominal,
      nCon),
    final mEnd_flow_nominal=mDis_flow_nominal,
    final allowFlowReversal=true,
    final iConDpSen=-1,
    redeclare Connection2PipeLossless con[nCon],
    redeclare model Model_pipDis=Fluid.FixedResistances.LosslessPipe);
end CollectorDistributor;


Model of a lossless connection to a collector/distributor. This is a model of a connection for a two-pipe system using
a pipe model with no flow resistance, no heat loss and no transport delay.

within Buildings.DHC.ETS.BaseClasses;
model Connection2PipeLossless
  "Model of a lossless connection to a collector/distributor"
  extends Buildings.DHC.Networks.BaseClasses.PartialConnection2Pipe(
    redeclare model Model_pipDisSup =
        Buildings.Fluid.FixedResistances.LosslessPipe,
    redeclare model Model_pipDisRet =
        Buildings.Fluid.FixedResistances.LosslessPipe,
    redeclare model Model_pipCon=Buildings.Fluid.FixedResistances.LosslessPipe);
end Connection2PipeLossless;


Fluid junction with zero pressure drop. This is a model of a fluid junction with zero pressure drop.
By default the model is configured in steady-state.

within Buildings.DHC.ETS.BaseClasses;
model Junction
  "Fluid junction with zero pressure drop"
  extends Fluid.FixedResistances.Junction(
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    final dp_nominal=fill(
      0,
      3),
    final from_dp=false);
end Junction;


Package with base classes

within Buildings.DHC.ETS;
package BaseClasses "Package with base classes"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


within Buildings.DHC.ETS.BaseClasses;
model PartialDirect
  "Partial direct ETS model for district energy systems with in-building pumping and deltaT control"
  extends
    Buildings.DHC.ETS.BaseClasses.PartialETS(
    final have_weaBus=false,
    final have_hotWat=false,
    final have_eleHea=false,
    final nFue=0,
    final have_eleCoo=false,
    final have_pum=false,
    final have_fan=false);
  // Mass flow rate
  parameter Modelica.Units.SI.MassFlowRate mBui_flow_nominal(
    final min=0)
    "Nominal mass flow rate of building side"
  // Pressure drops
  parameter Modelica.Units.SI.PressureDifference dpConVal_nominal(
    final min=0,
    displayUnit="Pa")=50
    "Nominal pressure drop in the control valve"
  parameter Modelica.Units.SI.PressureDifference dpCheVal_nominal(
    final min=0,
    displayUnit="Pa")=6000
    "Nominal pressure drop in the check valve"
  // Controller parameters
  parameter Modelica.Blocks.Types.SimpleController controllerType=Modelica.Blocks.Types.SimpleController.PI
    "Type of controller"
  parameter Real k(
    final min=0,
    final unit="1")=0.1
    "Gain of controller"
  parameter Modelica.Units.SI.Time Ti(
    final min=Modelica.Constants.small)=60
    "Time constant of integrator block"
  parameter Modelica.Units.SI.Time Td(
    final min=0)=0.1
    "Time constant of derivative block"
  parameter Real yMax=1
    "Upper limit of output"
  parameter Real yMin=0
    "Lower limit of output"
  // Advanced parameters
  parameter Modelica.Fluid.Types.Dynamics energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial
    "Type of energy balance: dynamic (3 initialization options) or steady state"
  parameter Real bandwidth=0.2
    "Bandwidth around reference signal for on/off controller"

  Modelica.Blocks.Interfaces.RealInput TDisRetSet(
     final unit="K",
     displayUnit="degC")
    "Setpoint for the district return temperature (min for cooling, max for heating)"
  Modelica.Blocks.Interfaces.RealOutput Q_flow(
    final quantity="HeatFlowRate",
    final unit="W",
    displayUnit="kW")
    "Measured heating demand at the ETS"
  Modelica.Blocks.Interfaces.RealOutput Q(
    final quantity="Energy",
    final unit="J",
    displayUnit="kW.h")
    "Measured energy consumption at the ETS"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTDisSup(
    redeclare final package Medium=MediumSer,
    final m_flow_nominal=mBui_flow_nominal)
    "District supply temperature sensor"
  Buildings.Fluid.Sensors.MassFlowRate senMasFlo(
    redeclare final package Medium=MediumSer)
    "District supply mass flow rate sensor"
  Buildings.Fluid.FixedResistances.Junction jun(
    redeclare final package Medium=MediumSer,
    final energyDynamics=energyDynamics,
    final m_flow_nominal=mBui_flow_nominal*{1,-1,1},
    final dp_nominal={0,0,0})
    "Bypass junction"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTDisRet(
    redeclare final package Medium=MediumSer,
    final m_flow_nominal=mBui_flow_nominal)
    "District return temperature sensor"
  Buildings.Fluid.FixedResistances.Junction spl(
    redeclare final package Medium=MediumSer,
    final energyDynamics=energyDynamics,
    final m_flow_nominal=mBui_flow_nominal*{1,-1,-1},
    final dp_nominal={0,0,0})
    "Bypass junction, splitter"
  Buildings.Fluid.Actuators.Valves.TwoWayEqualPercentage conVal(
    redeclare final package Medium=MediumSer,
    final m_flow_nominal=mBui_flow_nominal,
    final dpValve_nominal=dpConVal_nominal,
    use_strokeTime=true,
    strokeTime(displayUnit="s") = 60)
    "Control valve"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTBuiRet(
    redeclare final package Medium = MediumSer,
    final m_flow_nominal=mBui_flow_nominal)
    "Building return temperature sensor"
  Fluid.FixedResistances.CheckValve cheVal(
    redeclare final package Medium=MediumSer,
    final m_flow_nominal=mBui_flow_nominal,
    final dpValve_nominal=dpCheVal_nominal)
    "Check valve (backflow preventer)"
  Modelica.Blocks.Math.Add dTDis(
    final k1=-1)
    "Temperature difference on the district side"
  Modelica.Blocks.Math.Product pro
    "Product"
  Modelica.Blocks.Math.Gain cp(
    final k=cp_default)
    "Specific heat multiplier to calculate heat flow rate"
  Modelica.Blocks.Continuous.Integrator int(
    final k=1)
    "Integration"
  Controls.OBC.CDL.Reals.PID con(
    final controllerType=controllerType,
    final k=k,
    final Ti=Ti,
    final Td=Td,
    final yMax=yMax,
    final yMin=yMin)
    "District return temperature controller"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTBuiSup(
    redeclare final package Medium = MediumSer,
    final m_flow_nominal=mBui_flow_nominal)
    "Building supply temperature sensor"
protected
  final parameter MediumSer.ThermodynamicState sta_default=MediumSer.setState_pTX(
    T=MediumSer.T_default,
    p=MediumSer.p_default,
    X=MediumSer.X_default)
    "Medium state at default properties";
  final parameter Modelica.Units.SI.SpecificHeatCapacity cp_default=MediumSer.specificHeatCapacityCp(
    sta_default)
    "Specific heat capacity of the fluid";
equation
  connect(senTDisSup.port_b, senMasFlo.port_a)
  connect(senMasFlo.port_b, jun.port_1)
  connect(senTBuiRet.port_b, spl.port_1)
  connect(spl.port_2, conVal.port_a)
  connect(senMasFlo.m_flow, pro.u2)
  connect(pro.y, cp.u)
  connect(cp.y, Q_flow)
  connect(cp.y, int.u)
  connect(int.y, Q)
  connect(dTDis.y, pro.u1)
  connect(conVal.port_b, senTDisRet.port_a)
  connect(senTDisRet.T,dTDis. u1)
  connect(senTDisSup.T,dTDis. u2)
  connect(TDisRetSet, con.u_s)
  connect(senTBuiRet.T, con.u_m)
  connect(jun.port_2, senTBuiSup.port_a)
  connect(spl.port_3, cheVal.port_a)
  connect(cheVal.port_b, jun.port_3)
  connect(con.y, conVal.y)
end PartialDirect;


within Buildings.DHC.ETS.BaseClasses;
partial model PartialETS
  "Partial class for modeling an energy transfer station"
  import TypDisSys=Buildings.DHC.Types.DistrictSystemType
    "District system type enumeration";
  replaceable package MediumSer=Buildings.Media.Water
    constrainedby Modelica.Media.Interfaces.PartialMedium
    "Service side medium";
  replaceable package MediumSerHea_a=Buildings.Media.Water
    constrainedby Modelica.Media.Interfaces.PartialMedium
    "Service side medium at heating inlet"
  replaceable package MediumBui=Buildings.Media.Water
    constrainedby Modelica.Media.Interfaces.PartialMedium
    "Building side medium";
  parameter TypDisSys typ = Buildings.DHC.Types.DistrictSystemType.CombinedGeneration2to4
    "Type of district system"
  parameter Integer nPorts_aHeaWat=0
    "Number of heating water return ports"
  parameter Integer nPorts_bHeaWat=0
    "Number of heating water supply ports"
  parameter Integer nPorts_aChiWat=0
    "Number of chilled water return ports"
  parameter Integer nPorts_bChiWat=0
    "Number of chilled water supply ports"
  parameter Boolean have_heaWat=false
    "Set to true if the ETS supplies heating water"
  parameter Boolean have_hotWat=false
    "Set to true if the ETS supplies hot water"
  parameter Boolean have_chiWat=false
    "Set to true if the ETS supplies chilled water"
  parameter Boolean have_fan=false
    "Set to true if fan power is computed"
  parameter Boolean have_pum=false
    "Set to true if pump power is computed"
  parameter Boolean have_eleHea=false
    "Set to true if the ETS has electric heating system"
  parameter Integer nFue=0
    "Number of fuel types (0 means no combustion system)"
  parameter Boolean have_eleCoo=false
    "Set to true if the ETS has electric cooling system"
  parameter Boolean have_weaBus=false
    "Set to true to use a weather bus"
  parameter Boolean allowFlowReversalSer=false
    "Set to true to allow flow reversal on service side"
  parameter Boolean allowFlowReversalBui=false
    "Set to true to allow flow reversal on building side"
  parameter Modelica.Units.SI.HeatFlowRate QHeaWat_flow_nominal(min=0) = 0
    "Nominal capacity of heating system (>=0)"
  parameter Modelica.Units.SI.HeatFlowRate QHotWat_flow_nominal(min=0) = 0
    "Nominal capacity of hot water production system (>=0)"
  parameter Modelica.Units.SI.HeatFlowRate QChiWat_flow_nominal(max=0) = 0
    "Nominal capacity of cooling system (<=0)"
  parameter Buildings.Fluid.Data.Fuels.Generic fue[nFue]
    "Fuel type"
  // IO CONNECTORS
  Modelica.Fluid.Interfaces.FluidPorts_a ports_aHeaWat[nPorts_aHeaWat](
    redeclare each package Medium=MediumBui,
    each m_flow(
      min=
        if allowFlowReversalBui then
          -Modelica.Constants.inf
        else
          0),
    each h_outflow(
      start=MediumBui.h_default,
      nominal=MediumBui.h_default)) if have_heaWat
    "Fluid connectors for heating water return (from building)"
  Modelica.Fluid.Interfaces.FluidPorts_b ports_bHeaWat[nPorts_bHeaWat](
    redeclare each package Medium=MediumBui,
    each m_flow(
      max=
        if allowFlowReversalBui then
          +Modelica.Constants.inf
        else
          0),
    each h_outflow(
      start=MediumBui.h_default,
      nominal=MediumBui.h_default)) if have_heaWat
    "Fluid connectors for heating water supply (to building)"
  Modelica.Fluid.Interfaces.FluidPorts_a ports_aChiWat[nPorts_aChiWat](
    redeclare each package Medium=MediumBui,
    each m_flow(
      min=
        if allowFlowReversalBui then
          -Modelica.Constants.inf
        else
          0),
    each h_outflow(
      start=MediumBui.h_default,
      nominal=MediumBui.h_default)) if have_chiWat
    "Fluid connectors for chilled water return (from building)"
  Modelica.Fluid.Interfaces.FluidPorts_b ports_bChiWat[nPorts_bChiWat](
    redeclare each package Medium=MediumBui,
    each m_flow(
      max=
        if allowFlowReversalBui then
          +Modelica.Constants.inf
        else
          0),
    each h_outflow(
      start=MediumBui.h_default,
      nominal=MediumBui.h_default)) if have_chiWat
    "Fluid connectors for chilled water supply (to building)"
  Modelica.Fluid.Interfaces.FluidPort_a port_aSerAmb(
    redeclare package Medium = MediumSer,
    m_flow(min=if allowFlowReversalSer then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumSer.h_default, nominal=MediumSer.h_default))
 if typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration5
    "Fluid connector for ambient water service supply line"
  Modelica.Fluid.Interfaces.FluidPort_b port_bSerAmb(
    redeclare package Medium = MediumSer,
    m_flow(max=if allowFlowReversalSer then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumSer.h_default, nominal=MediumSer.h_default))
 if typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration5
    "Fluid connector for ambient water service return line"
  Modelica.Fluid.Interfaces.FluidPort_a port_aSerHea(
    redeclare package Medium = MediumSerHea_a,
    m_flow(min=if allowFlowReversalSer then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumSerHea_a.h_default, nominal=MediumSerHea_a.h_default))
 if typ <> Buildings.DHC.Types.DistrictSystemType.Cooling and
    typ <> Buildings.DHC.Types.DistrictSystemType.CombinedGeneration5
    "Fluid connector for heating service supply line"
  Modelica.Fluid.Interfaces.FluidPort_b port_bSerHea(
    redeclare package Medium = MediumSer,
    m_flow(max=if allowFlowReversalSer then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumSer.h_default, nominal=MediumSer.h_default))
 if typ <> Buildings.DHC.Types.DistrictSystemType.Cooling and
    typ <> Buildings.DHC.Types.DistrictSystemType.CombinedGeneration5
    "Fluid connector for heating service return line"
  Modelica.Fluid.Interfaces.FluidPort_a port_aSerCoo(
    redeclare package Medium = MediumSer,
    m_flow(min=if allowFlowReversalSer then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumSer.h_default, nominal=MediumSer.h_default))
 if typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration1 or
    typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration2to4 or
    typ == Buildings.DHC.Types.DistrictSystemType.Cooling
    "Fluid connector for cooling service supply line"
  Modelica.Fluid.Interfaces.FluidPort_b port_bSerCoo(
    redeclare package Medium = MediumSer,
    m_flow(max=if allowFlowReversalSer then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumSer.h_default, nominal=MediumSer.h_default))
 if typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration1 or
    typ == Buildings.DHC.Types.DistrictSystemType.CombinedGeneration2to4 or
    typ == Buildings.DHC.Types.DistrictSystemType.Cooling
    "Fluid connector for cooling service return line"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput PHea(
    final unit="W") if have_eleHea
    "Power drawn by heating system"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput PCoo(
    final unit="W") if have_eleCoo
    "Power drawn by cooling system"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput PFan(
    final unit="W") if have_fan
    "Power drawn by fan motors"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput PPum(
    final unit="W") if have_pum
    "Power drawn by pump motors"
  Buildings.Controls.OBC.CDL.Interfaces.RealOutput QFue_flow[nFue](
    each final unit="W") if nFue>0
    "Fuel energy input rate"
  BoundaryConditions.WeatherData.Bus weaBus if have_weaBus
    "Weather data bus"
initial equation
  assert(
    nPorts_aHeaWat == nPorts_bHeaWat,
    "In "+getInstanceName()+": The numbers of heating water supply ports ("+String(
      nPorts_bHeaWat)+") and return ports ("+String(
      nPorts_aHeaWat)+") must be equal.");
  assert(
    nPorts_aChiWat == nPorts_bChiWat,
    "In "+getInstanceName()+": The numbers of chilled water supply ports ("+String(
      nPorts_bChiWat)+") and return ports ("+String(
      nPorts_aChiWat)+") must be equal.");
  if have_chiWat then
    assert(
      QChiWat_flow_nominal <-Modelica.Constants.eps,
      "In "+getInstanceName()+": Design heat flow rate for chilled water production must be strictly
      negative. Obtained QChiWat_flow_nominal = "+String(
        QChiWat_flow_nominal));
  end if;
  if have_heaWat then
    assert(
      QHeaWat_flow_nominal > Modelica.Constants.eps,
      "In "+getInstanceName()+": Design heat flow rate for heating water production must be strictly
      positive. Obtained QHeaWat_flow_nominal = "+String(
        QHeaWat_flow_nominal));
  end if;
  if have_hotWat then
    assert(
      QHotWat_flow_nominal > Modelica.Constants.eps,
      "In "+getInstanceName()+": Design heat flow rate for heating water production must be strictly
      positive. Obtained QHotWat_flow_nominal = "+String(
        QHotWat_flow_nominal));
  end if;
end PartialETS;