within Buildings.Fluid.Geothermal.ZonedBorefields.Validation.FEFLOW;
model MassFlowRatePulse100 "Comparative model validation with FEFLOW for a pulse response"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water;

  parameter Modelica.Units.SI.Temperature T_start=286.65
    "Initial temperature of the soil";

  parameter ZonedBorefields.Data.Filling.Bentonite filDat(
    kFil=1.0)
    "Borehole filling data"
  parameter ZonedBorefields.Data.Soil.SandStone soiDat(
    kSoi=1.1,
    cSoi=1.4E6/1800,
    dSoi=1800)
    "Soil data"

  parameter Buildings.Fluid.Geothermal.ZonedBorefields.Data.Borefield.Template
    borFieDat(
    filDat=filDat,
    soiDat=soiDat,
    conDat=conDat) "Borefield data"

  parameter Data.Configuration.Template conDat(
    borCon=Buildings.Fluid.Geothermal.Borefields.Types.BoreholeConfiguration.DoubleUTubeParallel,
    mBor_flow_nominal=20*995.586/24/3600*{1, 1},
    each dp_nominal=85*4*100*{1, 1},
    hBor=85,
    rBor=0.075,
    dBor=0.5,
    nZon=2,
    iZon={1,1,1,1,1,1,1,1,
          1,1,1,1,1,1,1,1,
          1,1,1,1,1,1,1,1,
          2,2,2,2},
    cooBor={{3,  1.5}, {6,  1.5}, {9,  1.5}, {12,  1.5}, {1.5,  4.5}, {4.5,  4.5}, {7.5,  4.5}, {10.5,  4.5},
            {3,  7.5}, {6,  7.5}, {9,  7.5}, {12,  7.5}, {1.5, 10.5}, {4.5, 10.5}, {7.5, 10.5}, {10.5, 10.5},
            {3, 13.5}, {6, 13.5}, {9, 13.5}, {12, 13.5}, {1.5, 16.5}, {4.5, 16.5}, {7.5, 16.5}, {10.5, 16.5},
            {5.4, 22.5}, {10.8, 22.5}, {2.7, 28.5}, {8.1, 28.5}},
    rTub=0.016,
    kTub=0.42,
    eTub=0.0029,
    xC=(2*((0.04/2)^2))^(1/2)) "Construction data"

  final parameter Integer nZon(min=1) = borFieDat.conDat.nZon
    "Total number of independent bore field zones";

  replaceable Modelica.Blocks.Sources.Constant TIn[nZon](
    each k(each final unit="K",
      each displayUnit="degC")=293.15)
    constrainedby Modelica.Blocks.Interfaces.SO
    "Inlet temperature into each zone"

  Modelica.Blocks.Sources.CombiTimeTable TOut(
    tableOnFile=true,
    tableName="tab1",
    columns={2,3},
    smoothness=Modelica.Blocks.Types.Smoothness.LinearSegments,
    fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Geothermal/ZonedBorefields/Validation/FEFLOW/Pulse.txt"),
    y(each unit="K",
      each displayUnit="degC"))
    "Reference results for the borehole fluid outlet temperature in each zone from FEFLOW"

  Modelica.Units.SI.TemperatureDifference dTOut[2] =
    {if m_flow[i].y > 1E-5 then TOut.y[i] - TBorFieOut[i].T else 0 for i in 1:2}
    "Temperature difference FEFLOW minus Modelica outlet temperature";
  Real dQNor_flow[2] =
    {if m_flow[i].y > 1E-5 then (TOut.y[i] - sou[i].T) / (TBorFieOut[i].T - sou[i].T)-1 else 0 for i in 1:2}
    "Difference in heat extraction FEFLOW divided by Modelica";

  Buildings.Fluid.Geothermal.ZonedBorefields.TwoUTubes borFie(
    redeclare package Medium = Medium,
    allowFlowReversal=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    TExt0_start=T_start,
    borFieDat=borFieDat,
    dT_dz=0) "Borefield"
  Sensors.TemperatureTwoPort TBorFieIn[nZon](
    redeclare each package Medium = Medium,
    each allowFlowReversal=false,
    each T_start=T_start,
    m_flow_nominal=borFieDat.conDat.mZon_flow_nominal,
    each tau=0) "Inlet temperature of the borefield"
  Sensors.TemperatureTwoPort TBorFieOut[nZon](
    redeclare each package Medium = Medium,
    each allowFlowReversal=false,
    each T_start=T_start,
    m_flow_nominal=borFieDat.conDat.mZon_flow_nominal,
    each tau=0) "Outlet temperature of the borefield"
  Sources.Boundary_ph sin[nZon](
    redeclare each package Medium = Medium,
    each nPorts=1) "Sink"
  replaceable Modelica.Blocks.Sources.Pulse m_flow[nZon](
    amplitude=borFieDat.conDat.mZon_flow_nominal,
    each width=50,
    each period=3600*24*10,
    each startTime=0)
    constrainedby Modelica.Blocks.Interfaces.SO
    "Mass flow rate into each zone"

  Sources.MassFlowSource_T sou[nZon](
    redeclare each package Medium = Medium,
    each use_m_flow_in=true,
    each use_T_in=true,
    each nPorts=1) "Mass flow source"

equation
  connect(TBorFieIn.port_b,borFie. port_a)
  connect(borFie.port_b, TBorFieOut.port_a)
  connect(sin[:].ports[1],TBorFieOut[:]. port_b)
  connect(sou.ports[1], TBorFieIn.port_a)
  connect(m_flow.y, sou.m_flow_in)
  connect(TIn.y, sou.T_in)
end MassFlowRatePulse100;


Comparative model validation with FEFLOW for a response to a pulse in inlet mass flow rate. This validation cases compares the outlet temperature of a borefield with two
zones against the temperatures that were calculated with the FEFLOW software.
The mass flow rate in both zones is a pulse function.

within Buildings.Fluid.Geothermal.ZonedBorefields.Validation.FEFLOW;
model MassFlowRatePulse50
  "Comparative model validation with FEFLOW for a response to a pulse in inlet mass flow rate"
  extends Buildings.Fluid.Geothermal.ZonedBorefields.Validation.FEFLOW.MassFlowRatePulse100(
    redeclare Modelica.Blocks.Sources.Pulse m_flow(
      amplitude=0.5*borFieDat.conDat.mZon_flow_nominal,
      each period=3600*24*10,
      offset=0.5*borFieDat.conDat.mZon_flow_nominal,
      each startTime=0),
      TOut(
        fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Geothermal/ZonedBorefields/Validation/FEFLOW/MassFlowRatePulse.txt")));
end MassFlowRatePulse50;


Comparative model validation with FEFLOW for a step response. This validation cases compares the outlet temperature of a borefield with two
zones against the temperatures that were calculated with the FEFLOW software.
The mass flow rate in both zones is a step function.

within Buildings.Fluid.Geothermal.ZonedBorefields.Validation.FEFLOW;
model MassFlowRateStep100 "Comparative model validation with FEFLOW for a step response"
  extends Buildings.Fluid.Geothermal.ZonedBorefields.Validation.FEFLOW.MassFlowRatePulse100(
    redeclare Modelica.Blocks.Sources.Step m_flow(
      height=borFieDat.conDat.mZon_flow_nominal,
      each offset=0,
      each startTime=0),
      TOut(
        fileName=Modelica.Utilities.Files.loadResource("modelica://Buildings/Resources/Data/Fluid/Geothermal/ZonedBorefields/Validation/FEFLOW/Step.txt")));
end MassFlowRateStep100;


within Buildings.Fluid.Geothermal.ZonedBorefields.Validation;
package FEFLOW "Package with models for comparative model validation with FEFLOW"
  extends Modelica.Icons.ExamplesPackage;
end FEFLOW;


Heat exchanger with constant effectiveness. Model for a heat exchanger with constant effectiveness.

within Buildings.Fluid.HeatExchangers;
model ConstantEffectiveness "Heat exchanger with constant effectiveness"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialEffectiveness(
    sensibleOnly1 = true,
    sensibleOnly2 = true,
    final prescribedHeatFlowRate1=true,
    final prescribedHeatFlowRate2=true,
    Q1_flow = eps * QMax_flow,
    Q2_flow = -Q1_flow,
    mWat1_flow = 0,
    mWat2_flow = 0);

  parameter Modelica.Units.SI.Efficiency eps(max=1) = 0.8
    "Heat exchanger effectiveness";
end ConstantEffectiveness;


within Buildings.Fluid.HeatExchangers;
model DryCoilCounterFlow
  "Counterflow coil with discretization along the flow paths and without humidity condensation"
  extends Buildings.Fluid.Interfaces.PartialFourPortInterface(show_T=false);
  extends Buildings.Fluid.Interfaces.FourPortFlowResistanceParameters(
    final computeFlowResistance1=false,
    final computeFlowResistance2=false,
    from_dp1=false,
    from_dp2=false);

  parameter Modelica.Units.SI.ThermalConductance UA_nominal(min=0)
    "Thermal conductance at nominal flow, used to compute heat capacity"
  parameter Real r_nominal=2/3
    "Ratio between air-side and water-side convective heat transfer coefficient"
  parameter Integer nEle(min=1) = 4
    "Number of pipe segments used for discretization"

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

  parameter Modelica.Units.SI.Time tau1=10
    "Time constant at nominal flow for medium 1"
  parameter Modelica.Units.SI.Time tau2=2
    "Time constant at nominal flow for medium 2"
  parameter Modelica.Units.SI.Time tau_m=5
    "Time constant of metal at nominal UA value"

  parameter Boolean waterSideFlowDependent=true
    "Set to false to make water-side hA independent of mass flow rate"
  parameter Boolean airSideFlowDependent=true
    "Set to false to make air-side hA independent of mass flow rate"
  parameter Boolean waterSideTemperatureDependent=false
    "Set to false to make water-side hA independent of temperature"
  parameter Boolean airSideTemperatureDependent=false
    "Set to false to make air-side hA independent of temperature"

  parameter Modelica.Units.SI.ThermalConductance GDif=1E-2*UA_nominal/max(1, (nEle - 1))
    "Thermal conductance to approximate diffusion (which improves model at near-zero flow rates)"
  Modelica.Units.SI.HeatFlowRate Q1_flow=sum(ele[i].Q1_flow for i in 1:nEle)
    "Heat transferred from solid into medium 1";
  Modelica.Units.SI.HeatFlowRate Q2_flow=sum(ele[i].Q2_flow for i in 1:nEle)
    "Heat transferred from solid into medium 2";

  Modelica.Units.SI.Temperature T1[nEle]=ele[:].vol1.T "Water temperature";
  Modelica.Units.SI.Temperature T2[nEle]=ele[:].vol2.T "Air temperature";
  Modelica.Units.SI.Temperature T_m[nEle]=ele[:].con1.solid.T
    "Metal temperature";

  BaseClasses.HADryCoil hA(
    final UA_nominal=UA_nominal,
    final m_flow_nominal_a=m2_flow_nominal,
    final m_flow_nominal_w=m1_flow_nominal,
    final waterSideTemperatureDependent=waterSideTemperatureDependent,
    final waterSideFlowDependent=waterSideFlowDependent,
    final airSideTemperatureDependent=airSideTemperatureDependent,
    final airSideFlowDependent=airSideFlowDependent,
    r_nominal=r_nominal) "Model for convective heat transfer coefficient"
protected
  final parameter Boolean use_temSen_1=
    waterSideTemperatureDependent and allowFlowReversal1 and
    (energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState)
    "Flag, set to true if the temperature sensor 1 is used"

  final parameter Boolean use_temSen_2=
    airSideTemperatureDependent and allowFlowReversal2 and
    (energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState)
    "Flag, set to true if the temperature sensor 2 is used"

  Buildings.Fluid.Sensors.TemperatureTwoPort temSen_1(
   redeclare package Medium = Medium1,
    allowFlowReversal=allowFlowReversal1,
    m_flow_nominal=m1_flow_nominal,
    tau=if use_temSen_1 then 1 else 0)
    "Temperature sensor, used to obtain temperature for convective heat transfer calculation"
  Buildings.Fluid.Sensors.MassFlowRate masFloSen_1(
    redeclare package Medium = Medium1) "Mass flow rate sensor"

  Buildings.Fluid.Sensors.TemperatureTwoPort temSen_2(
    redeclare package Medium = Medium2,
    final allowFlowReversal=allowFlowReversal2,
    m_flow_nominal=m2_flow_nominal,
    tau=if use_temSen_2 then 1 else 0)
    "Temperature sensor, used to obtain temperature for convective heat transfer calculation"
  Buildings.Fluid.Sensors.MassFlowRate masFloSen_2(
    redeclare package Medium = Medium2)
    "Mass flow rate sensor"

  Modelica.Blocks.Math.Gain gai_1(k=1/nEle)
    "Gain medium-side 1 to take discretization into account"
  Modelica.Blocks.Math.Gain gai_2(k=1/nEle)
    "Gain medium-side 2 to take discretization into account"

  replaceable model HexElement = BaseClasses.HexElementSensible "Model for a heat exchanger element";

  HexElement ele[nEle](
    redeclare each package Medium1 = Medium1,
    redeclare each package Medium2 = Medium2,
    each allowFlowReversal1=allowFlowReversal1,
    each allowFlowReversal2=allowFlowReversal2,
    each tau1=tau1/nEle,
    each m1_flow_nominal=m1_flow_nominal,
    each tau2=tau2,
    each m2_flow_nominal=m2_flow_nominal,
    each tau_m=tau_m/nEle,
    each UA_nominal=UA_nominal/nEle,
    each energyDynamics=energyDynamics,
    initialize_p1 = {(i == 1 and (not Medium1.singleState)) for i in 1:nEle},
    initialize_p2 = {(i == 1 and (not Medium2.singleState)) for i in 1:nEle},
    each deltaM1=deltaM1,
    each deltaM2=deltaM2,
    each from_dp1=from_dp1,
    each from_dp2=from_dp2,
    dp1_nominal={if i == 1 then dp1_nominal else 0 for i in 1:nEle},
    dp2_nominal={if i == nEle then dp2_nominal else 0 for i in 1:nEle})
    "Heat exchanger element"
  Modelica.Blocks.Routing.Replicator rep1(nout=nEle) "Signal replicator"
  Modelica.Blocks.Routing.Replicator rep2(nout=nEle) "Signal replicator"

  Modelica.Blocks.Sources.RealExpression THA1(
    y=if waterSideTemperatureDependent then
        if allowFlowReversal1 then
          if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
            temSen_1.T
          else
            ele[1].vol1.T
        else
          Medium1.temperature(
            state=Medium1.setState_phX(p=port_a1.p, h=inStream(port_a1.h_outflow), X=inStream(port_a1.Xi_outflow)))
        else
          Medium1.T_default)
    "Temperature used for convective heat transfer calculation for medium 1 (water-side)"

  Modelica.Blocks.Sources.RealExpression THA2(
    y=if airSideTemperatureDependent then
        if allowFlowReversal1 then
          if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
            temSen_2.T
          else
            ele[nEle].vol2.T
        else
          Medium2.temperature(
            state=Medium2.setState_phX(p=port_a2.p, h=inStream(port_a2.h_outflow), X=inStream(port_a2.Xi_outflow)))
        else
          Medium2.T_default)
    "Temperature used for convective heat transfer calculation for medium 2 (air-side)"

  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theCon1[nEle-1](each final G=GDif)
    "Thermal connector to approximate diffusion in medium 1"

  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theCon2[nEle-1](each final G=GDif)
    "Thermal connector to approximate diffusion in medium 2"

initial equation
  assert(UA_nominal > 0,
    "Parameter UA_nominal is negative. Check heat exchanger parameters.");

equation
  connect(masFloSen_1.m_flow, hA.m1_flow)
  connect(port_a2, masFloSen_2.port_a)
  connect(masFloSen_2.port_b, temSen_2.port_a)
  connect(masFloSen_2.m_flow, hA.m2_flow)
  connect(hA.hA_1, gai_1.u)
  connect(hA.hA_2, gai_2.u)
  connect(port_a1, masFloSen_1.port_a)
  connect(masFloSen_1.port_b, temSen_1.port_a)

  connect(ele[nEle].port_b1, port_b1)
  connect(ele[1].port_b2, port_b2)
  for i in 1:nEle - 1 loop
    connect(ele[i].port_b1, ele[i + 1].port_a1)
    connect(ele[i].port_a2, ele[i + 1].port_b2)
  connect(theCon1[i].port_a, ele[i].heaPor1)
  connect(theCon1[i].port_b, ele[i+1].heaPor1)
  connect(theCon2[i].port_a, ele[i].heaPor2)
  connect(theCon2[i].port_b, ele[i+1].heaPor2)
  end for;

  connect(gai_1.y, rep1.u)
  connect(rep1.y, ele.Gc_1)
  connect(gai_2.y, rep2.u)
  connect(rep2.y, ele.Gc_2)
  connect(THA1.y, hA.T_1)
  connect(THA2.y, hA.T_2)

  connect(temSen_1.port_b, ele[1].port_a1)

  connect(temSen_2.port_b, ele[nEle].port_a2)
end DryCoilCounterFlow;


within Buildings.Fluid.HeatExchangers;
model DryCoilDiscretized
  "Coil with discretization along the flow paths and no humidity condensation"
  extends Buildings.Fluid.Interfaces.PartialFourPortInterface(show_T=false);
  extends Buildings.Fluid.Interfaces.FourPortFlowResistanceParameters(
    final computeFlowResistance1=true,
    final computeFlowResistance2=true,
    from_dp1 = false,
    from_dp2 = false);

  constant Boolean initialize_p1 = not Medium1.singleState
    "Set to true to initialize the pressure of volume 1"
  constant Boolean initialize_p2 = not Medium2.singleState
    "Set to true to initialize the pressure of volume 2"

  constant Boolean airSideTemperatureDependent = false
    "Set to false to make air-side hA independent of temperature"

  parameter Modelica.Units.SI.ThermalConductance UA_nominal(min=0)
    "Thermal conductance at nominal flow, used to compute heat capacity"
  parameter Integer nReg(min=2)=2 "Number of registers"
  parameter Integer nPipPar(min=1) = 3
    "Number of parallel pipes in each register"
  parameter Integer nPipSeg(min=1) = 4
    "Number of pipe segments per register used for discretization"
  parameter Boolean use_dh1 = false
    "Set to true to specify hydraulic diameter for pipe pressure drop"
  parameter Boolean use_dh2 = false
    "Set to true to specify hydraulic diameter for duct pressure drop)"

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

  parameter Modelica.Units.SI.Length dh1=0.025
    "Hydraulic diameter for a single pipe"
  parameter Real ReC_1=4000
    "Reynolds number where transition to turbulence starts inside pipes"
  parameter Real ReC_2=4000
    "Reynolds number where transition to turbulence starts inside ducts"
  parameter Modelica.Units.SI.Length dh2=1 "Hydraulic diameter for duct"
  parameter Modelica.Units.SI.Time tau1=20
    "Time constant at nominal flow for medium 1"
  parameter Modelica.Units.SI.Time tau2=10
    "Time constant at nominal flow for medium 2"
  parameter Modelica.Units.SI.Time tau_m=20
    "Time constant of metal at nominal UA value"
  parameter Boolean waterSideFlowDependent = false
    "Set to false to make water-side hA independent of mass flow rate"
  parameter Boolean airSideFlowDependent = false
    "Set to false to make air-side hA independent of mass flow rate"
  parameter Boolean waterSideTemperatureDependent = false
    "Set to false to make water-side hA independent of temperature"
  parameter Modelica.Units.SI.MassFlowRate mStart_flow_a1=m1_flow_nominal
    "Guess value for mass flow rate at port_a1"
  parameter Modelica.Units.SI.MassFlowRate mStart_flow_a2=m2_flow_nominal
    "Guess value for mass flow rate at port_a2"

  Modelica.Units.SI.HeatFlowRate Q1_flow=sum(hexReg[i].Q1_flow for i in 1:nReg)
    "Heat transferred from solid into medium 1";
  Modelica.Units.SI.HeatFlowRate Q2_flow=sum(hexReg[i].Q2_flow for i in 1:nReg)
    "Heat transferred from solid into medium 2";

  Buildings.Fluid.HeatExchangers.BaseClasses.CoilRegister hexReg[nReg](
    redeclare each package Medium1 = Medium1,
    redeclare each package Medium2 = Medium2,
    each final allowFlowReversal1=allowFlowReversal1,
    each final allowFlowReversal2=allowFlowReversal2,
    each final nPipPar=nPipPar,
    each final nPipSeg=nPipSeg,
    each final m1_flow_nominal=m1_flow_nominal/nPipPar,
    each final m2_flow_nominal=m2_flow_nominal/nPipPar/nPipSeg,
    each tau1=tau1,
    each tau2=tau2,
    each tau_m=tau_m,
    each final energyDynamics=energyDynamics,
    initialize_p1 = {(i == 1 and (not Medium1.singleState)) for i in 1:nReg},
    initialize_p2 = {(i == 1 and (not Medium2.singleState)) for i in 1:nReg},
    each from_dp1=from_dp1,
    each linearizeFlowResistance1=linearizeFlowResistance1,
    each deltaM1=deltaM1,
    each from_dp2=from_dp2,
    each linearizeFlowResistance2=linearizeFlowResistance2,
    each deltaM2=deltaM2,
    each dp1_nominal=0,
    each dp2_nominal=0,
    each final UA_nominal=UA_nominal/nReg) "Heat exchanger register"

  Buildings.Fluid.HeatExchangers.BaseClasses.PipeManifoldFixedResistance
    pipMan_a(
    redeclare package Medium = Medium1,
    final nPipPar=nPipPar,
    final m_flow_nominal=m1_flow_nominal,
    final dp_nominal=dp1_nominal,
    final dh=dh1,
    final ReC=ReC_1,
    final mStart_flow_a=mStart_flow_a1,
    final linearized=linearizeFlowResistance1,
    final use_dh=use_dh1,
    final deltaM=deltaM1,
    final from_dp=from_dp1,
    final allowFlowReversal=allowFlowReversal1) "Pipe manifold at port a"

  Buildings.Fluid.HeatExchangers.BaseClasses.PipeManifoldNoResistance pipMan_b(
    redeclare package Medium = Medium1,
    final nPipPar=nPipPar,
    final mStart_flow_a=-mStart_flow_a1,
    final allowFlowReversal=allowFlowReversal1) "Pipe manifold at port b"

  Buildings.Fluid.HeatExchangers.BaseClasses.DuctManifoldNoResistance ducMan_b(
    redeclare package Medium = Medium2,
    final nPipPar=nPipPar,
    final nPipSeg=nPipSeg,
    final mStart_flow_a=-mStart_flow_a2,
    final allowFlowReversal=allowFlowReversal2) "Duct manifold at port b"

  Buildings.Fluid.HeatExchangers.BaseClasses.DuctManifoldFixedResistance
    ducMan_a(
    redeclare package Medium = Medium2,
    final nPipPar = nPipPar,
    final nPipSeg = nPipSeg,
    final m_flow_nominal=m2_flow_nominal,
    final dp_nominal=dp2_nominal,
    final dh=dh2,
    final ReC=ReC_2,
    final mStart_flow_a=mStart_flow_a2,
    final linearized=linearizeFlowResistance2,
    final use_dh=use_dh2,
    final deltaM=deltaM2,
    final from_dp=from_dp2,
    final allowFlowReversal=allowFlowReversal2) "Duct manifold at port a"

  BaseClasses.HADryCoil hA(
    final UA_nominal=UA_nominal,
    final m_flow_nominal_a=m2_flow_nominal,
    final m_flow_nominal_w=m1_flow_nominal,
    final waterSideTemperatureDependent=waterSideTemperatureDependent,
    final waterSideFlowDependent=waterSideFlowDependent,
    final airSideTemperatureDependent=airSideTemperatureDependent,
    final airSideFlowDependent=airSideFlowDependent)
    "Model for convective heat transfer coefficient"

protected
  constant Boolean allowCondensation = false
    "Set to false to compute sensible heat transfer only"

  final parameter Boolean use_temSen_1=
    waterSideTemperatureDependent and allowFlowReversal1 and
    (energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState)
    "Flag, set to true if the temperature sensor 1 is used"

  final parameter Boolean use_temSen_2=
    airSideTemperatureDependent and allowFlowReversal2 and
    (energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState)
    "Flag, set to true if the temperature sensor 2 is used"

  Buildings.Fluid.Sensors.TemperatureTwoPort temSen_1(
   redeclare package Medium = Medium1,
    allowFlowReversal=allowFlowReversal1,
    m_flow_nominal=m1_flow_nominal,
    tau=if use_temSen_1 then 1 else 0)
    "Temperature sensor, used to obtain temperature for convective heat transfer calculation"
  Buildings.Fluid.Sensors.MassFlowRate masFloSen_1(
    redeclare package Medium = Medium1) "Mass flow rate sensor"

  Buildings.Fluid.Sensors.TemperatureTwoPort temSen_2(
    redeclare package Medium = Medium2,
    final allowFlowReversal=allowFlowReversal2,
    m_flow_nominal=m2_flow_nominal,
    tau=if use_temSen_2 then 1 else 0)
    "Temperature sensor, used to obtain temperature for convective heat transfer calculation"
  Buildings.Fluid.Sensors.MassFlowRate masFloSen_2(
    redeclare package Medium = Medium2)
    "Mass flow rate sensor"

  BaseClasses.CoilHeader hea1[div(nReg,2)](
      redeclare each final package Medium = Medium1,
      each final nPipPar = nPipPar,
      each final mStart_flow_a=mStart_flow_a1,
      each allowFlowReversal=allowFlowReversal1)
   if nReg > 1 "Pipe header to redirect flow into next register"
  BaseClasses.CoilHeader hea2[div(nReg,2)-1](
      redeclare each final package Medium = Medium1,
      each final nPipPar = nPipPar,
      each final mStart_flow_a=mStart_flow_a1,
      each allowFlowReversal=allowFlowReversal1)
   if nReg > 2 "Pipe header to redirect flow into next register"
  Modelica.Blocks.Math.Gain gai_1(k=1/nReg)
    "Gain medium-side 1 to take discretization into account"
  Modelica.Blocks.Math.Gain gai_2(k=1/nReg)
    "Gain medium-side 2 to take discretization into account"

  Modelica.Blocks.Sources.RealExpression THA1(
    y=if waterSideTemperatureDependent then
        if allowFlowReversal1 then
          if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
            temSen_1.T
          else
            hexReg[1].ele[1,1].vol1.T
        else
          Medium1.temperature(
            state=Medium1.setState_phX(p=port_a1.p, h=inStream(port_a1.h_outflow), X=inStream(port_a1.Xi_outflow)))
        else
          Medium1.T_default)
    "Temperature used for convective heat transfer calculation for medium 1 (water-side)"

  Modelica.Blocks.Sources.RealExpression THA2(
    y=if airSideTemperatureDependent then
        if allowFlowReversal1 then
          if energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState then
            temSen_2.T
          else
            hexReg[1].ele[1,1].vol2.T
        else
          Medium2.temperature(
            state=Medium2.setState_phX(p=port_a2.p, h=inStream(port_a2.h_outflow), X=inStream(port_a2.Xi_outflow)))
        else
          Medium2.T_default)
    "Temperature used for convective heat transfer calculation for medium 2 (air-side)"

  parameter Modelica.Units.SI.ThermalConductance GDif1=1E-2*UA_nominal/(nPipPar
      *max(1, nPipSeg - 1)*nReg)
    "Thermal conductance to approximate diffusion (which improves model at near-zero flow rates)"

  parameter Modelica.Units.SI.ThermalConductance GDif2=1E-2*UA_nominal/(nPipPar
      *nPipSeg*max(1, nReg - 1))
    "Thermal conductance to approximate diffusion (which improves model at near-zero flow rates)"

  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theCon1[nReg, nPipPar, nPipSeg-1](
    each final G=GDif1)
    "Thermal connector between the pipe segements to approximate diffusion in water (diffusion through the header is neglected)"

  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theCon2[nReg-1, nPipPar, nPipSeg](
    each final G=GDif2)
    "Thermal connector to approximate diffusion in air"

initial equation
  assert(UA_nominal>0, "Parameter UA_nominal is negative. Check heat exchanger parameters.");
equation

  // air stream connections
  for i in 2:nReg loop
    connect(hexReg[i].port_a2, hexReg[i-1].port_b2)
  end for;
  connect(ducMan_a.port_b, hexReg[1].port_a2)
  connect(hexReg[nReg].port_b2, ducMan_b.port_b)
  connect(pipMan_a.port_b, hexReg[1].port_a1)
  connect(hexReg[nReg].port_a1, pipMan_b.port_b)
  connect(pipMan_b.port_a, port_b1)
  connect(ducMan_b.port_a, port_b2)
  for i in 1:2:nReg loop

  // header after first hex register
    connect(hexReg[i].port_b1, hea1[div((i+1),2)].port_a)
    connect(hea1[div((i+1),2)].port_b, hexReg[i+1].port_b1)
  end for;
  // header after 2nd hex register
  for i in 2:2:(nReg-1) loop
    connect(hexReg[i].port_a1, hea2[div(i,2)].port_a)
    connect(hea2[div(i,2)].port_b, hexReg[i+1].port_a1)
  end for;
  connect(masFloSen_1.m_flow, hA.m1_flow)
  connect(port_a2, masFloSen_2.port_a)
  connect(masFloSen_2.port_b, temSen_2.port_a)
  connect(temSen_2.port_b, ducMan_a.port_a)
  connect(masFloSen_2.m_flow, hA.m2_flow)
  connect(hA.hA_1, gai_1.u)
  connect(hA.hA_2, gai_2.u)
  for i in 1:nReg loop
    connect(gai_1.y, hexReg[i].Gc_1)
    connect(gai_2.y, hexReg[i].Gc_2)
  end for;
  connect(port_a1, masFloSen_1.port_a)
  connect(masFloSen_1.port_b, temSen_1.port_a)
  connect(temSen_1.port_b, pipMan_a.port_a)
  connect(THA1.y, hA.T_1)
  connect(THA2.y, hA.T_2)

  // Heat diffusion approximation inside pipes
  for iReg in 1:nReg loop
    for iPipPar in 1:nPipPar loop
      for iPipSeg in 1:nPipSeg-1 loop
          connect(hexReg[iReg].heaPor1[iPipPar,iPipSeg],  theCon1[iReg, iPipPar,iPipSeg].port_a)
          connect(theCon1[iReg, iPipPar,iPipSeg].port_b, hexReg[iReg].heaPor1[iPipPar,iPipSeg+1])
      end for;
    end for;
  end for;

  // Heat diffusion approximation along air path
  for iReg in 1:nReg-1 loop
    for iPipPar in 1:nPipPar loop
      for iPipSeg in 1:nPipSeg loop
         connect(hexReg[iReg].heaPor2[iPipPar, iPipSeg], theCon2[iReg, iPipPar, iPipSeg].port_a)
         connect(theCon2[iReg, iPipPar, iPipSeg].port_b, hexReg[iReg+1].heaPor2[iPipPar, iPipSeg])
      end for;
    end for;
  end for;
end DryCoilDiscretized;


Heat exchanger with effectiveness - NTU relation and no moisture condensation. Model of a coil without humidity condensation.
This model transfers heat in the amount of

within Buildings.Fluid.HeatExchangers;
model DryCoilEffectivenessNTU
  "Heat exchanger with effectiveness - NTU relation and no moisture condensation"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialEffectivenessNTU(
    UA = 1/(1/hA.hA_1 + 1/hA.hA_2));

  parameter Real r_nominal(
    min=0,
    max=1) = 2/3
    "Ratio between air-side and water-side convective heat transfer (hA-value) at nominal condition";

  Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil hA(
    final r_nominal=r_nominal,
    final UA_nominal=UA_nominal,
    final m_flow_nominal_w=m1_flow_nominal,
    final m_flow_nominal_a=m2_flow_nominal,
    waterSideTemperatureDependent=false,
    airSideTemperatureDependent=false)
    "Model for convective heat transfer coefficient";

equation
  // Convective heat transfer coefficient
  hA.m1_flow = m1_flow;
  hA.m2_flow = m2_flow;
  hA.T_1 = T_in1;
  hA.T_2 = T_in2;
end DryCoilEffectivenessNTU;


Evaporator or condenser with refrigerant experiencing constant temperature phase change. Model for a constant temperature evaporator or condenser based on a ε-NTU
heat exchanger model.

within Buildings.Fluid.HeatExchangers;
model EvaporatorCondenser
  "Evaporator or condenser with refrigerant experiencing constant temperature phase change"
  extends Buildings.Fluid.Interfaces.TwoPortHeatMassExchanger(redeclare final
      Buildings.Fluid.MixingVolumes.MixingVolume vol(final
        prescribedHeatFlowRate=false));

  parameter Modelica.Units.SI.ThermalConductance UA
    "Thermal conductance of heat exchanger";
  parameter Modelica.Units.SI.ThermalConductance UA_small=UA/10
    "Small thermal conductance for regularisation of heat transfer "

  Modelica.Blocks.Interfaces.RealOutput Q_flow(unit="W")
    "Heat added to the fluid"
  Modelica.Blocks.Interfaces.RealOutput T(unit="K") "Medium temperature"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a port_ref
    "Temperature and heat flow from the refrigerant"

  Modelica.Units.SI.Efficiency NTU=UA/(Buildings.Utilities.Math.Functions.smoothMax(
      abs(port_a.m_flow),
      m_flow_small,
      m_flow_small)*cp_default) "Number of transfer units of heat exchanger";

  Modelica.Units.SI.Efficiency eps=Buildings.Utilities.Math.Functions.smoothMin(
      Buildings.Fluid.HeatExchangers.BaseClasses.epsilon_ntuZ(
        NTU,
        0,
        Integer(Buildings.Fluid.Types.HeatExchangerFlowRegime.ConstantTemperaturePhaseChange)),
      0.999,
      1.0e-4) "Effectiveness of heat exchanger";

  Modelica.Blocks.Sources.RealExpression UAeff(
    final y=Buildings.Utilities.Math.Functions.smoothMax(
      x1=UA,
      x2=eps*cp_default*abs(port_a.m_flow)/(1 - eps),
      deltaX=UA_small))
    "Effective heat transfer coefficient"

protected
  parameter Modelica.Units.SI.SpecificHeatCapacity cp_default=
      Medium.specificHeatCapacityCp(sta_default)
    "Specific heat capacity";

  Modelica.Thermal.HeatTransfer.Sensors.HeatFlowSensor heaFlo
    "Heat flow sensor"
  Modelica.Thermal.HeatTransfer.Components.Convection con
    "Convective heat transfer"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTem
    "Temperature sensor"
equation
  connect(heaFlo.port_b, vol.heatPort)
  connect(heaFlo.Q_flow, Q_flow)
  connect(port_ref, con.solid)
  connect(con.fluid, heaFlo.port_a)
  connect(UAeff.y, con.Gc)
  connect(senTem.port, vol.heatPort)
  connect(senTem.T, T)
end EvaporatorCondenser;


Heater or cooler with prescribed heat flow rate. Model for an ideal heater or cooler with prescribed heat flow rate to the medium.

within Buildings.Fluid.HeatExchangers;
model HeaterCooler_u "Heater or cooler with prescribed heat flow rate"
  extends Buildings.Fluid.Interfaces.TwoPortHeatMassExchanger(
    redeclare final Buildings.Fluid.MixingVolumes.MixingVolume vol(
    final prescribedHeatFlowRate=true));

  parameter Modelica.Units.SI.HeatFlowRate Q_flow_nominal
    "Heat flow rate at u=1, positive for heating";
  Modelica.Blocks.Interfaces.RealInput u(unit="1") "Control input"
  Modelica.Blocks.Interfaces.RealOutput Q_flow(unit="W")
    "Heat added to the fluid"
protected
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow preHea(
    final alpha=0)
    "Prescribed heat flow"
  Modelica.Blocks.Math.Gain gai(
    k(final unit="W")=Q_flow_nominal,
    y(final unit="W"))
    "Gain"
equation
  connect(u, gai.u)
  connect(gai.y, preHea.Q_flow)
  connect(preHea.port, vol.heatPort)
  connect(gai.y, Q_flow)
end HeaterCooler_u;


Heater with prescribed outlet temperature. Model for an ideal heater that controls its outlet temperature to
a prescribed outlet temperature.

within Buildings.Fluid.HeatExchangers;
model Heater_T "Heater with prescribed outlet temperature"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialPrescribedOutlet(
    outCon(
      final QMin_flow = 0,
      final QMax_flow = QMax_flow,
      final mWatMax_flow = 0,
      final mWatMin_flow = 0,
      final use_TSet = true,
      final use_X_wSet = false,
      final T_start=T_start,
      final X_start=Medium.X_default));

  parameter Modelica.Units.SI.HeatFlowRate QMax_flow(min=0) = Modelica.Constants.inf
    "Maximum heat flow rate for heating (positive)"

  parameter Modelica.Units.SI.Temperature T_start=Medium.T_default
    "Start value of temperature"

  Modelica.Blocks.Interfaces.RealInput TSet(
    unit="K",
    displayUnit="degC")
    "Set point temperature of the fluid that leaves port_b"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(unit="W")
    "Heat flow rate added to the fluid (if flow is from port_a to port_b)"

equation
  connect(TSet, outCon.TSet)
  connect(outCon.Q_flow, Q_flow)
end Heater_T;


Package with heat exchanger models

within Buildings.Fluid;
package HeatExchangers "Package with heat exchanger models"
  extends Modelica.Icons.VariantsPackage;
end HeatExchangers;


Plate heat exchanger with effectiveness - NTU relation and no moisture condensation. Model of a plate heat exchanger without humidity condensation.
This model transfers heat in the amount of

within Buildings.Fluid.HeatExchangers;
model PlateHeatExchangerEffectivenessNTU
  "Plate heat exchanger with effectiveness - NTU relation and no moisture condensation"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialEffectivenessNTU(
    UA = 1/(1/hA1 + 1/hA2));

  parameter Real r_nominal(min=0)=
      (k1_default * (m1_flow_nominal/eta1_default)^n1 * Pr1_default^(1/3)) /
      (k2_default * (m2_flow_nominal/eta2_default)^n2 * Pr2_default^(1/3))
    "Ratio between convective heat transfer coefficients at nominal conditions, r_nominal = hA1_nominal/hA2_nominal"

  parameter Real n1(min=0, max=1)=0.8
    "Exponent for convective heat transfer coefficient, h1~m1_flow^n1"
  parameter Real n2(min=0, max=1)=n1
   "Exponent for convective heat transfer coefficient, h2~m2_flow^n2"

  // The convection coefficients must be final as they must be consistent with UA_nominal
  final parameter Modelica.Units.SI.ThermalConductance hA1_nominal(min=0) = (1+r_nominal)*UA_nominal
    "Nominal convective heat transfer coefficient for medium 1";
  final parameter Modelica.Units.SI.ThermalConductance hA2_nominal(min=0) = hA1_nominal/r_nominal
    "Nominal convective heat transfer coefficient for medium 2";

  Modelica.Units.SI.ThermalConductance hA1
    "Convective heat transfer coefficient for medium 1";
  Modelica.Units.SI.ThermalConductance hA2
    "Convective heat transfer coefficient for medium 2";

  final parameter Medium1.DynamicViscosity eta1_default = Medium1.dynamicViscosity(sta1_default) "Dynamic viscosity";
  final parameter Medium1.ThermalConductivity k1_default = Medium1.thermalConductivity(sta1_default) "Thermal conductivity";
  final parameter Medium1.PrandtlNumber Pr1_default = Medium1.prandtlNumber(sta1_default) "Prandtl number";
  final parameter Medium2.DynamicViscosity eta2_default = Medium2.dynamicViscosity(sta2_default) "Dynamic viscosity";
  final parameter Medium2.ThermalConductivity k2_default = Medium2.thermalConductivity(sta2_default) "Thermal conductivity";
  final parameter Medium2.PrandtlNumber Pr2_default = Medium2.prandtlNumber(sta2_default) "Prandtl number";

equation
  // Convective heat transfer coefficients
 hA1 = hA1_nominal * Buildings.Utilities.Math.Functions.regNonZeroPower(
   x = m1_flow/m1_flow_nominal,
   n = n1,
   delta = 0.1);
 hA2 = hA2_nominal * Buildings.Utilities.Math.Functions.regNonZeroPower(
   x = m2_flow/m2_flow_nominal,
   n = n2,
   delta = 0.1);
end PlateHeatExchangerEffectivenessNTU;


Ideal heater, cooler, humidifier or dehumidifier with prescribed outlet conditions. Model that allows specifying the temperature and mass fraction of the fluid
that leaves the model from port_b.

within Buildings.Fluid.HeatExchangers;
model PrescribedOutlet
  "Ideal heater, cooler, humidifier or dehumidifier with prescribed outlet conditions"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialPrescribedOutlet(
    outCon(
      final T_start=T_start,
      final X_start=X_start,
      final use_TSet = use_TSet,
      final use_X_wSet = use_X_wSet,
      final QMax_flow = QMax_flow,
      final QMin_flow = QMin_flow,
      final mWatMax_flow = mWatMax_flow,
      final mWatMin_flow = mWatMin_flow));

  parameter Modelica.Units.SI.HeatFlowRate QMax_flow(min=0) = Modelica.Constants.inf
    "Maximum heat flow rate for heating (positive)"
  parameter Modelica.Units.SI.HeatFlowRate QMin_flow(max=0) = -Modelica.Constants.inf
    "Maximum heat flow rate for cooling (negative)"

  parameter Modelica.Units.SI.MassFlowRate mWatMax_flow(min=0) = Modelica.Constants.inf
    "Maximum water mass flow rate addition (positive)"
  parameter Modelica.Units.SI.MassFlowRate mWatMin_flow(max=0) = -Modelica.Constants.inf
    "Maximum water mass flow rate removal (negative)"

  parameter Modelica.Units.SI.Temperature T_start=Medium.T_default
    "Start value of temperature"
  parameter Modelica.Units.SI.MassFraction X_start[Medium.nX]=Medium.X_default
    "Start value of mass fractions m_i/m"

  parameter Boolean use_TSet = true
    "Set to false to disable temperature set point"

  parameter Boolean use_X_wSet = true
    "Set to false to disable water vapor set point"

  Modelica.Blocks.Interfaces.RealInput TSet(
    unit="K",
    displayUnit="degC") if use_TSet
    "Set point temperature of the fluid that leaves port_b"

  Modelica.Blocks.Interfaces.RealInput X_wSet(unit="1") if use_X_wSet
    "Set point for water vapor mass fraction of the fluid that leaves port_b"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(unit="W")
    "Heat flow rate added to the fluid (if flow is from port_a to port_b)"

  Modelica.Blocks.Interfaces.RealOutput mWat_flow(unit="kg/s")
    "Water vapor mass flow rate added to the fluid (if flow is from port_a to port_b)"

equation
  connect(outCon.X_wSet, X_wSet)
  connect(outCon.mWat_flow, mWat_flow)
  connect(outCon.TSet, TSet)
  connect(outCon.Q_flow, Q_flow)
end PrescribedOutlet;


Sensible cooling device with prescribed outlet temperature. Model for an ideal sensible-only cooler that controls its outlet temperature to
a prescribed outlet temperature.

within Buildings.Fluid.HeatExchangers;
model SensibleCooler_T
  "Sensible cooling device with prescribed outlet temperature"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialPrescribedOutlet(
    outCon(
      final QMin_flow = QMin_flow,
      final QMax_flow = 0,
      final mWatMax_flow = 0,
      final mWatMin_flow = 0,
      final use_TSet = true,
      final use_X_wSet = false,
      final T_start=T_start,
      final X_start=Medium.X_default));

  parameter Modelica.Units.SI.HeatFlowRate QMin_flow(max=0) = -Modelica.Constants.inf
    "Maximum heat flow rate for cooling (negative)"

  parameter Modelica.Units.SI.Temperature T_start=Medium.T_default
    "Start value of temperature"

  Modelica.Blocks.Interfaces.RealInput TSet(
    unit="K",
    displayUnit="degC")
    "Set point temperature of the fluid that leaves port_b"

  Modelica.Blocks.Interfaces.RealOutput Q_flow(unit="W")
    "Heat flow rate added to the fluid (if flow is from port_a to port_b)"

equation
  connect(TSet, outCon.TSet)
  connect(outCon.Q_flow, Q_flow)
end SensibleCooler_T;


within Buildings.Fluid.HeatExchangers;
model WetCoilCounterFlow
  "Counterflow coil with discretization along the flow paths and humidity condensation"
  extends Buildings.Fluid.HeatExchangers.DryCoilCounterFlow(
    redeclare replaceable package Medium2 =
      Modelica.Media.Interfaces.PartialCondensingGases,
    redeclare model HexElement =
      Buildings.Fluid.HeatExchangers.BaseClasses.HexElementLatent(simplify_mWat_flow=simplify_mWat_flow));

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

  Modelica.Units.SI.HeatFlowRate QSen2_flow=Q2_flow - QLat2_flow
    "Sensible heat input into air stream (negative if air is cooled)";

  Modelica.Units.SI.HeatFlowRate QLat2_flow=Buildings.Utilities.Psychrometrics.Constants.h_fg
      *mWat_flow "Latent heat input into air (negative if air is dehumidified)";

  Real SHR(
    min=0,
    max=1,
    unit="1") = QSen2_flow /
      noEvent(if (Q2_flow > 1E-6 or Q2_flow < -1E-6) then Q2_flow else 1)
       "Sensible to total heat ratio";

  Modelica.Units.SI.MassFlowRate mWat_flow=sum(ele[i].vol2.mWat_flow for i in 1
      :nEle) "Water flow rate";
end WetCoilCounterFlow;


within Buildings.Fluid.HeatExchangers;
model WetCoilDiscretized
  "Coil with discretization along the flow paths and humidity condensation"
  // When replacing the volume, the Medium is constrained so that the enthalpyOfLiquid
  // function is known. Otherwise, checkModel(...) will fail
  extends DryCoilDiscretized(
    redeclare replaceable package Medium2 =
      Modelica.Media.Interfaces.PartialCondensingGases,
    hexReg(
      redeclare final Buildings.Fluid.HeatExchangers.BaseClasses.HexElementLatent ele[nPipPar, nPipSeg]),
    temSen_1(m_flow_nominal=m1_flow_nominal),
    temSen_2(m_flow_nominal=m2_flow_nominal));

  Modelica.Units.SI.HeatFlowRate QSen2_flow=Q2_flow - QLat2_flow
    "Sensible heat input into air stream (negative if air is cooled)";

  Modelica.Units.SI.HeatFlowRate QLat2_flow=Buildings.Utilities.Psychrometrics.Constants.h_fg
      *mWat_flow "Latent heat input into air (negative if air is dehumidified)";

  Real SHR(
    min=0,
    max=1,
    unit="1") = QSen2_flow /
      noEvent(if (Q2_flow > 1E-6 or Q2_flow < -1E-6) then Q2_flow else 1)
       "Sensible to total heat ratio";

  Modelica.Units.SI.MassFlowRate mWat_flow=sum(hexReg[:].ele[:, :].vol2.mWat_flow)
    "Water flow rate";

end WetCoilDiscretized;


within Buildings.Fluid.HeatExchangers;
model WetCoilEffectivenessNTU
  "Heat exchanger with effectiveness - NTU relation and with moisture condensation"
  extends Buildings.Fluid.Interfaces.PartialFourPortInterface(
    redeclare replaceable package Medium2 = Buildings.Media.Air);
  extends Buildings.Fluid.Interfaces.FourPortFlowResistanceParameters(
    final computeFlowResistance1=true,
    final computeFlowResistance2=true);

  import con = Buildings.Fluid.Types.HeatExchangerConfiguration;
  import flo = Buildings.Fluid.Types.HeatExchangerFlowRegime;

  constant Boolean use_dynamicFlowRegime = false
    "If true, flow regime is determined using actual flow rates";
  // This switch is declared as a constant instead of a parameter
  //   as users typically need not to change this setting,
  //   and setting it true may generate events.
  //   See discussions in https://github.com/ibpsa/modelica-ibpsa/pull/1683

  parameter Buildings.Fluid.Types.HeatExchangerConfiguration configuration=
    con.CounterFlow
    "Heat exchanger configuration";
  parameter Real r_nominal=2/3
    "Ratio between air-side and water-side convective heat transfer coefficient";

  parameter Boolean use_Q_flow_nominal = false
    "Set to true to specify Q_flow_nominal and inlet conditions, or to false to specify UA_nominal"

  parameter Modelica.Units.SI.HeatFlowRate Q_flow_nominal(fixed=
        use_Q_flow_nominal)
    "Nominal heat flow rate (positive for heat transfer from 1 to 2)"
  parameter Modelica.Units.SI.Temperature T_a1_nominal(fixed=use_Q_flow_nominal)
    "Water inlet temperature at a rated condition"
  parameter Modelica.Units.SI.Temperature T_a2_nominal(fixed=use_Q_flow_nominal)
    "Air inlet temperature at a rated condition"
  parameter Modelica.Units.SI.MassFraction w_a2_nominal(start=0.01, fixed=
        use_Q_flow_nominal)
    "Humidity ratio of inlet air at a rated condition (in kg/kg dry air)"

  parameter Modelica.Units.SI.ThermalConductance UA_nominal(
    fixed=not use_Q_flow_nominal,
    min=0,
    start=1/(1/10 + 1/20))
    "Thermal conductance at nominal flow, used to compute heat capacity"

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

  Modelica.Units.SI.HeatFlowRate Q1_flow=-dryWetCalcs.QTot_flow
    "Heat input into water stream (positive if air is cooled)";
  Modelica.Units.SI.HeatFlowRate Q2_flow=dryWetCalcs.QTot_flow
    "Total heat input into air stream (negative if air is cooled)";
  Modelica.Units.SI.HeatFlowRate QSen2_flow=dryWetCalcs.QSen_flow
    "Sensible heat input into air stream (negative if air is cooled)";
  Modelica.Units.SI.HeatFlowRate QLat2_flow=Buildings.Utilities.Psychrometrics.Constants.h_fg
      *mWat_flow "Latent heat input into air (negative if air is dehumidified)";
  Real SHR(
    min=0,
    max=1,
    unit="1") = QSen2_flow /
      noEvent(if (Q2_flow > 1E-6 or Q2_flow < -1E-6) then Q2_flow else 1)
    "Sensible to total heat ratio";
  Modelica.Units.SI.MassFlowRate mWat_flow=dryWetCalcs.mCon_flow
    "Water flow rate of condensate removed from the air stream";

  Real dryFra(final unit="1", min=0, max=1) = dryWetCalcs.dryFra
    "Dry fraction, 0.3 means condensation occurs at 30% heat exchange length from air inlet";

protected
  final parameter Modelica.Units.SI.MassFraction X_w_a2_nominal=w_a2_nominal/(1
       + w_a2_nominal)
    "Water mass fraction of inlet air at a rated condition (in kg/kg total air)";

  parameter Boolean waterSideFlowDependent=true
    "Set to false to make water-side hA independent of mass flow rate"
  parameter Boolean airSideFlowDependent=true
    "Set to false to make air-side hA independent of mass flow rate"
  parameter Boolean waterSideTemperatureDependent=false
    "Set to false to make water-side hA independent of temperature"
  parameter Boolean airSideTemperatureDependent=false
    "Set to false to make air-side hA independent of temperature"

  Real delta=1E-2
    "Parameter for normalization";
  Real delta1=1E-1
    "Parameter for normalization";
  Real fac1 = Buildings.Utilities.Math.Functions.smoothMin(
    (1/delta * m1_flow/m1_flow_nominal)^2,1,delta1);
  Real fac2 = Buildings.Utilities.Math.Functions.smoothMin(
    (1/delta * m2_flow/m2_flow_nominal)^2,1,delta1);
  Real Qfac = fac1*fac2;

  Buildings.Fluid.HeatExchangers.BaseClasses.WetCoilUARated UAFroRated(
    final use_Q_flow_nominal=use_Q_flow_nominal,
    final QTot_flow=Q_flow_nominal,
    final UA=UA_nominal,
    final r_nominal=r_nominal,
    final TAirIn=T_a2_nominal,
    final X_wAirIn=X_w_a2_nominal,
    final TWatIn=T_a1_nominal,
    final mAir_flow=m2_flow_nominal,
    final mWat_flow=m1_flow_nominal)
    "Model that computes UA_nominal";

  Buildings.Fluid.HeatExchangers.HeaterCooler_u heaCoo(
    redeclare final package Medium = Medium1,
    final dp_nominal = dp1_nominal,
    final m_flow_nominal = m1_flow_nominal,
    final energyDynamics = energyDynamics,
    final Q_flow_nominal=-1,
    u(final unit="W"))
    "Heat exchange with water stream"

  Buildings.Fluid.Humidifiers.Humidifier_u heaCooHum_u(
    redeclare final package Medium = Medium2,
    final mWat_flow_nominal = 1,
    final dp_nominal = dp2_nominal,
    final m_flow_nominal = m2_flow_nominal,
    final energyDynamics = energyDynamics,
    u(final unit="kg/s"))
    "Heat and moisture exchange with air stream"
  Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil hA(
    final UA_nominal = UA_nominal,
    final m_flow_nominal_a = m2_flow_nominal,
    final m_flow_nominal_w = m1_flow_nominal,
    final waterSideTemperatureDependent = waterSideTemperatureDependent,
    final waterSideFlowDependent = waterSideFlowDependent,
    final airSideTemperatureDependent = airSideTemperatureDependent,
    final airSideFlowDependent = airSideFlowDependent,
    r_nominal = r_nominal)
    "Model for convective heat transfer coefficient"
  Buildings.Fluid.HeatExchangers.BaseClasses.WetCoilDryWetRegime dryWetCalcs(
    fullyWet(hAirOut(start=Medium2.h_default)),
    final cfg=flowRegime,
    final mWat_flow_nominal=m1_flow_nominal,
    final mAir_flow_nominal=m2_flow_nominal,
    final Qfac=Qfac)
    "Dry/wet calculations block"

  Modelica.Blocks.Sources.RealExpression cp_a1Exp(
    final y=Medium1.specificHeatCapacityCp(state_a1_inflow))
    "Expression for cp of air"
  Modelica.Blocks.Sources.RealExpression XWat_a2Exp(
    final y = if allowFlowReversal2
    then
      fra_a2 * state_a2_inflow.X[nWat] + fra_b2 * state_b2_inflow.X[nWat]
    else
      state_a2_inflow.X[nWat])
    "Expression for XWat"
  Modelica.Blocks.Sources.RealExpression p_a2Exp(
    final y = port_a2.p)
    "Pressure at port a2"
  Modelica.Blocks.Sources.RealExpression h_a2Exp(
    final y = if allowFlowReversal2
    then
      fra_a2 * Medium2.specificEnthalpy(state_a2_inflow)
      + fra_b2 * Medium2.specificEnthalpy(state_b2_inflow)
    else
      Medium2.specificEnthalpy(state_a2_inflow))
    "Specific enthalpy at port a2"
  Modelica.Blocks.Sources.RealExpression cp_a2Exp(final y=
        Medium2.specificHeatCapacityCp(state_a2_inflow))
    "Specific heat capacity at port a2"
  Modelica.Blocks.Sources.RealExpression TIn_a1Exp(
    final y = if allowFlowReversal1
    then
      fra_a1 * Medium1.temperature(state_a1_inflow)
      + fra_b1 * Medium1.temperature(state_b1_inflow)
    else
      Medium1.temperature(state_a1_inflow))
    "Temperature at port a1"
  Modelica.Blocks.Sources.RealExpression TIn_a2Exp(
    final y = if allowFlowReversal2
    then
      fra_a2 * Medium2.temperature(state_a2_inflow)
      + fra_b2 * Medium2.temperature(state_b2_inflow)
    else
      Medium2.temperature(state_a2_inflow))
    "Temperature at port a2"
  Modelica.Blocks.Sources.RealExpression m_flow_a1Exp(
    final y=Buildings.Utilities.Math.Functions.regNonZeroPower(
      x=port_a1.m_flow,n=1,delta=delta*m1_flow_nominal))
    "Absolute value of mass flow rate on water side"
  Modelica.Blocks.Sources.RealExpression m_flow_a2Exp(
    final y=Buildings.Utilities.Math.Functions.regNonZeroPower(
      x=port_a2.m_flow,n=1,delta=delta*m2_flow_nominal))
    "Absolute value of mass flow rate on air side"
  final parameter Integer nWat=
    Buildings.Fluid.HeatExchangers.BaseClasses.determineWaterIndex(
      Medium2.substanceNames)
    "Index of water";
  parameter flo flowRegime_nominal(fixed=false)
    "Heat exchanger flow regime at nominal flow rates";
  flo flowRegime(fixed=false, start=flowRegime_nominal)
    "Heat exchanger flow regime";

  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow preHea
    "Prescribed heat flow"
  Real fra_a1(min=0, max=1) = if allowFlowReversal1
    then Modelica.Fluid.Utilities.regStep(
      m1_flow,
      1,
      0,
      m1_flow_small)
    else 1
    "Fraction of incoming state taken from port a2
    (used to avoid excessive calls to regStep)";
  Real fra_b1(min=0, max=1) = if allowFlowReversal1
    then 1-fra_a1
    else 0
    "Fraction of incoming state taken from port b2
    (used to avoid excessive calls to regStep)";
  Real fra_a2(min=0, max=1) = if allowFlowReversal2
    then Modelica.Fluid.Utilities.regStep(
      m2_flow,
      1,
      0,
      m2_flow_small)
    else 1
    "Fraction of incoming state taken from port a2
    (used to avoid excessive calls to regStep)";
  Real fra_b2(min=0, max=1) = if allowFlowReversal2
    then 1-fra_a2
    else 0
    "Fraction of incoming state taken from port b2
    (used to avoid excessive calls to regStep)";

  Modelica.Units.SI.ThermalConductance C1_flow=abs(m1_flow)*(if
      allowFlowReversal1 then fra_a1*Medium1.specificHeatCapacityCp(
      state_a1_inflow) + fra_b1*Medium1.specificHeatCapacityCp(state_b1_inflow)
       else Medium1.specificHeatCapacityCp(state_a1_inflow))
    "Heat capacity flow rate medium 1";
  Modelica.Units.SI.ThermalConductance C2_flow=abs(m2_flow)*(if
      allowFlowReversal2 then fra_a2*Medium2.specificHeatCapacityCp(
      state_a2_inflow) + fra_b2*Medium2.specificHeatCapacityCp(state_b2_inflow)
       else Medium2.specificHeatCapacityCp(state_a2_inflow))
    "Heat capacity flow rate medium 2";
  parameter Modelica.Units.SI.SpecificHeatCapacity cp1_nominal(fixed=false)
    "Specific heat capacity of medium 1 at nominal condition";
  parameter Modelica.Units.SI.SpecificHeatCapacity cp2_nominal(fixed=false)
    "Specific heat capacity of medium 2 at nominal condition";
  parameter Modelica.Units.SI.ThermalConductance C1_flow_nominal(fixed=false)
    "Nominal capacity flow rate of Medium 1";
  parameter Modelica.Units.SI.ThermalConductance C2_flow_nominal(fixed=false)
    "Nominal capacity flow rate of Medium 2";
  final parameter Medium1.ThermodynamicState sta1_default = Medium1.setState_phX(
     h=Medium1.h_default,
     p=Medium1.p_default,
     X=Medium1.X_default[1:Medium1.nXi]) "Default state for medium 1";
  final parameter Medium2.ThermodynamicState sta2_default = Medium2.setState_phX(
     h=Medium2.h_default,
     p=Medium2.p_default,
     X=Medium2.X_default[1:Medium2.nXi]) "Default state for medium 2";

initial equation
  assert(m1_flow_nominal > Modelica.Constants.eps,
    "m1_flow_nominal must be positive, m1_flow_nominal = " + String(
    m1_flow_nominal));
  assert(m2_flow_nominal > Modelica.Constants.eps,
    "m2_flow_nominal must be positive, m2_flow_nominal = " + String(
    m2_flow_nominal));

  cp1_nominal = Medium1.specificHeatCapacityCp(sta1_default);
  cp2_nominal = Medium2.specificHeatCapacityCp(sta2_default);
  C1_flow_nominal = m1_flow_nominal*cp1_nominal;
  C2_flow_nominal = m2_flow_nominal*cp2_nominal;
  if (configuration == con.CrossFlowStream1MixedStream2Unmixed) then
    flowRegime_nominal = if (C1_flow_nominal < C2_flow_nominal)
      then
        flo.CrossFlowCMinMixedCMaxUnmixed
      else
        flo.CrossFlowCMinUnmixedCMaxMixed;
  elseif (configuration == con.CrossFlowStream1UnmixedStream2Mixed) then
    flowRegime_nominal = if (C1_flow_nominal < C2_flow_nominal)
      then
        flo.CrossFlowCMinUnmixedCMaxMixed
      else
        flo.CrossFlowCMinMixedCMaxUnmixed;
  elseif (configuration == con.ParallelFlow) then
    flowRegime_nominal = flo.ParallelFlow;
  elseif (configuration == con.CounterFlow) then
    flowRegime_nominal = flo.CounterFlow;
  elseif (configuration == con.CrossFlowUnmixed) then
    flowRegime_nominal = flo.CrossFlowUnmixed;
  else
    // Invalid flow regime. Assign a value to flowRegime_nominal, and stop with an assert
    flowRegime_nominal = flo.CrossFlowUnmixed;
    assert(configuration >= con.ParallelFlow and
      configuration <= con.CrossFlowStream1UnmixedStream2Mixed,
      "Invalid heat exchanger configuration.");
  end if;

equation
  // Assign the flow regime for the given heat exchanger configuration and
  // mass flow rates
  if use_dynamicFlowRegime then
    if (configuration == con.ParallelFlow) then
      flowRegime = if (C1_flow*C2_flow >= 0)
        then
          flo.ParallelFlow
        else
          flo.CounterFlow;
    elseif (configuration == con.CounterFlow) then
      flowRegime = if (C1_flow*C2_flow >= 0)
        then
          flo.CounterFlow
        else
          flo.ParallelFlow;
    elseif (configuration == con.CrossFlowUnmixed) then
      flowRegime = flo.CrossFlowUnmixed;
    elseif (configuration == con.CrossFlowStream1MixedStream2Unmixed) then
      flowRegime = if (C1_flow < C2_flow)
        then
          flo.CrossFlowCMinMixedCMaxUnmixed
        else
          flo.CrossFlowCMinUnmixedCMaxMixed;
    else
      // have ( configuration == con.CrossFlowStream1UnmixedStream2Mixed)
      flowRegime = if (C1_flow < C2_flow)
        then
          flo.CrossFlowCMinUnmixedCMaxMixed
        else
          flo.CrossFlowCMinMixedCMaxUnmixed;
    end if;
  else
    flowRegime = flowRegime_nominal;
    assert(noEvent(m1_flow > -0.1 * m1_flow_nominal)
       and noEvent(m2_flow > -0.1 * m2_flow_nominal),
"*** Warning in " + getInstanceName() +
      ": The flow direction reversed.
      However, because the constant use_dynamicFlowRegime is set to false,
      the model does not change equations based on the actual flow regime.
      To switch equations based on the actual flow regime during the simulation,
      set the constant use_dynamicFlowRegime=true.
      Note that this can lead to slow simulation because of events.",
      level = AssertionLevel.warning);
  end if;

  connect(heaCoo.port_b, port_b1)
  connect(heaCooHum_u.port_b, port_b2)
  connect(hA.hA_1, dryWetCalcs.UAWat)
  connect(hA.hA_2, dryWetCalcs.UAAir)
  connect(cp_a1Exp.y, dryWetCalcs.cpWat)
  connect(XWat_a2Exp.y, dryWetCalcs.X_wAirIn)
  connect(p_a2Exp.y, dryWetCalcs.pAir)
  connect(h_a2Exp.y, dryWetCalcs.hAirIn)
  connect(cp_a2Exp.y, dryWetCalcs.cpAir)
  connect(TIn_a1Exp.y, hA.T_1)
  connect(TIn_a1Exp.y, dryWetCalcs.TWatIn)
  connect(TIn_a2Exp.y, hA.T_2)
  connect(TIn_a2Exp.y, dryWetCalcs.TAirIn)
  connect(m_flow_a1Exp.y, hA.m1_flow)
  connect(m_flow_a1Exp.y, dryWetCalcs.mWat_flow)
  connect(port_a1, heaCoo.port_a)
  connect(m_flow_a2Exp.y, hA.m2_flow)
  connect(m_flow_a2Exp.y, dryWetCalcs.mAir_flow)
  connect(port_a2, heaCooHum_u.port_a)
  connect(preHea.port, heaCooHum_u.heatPort)
  connect(dryWetCalcs.QTot_flow, heaCoo.u)
  connect(dryWetCalcs.mCon_flow, heaCooHum_u.u)
  connect(preHea.Q_flow, dryWetCalcs.QTot_flow)
end WetCoilEffectivenessNTU;


Active beam unit for cooling

within Buildings.Fluid.HeatExchangers.ActiveBeams;
model Cooling "Active beam unit for cooling"

  replaceable package MediumWat =
    Modelica.Media.Interfaces.PartialMedium "Medium 1 in the component"
  replaceable package MediumAir =
    Modelica.Media.Interfaces.PartialMedium "Medium 2 in the component"

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

  replaceable parameter Data.Generic perCoo "Performance data for cooling"

  parameter Integer nBeams(min=1)=1 "Number of beams in parallel";

  parameter Boolean allowFlowReversalWat=true
    "= true to allow flow reversal in water circuit, false restricts to design direction (port_a -> port_b)"
  parameter Boolean allowFlowReversalAir=true
    "= true to allow flow reversal in air circuit, false restricts to design direction (port_a -> port_b)"

  parameter Modelica.Units.SI.Time tau=30
    "Time constant at nominal flow (if energyDynamics <> SteadyState)"

  // Flow resistance
  parameter Boolean from_dpWat = false
    "= true, use m_flow = f(dp) else dp = f(m_flow)"
  parameter Boolean linearizeFlowResistanceWat = false
    "= true, use linear relation between m_flow and dp for any flow rate"
  parameter Real deltaMWat = 0.1
    "Fraction of nominal flow rate where flow transitions to laminar"

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

  // Initialization
  parameter MediumWat.AbsolutePressure pWatCoo_start = MediumWat.p_default
    "Start value of pressure"
  parameter MediumWat.Temperature TWatCoo_start = MediumWat.T_default
    "Start value of temperature"

  parameter MediumWat.MassFlowRate mWat_flow_small(min=0) = 1E-4*abs(perCoo.mWat_flow_nominal)
    "Small mass flow rate for regularization of zero flow"
  parameter MediumAir.MassFlowRate mAir_flow_small(min=0) = 1E-4*abs(perCoo.mAir_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"

  // Ports
  Modelica.Fluid.Interfaces.FluidPort_a watCoo_a(
    redeclare final package Medium = MediumWat,
    m_flow(min=if allowFlowReversalWat then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumWat.h_default))
    "Fluid connector watCoo_a (positive design flow direction is from watCoo_a to watCoo_b)"
  Modelica.Fluid.Interfaces.FluidPort_b watCoo_b(
    redeclare final package Medium = MediumWat,
    m_flow(max=if allowFlowReversalWat then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumWat.h_default))
    "Fluid connector watCoo_b (positive design flow direction is from watCoo_a to watCoo_b)"

  Modelica.Fluid.Interfaces.FluidPort_a air_a(
    redeclare final package Medium = MediumAir,
    m_flow(min=if allowFlowReversalAir then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumAir.h_default))
    "Fluid connector air_a (positive design flow direction is from air_a to air_b)"
  Modelica.Fluid.Interfaces.FluidPort_b air_b(
    redeclare final package Medium = MediumAir,
    m_flow(max=if allowFlowReversalAir then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumAir.h_default))
    "Fluid connector air_b (positive design flow direction is from air_a to air_b)"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPor
    "Heat port, to be connected to room air"

  MediumWat.ThermodynamicState staWatCoo_a=
      MediumWat.setState_phX(watCoo_a.p,
                           noEvent(actualStream(watCoo_a.h_outflow)),
                           noEvent(actualStream(watCoo_a.Xi_outflow)))
      if show_T "Medium properties in port watCoo_a";
  MediumWat.ThermodynamicState staWatCoo_b=
      MediumWat.setState_phX(watCoo_b.p,
                           noEvent(actualStream(watCoo_b.h_outflow)),
                           noEvent(actualStream(watCoo_b.Xi_outflow)))
      if show_T "Medium properties in port watCoo_b";
  MediumAir.ThermodynamicState staAir_a=
      MediumAir.setState_phX(air_a.p,
                           noEvent(actualStream(air_a.h_outflow)),
                           noEvent(actualStream(air_a.Xi_outflow)))
      if show_T "Medium properties in port air_a";
  MediumAir.ThermodynamicState staAir_b=
      MediumAir.setState_phX(air_b.p,
                           noEvent(actualStream(air_b.h_outflow)),
                           noEvent(actualStream(air_b.Xi_outflow)))
      if show_T "Medium properties in port air_b";

  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow heaToRoo(
    final alpha=0)
    "Heat tranferred to the room (in addition to heat from supply air)"

  // Pressure drop
  Modelica.Units.SI.PressureDifference dpWatCoo(displayUnit="Pa") = watCoo_a.p
     - watCoo_b.p "Pressure difference watCoo_a minus watCoo_b";

  Modelica.Units.SI.PressureDifference dpAir(displayUnit="Pa") = air_a.p -
    air_b.p "Pressure difference air_a minus air_b";

  FixedResistances.PressureDrop res(
    redeclare final package Medium = MediumAir,
    final m_flow_nominal=perCoo.mAir_flow_nominal*nBeams,
    final dp_nominal=perCoo.dpAir_nominal)

protected
  BaseClasses.Convector conCoo(
    redeclare final package Medium = MediumWat,
    final per=perCoo,
    final allowFlowReversal=allowFlowReversalWat,
    final m_flow_small=mWat_flow_small,
    final show_T=false,
    final homotopyInitialization=homotopyInitialization,
    final from_dp=from_dpWat,
    final linearizeFlowResistance=linearizeFlowResistanceWat,
    final deltaM=deltaMWat,
    final tau=tau,
    final energyDynamics=energyDynamics,
    final p_start=pWatCoo_start,
    final T_start=TWatCoo_start,
    final nBeams=nBeams) "Cooling beam"

  Modelica.Blocks.Math.Sum sum "Connector for heating and cooling mode"

  Modelica.Blocks.Math.Gain gaiSig(
    final k=-1,
    u(final unit="W"),
    y(final unit="W")) "Gain to reverse the sign"

  Sensors.MassFlowRate senFloAir(
    redeclare final package Medium = MediumAir) "Mass flow rate sensor"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTemRooAir
    "Temperature sensor for room air"

initial equation
  assert(perCoo.primaryAir.r_V[1]<=0.000001 and perCoo.primaryAir.f[1]<=0.00001,
    "Performance curve perCoo.primaryAir must pass through (0,0).");
  assert(perCoo.water.r_V[1]<=0.000001      and perCoo.water.f[1]<=0.00001,
    "Performance curve perCoo.water must pass through (0,0).");
  assert(perCoo.dT.r_dT[1]<=0.000001      and perCoo.dT.f[1]<=0.00001,
    "Performance curve perCoo.dT must pass through (0,0).");
  assert(homotopyInitialization, "In " + getInstanceName() +
    ": The constant homotopyInitialization has been modified from its default value. This constant will be removed in future releases.",
    level = AssertionLevel.warning);

equation
  connect(heaToRoo.port, heaPor)
  connect(sum.y, gaiSig.u)
  connect(gaiSig.y, heaToRoo.Q_flow)
  connect(senTemRooAir.port, heaPor)
  connect(air_b, senFloAir.port_b)
  connect(conCoo.port_b, watCoo_b)
  connect(conCoo.Q_flow, sum.u[1])
  connect(senTemRooAir.T, conCoo.TRoo)

  connect(air_a, res.port_a)
  connect(senFloAir.port_a, res.port_b)
  connect(watCoo_a, conCoo.port_a)
  connect(senFloAir.m_flow, conCoo.mAir_flow)
end Cooling;


within Buildings.Fluid.HeatExchangers.ActiveBeams;
model CoolingAndHeating "Active beam unit for heating and cooling"
  extends Buildings.Fluid.HeatExchangers.ActiveBeams.Cooling(sum(nin=2));

  replaceable parameter Data.Generic perHea "Performance data for heating"

  // Initialization
  parameter MediumWat.AbsolutePressure pWatHea_start = pWatCoo_start
    "Start value of pressure"

  parameter MediumWat.Temperature TWatHea_start = TWatCoo_start
    "Start value of temperature"

  Modelica.Fluid.Interfaces.FluidPort_a watHea_a(
    redeclare final package Medium = MediumWat,
    m_flow(min=if allowFlowReversalWat then -Modelica.Constants.inf else 0),
    h_outflow(start=MediumWat.h_default))
    "Fluid connector a (positive design flow direction is from watHea_a to watHea_b)"
  Modelica.Fluid.Interfaces.FluidPort_b watHea_b(
    redeclare final package Medium = MediumWat,
    m_flow(max=if allowFlowReversalWat then +Modelica.Constants.inf else 0),
    h_outflow(start=MediumWat.h_default))
    "Fluid connector b (positive design flow direction is from watHea_a to watHea_b)"

  MediumWat.ThermodynamicState staHea_a=
      MediumWat.setState_phX(watHea_a.p,
                          noEvent(actualStream(watHea_a.h_outflow)),
                          noEvent(actualStream(watHea_a.Xi_outflow)))
      if show_T "Medium properties in port watHea_a";

  MediumWat.ThermodynamicState staHea_b=
      MediumWat.setState_phX(watHea_b.p,
                          noEvent(actualStream(watHea_b.h_outflow)),
                          noEvent(actualStream(watHea_b.Xi_outflow)))
       if show_T "Medium properties in port watHea_b";

  Modelica.Units.SI.PressureDifference dpWatHea(displayUnit="Pa") = watHea_a.p
     - watHea_b.p "Pressure difference between watHea_a and watHea_b";

protected
  BaseClasses.Convector conHea(
    redeclare final package Medium = MediumWat,
    final per=perHea,
    final allowFlowReversal=allowFlowReversalWat,
    final m_flow_small=mWat_flow_small,
    final show_T=false,
    final homotopyInitialization=homotopyInitialization,
    final from_dp=from_dpWat,
    final linearizeFlowResistance=linearizeFlowResistanceWat,
    final deltaM=deltaMWat,
    final tau=tau,
    final energyDynamics=energyDynamics,
    final p_start=pWatHea_start,
    final T_start=TWatHea_start,
    final nBeams=nBeams) "Heating beam"

initial equation
  assert(perHea.primaryAir.r_V[1]<=0.000001 and perHea.primaryAir.f[1]<=0.00001,
    "Performance curve perHea.primaryAir must pass through (0,0).");
  assert(perHea.water.r_V[1]<=0.000001      and perHea.water.f[1]<=0.00001,
    "Performance curve perHea.water must pass through (0,0).");
  assert(perHea.dT.r_dT[1]<=0.000001        and perHea.dT.f[1]<=0.00001,
    "Performance curve perHea.dT must pass through (0,0).");

equation
  connect(conHea.port_b, watHea_b)
  connect(conHea.Q_flow, sum.u[2])
  connect(conHea.TRoo, senTemRooAir.T)
  connect(watHea_a, conHea.port_a)
  connect(conHea.mAir_flow, senFloAir.m_flow)
end CoolingAndHeating;


Package with active beams

within Buildings.Fluid.HeatExchangers;
package ActiveBeams "Package with active beams"
  extends Modelica.Icons.VariantsPackage;
end ActiveBeams;


User's Guide

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


Heat exchanger for the water stream

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses;
model Convector "Heat exchanger for the water stream"
  extends Buildings.Fluid.Interfaces.PartialTwoPortInterface(
    final m_flow_nominal = per.mWat_flow_nominal*nBeams);
  extends Buildings.Fluid.Interfaces.TwoPortFlowResistanceParameters(
    final computeFlowResistance=true,
    final dp_nominal = per.dpWat_nominal "Don't multiply with nBeams, as the beams are in parallel");

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

  parameter Data.Generic per "Performance data"

  parameter Integer nBeams(min=1) "Number of beams in parallel";

  parameter Modelica.Units.SI.Time tau=30
    "Time constant at nominal flow (if energyDynamics <> SteadyState)"

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

  // 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"

  Modelica.Blocks.Interfaces.RealInput mAir_flow(
    final unit="kg/s") "Air mass flow rate of a single beam"
  Modelica.Blocks.Interfaces.RealInput TRoo(
    final unit="K",
    displayUnit="degC") "Room air temperature"
  Modelica.Blocks.Interfaces.RealOutput Q_flow(final unit="W")
    "Actual capacity of a single beam"

protected
  HeaterCooler_u hex(
    redeclare final package Medium = Medium,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_nominal=m_flow_nominal,
    final m_flow_small=m_flow_small,
    final show_T=false,
    final from_dp=from_dp,
    final dp_nominal=dp_nominal,
    final linearizeFlowResistance=linearizeFlowResistance,
    final deltaM=deltaM,
    final tau=tau,
    final homotopyInitialization=homotopyInitialization,
    final energyDynamics=energyDynamics,
    final p_start=p_start,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final Q_flow_nominal=-nBeams * per.Q_flow_nominal)
    "Heat exchanger for the water stream"

  ModificationFactor mod(
    final nBeams=nBeams,
    final per=per) "Performance modification for part load"

  Modelica.Blocks.Sources.RealExpression senTem(final y=Medium.temperature(port_a_inflow))
    "Actual water temperature entering the beam"

  Medium.ThermodynamicState port_a_inflow=
    Medium.setState_phX(port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow))
    "state for medium inflowing through port_a";

  Sensors.MassFlowRate senFloWatCoo(
    redeclare final package Medium = Medium) "Mass flow rate sensor"

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
  connect(hex.Q_flow, Q_flow)
  connect(hex.port_b, port_b)
  connect(mod.y, hex.u)
  connect(senTem.y, mod.TWat)
  connect(TRoo, mod.TRoo)
  connect(mAir_flow, mod.mAir_flow)
  connect(senFloWatCoo.port_a, port_a)
  connect(senFloWatCoo.port_b, hex.port_a)
  connect(senFloWatCoo.m_flow, mod.mWat_flow)
end Convector;


Cubic spline for interpolation. This model calculates the output based on the cubic hermite interpolation
and linear extrapolation of predefined values. The predefined values must create a monotone curve.

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses;
model DerivativesCubicSpline "Cubic spline for interpolation"
  extends Modelica.Blocks.Icons.Block;
  parameter Real[:] xd={0,0.5,1};
  parameter Real[size(xd, 1)] yd={0,0.75,1};

  Modelica.Blocks.Interfaces.RealInput u
    "Independent variable for interpolation"
  Modelica.Blocks.Interfaces.RealOutput y "Interpolated value"
protected
  parameter Real[size(xd, 1)] dMonotone(each fixed=false) "Derivatives";
  Integer i "Counter to pick the interpolation interval";

initial algorithm
  // Get the derivative values at the support points

  dMonotone := Buildings.Utilities.Math.Functions.splineDerivatives(
    x=xd,
    y=yd,
    ensureMonotonicity=true);

algorithm
  i := 1;
  for j in 1:size(xd, 1) - 1 loop
    if u > xd[j] then
      i := j;
    end if;
  end for;
  // Extrapolate or interpolate the data
  y :=
    Buildings.Utilities.Math.Functions.cubicHermiteLinearExtrapolation(
    x=u,
    x1=xd[i],
    x2=xd[i + 1],
    y1=yd[i],
    y2=yd[i + 1],
    y1d=dMonotone[i],
    y2d=dMonotone[i + 1]);
end DerivativesCubicSpline;


Factor to modify nominal capacity. This model determines the three modification factors described in

Buildings.Fluid.HeatExchangers.ActiveBeams.UsersGuide
by comparing the actual values of air mass flow rate,
water mass flow rate and room-water temperature difference with the nominal values.
The three modification factors are then multiplied.
Input to this model are the total mass flow rates of all parallel beams combined.

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses;
model ModificationFactor "Factor to modify nominal capacity"
  extends Modelica.Blocks.Icons.Block;

  parameter Integer nBeams(min=1) "Number of beams in parallel";

  parameter Data.Generic per "Performance data"

  Modelica.Blocks.Interfaces.RealInput TWat(
    final unit="K",
    displayUnit="degC") "Temperature of the water entering the beams"
  Modelica.Blocks.Interfaces.RealInput mWat_flow(
    final unit="kg/s") "Mass flow rate of the water entering the beams"
  Modelica.Blocks.Interfaces.RealInput mAir_flow(
    final unit="kg/s") "Mass flow rate of the primary air entering the beams"

  Modelica.Blocks.Interfaces.RealInput TRoo(
    final unit="K",
    displayUnit="degC") "Room air temperature"

  Modelica.Blocks.Interfaces.RealOutput y(final unit="1")
    "Total modification factor"

protected
  Modelica.Blocks.Sources.Constant temDif_nom(
    final k=1/per.dT_nominal)
    "Nominal temperature difference between water and room air"
  Modelica.Blocks.Sources.Constant watFlo_nom(final k=1/(nBeams*per.mWat_flow_nominal))
                                     "Nominal water mass flow rate"
  Modelica.Blocks.Sources.Constant airFlo_nom(final k=1/(nBeams*per.mAir_flow_nominal))
                                     "Nominal water mass flow rate"

  DerivativesCubicSpline temDif_mod(
    final xd=per.dT.r_dT,
    final yd=per.dT.f)
    "Derivatives of the cubic spline for the temperature difference between room and water"
  DerivativesCubicSpline watFlo_mod(
    final xd=per.water.r_V,
    final yd=per.water.f) "Derivatives of the cubic spline for the water flow"
  DerivativesCubicSpline airFlo_mod(
    final xd=per.primaryAir.r_V,
    final yd=per.primaryAir.f)
    "Derivatives of the cubic spline for the air flow"

  Modelica.Blocks.Math.Product pro_3
    "Ratio of actual/nominal temperature difference"
  Modelica.Blocks.Math.Product pro_2 "Ratio of actual/nominal water flow rate"
  Modelica.Blocks.Math.Product pro_1 "Ratio of actual/nominal air flow rate"

  Modelica.Blocks.Math.MultiProduct mulPro(final nu=3)
    "Product of the three modification factors"

  Modelica.Blocks.Math.Add add(final k1=+1, final k2=-1)
    "Temperature difference between water and room air"

equation
  connect(mAir_flow, pro_1.u1)
  connect(airFlo_nom.y, pro_1.u2)
  connect(pro_1.y, airFlo_mod.u)
  connect(mulPro.y, y)
  connect(mWat_flow, pro_2.u1)
  connect(pro_2.y, watFlo_mod.u)
  connect(watFlo_nom.y, pro_2.u2)
  connect(TWat, add.u1)
  connect(add.u2, TRoo)
  connect(temDif_nom.y, pro_3.u2)
  connect(add.y, pro_3.u1)
  connect(pro_3.y, temDif_mod.u)
  connect(watFlo_mod.y, mulPro.u[1])
  connect(airFlo_mod.y, mulPro.u[2])
  connect(temDif_mod.y, mulPro.u[3])
end ModificationFactor;


Base classes for active beam models

within Buildings.Fluid.HeatExchangers.ActiveBeams;
package BaseClasses "Base classes for active beam models"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


Water model

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.Examples;
model Convector
  extends Modelica.Icons.Example;

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

  Buildings.Fluid.Sources.MassFlowSource_T wat(
    redeclare package Medium = Medium,
    m_flow=0.094,
    T=288.15,
    nPorts=1)
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = Medium, nPorts=1) "Pressure boundary condition"
  Modelica.Blocks.Sources.Ramp airFlo(height=0.0792, duration=4)
    "Air mass flow rate"
  Modelica.Blocks.Sources.Constant rooTem(k=273.15 + 25) "Room air temperature"
  Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.Convector con(
    redeclare package Medium = Medium,
    nBeams=1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    per(
      mAir_flow_nominal=0.0792,
      mWat_flow_nominal=0.094,
      dT_nominal=-10,
      Q_flow_nominal=1092,
      dpWat_nominal=10000,
      dpAir_nominal=100)) "Convector model"
  Buildings.Fluid.Sensors.TemperatureTwoPort senTem(
    redeclare package Medium = Medium,
    m_flow_nominal=0.094) "Temperature sensor"
equation
  connect(airFlo.y, con.mAir_flow)
  connect(rooTem.y, con.TRoo)
  connect(con.port_b, senTem.port_a)
  connect(senTem.port_b, bou.ports[1])
  connect(wat.ports[1], con.port_a)
end Convector;


Clock. The example tests the implementation of

Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.DerivativesCubicSpline.
Default vectors are: x=[0,0.5,1] and y=[0,0.75,1].
Input to the model is the simulation time.

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.Examples;
model DerivateCubicSpline
  extends Modelica.Icons.Example;

  Modelica.Blocks.Sources.ContinuousClock clock "Clock"
  Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.DerivativesCubicSpline
    cubSpl "Derivatives of cubic spline"
equation
  connect(clock.y, cubSpl.u)
end DerivateCubicSpline;


Constant input signal. This example tests the implementation of

Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.ModificationFactor.

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.Examples;
model ModificationFactor
   extends Modelica.Icons.Example;

  Modelica.Blocks.Sources.Constant const1(k=20) "Constant input signal"
  Modelica.Blocks.Sources.Ramp ramp(height=0.0792, duration=1)
    "Ramp input signal"
  Modelica.Blocks.Sources.Ramp ramp1(height=0.094, duration=1)
    "Ramp input signal"
  Modelica.Blocks.Sources.Ramp ramp2(
    height=27.8,
    duration=1,
    offset=20) "Ramp input signal"
  Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses.ModificationFactor mod(
    nBeams=1,
    per(
      Q_flow_nominal=0.094*2*4200,
      mAir_flow_nominal=0.0792,
      mWat_flow_nominal=0.094,
      dT_nominal=27.8,
      dpWat_nominal=10000,
      dpAir_nominal=100)) "Modification factor"
equation
  connect(ramp.y, mod.mAir_flow)
  connect(ramp1.y, mod.mWat_flow)
  connect(ramp2.y, mod.TWat)
  connect(const1.y, mod.TRoo)
end ModificationFactor;


This package contains examples for the use of models that can be found in
<a href=\

within Buildings.Fluid.HeatExchangers.ActiveBeams.BaseClasses;
package Examples
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Package with performance data

within Buildings.Fluid.HeatExchangers.ActiveBeams;
package Data "Package with performance data"
  extends Modelica.Icons.MaterialPropertiesPackage;
end Data;


Performance data for Trox

within Buildings.Fluid.HeatExchangers.ActiveBeams.Data;
package Trox "Performance data for Trox"
  record DID632A_nozzleH_length6ft_cooling =
    Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Generic (
      primaryAir(
        r_V = {0,0.714286, 1,1.2857},
        f =   {0,0.823403, 1,1.1256}),
      dT(
        f = {0,0.5,1},
        r_dT=
            {0,0.5,1}),
      water(
        r_V = {0,0.33333,0.5,0.666667,0.833333,1,1.333333},
        f =   {0,0.71,0.85,0.92,0.97,1,1.04}),
      mAir_flow_nominal=0.0792,
      mWat_flow_nominal =   0.094,
      dpWat_nominal = 10000,
      dpAir_nominal = 100,
      dT_nominal =          -10,
      Q_flow_nominal =   -1092)
  "Performance data for Trox DID 632A for cooling mode"

  record DID632A_nozzleH_length6ft_heating =
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Generic (
      dT(
        f = {0,0.5,1},
        r_dT=
            {0,0.5,1}),
      primaryAir(
        r_V = {0,0.714286, 1, 1.2857},
        f =   {0,0.8554, 1, 1.0778}),
      water(
        r_V = {0,0.33333,0.5,0.666667,0.833333,1,1.333333},
        f =   {0,0.71,0.85,0.92,0.97,1,1.04}),
      mAir_flow_nominal=0.0792,
      mWat_flow_nominal =   0.094,
      dpWat_nominal = 10000,
      dpAir_nominal = 100,
      dT_nominal =          27.8,
      Q_flow_nominal =   2832)
  "Performance data for Trox DID 632A for heating mode"
end Trox;


Base classes for performance data

within Buildings.Fluid.HeatExchangers.ActiveBeams.Data;
package BaseClasses "Base classes for performance data"
end BaseClasses;


Medium model for air

within Buildings.Fluid.HeatExchangers.ActiveBeams.Examples;
model CoolingAndHeating
  extends Modelica.Icons.Example;

  package MediumA = Buildings.Media.Air "Medium model for air";

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

  Buildings.Fluid.Sources.Boundary_pT sin_1(
    redeclare package Medium = MediumW,
    nPorts=1) "Sink chilled water"
  Buildings.Fluid.Sources.MassFlowSource_T souAir(
    redeclare package Medium = MediumA,
    m_flow=0.0792,
    use_m_flow_in=false,
    nPorts=1,
    T=285.85) "Source air"
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = MediumA,
    nPorts=1) "Sink air"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theConWal(G=200)
    "Thermal conductor for wall"
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow heaFlo
    "Thermal loads"
  Modelica.Blocks.Sources.Constant TSetHea(k=273.15 + 22)
    "Heating set-point temperature"
  Buildings.HeatTransfer.Sources.FixedTemperature TOut(T=301.15)
    "Outdoor air temperature"
  Buildings.Controls.Continuous.LimPID conHea(
    yMax=0.094,
    Td=0,
    Ti=100,
    k=0.1)
    "Controller for heating"
  Sources.MassFlowSource_T pumCoo(
    redeclare package Medium = MediumW,
    use_m_flow_in=true,
    nPorts=1,
    T=288.15) "Source chilled water"
  Modelica.Blocks.Math.Gain gain(k=1200) "Gain for thermal loads"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTem
    "Room air temperature sensor"
  Sources.MassFlowSource_T pumHea(
    redeclare package Medium = MediumW,
    use_m_flow_in=true,
    nPorts=1,
    T=320.95) "Source hot water"
  Buildings.Fluid.Sources.Boundary_pT sin_2(
    redeclare package Medium = MediumW,
    nPorts=1) "Sink hot water"
  Modelica.Blocks.Sources.Sine sine(
    f=1/86400,
    amplitude=1,
    phase=-1.5707963267949) "Source for thermal loads"
  Buildings.Fluid.MixingVolumes.MixingVolume vol(nPorts=2,
    redeclare package Medium = MediumA,
    m_flow_nominal=0.1,
    V=30,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Air volume for room"
  Modelica.Blocks.Sources.Constant TSetCoo(k=273.15 + 25)
    "Cooling set-point temperature"
  Buildings.Controls.Continuous.LimPID conCoo(
    yMax=0.094,
    reverseActing=false,
    Td=0,
    k=0.5,
    Ti=70)
    "Controller for cooling"

  Buildings.Fluid.HeatExchangers.ActiveBeams.CoolingAndHeating beaCooHea(
    redeclare package MediumWat = MediumW,
    redeclare package MediumAir = MediumA,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_cooling perCoo,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_heating perHea,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyStateInitial)
    "Active Beam"
equation
  connect(TOut.port, theConWal.port_a)
  connect(gain.y, heaFlo.Q_flow)
  connect(senTem.T,conHea. u_m)
  connect(sine.y, gain.u)
  connect(bou.ports[1], vol.ports[1])
  connect(theConWal.port_b, vol.heatPort)
  connect(heaFlo.port, vol.heatPort)
  connect(senTem.port, vol.heatPort)
  connect(TSetHea.y, conHea.u_s)
  connect(TSetCoo.y, conCoo.u_s)
  connect(conCoo.u_m, senTem.T)
  connect(beaCooHea.watCoo_b, sin_1.ports[1])
  connect(sin_2.ports[1], beaCooHea.watHea_b)
  connect(souAir.ports[1], beaCooHea.air_a)
  connect(beaCooHea.air_b, vol.ports[2])
  connect(beaCooHea.heaPor, vol.heatPort)
  connect(conCoo.y, pumCoo.m_flow_in)
  connect(conHea.y, pumHea.m_flow_in)
  connect(pumCoo.ports[1], beaCooHea.watCoo_a)
  connect(pumHea.ports[1], beaCooHea.watHea_a)
end CoolingAndHeating;


Medium model for air

within Buildings.Fluid.HeatExchangers.ActiveBeams.Examples;
model CoolingOnly
  extends Modelica.Icons.Example;

  package MediumA = Buildings.Media.Air "Medium model for air";

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

  Buildings.Fluid.Sources.Boundary_pT sin_1(
    redeclare package Medium = MediumW,
    nPorts=1) "Sink for water"
  Buildings.Fluid.Sources.MassFlowSource_T souAir(
    redeclare package Medium = MediumA,
    use_m_flow_in=false,
    m_flow=0.0792,
    nPorts=1,
    T=285.85) "Source for air"
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = MediumA,
    nPorts=1) "Sink for air"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theConWal(G=200)
    "Thermal conductor for wall"
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow heaFlo
    "Thermal loads"
  Modelica.Blocks.Sources.Constant TSetCoo(k=273.15 + 25)
    "Set-point temperature"
  Buildings.HeatTransfer.Sources.FixedTemperature TOut(T=301.15)
    "Outdoor air temperature"
  Buildings.Controls.Continuous.LimPID conPID(
    reverseActing=false,
    Td=0,
    k=0.5,
    Ti=70,
    yMax=0.094) "Controller"
  Sources.MassFlowSource_T pum(
    redeclare package Medium = MediumW,
    use_m_flow_in=true,
    nPorts=1,
    T=288.15) "Source for water"
  Modelica.Blocks.Math.Gain gain(k=1200) "Gain thermal loads"
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTem
    "Room air temperature sensor"
  Modelica.Blocks.Sources.Sine sine(
    f=1/86400,
    amplitude=1,
    phase=-1.5707963267949) "Source for thermal loads"
  Buildings.Fluid.MixingVolumes.MixingVolume vol(
    nPorts=2,
    redeclare package Medium = MediumA,
    m_flow_nominal=0.1,
    V=30,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    T_start=293.15) "Air volume for room"
  Buildings.Fluid.HeatExchangers.ActiveBeams.Cooling beaCoo(
    redeclare package MediumWat = MediumW,
    redeclare package MediumAir = MediumA,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_cooling perCoo,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Active beam"
equation
  connect(TOut.port, theConWal.port_a)
  connect(gain.y, heaFlo.Q_flow)
  connect(TSetCoo.y, conPID.u_s)
  connect(senTem.T, conPID.u_m)
  connect(vol.ports[1], bou.ports[1])
  connect(heaFlo.port, vol.heatPort)
  connect(theConWal.port_b, vol.heatPort)
  connect(senTem.port, vol.heatPort)
  connect(sine.y, gain.u)
  connect(beaCoo.watCoo_b, sin_1.ports[1])
  connect(beaCoo.heaPor, vol.heatPort)
  connect(souAir.ports[1], beaCoo.air_a)
  connect(beaCoo.air_b, vol.ports[2])
  connect(pum.ports[1], beaCoo.watCoo_a)
  connect(pum.m_flow_in, conPID.y)
end CoolingOnly;


Medium model for air

within Buildings.Fluid.HeatExchangers.ActiveBeams.Examples;
model HeatingOnly
  extends Modelica.Icons.Example;

  package MediumA = Buildings.Media.Air "Medium model for air";

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

  Buildings.Fluid.Sources.Boundary_pT sin_1(
    redeclare package Medium = MediumW,
    nPorts=1) "Sink for chilled water"
  Buildings.Fluid.Sources.MassFlowSource_T souAir(
    redeclare package Medium = MediumA,
    m_flow=0.0792,
    use_m_flow_in=false,
    nPorts=1,
    T=285.85) "Source for air"
  Buildings.Fluid.Sources.Boundary_pT bou(
    redeclare package Medium = MediumA,
    nPorts=1) "Sink for air"
  Modelica.Thermal.HeatTransfer.Components.ThermalConductor theConWal(G=200)
    "Thermal conductor for wall"
  Modelica.Thermal.HeatTransfer.Sources.PrescribedHeatFlow heaFlo
    "Thermal loads"
  Modelica.Blocks.Sources.Constant TSetHea(k=273.15 + 22)
    "Heating set-point temperature"
  Buildings.HeatTransfer.Sources.FixedTemperature TOut(T=301.15)
    "Outdoor air temperature"
  Buildings.Controls.Continuous.LimPID conPID(
    yMax=0.094,
    Td=0,
    Ti=100,
    k=0.1) "Controller"
  Buildings.Fluid.Sources.Boundary_pT sou_1(
    redeclare package Medium = MediumW,
    T=288.15,
    nPorts=1) "Soure chilled water"
  Modelica.Blocks.Math.Gain gain(k=1200)
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor senTem
    "Room air temperature sensor"
  Sources.MassFlowSource_T pumHea(
    redeclare package Medium = MediumW,
    use_m_flow_in=true,
    nPorts=1,
    T=320.95) "Source for heating"
  Buildings.Fluid.Sources.Boundary_pT sin_2(
    redeclare package Medium = MediumW,
    nPorts=1) "Sink for hot water"
  Modelica.Blocks.Sources.Sine sine(
    f=1/86400,
    amplitude=1,
    phase=-1.5707963267949) "Source for thermal loads"
  Buildings.Fluid.MixingVolumes.MixingVolume vol(nPorts=2,
    redeclare package Medium = MediumA,
    m_flow_nominal=0.1,
    V=30,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    T_start=293.15) "Air volume for room"

  Buildings.Fluid.HeatExchangers.ActiveBeams.CoolingAndHeating beaCooHea(
    redeclare package MediumWat = MediumW,
    redeclare package MediumAir = MediumA,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_cooling perCoo,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_heating perHea,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Active beam"
equation
  connect(TOut.port, theConWal.port_a)
  connect(gain.y, heaFlo.Q_flow)
  connect(TSetHea.y, conPID.u_s)
  connect(senTem.T, conPID.u_m)
  connect(sine.y, gain.u)
  connect(bou.ports[1], vol.ports[1])
  connect(theConWal.port_b, vol.heatPort)
  connect(heaFlo.port, vol.heatPort)
  connect(senTem.port, vol.heatPort)
  connect(beaCooHea.watHea_b, sin_2.ports[1])
  connect(beaCooHea.heaPor, vol.heatPort)
  connect(souAir.ports[1], beaCooHea.air_a)
  connect(sin_1.ports[1], beaCooHea.watCoo_b)
  connect(sou_1.ports[1], beaCooHea.watCoo_a)
  connect(beaCooHea.air_b, vol.ports[2])
  connect(pumHea.ports[1], beaCooHea.watHea_a)
  connect(pumHea.m_flow_in, conPID.y)
end HeatingOnly;


within Buildings.Fluid.HeatExchangers.ActiveBeams;
package Examples "Package with examples of active beam models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


Medium model for air

within Buildings.Fluid.HeatExchangers.ActiveBeams.Validation;
model NumberOfBeams
  extends Modelica.Icons.Example;

  package MediumA = Buildings.Media.Air "Medium model for air";

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

  parameter Integer nBeams(min=1) = 10 "Number of beams";

  Buildings.Fluid.Sources.Boundary_pT sin_1(
    redeclare package Medium = MediumW,
    nPorts=2) "Sink for chilled water"
  Buildings.Fluid.Sources.MassFlowSource_T souAir(
    redeclare package Medium = MediumA,
    use_m_flow_in=false,
    nPorts=1,
    m_flow=0.0792,
    T=285.85) "Source for air"
  Buildings.Fluid.Sources.Boundary_pT sin_3(
    redeclare package Medium = MediumA,
    nPorts=2)
  Buildings.Fluid.Sources.Boundary_pT sou_1(
    redeclare package Medium = MediumW,
    nPorts=2,
    T=288.15) "Source for chilled water"
  Buildings.Fluid.Movers.FlowControlled_m_flow pumHotWat(
    redeclare package Medium = MediumW,
    m_flow_nominal=0.094,
    addPowerToMedium=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    use_riseTime=false,
    nominalValuesDefineDefaultPressureCurve=true) "Pump for hot water"
  Buildings.Fluid.Sources.Boundary_pT sou_2(
    redeclare package Medium = MediumW,
    nPorts=2,
    T=320.95) "Source for hot water"
  Buildings.Fluid.Sources.Boundary_pT sin_2(
    redeclare package Medium = MediumW,
    nPorts=2) "Sink for hot water"
  Buildings.Fluid.Movers.FlowControlled_m_flow pumChiWat(
    redeclare package Medium = MediumW,
    m_flow_nominal=0.094,
    addPowerToMedium=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    use_riseTime=false,
    nominalValuesDefineDefaultPressureCurve=true) "Pump for chilled water"
  Buildings.Fluid.HeatExchangers.ActiveBeams.CoolingAndHeating beaCooHea(
    redeclare package MediumWat = MediumW,
    redeclare package MediumAir = MediumA,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_cooling perCoo,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_heating perHea,
    nBeams=1,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Active beam"

  Buildings.HeatTransfer.Sources.PrescribedTemperature
    prescribedTemperature "Room temperature"

  Buildings.Fluid.Sources.MassFlowSource_T souAir10(
    redeclare package Medium = MediumA,
    use_m_flow_in=false,
    nPorts=1,
    m_flow=0.0792*nBeams,
    T=285.85) "Source for air"

  Buildings.Fluid.Movers.FlowControlled_m_flow pumHotWat10(
    redeclare package Medium = MediumW,
    addPowerToMedium=false,
    m_flow_nominal=0.094*nBeams,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    use_riseTime=false,
    nominalValuesDefineDefaultPressureCurve=true) "Pump for hot water"

  Buildings.Fluid.Movers.FlowControlled_m_flow pumChiWat10(
    redeclare package Medium = MediumW,
    addPowerToMedium=false,
    m_flow_nominal=0.094*nBeams,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    use_riseTime=false,
    nominalValuesDefineDefaultPressureCurve=true) "Pump for chilled water"

  Buildings.Fluid.HeatExchangers.ActiveBeams.CoolingAndHeating beaCooHea10(
    redeclare package MediumWat = MediumW,
    redeclare package MediumAir = MediumA,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_cooling
      perCoo,
    redeclare
      Buildings.Fluid.HeatExchangers.ActiveBeams.Data.Trox.DID632A_nozzleH_length6ft_heating
      perHea,
    nBeams=nBeams,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Active beam"
  Modelica.Blocks.Sources.Step step(
    height=-0.094,
    offset=0.094,
    startTime=2000) "Chilled water mass flow rate"
  Modelica.Blocks.Sources.Step step1(
    height=0.094,
    startTime=3000)
    "Hot water mass flow rate"
  Modelica.Blocks.Sources.Step step2(
    height=0.094*nBeams,
    startTime=3000) "Hot water mass flow rate"
  Modelica.Blocks.Sources.Step step3(
    height=-0.094*nBeams,
    offset=0.094*nBeams,
    startTime=2000) "Chilled water mass flow rate"
  Modelica.Blocks.Sources.Step step4(
    offset=273.15 + 25,
    height=-5,
    startTime=2500) "Room air temperature variation"
equation
  connect(sou_2.ports[1], pumHotWat.port_a)
  connect(pumChiWat.port_a, sou_1.ports[1])
  connect(pumChiWat.port_b, beaCooHea.watCoo_a)
  connect(beaCooHea.watCoo_b, sin_1.ports[1])
  connect(sin_2.ports[1], beaCooHea.watHea_b)
  connect(beaCooHea.watHea_a, pumHotWat.port_b)
  connect(souAir.ports[1], beaCooHea.air_a)
  connect(sin_3.ports[1], beaCooHea.air_b)
  connect(pumChiWat10.port_b, beaCooHea10.watCoo_a)
  connect(beaCooHea10.watHea_a, pumHotWat10.port_b)
  connect(souAir10.ports[1], beaCooHea10.air_a)
  connect(pumChiWat10.port_a, sou_1.ports[2])
  connect(pumHotWat10.port_a, sou_2.ports[2])
  connect(beaCooHea10.air_b, sin_3.ports[2])
  connect(beaCooHea10.watCoo_b, sin_1.ports[2])
  connect(beaCooHea10.watHea_b, sin_2.ports[2])
  connect(step.y, pumChiWat.m_flow_in)
  connect(step1.y, pumHotWat.m_flow_in)
  connect(step3.y, pumChiWat10.m_flow_in)
  connect(step2.y, pumHotWat10.m_flow_in)
  connect(step4.y, prescribedTemperature.T)
  connect(prescribedTemperature.port, beaCooHea10.heaPor)
  connect(beaCooHea.heaPor, prescribedTemperature.port)
end NumberOfBeams;


Validation model for the dynamic response for one and multiple beams. This model validates whether the transient response is indeed
independent of the number of beams.
The model is similar to

Buildings.Fluid.HeatExchangers.ActiveBeams.Validation.NumberOfBeams,
except that it is configured with a dynamic balance and non-default initial conditions.

within Buildings.Fluid.HeatExchangers.ActiveBeams.Validation;
model NumberOfBeamsDynamics
  "Validation model for the dynamic response for one and multiple beams"
  extends NumberOfBeams(
    nBeams = 2,
    beaCooHea(
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
      show_T=true,
      tau=120,
      TWatCoo_start=278.15,
      TWatHea_start=303.15),
    beaCooHea10(
      energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
      show_T=true,
      tau=120,
      TWatCoo_start=278.15,
      TWatHea_start=303.15),
    step(startTime=200),
    step1(startTime=300),
    step3(startTime=200),
    step2(startTime=300));

end NumberOfBeamsDynamics;


Collection of validation models

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


Header for a heat exchanger register

within Buildings.Fluid.HeatExchangers.BaseClasses;
model CoilHeader "Header for a heat exchanger register"
  extends Buildings.BaseClasses.BaseIcon;

  replaceable package Medium =
      Modelica.Media.Interfaces.PartialMedium "Medium in the component"
  parameter Boolean allowFlowReversal = true
    "= true to allow flow reversal, false restricts to design direction (port_a -> port_b)"

  parameter Integer nPipPar(min=1) "Number of parallel pipes in each register";
  parameter Modelica.Units.SI.MassFlowRate mStart_flow_a
    "Guess value for mass flow rate at port_a"

  Modelica.Fluid.Interfaces.FluidPort_a port_a[nPipPar](
        redeclare each final package Medium = Medium,
        each m_flow(start=mStart_flow_a/nPipPar, min=if allowFlowReversal then -Modelica.Constants.inf else 0))
    "Fluid connector a for medium (positive design flow direction is from port_a to port_b)"

  Modelica.Fluid.Interfaces.FluidPort_b port_b[nPipPar](
        redeclare each final package Medium = Medium,
        each m_flow(max=if allowFlowReversal then +Modelica.Constants.inf else 0))
    "Fluid connector b for medium (positive design flow direction is from port_a to port_b)"

equation
  connect(port_a, port_b)
end CoilHeader;