within Buildings.Fluid.Sources.Examples;
model PropertySource_T "Model that illustrates the use of PropertySource_T"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Air(extraPropertiesNames={"CO2"});

  Buildings.Fluid.Sources.PropertySource_T proSouXi(
    redeclare package Medium = Medium,
    use_Xi_in=true)
    "Property source that prescribes Xi"
  Buildings.Fluid.Sources.MassFlowSource_T bouXi(
    redeclare package Medium = Medium,
    nPorts=1,
    use_m_flow_in=true)
    "Boundary for Xi test"

  Buildings.Fluid.Sources.Boundary_ph sin(
    redeclare package Medium = Medium,
    nPorts=3)
   "Sink model"
  Buildings.Fluid.Sources.PropertySource_T proSouT(
    redeclare package Medium = Medium,
    use_T_in=true)
    "Property source that prescribes the temperature"
  Buildings.Fluid.Sources.PropertySource_T proSouC(
    redeclare package Medium = Medium,
    use_C_in=true)
    "Property source that prescribes C"
  Buildings.Fluid.Sources.MassFlowSource_T bouT(
    redeclare package Medium = Medium,
    nPorts=1,
    use_m_flow_in=true)
    "Boundary for temperature test"
  Buildings.Fluid.Sources.MassFlowSource_T bouC(
    redeclare package Medium = Medium,
    nPorts=1,
    use_m_flow_in=true)
    "Boundary for C test"
  Modelica.Blocks.Sources.Constant T(k=Medium.T_default + 1)
    "Fixed temperature value"
  Modelica.Blocks.Sources.Constant Xi(k=0.0123)
    "Fixed Xi value"
  Modelica.Blocks.Sources.Constant C(k=0.1)
    "Fixed C value"
  Modelica.Blocks.Sources.Ramp ramp(
    height=-2,
    duration=1,
    offset=1)
    "Ramp for mass flow rate"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemT(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0)
    "Temperature sensor for when using temperature input"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemXi(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0)
    "Temperature sensor for when using Xi input"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemC(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    tau=0)
    "Temperature sensor for when using C input"
equation
  connect(bouXi.ports[1], proSouXi.port_a)
  connect(bouC.ports[1], proSouC.port_a)
  connect(bouT.ports[1], proSouT.port_a)
  connect(Xi.y, proSouXi.Xi_in[1])
  connect(C.y, proSouC.C_in[1])
  connect(bouT.m_flow_in, ramp.y)
  connect(ramp.y, bouXi.m_flow_in)
  connect(ramp.y, bouC.m_flow_in)
  connect(T.y, proSouT.T_in)
  connect(proSouC.port_b, senTemC.port_a)
  connect(senTemC.port_b, sin.ports[1])
  connect(senTemXi.port_b, sin.ports[2])
  connect(senTemT.port_b, sin.ports[3])
  connect(proSouT.port_b, senTemT.port_a)
  connect(proSouXi.port_b, senTemXi.port_a)
end PropertySource_T;


within Buildings.Fluid.Sources.Examples;
model TraceSubstancesFlowSource
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Air(extraPropertiesNames={"CO2"});

  MixingVolumes.MixingVolume vol(
    redeclare package Medium = Medium,
    V=100,
    m_flow_nominal=1,
    nPorts=3,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Mixing volume"
  Sources.TraceSubstancesFlowSource sou(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    nPorts=1)
  Modelica.Blocks.Sources.Step step(
    startTime=0.5,
    height=-2,
    offset=2)
  FixedResistances.PressureDrop res(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  MixingVolumes.MixingVolume vol1(
    redeclare package Medium = Medium,
    V=100,
    m_flow_nominal=1,
    nPorts=3,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Mixing volume"
  Sources.TraceSubstancesFlowSource sou1(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    nPorts=1)
  Buildings.Utilities.Diagnostics.AssertEquality assEqu(threShold=1E-4)
    "Assert that both volumes have the same concentration"
  MixingVolumes.MixingVolume vol2(
    redeclare package Medium = Medium,
    p_start=Medium.p_default,
    V=100,
    m_flow_nominal=1,
    nPorts=3,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Mixing volume"
  MixingVolumes.MixingVolume vol3(
    redeclare package Medium = Medium,
    p_start=Medium.p_default,
    V=100,
    m_flow_nominal=1,
    nPorts=3,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Mixing volume"
  Buildings.Utilities.Diagnostics.AssertEquality assEqu1(threShold=1E-4)
    "Assert that both volumes have the same concentration"
  MixingVolumes.MixingVolume vol4(
    redeclare package Medium = Medium,
    nPorts=4,
    p_start=Medium.p_default,
    V=100,
    m_flow_nominal=1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Mixing volume"
  Sources.TraceSubstancesFlowSource sou2(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    nPorts=1)
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = Medium,
    p=101325,
    nPorts=1,
    T=293.15)
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=2,
    p=101320,
    T=293.15) "Sink boundary conditions"
  FixedResistances.PressureDrop res1(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  FixedResistances.PressureDrop res2(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  FixedResistances.PressureDrop res3(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"

  Sensors.TraceSubstances C(
    redeclare package Medium = Medium,
    warnAboutOnePortConnection = false)
    "Trace substance sensor"
  Sensors.TraceSubstances C1(
    redeclare package Medium = Medium,
    warnAboutOnePortConnection = false)
    "Trace substance sensor"
  Sensors.TraceSubstances C2(
    redeclare package Medium = Medium,
    warnAboutOnePortConnection = false)
    "Trace substance sensor"
  Sensors.TraceSubstances C3(
    redeclare package Medium = Medium,
    warnAboutOnePortConnection = false)
    "Trace substance sensor"
  FixedResistances.PressureDrop res4(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  FixedResistances.PressureDrop res6(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  FixedResistances.PressureDrop res5(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  FixedResistances.PressureDrop res7(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dp_nominal=1)
    "Resistance, used to check if species are transported between ports"
  Buildings.Fluid.Sources.Boundary_pT sin1(
    redeclare package Medium = Medium,
    nPorts=2,
    p=101320,
    T=293.15) "Sink boundary conditions"
equation
  connect(res3.port_b, vol4.ports[2])
  connect(res1.port_b, sin.ports[1])
  connect(res2.port_b, sin.ports[2])
  connect(bou.ports[1], res3.port_a)
  connect(sou1.ports[1], res.port_a)
  connect(sou2.ports[1], vol4.ports[1])
  connect(step.y, sou.m_flow_in)
  connect(step.y, sou1.m_flow_in)
  connect(step.y, sou2.m_flow_in)
  connect(assEqu.u1, C.C)
  connect(C1.C, assEqu.u2)
  connect(assEqu1.u1, C2.C)
  connect(C3.C, assEqu1.u2)
  connect(sou.ports[1], vol.ports[1])
  connect(vol.ports[2], C.port)
  connect(res.port_b, vol1.ports[1])
  connect(vol1.ports[2], C1.port)
  connect(vol2.ports[1], res1.port_a)
  connect(vol3.ports[1], res2.port_a)
  connect(C2.port, vol2.ports[2])
  connect(C3.port, vol3.ports[2])
  connect(vol4.ports[3], res4.port_a)
  connect(vol4.ports[4], res6.port_a)
  connect(res6.port_b, vol3.ports[3])
  connect(res4.port_b, vol2.ports[3])
  connect(vol.ports[3], res5.port_a)
  connect(res5.port_b, sin1.ports[1])
  connect(vol1.ports[3], res7.port_a)
  connect(res7.port_b, sin1.ports[2])
end TraceSubstancesFlowSource;


within Buildings.Fluid.Sources.Validation;
model Boundary "Validation model for boundary with different media"
  extends Modelica.Icons.Example;

 Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouWat(
   redeclare package Medium = Buildings.Media.Water)
   "Boundary with water"
 Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouProGly(
    redeclare package Medium = Media.Antifreeze.PropyleneGlycolWater(property_T=293.15, X_a=0.40))
    "Boundary with propylene glycol"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouMoiAir(
    redeclare package Medium = Buildings.Media.Air)
    "Boundary with moist air"
  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouMoiAirCO2(
    redeclare package Medium = Buildings.Media.Air(extraPropertiesNames={"CO2"}))
    "Boundary with moist air"
 Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouProFluGas(
   redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.FlueGasSixComponents)
    "Boundary with flue gas"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouNatGas(
    redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.SimpleNaturalGas)
   "Boundary with natural gas"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem bouNatGasFix(
    redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.SimpleNaturalGasFixedComposition)
    "Boundary with natural gas"
end Boundary;


within Buildings.Fluid.Sources.Validation;
model BoundaryWithXi_in
  "Validation model for boundary with different media and independent mass fraction input"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithXi_in bouMoiAir(
    redeclare package Medium = Buildings.Media.Air)
    "Boundary with moist air"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithXi_in bouMoiAirCO2(
    redeclare package Medium = Buildings.Media.Air(extraPropertiesNames={"CO2"}))
    "Boundary with moist air"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithXi_in bouProFluGas(
   redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.FlueGasSixComponents)
    "Boundary with flue gas"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithXi_in bouNatGas(
    redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.SimpleNaturalGas)
    "Boundary with natural gas"

  Modelica.Blocks.Sources.Constant Xi_1[1](k={0.015})
    "Prescribed mass fraction"
  Modelica.Blocks.Sources.Constant Xi_6[6](k={0.5,0.2,0.0,0.3,0.0,0.0})
    "Prescribed mass fraction"

equation
  connect(Xi_6.y, bouProFluGas.Xi_in)
  connect(Xi_1.y, bouMoiAir.Xi_in)
  connect(Xi_6.y, bouNatGas.Xi_in)
  connect(Xi_1.y, bouMoiAirCO2.Xi_in)
end BoundaryWithXi_in;


within Buildings.Fluid.Sources.Validation;
model BoundaryWithX_in
  "Validation model for boundary with different media and mass fraction input"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithX_in bouMoiAir(
    redeclare package Medium = Buildings.Media.Air)
    "Boundary with moist air"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithX_in bouMoiAirCO2(
    redeclare package Medium = Buildings.Media.Air(extraPropertiesNames={"CO2"}))
    "Boundary with moist air"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithX_in bouProFluGas(
   redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.FlueGasSixComponents)
    "Boundary with flue gas"

  Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystemWithX_in bouNatGas(
    redeclare package Medium =
        Modelica.Media.IdealGases.MixtureGases.SimpleNaturalGas)
    "Boundary with natural gas"

  Modelica.Blocks.Sources.Constant X_2[2](k={0.015, 0.985})
    "Prescribed mass fraction"
  Modelica.Blocks.Sources.Constant X_6[6](k={0.5,0.2,0.0,0.3,0.0,0.0})
    "Prescribed mass fraction"

equation
  connect(X_6.y, bouProFluGas.X_in)
  connect(X_2.y, bouMoiAir.X_in)
  connect(X_6.y, bouNatGas.X_in)
  connect(X_2.y, bouMoiAirCO2.X_in)
end BoundaryWithX_in;


within Buildings.Fluid.Sources.Validation;
model Outside_CpData_Directions
  "Test model for wind pressure using a wind pressure profile that is 1 for normal angle of attack, and goes to zero otherwise"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Air "Medium model for air";

  parameter Modelica.Units.SI.Angle incAngSurNor[:]=
    {0,1,5,90,180,315,355,359}*2*Modelica.Constants.pi/360
    "Wind incidence angles";
  parameter Real Cp[:]={1,0.01,0,0,0,0,0,0.01}
    "Cp values at the relative surface wind incidence angles";
  Buildings.Fluid.Sources.Outside_CpData nor(
    redeclare package Medium = Medium,
    incAngSurNor=incAngSurNor,
    Cp=Cp,
    azi=Buildings.Types.Azimuth.N)
    "Model to compute wind pressure on North-facing surface"
  Buildings.Fluid.Sources.Outside_CpData eas(
    redeclare package Medium = Medium,
    incAngSurNor=incAngSurNor,
    Cp=Cp,
    azi=Buildings.Types.Azimuth.E)
    "Model to compute wind pressure on East-facing surface"
  Buildings.Fluid.Sources.Outside_CpData sou(
    redeclare package Medium = Medium,
    incAngSurNor=incAngSurNor,
    Cp=Cp,
    azi=Buildings.Types.Azimuth.S)
    "Model to compute wind pressure on South-facing surface"
  Buildings.Fluid.Sources.Outside_CpData wes(
    redeclare package Medium = Medium,
    incAngSurNor=incAngSurNor,
    Cp=Cp,
    azi=Buildings.Types.Azimuth.W)
    "Model to compute wind pressure on West-facing surface"

  Buildings.BoundaryConditions.WeatherData.ReaderTMY3 weaDat(
    filNam=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/weatherdata/USA_CA_San.Francisco.Intl.AP.724940_TMY3.mos"),
    winSpeSou=Buildings.BoundaryConditions.Types.DataSource.Parameter,
    winSpe=1,
    winDirSou=Buildings.BoundaryConditions.Types.DataSource.Input)
    "Weather data"

  Modelica.Blocks.Sources.Ramp winDir(
    height=2*Modelica.Constants.pi,
    duration=24*3600)
    "Wind direction"

equation
  connect(weaDat.winDir_in, winDir.y)
  connect(weaDat.weaBus, nor.weaBus)
  connect(weaDat.weaBus, eas.weaBus)
  connect(weaDat.weaBus, sou.weaBus)
  connect(weaDat.weaBus, wes.weaBus)
end Outside_CpData_Directions;


Collection of validation models

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


within Buildings.Fluid.Sources.Validation.BaseClasses;
model BoundarySystem "System model for testing of boundary condition"
  extends Modelica.Blocks.Icons.Block;
  replaceable package Medium =
    Modelica.Media.Interfaces.PartialMedium "Medium in the component";

 Boundary_pT sou(redeclare final package Medium = Medium, nPorts=1) "Boundary"
  MassFlowSource_T sin(
    redeclare final package Medium = Medium,
    m_flow=-1,
    nPorts=1) "Mass flow source"
  Modelica.Units.SI.Temperature T=senTem.T
    "Temperature coming out of the source";
  Modelica.Units.SI.MassFraction Xi[Medium.nXi]=sou.ports[1].Xi_outflow
    "Mass fraction coming out of the source";
  Modelica.Units.SI.Pressure p=sou.ports[1].p "Pressure in the source";
  Modelica.Fluid.Sensors.TemperatureTwoPort senTem(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    m_flow_nominal=1,
    m_flow_small=1E-4) "Temperature sensor"
  inner Modelica.Fluid.System system
equation
  connect(sou.ports[1], senTem.port_a)
  connect(sin.ports[1], senTem.port_b)
end BoundarySystem;


System model for testing boundary condition with mass fraction input. System model used to test the boundary conditions for different media
with independent prescribed mass fraction Xi.

within Buildings.Fluid.Sources.Validation.BaseClasses;
model BoundarySystemWithXi_in
  "System model for testing boundary condition with mass fraction input"
  extends Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem(sou(use_Xi_in=true));
  Modelica.Blocks.Interfaces.RealInput Xi_in[Medium.nXi]
    "Prescribed boundary composition"
equation
  for i in 1:Medium.nXi loop
    assert(abs(Xi[i] - Xi_in[i]) < 1E-6, "Error in implementation of mass fraction");
  end for;
  connect(sou.Xi_in, Xi_in)
end BoundarySystemWithXi_in;


System model for testing boundary condition with mass fraction input. System model used to test the boundary conditions for different media
with prescribed mass fraction X.

within Buildings.Fluid.Sources.Validation.BaseClasses;
model BoundarySystemWithX_in
  "System model for testing boundary condition with mass fraction input"
  extends Buildings.Fluid.Sources.Validation.BaseClasses.BoundarySystem(sou(use_X_in=true));
  Modelica.Blocks.Interfaces.RealInput X_in[Medium.nX]
    "Prescribed boundary composition"
equation
  for i in 1:Medium.nXi loop
    assert(abs(Xi[i] - X_in[i]) < 1E-6, "Error in implementation of mass fraction");
  end for;
  connect(sou.X_in, X_in)
end BoundarySystemWithX_in;


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


Expansion vessel with fixed pressure

within Buildings.Fluid.Storage;
model ExpansionVessel "Expansion vessel with fixed pressure"
 extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
   final energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
   final massDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
   final mSenFac=1);
  parameter Modelica.Units.SI.Volume V_start(start=1)
    "Volume of liquid stored in the vessel at the start of the simulation";

  Modelica.Fluid.Interfaces.FluidPort_a port_a(
    redeclare package Medium = Medium) "Fluid port"
  Modelica.Units.SI.Mass m "Mass of liquid in the vessel";

protected
  final parameter Medium.ThermodynamicState state_start = Medium.setState_pTX(
      T=T_start,
      p=p_start,
      X=X_start[1:Medium.nXi]) "Medium state at start values";
  final parameter Modelica.Units.SI.Density rho_start=Medium.density(state=
      state_start) "Density, used to compute start and guess values";

  Modelica.Units.SI.Energy H "Internal energy of fluid";
  Modelica.Units.SI.Mass[Medium.nXi] mXi
    "Masses of independent components in the fluid";
  Modelica.Units.SI.Mass[Medium.nC] mC
    "Masses of trace substances in the fluid";
  Medium.ExtraProperty C[Medium.nC](nominal=C_nominal)
    "Trace substance mixture content";

initial equation
  m = V_start * rho_start;
  H = m*Medium.specificInternalEnergy(
          Medium.setState_pTX(p=p_start, T=T_start, X= X_start[1:Medium.nXi]));
  mXi = m*X_start[1:Medium.nXi];
  mC = m*C_start[1:Medium.nC];
equation
  assert(m > 1.0E-8,
    "Expansion vessel is undersized. You need to increase the value of the parameter V_start.");
  // Conservation equations
  der(m)   = port_a.m_flow;
  der(H)   = port_a.m_flow * actualStream(port_a.h_outflow);
  der(mXi) = port_a.m_flow * actualStream(port_a.Xi_outflow);
  der(mC)  = port_a.m_flow * actualStream(port_a.C_outflow);
  // Properties of outgoing flow.
  // The port pressure is set to a constant value.
  port_a.p          = p_start;
  m*port_a.h_outflow  = H;
  m*port_a.Xi_outflow = mXi;
  m*port_a.C_outflow  = mC;
end ExpansionVessel;


within Buildings.Fluid;
package Storage "Package with thermal energy storage models"
  extends Modelica.Icons.VariantsPackage;
end Storage;


within Buildings.Fluid.Storage;
model Stratified "Model of a stratified tank for thermal energy storage"
  extends Buildings.Fluid.Storage.BaseClasses.PartialStratified(vol(each nPorts=3));

  Modelica.Fluid.Interfaces.FluidPorts_a fluPorVol[nSeg](
    redeclare each final package Medium = Medium)
    "Fluid port that connects to the control volumes of the tank"
equation
  connect(port_a, vol[1].ports[1])
  connect(vol[nSeg].ports[2], port_b)
  for i in 1:(nSeg-1) loop
    connect(vol[i].ports[2], vol[i + 1].ports[1])
  end for;
  for i in 1:nSeg loop
    connect(fluPorVol[i], vol[i].ports[3])
  end for;
end Stratified;


within Buildings.Fluid.Storage;
model StratifiedEnhanced "Stratified tank model with enhanced discretization"
  extends BaseClasses.PartialStratified(
    nSeg=4,
    vol(each prescribedHeatFlowRate=true,
        each nPorts=3));

protected
  Buildings.Fluid.Sensors.EnthalpyFlowRate H_a_flow(
    redeclare package Medium = Medium,
    final m_flow_nominal=m_flow_nominal,
    final tau=0,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_small=m_flow_small) "Enthalpy flow rate at port a"
  Buildings.Fluid.Sensors.EnthalpyFlowRate[nSeg - 1] H_vol_flow(
    redeclare package Medium = Medium,
    each final m_flow_nominal=m_flow_nominal,
    each final tau=0,
    each final allowFlowReversal=allowFlowReversal,
    each final m_flow_small=m_flow_small)
    "Enthalpy flow rate between the volumes"
  Buildings.Fluid.Sensors.EnthalpyFlowRate H_b_flow(
    redeclare package Medium = Medium,
    final m_flow_nominal=m_flow_nominal,
    final tau=0,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_small=m_flow_small) "Enthalpy flow rate at port b"

  BaseClasses.ThirdOrderStratifier str(
    redeclare package Medium = Medium,
    nSeg=nSeg,
    m_flow_small=m_flow_small) "Model to reduce numerical dissipation"

  Modelica.Blocks.Sources.RealExpression mTan_flow(y=port_a.m_flow)
    "Mass flow rate at port a"
equation
  connect(H_a_flow.port_b, vol[1].ports[1])
  connect(vol[nSeg].ports[2], H_b_flow.port_a)
  connect(H_b_flow.port_b, port_b)
  for i in 1:(nSeg-1) loop
    connect(vol[i].ports[2], H_vol_flow[i].port_a)
    connect(H_vol_flow[i].port_b, vol[i + 1].ports[1])
  end for;
  connect(port_a, H_a_flow.port_a)

  connect(vol[1:nSeg].ports[3], str.fluidPort[2:nSeg+1])
  connect(H_a_flow.H_flow, str.H_flow[1])
  connect(H_vol_flow[1:nSeg-1].H_flow, str.H_flow[2:nSeg])
  connect(H_b_flow.H_flow, str.H_flow[nSeg + 1])
  connect(str.heatPort, vol.heatPort)
  connect(port_a, str.fluidPort[1])
  connect(port_b, str.fluidPort[nSeg + 2])
  connect(mTan_flow.y, str.m_flow)
end StratifiedEnhanced;


within Buildings.Fluid.Storage;
model StratifiedEnhancedInternalHex
  "A model of a water storage tank with a secondary loop and intenral heat exchanger"
  extends StratifiedEnhanced;

  replaceable package MediumHex =
      Modelica.Media.Interfaces.PartialMedium "Medium in the heat exchanger"

  parameter Modelica.Units.SI.Height hHex_a
    "Height of portHex_a of the heat exchanger, measured from tank bottom"

  parameter Modelica.Units.SI.Height hHex_b
    "Height of portHex_b of the heat exchanger, measured from tank bottom"

  parameter Integer hexSegMult(min=1) = 2
    "Number of heat exchanger segments in each tank segment"

  parameter Modelica.Units.SI.Diameter dExtHex=0.025
    "Exterior diameter of the heat exchanger pipe"

  parameter Modelica.Units.SI.HeatFlowRate Q_flow_nominal
    "Heat transfer at nominal conditions"
  parameter Modelica.Units.SI.Temperature TTan_nominal
    "Temperature of fluid inside the tank at nominal heat transfer conditions"
  parameter Modelica.Units.SI.Temperature THex_nominal
    "Temperature of fluid inside the heat exchanger at nominal heat transfer conditions"
  parameter Real r_nominal(min=0, max=1)=0.5
    "Ratio between coil inside and outside convective heat transfer at nominal heat transfer conditions"

  parameter Modelica.Units.SI.MassFlowRate mHex_flow_nominal
    "Nominal mass flow rate through the heat exchanger"

  parameter Modelica.Units.SI.PressureDifference dpHex_nominal(displayUnit="Pa")=
       2500 "Pressure drop across the heat exchanger at nominal conditions"

  parameter Boolean computeFlowResistance=true
    "=true, compute flow resistance. Set to false to assume no friction"

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

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

  parameter Real deltaM=0.1
    "Fraction of nominal flow rate where flow transitions to laminar"

  parameter Modelica.Fluid.Types.Dynamics energyDynamicsHex=
    Modelica.Fluid.Types.Dynamics.DynamicFreeInitial
    "Formulation of energy balance for heat exchanger internal fluid mass"

  parameter Modelica.Fluid.Types.Dynamics energyDynamicsHexSolid=energyDynamicsHex
    "Formulation of energy balance for heat exchanger solid mass"

  parameter Modelica.Units.SI.Length lHex=rTan*abs(segHex_a - segHex_b)*
      Modelica.Constants.pi "Approximate length of the heat exchanger"

  parameter Modelica.Units.SI.Area ACroHex=(dExtHex^2 - (0.8*dExtHex)^2)*
      Modelica.Constants.pi/4 "Cross sectional area of the heat exchanger"

  parameter Modelica.Units.SI.SpecificHeatCapacity cHex=490
    "Specific heat capacity of the heat exchanger material"

  parameter Modelica.Units.SI.Density dHex=8000
    "Density of the heat exchanger material"

  parameter Modelica.Units.SI.HeatCapacity CHex=ACroHex*lHex*dHex*cHex
    "Capacitance of the heat exchanger without the fluid"
  parameter Boolean allowFlowReversalHex = true
    "= true to allow flow reversal in heat exchanger, false restricts to design direction (portHex_a -> portHex_b)"
  Modelica.Fluid.Interfaces.FluidPort_a portHex_a(
    redeclare final package Medium =MediumHex,
     m_flow(min=if allowFlowReversalHex then -Modelica.Constants.inf else 0))
    "Heat exchanger inlet"
  Modelica.Fluid.Interfaces.FluidPort_b portHex_b(
     redeclare final package Medium = MediumHex,
     m_flow(max=if allowFlowReversalHex then Modelica.Constants.inf else 0))
    "Heat exchanger outlet"

  BaseClasses.IndirectTankHeatExchanger indTanHex(
    redeclare final package MediumTan = Medium,
    redeclare final package MediumHex = MediumHex,
    final nSeg=nSegHex,
    final CHex=CHex,
    final volHexFlu=volHexFlu,
    final Q_flow_nominal=Q_flow_nominal,
    final TTan_nominal=TTan_nominal,
    final THex_nominal=THex_nominal,
    final r_nominal=r_nominal,
    final dExtHex=dExtHex,
    final dp_nominal=dpHex_nominal,
    final m_flow_nominal=mHex_flow_nominal,
    final energyDynamics=energyDynamicsHex,
    final energyDynamicsSolid=energyDynamicsHexSolid,
    final computeFlowResistance=computeFlowResistance,
    from_dp=from_dp,
    final linearizeFlowResistance=linearizeFlowResistance,
    final deltaM=deltaM,
    final allowFlowReversal=allowFlowReversalHex,
    final m_flow_small=1e-4*abs(mHex_flow_nominal))
    "Heat exchanger inside the tank"

  Modelica.Units.SI.HeatFlowRate QHex_flow=-sum(indTanHex.port.Q_flow)
    "Heat transferred from the heat exchanger to the tank";
protected
  final parameter Integer segHex_a = nSeg-integer(hHex_a/segHeight)
    "Tank segment in which port a1 of the heat exchanger is located in"

  final parameter Integer segHex_b = nSeg-integer(hHex_b/segHeight)
    "Tank segment in which port b1 of the heat exchanger is located in"

  final parameter Modelica.Units.SI.Height segHeight=hTan/nSeg
    "Height of each tank segment (relative to bottom of same segment)";

  final parameter Modelica.Units.SI.Length dHHex=abs(hHex_a - hHex_b)
    "Vertical distance between the heat exchanger inlet and outlet";

  final parameter Modelica.Units.SI.Volume volHexFlu=Modelica.Constants.pi*(0.8
      *dExtHex)^2/4*lHex "Volume of the heat exchanger";

  final parameter Integer nSegHexTan=
    if segHex_a > segHex_b then segHex_a-segHex_b + 1 else segHex_b-segHex_a + 1
    "Number of tank segments the heat exchanger resides in";

  final parameter Integer nSegHex = nSegHexTan*hexSegMult
    "Number of heat exchanger segments";

initial equation
  assert(hHex_a >= 0 and hHex_a <= hTan,
    "The parameter hHex_a is outside its valid range.");

  assert(hHex_b >= 0 and hHex_b <= hTan,
    "The parameter hHex_b is outside its valid range.");

  assert(dHHex > 0,
    "The parameters hHex_a and hHex_b must not be equal.");
equation
  for j in 0:nSegHexTan-1 loop
    for i in 1:hexSegMult loop
      connect(indTanHex.port[j*hexSegMult+i], heaPorVol[segHex_a + (if hHex_a > hHex_b then j else -j)])
    end for;
  end for;
  connect(portHex_a, indTanHex.port_a)
  connect(indTanHex.port_b, portHex_b)
end StratifiedEnhancedInternalHex;


User's Guide

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


within Buildings.Fluid.Storage.BaseClasses;
model Buoyancy
  "Model to add buoyancy if there is a temperature inversion in the tank"
  extends Modelica.Blocks.Icons.Block;

  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium "Medium model"
  parameter Modelica.Units.SI.Volume V "Volume";
  parameter Integer nSeg(min=2) = 2 "Number of volume segments";
  parameter Modelica.Units.SI.Time tau(min=0) "Time constant for mixing";

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a[nSeg] heatPort
    "Heat input into the volumes"

  Modelica.Units.SI.HeatFlowRate[nSeg - 1] Q_flow
    "Heat flow rate from segment i+1 to i";
protected
   parameter Medium.ThermodynamicState sta_default = Medium.setState_pTX(T=Medium.T_default,
         p=Medium.p_default, X=Medium.X_default[1:Medium.nXi])
    "Medium state at default properties";
  parameter Modelica.Units.SI.Density rho_default=Medium.density(sta_default)
    "Density, used to compute fluid mass";
  parameter Modelica.Units.SI.SpecificHeatCapacity cp_default=
      Medium.specificHeatCapacityCp(sta_default) "Specific heat capacity";
   parameter Real k(unit="W/K") = V*rho_default*cp_default/tau/nSeg
    "Proportionality constant, since we use dT instead of dH";
  Modelica.Units.SI.TemperatureDifference dT[nSeg - 1]
    "Temperature difference between adjoining volumes";
equation
  for i in 1:nSeg-1 loop
    dT[i] = heatPort[i+1].T-heatPort[i].T;
    Q_flow[i] = k*noEvent(smooth(1, if dT[i]>0 then dT[i]^2 else 0));
  end for;

  heatPort[1].Q_flow = -Q_flow[1];
  for i in 2:nSeg-1 loop
       heatPort[i].Q_flow = -Q_flow[i]+Q_flow[i-1];
  end for;
  heatPort[nSeg].Q_flow = Q_flow[nSeg-1];
since the equation returns the same value to the left and right of
dT[i]>0.

September 16, 2011 by Michael Wetter:

Changed the implementation from Q_flow[i] = k*max(heatPort[i+1].T-heatPort[i].T, 0); to
Q_flow[i] = k*smooth(1, if dT[i]>0 then dT[i]^2 else 0);.
The previous implementation was not differentiable. In modeling a solar system, this
change reduced the computing time by a factor of 20 during the time when the pumps
were almost switched off and colder temperature was fed from the collector to the tank.

October 28, 2008 by Michael Wetter:

First implementation.

"),
Icon(coordinateSystem(preserveAspectRatio=false, extent={{-100,-100},{100,100}}),
        graphics={
        Rectangle(
          extent={{-44,68},{36,28}},
          lineColor={0,0,255},
          pattern=LinePattern.None,
          fillColor={0,0,127},
          fillPattern=FillPattern.Solid),
        Rectangle(
          extent={{-42,-26},{38,-66}},
          lineColor={255,0,0},
          fillColor={255,0,0},
          fillPattern=FillPattern.Solid),
        Rectangle(
          extent={{26,10},{32,-22}},
          lineColor={0,0,255},
          pattern=LinePattern.None,
          fillColor={255,0,0},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{28,22},{22,10},{36,10},{36,10},{28,22}},
          lineColor={0,0,255},
          pattern=LinePattern.None,
          fillColor={255,0,0},
          fillPattern=FillPattern.Solid),
        Rectangle(
          extent={{-32,22},{-26,-10}},
          lineColor={0,0,255},
          pattern=LinePattern.None,
          fillColor={0,0,127},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{-28,-18},{-36,-6},{-22,-6},{-22,-6},{-28,-18}},
          lineColor={0,0,255},
          pattern=LinePattern.None,
          fillColor={0,0,127},
          fillPattern=FillPattern.Solid)}));
end Buoyancy;


within Buildings.Fluid.Storage.BaseClasses;
model IndirectTankHeatExchanger
  "Heat exchanger typically submerged in a fluid with a second fluid circulating through it"

  replaceable package MediumHex = Modelica.Media.Interfaces.PartialMedium
    "Heat transfer fluid flowing through the heat exchanger";
  replaceable package MediumTan = Modelica.Media.Interfaces.PartialMedium
    "Heat transfer fluid inside the tank";

  extends Buildings.Fluid.Interfaces.TwoPortFlowResistanceParameters;
  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
    final massDynamics=energyDynamics,
    redeclare final package Medium = MediumHex);
  extends Buildings.Fluid.Interfaces.PartialTwoPortInterface(
    redeclare final package Medium = MediumHex,
    final show_T=false);

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

  parameter Integer nSeg(min=2) "Number of segments in the heat exchanger";
  parameter Modelica.Units.SI.HeatCapacity CHex
    "Capacitance of the heat exchanger";
  parameter Modelica.Units.SI.Volume volHexFlu
    "Volume of heat transfer fluid in the heat exchanger";
  parameter Modelica.Units.SI.HeatFlowRate Q_flow_nominal
    "Heat transfer at nominal conditions"

  final parameter Modelica.Units.SI.ThermalConductance UA_nominal=abs(
      Q_flow_nominal/(THex_nominal - TTan_nominal))
    "Nominal UA value for the heat exchanger";
  parameter Modelica.Units.SI.Temperature TTan_nominal
    "Temperature of fluid inside the tank at UA_nominal"
  parameter Modelica.Units.SI.Temperature THex_nominal
    "Temperature of fluid inside the heat exchanger at UA_nominal"
  parameter Real r_nominal(min=0, max=1)=0.5
    "Ratio between coil inside and outside convective heat transfer"

  parameter Modelica.Units.SI.Diameter dExtHex
    "Exterior diameter of the heat exchanger pipe";

  parameter Modelica.Fluid.Types.Dynamics energyDynamics=Modelica.Fluid.Types.Dynamics.DynamicFreeInitial
    "Formulation of energy balance for heat exchanger internal fluid mass"
  parameter Modelica.Fluid.Types.Dynamics energyDynamicsSolid=energyDynamics
    "Formulation of energy balance for heat exchanger solid mass"

  parameter Boolean hA_flowDependent = true
    "Set to false to make the convective heat coefficient calculation of the fluid inside the coil independent of mass flow rate"
  parameter Boolean hA_temperatureDependent = true
    "Set to false to make the convective heat coefficient calculation of the fluid inside the coil independent of temperature"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a port[nSeg]
    "Heat port connected to water inside the tank"

  Buildings.Fluid.FixedResistances.PressureDrop res(
    redeclare final package Medium = MediumHex,
    final dp_nominal=dp_nominal,
    final m_flow_nominal=m_flow_nominal,
    final allowFlowReversal=allowFlowReversal,
    final homotopyInitialization=homotopyInitialization,
    final show_T=show_T,
    final from_dp=from_dp,
    final linearized=linearizeFlowResistance)
    "Calculates the flow resistance and pressure drop through the heat exchanger"

  Buildings.Fluid.MixingVolumes.MixingVolume vol[nSeg](
    redeclare each package Medium = MediumHex,
    each nPorts=2,
    each m_flow_nominal=m_flow_nominal,
    each V=volHexFlu/nSeg,
    each energyDynamics=energyDynamics,
    each massDynamics=energyDynamics,
    each p_start=p_start,
    each T_start=T_start,
    each X_start=X_start,
    each C_start=C_start,
    each C_nominal=C_nominal,
    each final prescribedHeatFlowRate=false,
    each final allowFlowReversal=allowFlowReversal) "Heat exchanger fluid"
  Modelica.Thermal.HeatTransfer.Components.HeatCapacitor cap[nSeg](
     each C=CHex/nSeg,
     each T(start=T_start,
            fixed=(energyDynamicsSolid == Modelica.Fluid.Types.Dynamics.FixedInitial)),
     each der_T(
            fixed=(energyDynamicsSolid == Modelica.Fluid.Types.Dynamics.SteadyStateInitial)))
          if not energyDynamicsSolid == Modelica.Fluid.Types.Dynamics.SteadyState
    "Thermal mass of the heat exchanger"
protected
  Buildings.Fluid.Sensors.MassFlowRate senMasFlo(
    redeclare package Medium = MediumHex,
    allowFlowReversal=allowFlowReversal)
    "Mass flow rate of the heat transfer fluid"
  Modelica.Thermal.HeatTransfer.Components.Convection htfToHex[nSeg]
    "Convection coefficient between the heat transfer fluid and heat exchanger"
  Modelica.Thermal.HeatTransfer.Components.Convection HexToTan[nSeg]
    "Convection coefficient between the heat exchanger and the surrounding medium"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temSenHex[nSeg]
    "Temperature of the heat transfer fluid"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temSenWat[nSeg]
    "Temperature sensor of the fluid surrounding the heat exchanger"
  Modelica.Blocks.Routing.Replicator rep(nout=nSeg)
    "Replicates senMasFlo signal from 1 seg to nSeg"
  Buildings.Fluid.HeatExchangers.BaseClasses.HACoilInside hAPipIns[nSeg](
    each m_flow_nominal=m_flow_nominal,
    each hA_nominal=UA_nominal/nSeg*(r_nominal + 1)/r_nominal,
    each T_nominal=THex_nominal,
    each final flowDependent=hA_flowDependent,
    each final temperatureDependent=hA_temperatureDependent)
    "Computation of convection coefficients inside the coil"
  Buildings.Fluid.HeatExchangers.BaseClasses.HANaturalCylinder hANatCyl[nSeg](
    redeclare each final package Medium = Medium,
    each final ChaLen=dExtHex,
    each final hA_nominal=UA_nominal/nSeg*(1 + r_nominal),
    each final TFlu_nominal=TTan_nominal,
    each final TSur_nominal=TTan_nominal - (r_nominal/(1 + r_nominal))*(
        TTan_nominal - THex_nominal))
    "Calculates an hA value for each side of the heat exchanger"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temSenSur[nSeg]
    "Temperature at the external surface of the heat exchanger"

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

equation
  for i in 1:(nSeg - 1) loop
    connect(vol[i].ports[2], vol[i + 1].ports[1]);
  end for;

  connect(rep.u,senMasFlo. m_flow)
  connect(port,HexToTan. fluid)
  connect(vol[1].ports[1],senMasFlo.port_b)
  connect(cap.port,HexToTan.solid)
  connect(vol.heatPort,htfToHex. fluid)
  connect(htfToHex.solid,HexToTan. solid)
  connect(temSenHex.T, hAPipIns.T)
  connect(hAPipIns.hA, htfToHex.Gc)
  connect(HexToTan.solid,temSenSur. port)
  connect(temSenWat.port, port)
  connect(temSenSur.T, hANatCyl.TSur)
  connect(hANatCyl.TFlu, temSenWat.T)
  connect(port_a, senMasFlo.port_a)
  connect(vol[nSeg].ports[2], res.port_a)
  connect(res.port_b, port_b)
  connect(temSenHex.port, vol.heatPort)
  connect(rep.y, hAPipIns.m_flow)
  connect(hANatCyl.hA,HexToTan. Gc)
end IndirectTankHeatExchanger;


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


within Buildings.Fluid.Storage.BaseClasses;
model PartialStratified
  "Partial model of a stratified tank for thermal energy storage"
  extends Buildings.Fluid.Storage.BaseClasses.PartialTwoPortInterface;

  import Modelica.Fluid.Types;
  import Modelica.Fluid.Types.Dynamics;

  parameter Modelica.Units.SI.Volume VTan "Tank volume";
  parameter Modelica.Units.SI.Length hTan "Height of tank (without insulation)";
  parameter Modelica.Units.SI.Length dIns "Thickness of insulation";
  parameter Modelica.Units.SI.ThermalConductivity kIns=0.04
    "Specific heat conductivity of insulation";
  parameter Integer nSeg(min=2) = 2 "Number of volume segments";

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

  // Initialization
  parameter Medium.AbsolutePressure p_start = Medium.p_default
    "Start value of pressure"
  parameter Medium.Temperature T_start=Medium.T_default
    "Start value of temperature"
  parameter Modelica.Units.SI.Temperature TFlu_start[nSeg]=T_start*ones(nSeg)
    "Initial temperature of the tank segments, with TFlu_start[1] being the top segment"
  parameter Medium.MassFraction X_start[Medium.nX] = Medium.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium.ExtraProperty C_start[Medium.nC](
       quantity=Medium.extraPropertiesNames)=fill(0, Medium.nC)
    "Start value of trace substances"

  // Dynamics
  parameter Modelica.Units.SI.Time tau=1 "Time constant for mixing";

  ////////////////////////////////////////////////////////////////////
  // Connectors

  Modelica.Blocks.Interfaces.RealOutput Ql_flow
    "Heat loss of tank (positive if heat flows from tank to ambient)"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a[nSeg] heaPorVol
    "Heat port that connects to the control volumes of the tank"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorSid
    "Heat port tank side (outside insulation)"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorTop
    "Heat port tank top (outside insulation)"
  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPorBot
    "Heat port tank bottom (outside insulation). Leave unconnected for adiabatic condition"

  // Models
  Buildings.Fluid.MixingVolumes.MixingVolume[nSeg] vol(
    redeclare each package Medium = Medium,
    each energyDynamics=energyDynamics,
    each massDynamics=energyDynamics,
    each p_start=p_start,
    T_start=TFlu_start,
    each X_start=X_start,
    each C_start=C_start,
    each V=VTan/nSeg,
    each m_flow_nominal=m_flow_nominal,
    each final mSenFac=1,
    each final m_flow_small=m_flow_small,
    each final allowFlowReversal=allowFlowReversal) "Tank segment"

protected
  parameter Medium.ThermodynamicState sta_default = Medium.setState_pTX(
    T=Medium.T_default,
    p=Medium.p_default,
    X=Medium.X_default[1:Medium.nXi]) "Medium state at default properties";
  parameter Modelica.Units.SI.Length hSeg=hTan/nSeg "Height of a tank segment";
  parameter Modelica.Units.SI.Area ATan=VTan/hTan
    "Tank cross-sectional area (without insulation)";
  parameter Modelica.Units.SI.Length rTan=sqrt(ATan/Modelica.Constants.pi)
    "Tank diameter (without insulation)";
  parameter Modelica.Units.SI.ThermalConductance conFluSeg=ATan*
      Medium.thermalConductivity(sta_default)/hSeg
    "Thermal conductance between fluid volumes";
  parameter Modelica.Units.SI.ThermalConductance conTopSeg=ATan*kIns/dIns
    "Thermal conductance from center of top (or bottom) volume through tank insulation at top (or bottom)";

  BaseClasses.Buoyancy buo(
    redeclare final package Medium = Medium,
    final V=VTan,
    final nSeg=nSeg,
    final tau=tau) "Model to prevent unstable tank stratification"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor[nSeg - 1] conFlu(
    each G=conFluSeg) "Thermal conductance in fluid between the segments"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor[nSeg] conWal(
     each G=2*Modelica.Constants.pi*kIns*hSeg/Modelica.Math.log((rTan+dIns)/rTan))
    "Thermal conductance through tank wall"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor conTop(
     G=conTopSeg) "Thermal conductance through tank top"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor conBot(
     G=conTopSeg) "Thermal conductance through tank bottom"

  Modelica.Thermal.HeatTransfer.Sensors.HeatFlowSensor heaFloTop
    "Heat flow at top of tank (outside insulation)"
  Modelica.Thermal.HeatTransfer.Sensors.HeatFlowSensor heaFloBot
    "Heat flow at bottom of tank (outside insulation)"
  Modelica.Thermal.HeatTransfer.Sensors.HeatFlowSensor heaFloSid[nSeg]
    "Heat flow at wall of tank (outside insulation)"

  Modelica.Blocks.Routing.Multiplex3 mul(
    n1=1,
    n2=nSeg,
    n3=1) "Multiplex to collect heat flow rates"
  Modelica.Blocks.Math.Sum sum1(nin=nSeg + 2)

  Modelica.Thermal.HeatTransfer.Components.ThermalCollector theCol(m=nSeg)
    "Connector to assign multiple heat ports to one heat port"
equation
  connect(buo.heatPort, vol.heatPort)
  for i in 1:nSeg-1 loop
  // heat conduction between fluid nodes
     connect(vol[i].heatPort, conFlu[i].port_a)
    connect(vol[i+1].heatPort, conFlu[i].port_b)
  end for;
  connect(vol[1].heatPort, conTop.port_a)
  connect(vol.heatPort, conWal.port_a)
  connect(conBot.port_a, vol[nSeg].heatPort)
  connect(vol.heatPort, heaPorVol)
  connect(conWal.port_b, heaFloSid.port_a)

  connect(conTop.port_b, heaFloTop.port_a)
  connect(conBot.port_b, heaFloBot.port_a)
  connect(heaFloTop.port_b, heaPorTop)
  connect(heaFloBot.port_b, heaPorBot)
  connect(heaFloTop.Q_flow, mul.u1[1])
  connect(heaFloSid.Q_flow, mul.u2)
  connect(heaFloBot.Q_flow, mul.u3[1])
  connect(mul.y, sum1.u)
  connect(sum1.y, Ql_flow)
  connect(heaFloSid.port_b, theCol.port_a)
  connect(theCol.port_b, heaPorSid)
end PartialStratified;


within Buildings.Fluid.Storage.BaseClasses;
partial model PartialTwoPortInterface
  "Partial model transporting fluid between two ports without storing mass or energy"

  replaceable package Medium =
    Modelica.Media.Interfaces.PartialMedium "Medium in the component"

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

  Modelica.Fluid.Interfaces.FluidPort_a port_a(
    redeclare final package Medium = Medium,
    m_flow(min=if allowFlowReversal then -Modelica.Constants.inf else 0),
    p(start=Medium.p_default),
    h_outflow(start = Medium.h_default, nominal = Medium.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,
    m_flow(max=if allowFlowReversal then +Modelica.Constants.inf else 0),
    p(start=Medium.p_default),
    h_outflow(start = Medium.h_default, nominal = Medium.h_default))
    "Fluid connector b (positive design flow direction is from port_a to port_b)"

  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.ThermodynamicState sta_a=
    if allowFlowReversal then
      Medium.setState_phX(port_a.p,
                          noEvent(actualStream(port_a.h_outflow)),
                          noEvent(actualStream(port_a.Xi_outflow)))
    else
      Medium.setState_phX(port_a.p,
                          noEvent(inStream(port_a.h_outflow)),
                          noEvent(inStream(port_a.Xi_outflow)))
      if show_T "Medium properties in port_a";

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

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 PartialTwoPortInterface;


within Buildings.Fluid.Storage.BaseClasses;
model ThirdOrderStratifier
  "Model to reduce the numerical dissipation in a tank"
  extends Modelica.Blocks.Icons.Block;

  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    "Medium model"

  parameter Modelica.Units.SI.MassFlowRate m_flow_small(min=0)
    "Small mass flow rate for regularization of zero flow";
  parameter Integer nSeg(min=4) "Number of volume segments";

  parameter Real alpha(
    min=0,
    max=1) = 0.5 "Under-relaxation coefficient (1: QUICK; 0: 1st order upwind)";

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a[nSeg] heatPort
    "Heat input into the volumes"

  Modelica.Blocks.Interfaces.RealInput m_flow
    "Mass flow rate from port a to port b"

  Modelica.Blocks.Interfaces.RealInput[nSeg + 1] H_flow
    "Enthalpy flow between the volumes"

  Modelica.Fluid.Interfaces.FluidPort_a[nSeg + 2] fluidPort(redeclare each
      package Medium = Medium)
    "Fluid port, needed to get pressure, temperature and species concentration"

protected
  Modelica.Units.SI.SpecificEnthalpy[nSeg + 1] hOut
    "Extended vector with new outlet enthalpies to reduce numerical dissipation (at the boundary between two volumes)";
  Modelica.Units.SI.SpecificEnthalpy[nSeg + 2] h
    "Extended vector with port enthalpies, needed to simplify loop";
  Modelica.Units.SI.HeatFlowRate Q_flow[nSeg]
    "Heat exchange computed using upwind third order discretization scheme";
  //    Modelica.Units.SI.HeatFlowRate Q_flow_upWind
  //     "Heat exchange computed using upwind third order discretization scheme"; //Used to test the energy conservation
  Real sig
    "Sign used to implement the third order upwind scheme without triggering a state event";
  Real comSig
    "Sign used to implement the third order upwind scheme without triggering a state event";

equation
  assert(nSeg >= 4,
  "Number of segments of the enhanced stratified tank should be no less than 4 (nSeg>=4).");

  // assign zero flow conditions at port
  fluidPort[:].m_flow = zeros(nSeg + 2);
  fluidPort[:].h_outflow = zeros(nSeg + 2);
  fluidPort[:].Xi_outflow = zeros(nSeg + 2, Medium.nXi);
  fluidPort[:].C_outflow = zeros(nSeg + 2, Medium.nC);

  // assign extended enthalpy vectors
  for i in 1:nSeg + 2 loop
    h[i] = inStream(fluidPort[i].h_outflow);
  end for;

  // Value that transitions between 0 and 1 as the flow reverses.
  sig = Modelica.Fluid.Utilities.regStep(
    m_flow,
    1,
    0,
    m_flow_small);
             // at surface between port_a and vol1

  comSig = 1 - sig;

  // at surface between port_a and vol1
  hOut[1] = sig*h[1] + comSig*h[2];
  // at surface between vol[nSeg] and port_b
  hOut[nSeg + 1] = sig*h[nSeg + 1] + comSig*h[nSeg + 2];

  // Pros: These two equations can further reduce the temperature overshoot by using the upwind
  // Cons: The minimum of nSeg hase to be 4 instead of 2.
  hOut[2] = sig*h[2] + comSig*h[3];
  // at surface between vol1 and vol2
  hOut[nSeg] = sig*h[nSeg] + comSig*h[nSeg + 1];
  // at surface between vol[nSeg-1] and vol[nSeg]

  for i in 3:nSeg - 1 loop
    // at surface between vol[i-1] and vol[i]
    // QUICK method
    hOut[i] = 0.5*(h[i] + h[i + 1]) - comSig*0.125*(h[i + 2] + h[i] - 2*h[i + 1])
       - sig*0.125*(h[i - 1] + h[i + 1] - 2*h[i]);
    //     hOut[i] = 0.5*(h[i]+h[i+1]); // Central difference method
  end for;

  for i in 1:nSeg loop
    // difference between QUICK and UPWIND; index of H_flow is same as hOut
    Q_flow[i] = m_flow*(hOut[i + 1] - hOut[i]) - (H_flow[i + 1] - H_flow[i]);
  end for;

  //   Q_flow_upWind = sum(Q_flow[i] for i in 1:nSeg); //Used to test the energy conservation

  for i in 1:nSeg loop
    // Add the difference back to the volume as heat flow. An under-relaxation is needed to reduce
    // oscillations caused by high order method
    heatPort[i].Q_flow = Q_flow[i]*alpha;
  end for;
end ThirdOrderStratifier;


within Buildings.Fluid.Storage.BaseClasses.Examples;
model IndirectTankHeatExchanger
  "Example showing the use of IndirectTankHeatExchanger"
  extends Modelica.Icons.Example;

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

  Buildings.Fluid.Storage.BaseClasses.IndirectTankHeatExchanger indTanHex(
    nSeg=3,
    CHex=50,
    Q_flow_nominal=3000,
    m_flow_nominal=3000/20/4200,
    volHexFlu=0.0004,
    dExtHex=0.01905,
    redeclare package MediumTan = Medium,
    redeclare package MediumHex = Medium,
    dp_nominal=10000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    TTan_nominal=293.15,
    THex_nominal=323.15) "Heat exchanger"

  Buildings.Fluid.Sources.Boundary_pT bou1(
    nPorts=1,
    redeclare package Medium = Medium)
  Buildings.Fluid.Sources.MassFlowSource_T bou(
    m_flow=0.1,
    nPorts=1,
    redeclare package Medium = Medium,
    T=323.15)

  Buildings.HeatTransfer.Sources.FixedTemperature watTem[3](each T=293.15)
equation
  connect(bou1.ports[1], indTanHex.port_a)
  connect(bou.ports[1], indTanHex.port_b)

  connect(watTem.port, indTanHex.port)
end IndirectTankHeatExchanger;


Examples for BaseClasses models

within Buildings.Fluid.Storage.BaseClasses;
package Examples "Examples for BaseClasses models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Test model for expansion vessel

within Buildings.Fluid.Storage.Examples;
model ExpansionVessel "Test model for expansion vessel"
  extends Modelica.Icons.Example;

// package Medium = Modelica.Media.Water.WaterIF97OnePhase_ph "Medium model";
 package Medium = Buildings.Media.Water "Medium model";

  Buildings.Fluid.Storage.ExpansionVessel expVes(
    redeclare package Medium = Medium, V_start=1) "Expansion vessel"
  Buildings.Fluid.MixingVolumes.MixingVolume vol(
    redeclare package Medium = Medium,
    V=1,
    nPorts=1,
    m_flow_nominal=0.001,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Volume of water"
  Modelica.Blocks.Sources.Pulse pulse(
    amplitude=20,
    period=3600,
    offset=293.15)
  Buildings.HeatTransfer.Sources.PrescribedTemperature preTem
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theCon(G=10000)
equation
  connect(pulse.y, preTem.T)
  connect(preTem.port, theCon.port_a)
  connect(theCon.port_b, vol.heatPort)
  connect(vol.ports[1], expVes.port_a)
end ExpansionVessel;


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


Test model for stratified tank

within Buildings.Fluid.Storage.Examples;
model Stratified "Test model for stratified tank"
  extends Modelica.Icons.Example;

 package Medium = Buildings.Media.Water "Medium model";

  Buildings.Fluid.Storage.Stratified tanSim(
    redeclare package Medium = Medium,
    hTan=3,
    dIns=0.3,
    nSeg=10,
    m_flow_nominal=0.1,
    VTan=3) "Tank"
    Modelica.Blocks.Sources.TimeTable TWat(table=[0,273.15 + 40; 3600,273.15 +
        40; 3600,273.15 + 20; 7200,273.15 + 20]) "Water temperature"
  Buildings.Fluid.Sources.Boundary_pT sou_1(
    p=300000 + 5000,
    T=273.15 + 50,
    redeclare package Medium = Medium,
    use_T_in=true,
    nPorts=2)
  Buildings.Fluid.Sources.Boundary_pT sin_1(
    redeclare package Medium = Medium,
    T=273.15 + 20,
    use_p_in=true,
    p=300000,
    nPorts=2)
  Buildings.Fluid.FixedResistances.PressureDrop res_1(
    from_dp=true,
    redeclare package Medium = Medium,
    dp_nominal=5000,
    m_flow_nominal=0.1)
  Buildings.Fluid.Storage.StratifiedEnhanced tanEnh(
    redeclare package Medium = Medium,
    hTan=3,
    dIns=0.3,
    nSeg=10,
    m_flow_nominal=0.1,
    VTan=3) "Tank"
  Buildings.Fluid.FixedResistances.PressureDrop res_2(
    from_dp=true,
    redeclare package Medium = Medium,
    dp_nominal=5000,
    m_flow_nominal=0.1)
  Buildings.Fluid.Sensors.EnthalpyFlowRate HOut_flow(redeclare package Medium =
        Medium, m_flow_nominal=0.1) "Enthalpy flow rate"
  Buildings.Fluid.Sensors.EnthalpyFlowRate HOut_flow1(redeclare package Medium =
        Medium, m_flow_nominal=0.1) "Enthalpy flow rate"
  Modelica.Blocks.Continuous.Integrator dH
    "Differenz in enthalpy (should be zero at steady-state)"
  Modelica.Blocks.Math.Add add(k2=-1)
    Modelica.Blocks.Sources.TimeTable P(table=[0,300000; 4200,300000; 4200,
        305000; 7200,305000; 7200,310000; 10800,310000; 10800,305000])
    "Pressure boundary condition"
  Modelica.Blocks.Sources.Sine sine(
    f=1/86400,
    amplitude=10,
    offset=273.15 + 20)
  Buildings.HeatTransfer.Sources.PrescribedTemperature TBCSid2
    "Boundary condition for tank"
  Buildings.HeatTransfer.Sources.PrescribedTemperature TBCSid1
    "Boundary condition for tank"
  Buildings.HeatTransfer.Sources.PrescribedTemperature TBCTop1
    "Boundary condition for tank"
  Buildings.HeatTransfer.Sources.PrescribedTemperature TBCTop2
    "Boundary condition for tank"
equation
  connect(TWat.y, sou_1.T_in)
  connect(tanSim.port_b, HOut_flow.port_a)
  connect(HOut_flow.port_b, res_1.port_a)
  connect(tanEnh.port_b, HOut_flow1.port_a)
  connect(HOut_flow1.port_b, res_2.port_a)
  connect(add.y, dH.u)
  connect(HOut_flow.H_flow, add.u1)
  connect(HOut_flow1.H_flow, add.u2)
  connect(P.y, sin_1.p_in)
  connect(sine.y, TBCSid1.T)
  connect(sine.y, TBCTop1.T)
  connect(sine.y, TBCSid2.T)
  connect(sine.y, TBCTop2.T)
  connect(TBCSid2.port, tanEnh.heaPorSid)
  connect(TBCTop2.port, tanEnh.heaPorTop)
  connect(sin_1.ports[1], res_1.port_b)
  connect(sin_1.ports[2], res_2.port_b)
  connect(sou_1.ports[1], tanSim.port_a)
  connect(sou_1.ports[2], tanEnh.port_a)
  connect(TBCSid1.port, tanSim.heaPorSid)
  connect(TBCTop1.port, tanSim.heaPorTop)
end Stratified;


within Buildings.Fluid.Storage.Examples;
model StratifiedEnhancedInternalHex
  "Example showing the use of StratifiedEnhancedInternalHex"
  extends Modelica.Icons.Example;

  package MediumTan = Buildings.Media.Water "Medium in the tank";
  package MediumHex = Buildings.Media.Water "Medium in the heat exchanger";

  parameter Modelica.Units.SI.PressureDifference dpHex_nominal=2500
    "Pressure drop across the heat exchanger at nominal conditions";

  parameter Modelica.Units.SI.MassFlowRate mHex_flow_nominal=0.278
    "Mass flow rate of heat exchanger";

  Buildings.Fluid.Sources.Boundary_pT bouWat(redeclare package Medium =
        MediumTan, nPorts=3)
    "Boundary condition for water (used to set pressure)"
  Buildings.Fluid.Sources.Boundary_pT solColSup(
    redeclare package Medium = MediumHex,
    nPorts=3,
    use_p_in=true,
    T=353.15) "Water from solar collector"
  Buildings.Fluid.Sources.Boundary_pT toSolCol(
    redeclare package Medium = MediumHex,
    nPorts=3,
    p(displayUnit="Pa") = 3E5,
    T=283.15) "Water to solar collector"
  Buildings.Fluid.Storage.StratifiedEnhancedInternalHex tanSte(
    redeclare package Medium = MediumTan,
    m_flow_nominal=0.001,
    VTan=0.151416,
    dIns=0.0762,
    redeclare package MediumHex = MediumHex,
    CHex=40,
    Q_flow_nominal=0.278*4200*20,
    hTan=1.746,
    hHex_a=0.995,
    hHex_b=0.1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.SteadyState,
    allowFlowReversal=false,
    allowFlowReversalHex=false,
    mHex_flow_nominal=mHex_flow_nominal,
    TTan_nominal=293.15,
    THex_nominal=323.15,
    dpHex_nominal=dpHex_nominal)
    "Tank with heat exchanger configured as steady state"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemSte(
    redeclare package Medium = MediumHex,
    allowFlowReversal=false,
    m_flow_nominal=mHex_flow_nominal,
    tau=0) "Temperature sensor for outlet of steady-state heat exchanger"
  Modelica.Blocks.Sources.Step step(
    height=dpHex_nominal,
    offset=3E5,
    startTime=300) "Step input for mass flow rate"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemDyn(
    redeclare package Medium = MediumHex,
    allowFlowReversal=false,
    m_flow_nominal=mHex_flow_nominal,
    tau=0) "Temperature sensor for outlet of dynamic heat exchanger"
  Buildings.Fluid.Storage.StratifiedEnhancedInternalHex tanDyn(
    redeclare package Medium = MediumTan,
    m_flow_nominal=0.001,
    VTan=0.151416,
    dIns=0.0762,
    redeclare package MediumHex = MediumHex,
    CHex=40,
    Q_flow_nominal=0.278*4200*20,
    hTan=1.746,
    hHex_a=0.995,
    hHex_b=0.1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    allowFlowReversal=false,
    allowFlowReversalHex=false,
    mHex_flow_nominal=mHex_flow_nominal,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.FixedInitial,
    TTan_nominal=293.15,
    THex_nominal=323.15) "Tank with heat exchanger configured as dynamic"
  Buildings.Fluid.Storage.StratifiedEnhancedInternalHex tanDynSol(
    redeclare package Medium = MediumTan,
    m_flow_nominal=0.001,
    VTan=0.151416,
    dIns=0.0762,
    redeclare package MediumHex = MediumHex,
    CHex=40,
    Q_flow_nominal=0.278*4200*20,
    hTan=1.746,
    hHex_a=0.995,
    hHex_b=0.1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    allowFlowReversal=false,
    allowFlowReversalHex=false,
    mHex_flow_nominal=mHex_flow_nominal,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.SteadyState,
    energyDynamicsHexSolid=Modelica.Fluid.Types.Dynamics.SteadyStateInitial,
    TTan_nominal=293.15,
    THex_nominal=323.15)
    "Tank with heat exchanger configured as steady-state except for metal which is dynamic"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTemDynSol(
    redeclare package Medium = MediumHex,
    allowFlowReversal=false,
    m_flow_nominal=mHex_flow_nominal,
    tau=0)
    "Temperature sensor for outlet of steady state heat exchanger with solid configured dynamic"
equation
  connect(solColSup.ports[1], tanSte.portHex_a)
  connect(senTemSte.port_b, toSolCol.ports[1])
  connect(senTemSte.port_a, tanSte.portHex_b)
  connect(senTemDyn.port_a, tanDyn.portHex_b)
  connect(senTemDyn.port_b, toSolCol.ports[2])
  connect(tanDynSol.portHex_b, senTemDynSol.port_a)
  connect(senTemDynSol.port_b, toSolCol.ports[3])
  connect(solColSup.ports[2], tanDyn.portHex_a)
  connect(solColSup.ports[3], tanDynSol.portHex_a)
  connect(bouWat.ports[1], tanSte.port_b)
  connect(bouWat.ports[2], tanDyn.port_b)
  connect(bouWat.ports[3], tanDynSol.port_b)
  connect(step.y, solColSup.p_in)
end StratifiedEnhancedInternalHex;


within Buildings.Fluid.Storage.Examples;
model StratifiedUnloadAtMinimumTemperature
  "Example that demonstrates how to draw from a hot water tank at the minimum temperature"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water "Medium model";

  parameter Modelica.Units.SI.Volume VTan=3 "Tank volume";

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=3*1000/3600
    "Nominal mass flow rate";

  constant Integer nSeg=5 "Number of volume segments";

  Buildings.Fluid.Storage.Stratified tan(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    VTan=VTan,
    hTan=2,
    dIns=0.2,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    nSeg=nSeg,
    T_start=353.15) "Hot water storage tank"
  Buildings.Fluid.Sources.Boundary_pT loa(redeclare package Medium = Medium,
      nPorts=1)
    "Load (imposed by a constant pressure boundary condition and the flow of masSou)"
  Buildings.Fluid.Sources.MassFlowSource_T masSou(
    nPorts=1,
    redeclare package Medium = Medium,
    m_flow=m_flow_nominal) "Mass flow rate into the tank"

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valTop(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dpValve_nominal=3000,
    use_strokeTime=false) "Control valve at top"

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valMid(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dpValve_nominal=3000,
    use_strokeTime=false) "Control valve at middle"

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valBot(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dpValve_nominal=3000,
    use_strokeTime=false) "Control valve at bottom"

  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor TMid
    "Temperature tank middle"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor TBot
    "Temperature tank bottom"
  Modelica.Blocks.Logical.Hysteresis onOffBot(uLow=273.15 + 40 - 0.05, uHigh=
        273.15 + 40 + 0.05)
    "Controller for valve at bottom"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTem(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    tau=0) "Outflowing temperature"
  Modelica.Blocks.Logical.Hysteresis onOffMid(uLow=273.15 + 40 - 0.05, uHigh=
        273.15 + 40 + 0.05)
    "Controller for valve at middle of tank"
  Modelica.Blocks.Logical.And and2
    "And block to compute control action for middle valve"
  Modelica.Blocks.Math.BooleanToReal yMid
    "Boolean to real conversion for valve at middle"
  Modelica.Blocks.Math.BooleanToReal yTop
    "Boolean to real conversion for valve at top"
  Modelica.Blocks.Logical.Nor nor
    "Nor block for top-most control valve"
  Modelica.Blocks.Logical.Not not1
    "Not block to negate control action of upper level control"
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow hea
    "Heat input at the bottom of the tank"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor TTop
    "Temperature tank top"
  Modelica.Blocks.Logical.Hysteresis onOffHea(uLow=273.15 + 50 - 0.05, uHigh=
        273.15 + 50 + 0.05)
    "Controller for heater at bottom"
  Modelica.Blocks.Math.BooleanToReal yHea(realFalse=150000)
    "Boolean to real for valve at bottom"
  Modelica.Blocks.Math.BooleanToReal yBot
    "Boolean to real conversion for valve at bottom"
equation
  connect(masSou.ports[1], tan.port_b)
  connect(TMid.port, tan.heaPorVol[3])
  connect(TBot.port, tan.heaPorVol[5])
  connect(valTop.port_b, senTem.port_a)
  connect(valMid.port_b, senTem.port_a)
  connect(valBot.port_b, senTem.port_a)
  connect(senTem.port_b,loa. ports[1])
  connect(valTop.port_a, tan.fluPorVol[1])
  connect(valMid.port_a, tan.fluPorVol[3])
  connect(valBot.port_a, tan.fluPorVol[5])
  connect(onOffMid.y, and2.u1)
  connect(yMid.u, and2.y)
  connect(yTop.u, nor.y)
  connect(and2.y, nor.u1)
  connect(onOffBot.y, nor.u2)
  connect(yTop.y, valTop.y)
  connect(yMid.y, valMid.y)
  connect(not1.u, onOffBot.y)
  connect(not1.y, and2.u2)
  connect(hea.port, tan.heaPorVol[5])
  connect(TTop.port, tan.heaPorVol[1])
  connect(onOffHea.u, TTop.T)
  connect(onOffHea.y, yHea.u)
  connect(hea.Q_flow, yHea.y)
  connect(onOffBot.y, yBot.u)
  connect(yBot.y, valBot.y)
  connect(TBot.T, onOffBot.u)
  connect(onOffMid.u, TMid.T)
end StratifiedUnloadAtMinimumTemperature;


Ice tank with performance based on performance curves and built-in control for outlet temperature. This model implements an ice tank model with built-in idealized control
that tracks the set point TSet for the temperature of the working fluid
that leaves the tank, as shown in the figure below.

within Buildings.Fluid.Storage.Ice;
model ControlledTank
  "Ice tank with performance based on performance curves and built-in control for outlet temperature"
  extends Buildings.Fluid.Storage.Ice.Tank(
    limQ_flow(y=if tanHeaTra.canMelt.y
                then m_flow*cp*(max(per.TFre, TSet) - TIn.y)
                elseif tanHeaTra.canFreeze.y
                then m_flow*cp*(max(TIn.y, min(per.TFre, TSet)) - TIn.y)
                else m_flow*cp*(per.TFre - TIn.y)));

  Modelica.Blocks.Interfaces.RealInput TSet(
    final unit="K",
    final displayUnit="degC")
    "Outlet temperature setpoint during discharging"
end ControlledTank;


Ice tank model

within Buildings.Fluid.Storage;
package Ice "Ice tank model"
  extends Modelica.Icons.Package;
end Ice;


Ice tank with performance based on performance curves. This model implements an ice tank model whose performance is computed based on
performance curves.

within Buildings.Fluid.Storage.Ice;
model Tank "Ice tank with performance based on performance curves"
  extends Buildings.Fluid.Interfaces.TwoPortHeatMassExchanger(
    final allowFlowReversal = false,
    final tau=tauHex,
    final energyDynamics=energyDynamicsHex,
    redeclare final Buildings.Fluid.MixingVolumes.MixingVolume vol);

  parameter Real SOC_start(min=0, max=1, final unit="1")
   "Start value for state of charge"

  replaceable parameter Buildings.Fluid.Storage.Ice.Data.Tank.Generic per
    "Performance data"

  parameter Modelica.Fluid.Types.Dynamics energyDynamicsHex=
    Modelica.Fluid.Types.Dynamics.DynamicFreeInitial
    "Formulation of energy balance for heat exchanger internal fluid mass"

  parameter Modelica.Units.SI.Time tauHex(min=1) = 30
    "Time constant of working fluid through the heat exchanger at nominal flow"

  // Initialization
  parameter Medium.AbsolutePressure p_start = Medium.p_default
    "Start value of pressure"
  parameter Medium.Temperature T_start = Medium.T_default
    "Start value of temperature"
  parameter Medium.MassFraction X_start[Medium.nX](
    final quantity=Medium.substanceNames) = Medium.X_default
    "Start value of mass fractions m_i/m"
  parameter Medium.ExtraProperty C_start[Medium.nC](
    final quantity=Medium.extraPropertiesNames)=fill(0, Medium.nC)
    "Start value of trace substances"

  final parameter Modelica.Units.SI.SpecificHeatCapacity cp=
    Medium.specificHeatCapacityCp(
      Medium.setState_pTX(
        p=Medium.p_default,
        T=273.15,
        X=Medium.X_default))
    "Specific heat capacity of working fluid";
  Modelica.Blocks.Interfaces.RealOutput SOC(
    final unit = "1")
    "state of charge"

  Modelica.Blocks.Interfaces.RealOutput T(
    final quantity="ThermodynamicTemperature",
    final unit = "K",
    displayUnit = "degC",
    min=0)
    "Temperature of the fluid leaving at port_b"

  Modelica.Blocks.Interfaces.RealOutput mIce(
    quantity="Mass",
    unit="kg") "Mass of remaining ice"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(final unit="W")
    "Heat flow rate, positive during charging, negative when melting the ice"

protected
  Buildings.Fluid.Storage.Ice.BaseClasses.Tank tanHeaTra(
    final SOC_start=SOC_start,
    final per=per,
    final cp=cp)
    "Model for tank heat transfer between working fluid and ice"

  Buildings.HeatTransfer.Sources.PrescribedHeatFlow preHeaFlo
    "Prescribed heat flow"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temSen
    "Temperature of fluid"

  Modelica.Blocks.Sources.RealExpression TIn(
    final y=Medium.temperature(state=
        Medium.setState_phX(
        p=port_a.p,
        h=inStream(port_a.h_outflow),
        X=inStream(port_a.Xi_outflow)))) "Inlet temperature into tank"

  Modelica.Blocks.Sources.RealExpression limQ_flow(y=m_flow*cp*(per.TFre - TIn.y))
   "Upper/Lower limit for charging/discharging rate"
equation
  connect(tanHeaTra.TIn, TIn.y)
  connect(preHeaFlo.port, vol.heatPort)
  connect(tanHeaTra.SOC, SOC)
  connect(tanHeaTra.mIce, mIce)
  connect(limQ_flow.y, tanHeaTra.QLim_flow)
  connect(tanHeaTra.Q_flow, Q_flow)
  connect(tanHeaTra.Q_flow, preHeaFlo.Q_flow)
  connect(temSen.T, T)
  connect(temSen.T, tanHeaTra.TOut)
  connect(vol.heatPort, temSen.port)
end Tank;


Normalized log mean temperature difference across the ice storage unit. This subroutine calculates the log mean temperature difference for the detailed ice storage unit
using the function

Buildings.Fluid.Storage.Ice.BaseClasses.calculateLMTDStar.

within Buildings.Fluid.Storage.Ice.BaseClasses;
model LMTDStar
  "Normalized log mean temperature difference across the ice storage unit"
  extends Modelica.Blocks.Icons.Block;

  parameter Modelica.Units.SI.Temperature TFre = 273.15 "Freezing temperature of water or the latent energy storage material";
  parameter Modelica.Units.SI.TemperatureDifference dT_nominal = 10
   "Nominal temperature difference";

  Modelica.Blocks.Interfaces.RealInput TIn(unit="K", displayUnit="degC")
    "Inlet temperature"
  Modelica.Blocks.Interfaces.RealInput TOut(unit="K", displayUnit="degC")
    "Outlet temperature"

  Modelica.Blocks.Interfaces.RealOutput lmtdSta(final quantity="1")
    "Normalized LMTD"

equation

  lmtdSta = Buildings.Utilities.Math.Functions.smoothMin(
      x1=10,
      x2=Buildings.Fluid.Storage.Ice.BaseClasses.calculateLMTDStar(
        TIn=TIn,
        TOut=TOut,
        TFre=TFre,
        dT_nominal=dT_nominal),
      deltaX=1E-6);
end LMTDStar;


Charging or discharging rate based on the curves. This blocks calculate the normalized heat transfer rate for the ice tank in charging or discharging mode.

within Buildings.Fluid.Storage.Ice.BaseClasses;
model NormalizedHeatFlowRate
  "Charging or discharging rate based on the curves"
  extends Modelica.Blocks.Icons.Block;

  parameter Real coeCha[6]
    "Coefficients for charging curve";
  parameter Real dtCha "Time step of curve fitting data";

  parameter Real coeDisCha[6]
    "Coefficients for discharging curve";
  parameter Real dtDisCha "Time step of curve fitting data";

  Modelica.Blocks.Interfaces.RealOutput qNor(final quantity="1/s")
    "Normalized heat transfer rate: charging when postive, discharge when negative"
  Modelica.Blocks.Interfaces.RealInput SOC "State of charge"
  Modelica.Blocks.Interfaces.RealInput lmtdSta "LMTD star"

  Modelica.Blocks.Interfaces.BooleanInput canFreeze
    "Set to true if tank can be charged"
  Modelica.Blocks.Interfaces.BooleanInput canMelt
    "Set to true if tank can be melted"

protected
  Modelica.Blocks.Math.Add SOCCom(final k2=-1) "1 - SOC"
  Modelica.Blocks.Sources.Constant const(k=1) "Constant output of 1"

  Buildings.Fluid.Storage.Ice.BaseClasses.QStar qStaCha(
    final coeff=coeCha,
    final dt=dtCha)
    "q* for charing mode"
  Buildings.Fluid.Storage.Ice.BaseClasses.QStar qStaDisCha(
    final coeff=coeDisCha,
    final dt=dtDisCha) "q* for discharging mode"
  Buildings.Controls.OBC.CDL.Reals.Subtract qSta
    "Effective normalized heat flow rate"

equation
  connect(qStaCha.lmtdSta, lmtdSta)
  connect(lmtdSta, qStaDisCha.lmtdSta)
  connect(SOC, qStaCha.x)
  connect(qStaCha.active, canFreeze)
  connect(qStaDisCha.active, canMelt)
  connect(qNor, qSta.y)
  connect(SOCCom.u2, SOC)
  connect(const.y, SOCCom.u1)
  connect(qStaDisCha.x, SOCCom.y)
  connect(qStaCha.qNor, qSta.u1)
  connect(qStaDisCha.qNor, qSta.u2)
end NormalizedHeatFlowRate;


Base classes for ice tank models

within Buildings.Fluid.Storage.Ice;
package BaseClasses "Base classes for ice tank models"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


Calculator for q* under charging mode. This block calculates the normalized heat transfer rate <i>q*</i> between the chilled water
and the ice in the thermal storage tank using

within Buildings.Fluid.Storage.Ice.BaseClasses;
model QStar "Calculator for q* under charging mode"
  extends Modelica.Blocks.Icons.Block;

  parameter Real coeff[6] "Coefficients for qstar curve";
  parameter Real dt "Time step of curve fitting data";

  Modelica.Blocks.Interfaces.BooleanInput active
    "Set to true if this tank mode can be active"

  Modelica.Blocks.Interfaces.RealInput x(unit="1")
    "SOC for charging, or 1-SOC for discharging"

  Modelica.Blocks.Interfaces.RealInput lmtdSta(unit="1") "Normalized LMTD"

  Modelica.Blocks.Interfaces.RealOutput qNor(final quantity="1")
    "Normalized heat transfer rate"

equation
  if active then
    qNor =Buildings.Utilities.Math.Functions.smoothMax(
      x1=0,
      x2=Buildings.Utilities.Math.Functions.polynomial(x, coeff[1:3]) +
        Buildings.Utilities.Math.Functions.polynomial(x, coeff[4:6])*lmtdSta,
      deltaX=1E-7)/dt;
  else
    qNor = 0;
  end if;
end QStar;


Mass of ice remaining in the tank. This block calculates the state of charge using

within Buildings.Fluid.Storage.Ice.BaseClasses;
model StateOfCharge "Mass of ice remaining in the tank"
  extends Modelica.Blocks.Icons.Block;

  parameter Real SOC_start(min=0, max=1, final unit="1")
   "Start value for state of charge";

  parameter Modelica.Units.SI.Energy E_nominal "Storage capacity";

  Modelica.Blocks.Interfaces.RealInput Q_flow(final unit="W")
    "Heat transfer rate: positive for charging, negative for discharging"
  Modelica.Blocks.Interfaces.RealOutput QEff_flow(final unit="W")
    "Actual heat flow rate, taking into account 0 ≤ SOC ≤ 1"
  Modelica.Blocks.Interfaces.RealOutput SOC(
    final min=0,
    final max=1,
    final unit="1")
    "State of charge"
  Boolean charged "True if tank is fully charged";
  Boolean discharged "True if tank is fully discharged";
  Boolean suspend "True if either fully charged or fully discharged";

initial equation
  SOC = SOC_start;
  charged = SOC_start >= 1;
  discharged = SOC_start <= 0;

equation
  der(SOC) = if suspend then 0 else Q_flow/E_nominal;
  suspend = charged or discharged;
  QEff_flow = if suspend then 0 else Q_flow;

  when SOC < 0 then
    discharged = true;
    charged = pre(charged);
  elsewhen SOC > 1 then
    charged = true;
    discharged = pre(discharged);
  elsewhen (pre(discharged) and Q_flow > 1) then
    charged = pre(charged);
    discharged = false;
  elsewhen (pre(charged) and Q_flow < -1) then
    charged = false;
    discharged = pre(discharged);
  end when;
end StateOfCharge;


Block to compute the tank heat transfer. Model that implements the heat transfer rate between the working fluid and the ice,
and that computes the state of charge of the tank.

within Buildings.Fluid.Storage.Ice.BaseClasses;
model Tank "Block to compute the tank heat transfer"
  extends Modelica.Blocks.Icons.Block;

  constant Modelica.Units.SI.TemperatureDifference dTSmall(min=1E-3) = 0.01 "Small temperature difference";

  parameter Real SOC_start(min=0, max=1, final unit="1")
   "Start value for state of charge"

  replaceable parameter Buildings.Fluid.Storage.Ice.Data.Tank.Generic per
    "Performance data"

  final parameter Modelica.Units.SI.Energy E_nominal=per.Hf*per.mIce_max
    "Storage capacity";
  parameter Modelica.Units.SI.SpecificHeatCapacity cp "Specific heat capacity of working fluid";

  Modelica.Blocks.Interfaces.RealInput TIn(final unit="K", displayUnit="degC")
    "Inlet temperature"
  Modelica.Blocks.Interfaces.RealInput TOut(final unit="K", displayUnit="degC")
    "Outlet temperature"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(final unit="W")
    "Actual heat flow rate, taking into account 0 ≤ SOC ≤ 1"

  Modelica.Blocks.Interfaces.RealOutput SOC(
    final unit = "1")
    "state of charge"
  Modelica.Blocks.Interfaces.RealOutput mIce(
    quantity="Mass",
    unit="kg") "Mass of remaining ice"
  Modelica.Blocks.Interfaces.RealInput QLim_flow(final unit="W")
    "Limit on heat flow rate due to temperatures and mass flow rate"

  Buildings.Fluid.Storage.Ice.BaseClasses.StateOfCharge SOCCalc(
    final SOC_start=SOC_start,
    final E_nominal=E_nominal) "State of charge calculation"

  Controls.OBC.CDL.Reals.LessThreshold canFreeze(
    final t=per.TFre - dTSmall,
    final h=dTSmall/2)
    "Outputs true if temperatures allow ice to be produced"
  Controls.OBC.CDL.Reals.GreaterThreshold canMelt(
    final t=per.TFre + dTSmall,
    final h=dTSmall/2)
    "Outputs true if temperature allows tank to be melted"

protected
  Modelica.Blocks.Math.Max QMax_flow "Maximum heat flow rate"
  Modelica.Blocks.Math.Min QMin_flow "Miminum heat flow rate"
  Modelica.Blocks.Logical.Switch QThe_flow
    "Heat flow rate extracted from fluid, not taking into account state of charge"
  Modelica.Blocks.Math.Gain masIce(
    final k=per.mIce_max,
    y(final unit="kg"))
    "Mass of ice"
  Buildings.Fluid.Storage.Ice.BaseClasses.NormalizedHeatFlowRate norQSta(
    final coeCha=per.coeCha,
    final coeDisCha=per.coeDisCha,
    final dtCha=per.dtCha,
    final dtDisCha=per.dtDisCha)
  Modelica.Blocks.Math.Gain QCoe_flow(k=E_nominal, y(final unit="W"))
    "Heat flow rate based on performance curves, without any limitation"
  Buildings.Fluid.Storage.Ice.BaseClasses.LMTDStar lmtdSta(
    final TFre=per.TFre,
    final dT_nominal=per.dT_nominal)

equation
  connect(norQSta.qNor, QCoe_flow.u)
  connect(QThe_flow.y, SOCCalc.Q_flow)
  connect(QMin_flow.y, QThe_flow.u1)
  connect(QCoe_flow.y, QMin_flow.u2)
  connect(QCoe_flow.y, QMax_flow.u1)
  connect(SOCCalc.SOC, SOC)
  connect(QMax_flow.y, QThe_flow.u3)
  connect(lmtdSta.lmtdSta, norQSta.lmtdSta)
  connect(SOCCalc.SOC, norQSta.SOC)
  connect(norQSta.canMelt, canMelt.y)
  connect(canFreeze.y, norQSta.canFreeze)
  connect(canFreeze.y, QThe_flow.u2)
  connect(SOCCalc.QEff_flow, Q_flow)
  connect(lmtdSta.TIn, TIn)
  connect(canFreeze.u, TIn)
  connect(lmtdSta.TOut, TOut)
  connect(TIn, canMelt.u)
  connect(QLim_flow, QMin_flow.u1)
  connect(QLim_flow, QMax_flow.u2)
  connect(SOCCalc.SOC, masIce.u)
  connect(masIce.y, mIce)
end Tank;


Example that tests the LMTDStar model.

within Buildings.Fluid.Storage.Ice.BaseClasses.Examples;
model LMTDStar "Example that tests the LMTDStar model"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Storage.Ice.BaseClasses.LMTDStar lmtdSta "LMTD star"
  Modelica.Blocks.Sources.Cosine TIn(
    amplitude=4,
    f=1/3600,
    offset=273.15 + 2) "Inlet temperature"
  Modelica.Blocks.Sources.Cosine TOut(
    amplitude=4,
    f=1/3600,
    offset=273.15 + 2,
    phase=3.1415926535898) "Outlet temperature"
equation
  connect(TIn.y, lmtdSta.TIn)
  connect(TOut.y, lmtdSta.TOut)
end LMTDStar;


Example to calculate qStar. This example is to validate the

Buildings.Fluid.Storage.Ice.BaseClasses.NormalizedHeatFlowRate.

within Buildings.Fluid.Storage.Ice.BaseClasses.Examples;
model NormalizedHeatFlowRate "Example to calculate qStar"
  extends Modelica.Icons.Example;
  parameter Real coeCha[6] = {0, 0.09, -0.15, 0.612, -0.324, -0.216} "Coefficient for charging curve";
  parameter Real coeDisCha[6] = {0, 0.09, -0.15, 0.612, -0.324, -0.216} "Coefficient for discharging curve";
  parameter Real dt = 3600 "Time step used in the samples for curve fitting";

  Modelica.Blocks.Sources.Cosine fra(
    amplitude=0.5,
    offset=0.5,
    f=1/7200) "Fraction of charge"
  Buildings.Fluid.Storage.Ice.BaseClasses.NormalizedHeatFlowRate norQSta(
    coeCha=coeCha,
    dtCha=dt,
    coeDisCha=coeDisCha,
    dtDisCha=dt) "Storage heat transfer rate"

  Modelica.Blocks.Sources.Step lmtd(
    startTime=3600,
    offset=1,
    height=-0.5)
               "lmtd start"
  Controls.OBC.CDL.Reals.GreaterThreshold greThr(t=0.75)
    "Switch the change between charging and discharging mode"
  Controls.OBC.CDL.Logical.Not not1
equation
  connect(fra.y, norQSta.SOC)
  connect(lmtd.y, norQSta.lmtdSta)
  connect(lmtd.y, greThr.u)
  connect(norQSta.canMelt, greThr.y)
  connect(greThr.y, not1.u)
  connect(not1.y, norQSta.canFreeze)
end NormalizedHeatFlowRate;


within Buildings.Fluid.Storage.Ice.BaseClasses;
package Examples "Examples that test models in the base classes"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Example to calculate QStar. This example is to validate the

Buildings.Fluid.Storage.Ice.BaseClasses.QStar.

within Buildings.Fluid.Storage.Ice.BaseClasses.Examples;
model QStar "Example to calculate QStar"
  extends Modelica.Icons.Example;

  parameter Real coeCha[6] = {0, 0.09, -0.15, 0.612, -0.324, -0.216} "Coefficient for charging curve";
  parameter Real dt = 3600 "Time step used in the samples for curve fitting";

  Modelica.Blocks.Sources.Cosine fra(
    amplitude=0.5,
    f=1/86400,
    offset=0.5) "Fraction of charge"
  Modelica.Blocks.Sources.Constant lmtd(k=1) "Log mean temperature difference"
  Buildings.Fluid.Storage.Ice.BaseClasses.QStar qSta(coeff=coeCha, dt=dt)
  Controls.OBC.CDL.Logical.Sources.Constant active(k=true)
    "Outputs true to activate the component"
equation
  connect(fra.y, qSta.x)
  connect(lmtd.y, qSta.lmtdSta)
  connect(active.y, qSta.active)
end QStar;


Example that tests the ice mass calculation. This example is to validate the model that calculates the ice mass.

within Buildings.Fluid.Storage.Ice.BaseClasses.Examples;
model StateOfCharge "Example that tests the ice mass calculation"
  extends Modelica.Icons.Example;

  Buildings.Fluid.Storage.Ice.BaseClasses.StateOfCharge soc(E_nominal=2846.35*
        333550, SOC_start=1/2) "State of charge"
  Modelica.Blocks.Sources.Cosine q(
    f=1/3600,
    amplitude=1,
    offset=0)
    "Heat transfer rate: postive for charging, negative for discharging"
  Modelica.Blocks.Math.Gain hf(k=333550) "Fusion of heat of ice"
equation
  connect(q.y, hf.u)
  connect(hf.y, soc.Q_flow)
end StateOfCharge;


Performance data for ice thermal tank

within Buildings.Fluid.Storage.Ice;
package Data "Performance data for ice thermal tank"
  extends Modelica.Icons.MaterialPropertiesPackage;
end Data;


Performance data for ice tank model

within Buildings.Fluid.Storage.Ice.Data;
package Tank "Performance data for ice tank model"
  extends Modelica.Icons.MaterialPropertiesPackage;
end Tank;


within Buildings.Fluid.Storage.Ice;
package Validation "Package that validates the ice tank model"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


Example that test the tank model

within Buildings.Fluid.Storage.Ice.Validation;
model Tank "Example that test the tank model"
  extends Modelica.Icons.Example;

  package Medium = Buildings.Media.Antifreeze.PropyleneGlycolWater (
    property_T=293.15,
    X_a=0.30) "Fluid medium";

  parameter Modelica.Units.SI.Mass SOC_start=3/4
    "Start value of ice mass in the tank";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=1
    "Nominal mass flow rate";
  parameter Modelica.Units.SI.PressureDifference dp_nominal=100000
    "Pressure difference";
  parameter Buildings.Fluid.Storage.Ice.Data.Tank.Generic per(
    mIce_max=1/4*2846.35,
    coeCha={1.76953858E-04,0,0,0,0,0},
    dtCha=10,
    coeDisCha={5.54E-05,-1.45679E-04,9.28E-05,1.126122E-03,-1.1012E-03,3.00544E-04},
    dtDisCha=10)
    "Tank performance data"

  Buildings.Fluid.Storage.Ice.ControlledTank iceTan(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal,
    SOC_start=SOC_start,
    per=per,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.FixedInitial)
  Buildings.Fluid.Sources.MassFlowSource_T sou(
    redeclare package Medium = Medium,
    m_flow=2*m_flow_nominal,
    use_T_in=true,
    nPorts=2)
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = Medium,
    nPorts=2)
  Buildings.Fluid.FixedResistances.PressureDrop res(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=500) "Flow resistance"

  Modelica.Blocks.Sources.CombiTimeTable TSou(
    table=[
      0,       273.15 - 5;
      3600*10, 273.15 - 5;
      3600*10, 273.15 + 10;
      3600*11, 273.15 + 10;
      3600*18, 273.15 + 10;
      3600*18, 273.15 - 5],
      y(each unit="K",
        each displayUnit="degC"))
      "Source temperature"

  Modelica.Blocks.Sources.TimeTable TSet(table=[
    0,273.15 -10;
    3600*10,273.15 + 8;
    3600*11,273.15 + 6;
    3600*20,273.15 -12;
    3600*24,273.15 +10], y(unit="K", displayUnit="degC"))
    "Table with set points for leaving water temperature which will be tracked subject to thermodynamic constraints"
  Buildings.Fluid.Storage.Ice.Tank iceTanUnc(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal,
    SOC_start=SOC_start,
    per=per,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.FixedInitial)
             "Uncontrolled ice tank"
  FixedResistances.PressureDrop resUnc(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=500) "Flow resistance"
equation
  connect(res.port_b, bou.ports[1])
  connect(TSou.y[1], sou.T_in)
  connect(TSet.y, iceTan.TSet)
  connect(resUnc.port_b, bou.ports[2])
  connect(iceTan.port_b, res.port_a)
  connect(iceTanUnc.port_b, resUnc.port_a)
  connect(iceTanUnc.port_a, sou.ports[1])
  connect(iceTan.port_a, sou.ports[2])
end Tank;


Validation against charging experiment. This example is to validate the developed ice tank model for charging mode.

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST;
model Charging "Validation against charging experiment"
  extends Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST.BaseClasses.PartialChargingDischarging(
    fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Storage/Ice/Validation/Experiment/charging.txt"),
    SOC_start=0.158);
end Charging;


Validation against discharging experiment 1. This example is to validate the developed ice tank model for discharging mode using data generated from experiment 1.
The outlet temperature setpoint is set to 2 degree below the measured temperature because the measurement data was taken when the bypass was fully closed.
In this way, the implemented model will automatically close the bypass valve during charging.

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST;
model Discharging1 "Validation against discharging experiment 1"
  extends Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST.BaseClasses.PartialChargingDischarging(
    fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Storage/Ice/Validation/Experiment/discharging1.txt"),
    SOC_start=0.90996030,
    offSet(k=-2));
end Discharging1;


Validation against discharging experiment 2. This example is to validate the developed ice tank model for discharging mode using data generated from experiment 2.
The outlet temperature setpoint is set to 2 degree below the measured temperature because the measurement data was taken when the bypass was fully closed.
In this way, the implemented model will automatically close the bypass valve during charging.

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST;
model Discharging2 "Validation against discharging experiment 2"
  extends Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST.BaseClasses.PartialChargingDischarging(
    fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Storage/Ice/Validation/Experiment/discharging2.txt"),
    SOC_start=0.96645368,
    offSet(k=-2));
end Discharging2;


Validation against discharging experiment 3. This example is to validate the developed ice tank model for discharging mode using data generated from experiment 3.
The outlet temperature setpoint is set to 2 degree below the measured temperature because the measurement data was taken when the bypass was fully closed.
In this way, the implemented model will automatically close the bypass valve during charging.

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST;
model Discharging3 "Validation against discharging experiment 3"
  extends Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST.BaseClasses.PartialChargingDischarging(
    fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Storage/Ice/Validation/Experiment/discharging3.txt"),
    SOC_start=0.969633826,
    offSet(k=-2));
end Discharging3;


Validation from experimental data

within Buildings.Fluid.Storage.Ice.Validation;
package ExperimentNIST "Validation from experimental data"
  extends Modelica.Icons.ExamplesPackage;
end ExperimentNIST;


Base classes for validation package

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST;
package BaseClasses "Base classes for validation package"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


Base example

within Buildings.Fluid.Storage.Ice.Validation.ExperimentNIST.BaseClasses;
partial model PartialChargingDischarging "Base example"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Antifreeze.PropyleneGlycolWater (
    property_T=293.15,
    X_a=0.30);
  parameter String fileName "Calibration data file";

  parameter Real SOC_start=0.90996030
    "Start value of state of charge";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=1
    "Nominal mass flow rate";
  parameter Modelica.Units.SI.PressureDifference dp_nominal=100000
    "Pressure difference";
  parameter Buildings.Fluid.Storage.Ice.Data.Tank.Generic per(
    mIce_max = 2846.35,
    coeCha={1.76953858E-04,0,0,0,0,0},
    dtCha=10,
    coeDisCha={5.54E-05,-1.45679E-04,9.28E-05,1.126122E-03,-1.1012E-03,
        3.00544E-04},
    dtDisCha=10)
    "Performance curve obtained from onsite experiment"

  Buildings.Fluid.Storage.Ice.ControlledTank iceTan(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal,
    SOC_start=SOC_start,
    per=per,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.FixedInitial)
             "Ice tank"
  Buildings.Fluid.Sources.MassFlowSource_T sou(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    use_T_in=true,
    nPorts=1) "Mass flow source"
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = Medium,
    nPorts=1)
    "Pressure source"
  Buildings.Fluid.FixedResistances.PressureDrop res(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=500)
    "Flow resistance"
  Modelica.Blocks.Sources.CombiTimeTable dat(
    tableOnFile=true,
    tableName="tab",
    columns=2:5,
    fileName=fileName)
    "Flowrate measurements"

  Modelica.Thermal.HeatTransfer.Celsius.ToKelvin TIn
    "Conversion from Celsius to Kelvin"
  Modelica.Thermal.HeatTransfer.Celsius.ToKelvin TOutSet
    "Outlet temperature in Kelvin"

  Modelica.Blocks.Math.Add TSet "Temperature setpoint"
  Modelica.Blocks.Sources.Constant offSet(k=0) "An offset for setpoint control"

  Sensors.TemperatureTwoPort TOut(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    m_flow_nominal=m_flow_nominal,
    tau=0) "Tank outlet temperature"
equation
  connect(sou.ports[1], iceTan.port_a)
  connect(res.port_b, bou.ports[1])
  connect(dat.y[3], sou.m_flow_in)
  connect(dat.y[1], TIn.Celsius)
  connect(TIn.Kelvin, sou.T_in)
  connect(dat.y[2], TOutSet.Celsius)
  connect(TOutSet.Kelvin, TSet.u1)
  connect(offSet.y, TSet.u2)
  connect(TSet.y, iceTan.TSet)
  connect(iceTan.port_b, TOut.port_a)
  connect(TOut.port_b, res.port_a)
end PartialChargingDischarging;


within Buildings.Fluid.Storage.Validation;
model HeatExchangerDynamics
  "Test model for stratified tank with steady-state and dynamic heat exchanger balance"
  extends Modelica.Icons.Example;

  package Medium = Buildings.Media.Water "Medium model";

  constant Integer nSeg = 7 "Number of segments in tank";

  parameter Modelica.Units.SI.HeatFlowRate QHex_flow_nominal=2000
    "Design heat flow rate of heat exchanger";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=QHex_flow_nominal/
      4200/4;

  Buildings.Fluid.Sources.Boundary_pT watInTan(
    redeclare package Medium = Medium,
    use_T_in=false,
    nPorts=2,
    T=273.15 + 30,
    p(displayUnit="Pa")) "Boundary condition for water in the tank"
  Buildings.Fluid.Sources.MassFlowSource_T mHex_flow1(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    T=273.15 + 60,
    nPorts=1) "Mass flow rate through the heat exchanger"
  model Tank = StratifiedEnhancedInternalHex (
    redeclare package Medium = Medium,
    redeclare package MediumHex = Medium,
    hTan=2,
    dIns=0.3,
    VTan=0.3,
    nSeg=nSeg,
    hHex_a=1,
    hHex_b=0.2,
    Q_flow_nominal=QHex_flow_nominal,
    TTan_nominal=313.15,
    THex_nominal=333.15,
    mHex_flow_nominal=m_flow_nominal,
    show_T=true,
    m_flow_nominal=m_flow_nominal,
    energyDynamicsHex=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Tank with dynamic heat exchanger balance";

  Tank tanDyn "Tank with dynamic heat exchanger balance"

  Tank tanSte(energyDynamicsHex=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Tank with steady-state heat exchanger balance"

  Modelica.Blocks.Sources.Trapezoid mHex_flow_in(
    period=7200,
    amplitude=m_flow_nominal,
    offset=0,
    rising=1800,
    width=1800,
    falling=1800,
    startTime=900) "Control signal for mass flow rate in heat exchanger"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    T=273.15 + 30,
    p(displayUnit="Pa"),
    nPorts=2) "Sink boundary condition"

  Buildings.Fluid.Sources.MassFlowSource_T mHex_flow2(
    redeclare package Medium = Medium,
    use_m_flow_in=true,
    T=273.15 + 60,
    nPorts=1) "Mass flow rate through the heat exchanger"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTanDyn(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    m_flow_nominal=m_flow_nominal,
    tau=0) "Temperature sensor at tank outlet"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTanSte(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    m_flow_nominal=m_flow_nominal,
    tau=0) "Temperature sensor at tank outlet"
  Buildings.Fluid.Sources.MassFlowSource_T mWatTanDyn_flow(
    redeclare package Medium = Medium,
    nPorts=1) "Mass flow rate through the tank"
  Buildings.Fluid.Sources.MassFlowSource_T mWatTanSte_flow(
    redeclare package Medium = Medium,
    nPorts=1) "Mass flow rate through the tank"
equation
  connect(mHex_flow_in.y, mHex_flow1.m_flow_in)
  connect(mHex_flow2.m_flow_in, mHex_flow_in.y)
  connect(mHex_flow2.ports[1], tanSte.portHex_a)
  connect(watInTan.ports[1], tanSte.port_a)
  connect(mHex_flow1.ports[1], tanDyn.portHex_a)
  connect(watInTan.ports[2], tanDyn.port_a)
  connect(senTanDyn.port_a, tanDyn.portHex_b)
  connect(senTanSte.port_a, tanSte.portHex_b)
  connect(senTanDyn.port_b, sin.ports[1])
  connect(senTanSte.port_b, sin.ports[2])
  connect(mWatTanDyn_flow.ports[1], tanDyn.port_b)
  connect(mWatTanSte_flow.ports[1], tanSte.port_b)
end HeatExchangerDynamics;