Register for a heat exchanger

within Buildings.Fluid.HeatExchangers.BaseClasses;
model CoilRegister "Register for a heat exchanger"
  import Modelica.Constants;
  extends Buildings.Fluid.Interfaces.FourPortFlowResistanceParameters(
     final computeFlowResistance1=true, final computeFlowResistance2=true);
  replaceable package Medium1 =
      Modelica.Media.Interfaces.PartialMedium "Medium 1 in the component"
  replaceable package Medium2 =
      Modelica.Media.Interfaces.PartialMedium "Medium 2 in the component"

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

  parameter Integer nPipPar(min=1)=2
    "Number of parallel pipes in each register";
  parameter Integer nPipSeg(min=1)=3
    "Number of pipe segments per register used for discretization";
  final parameter Integer nEle = nPipPar * nPipSeg
    "Number of heat exchanger elements";

  parameter Modelica.Units.SI.ThermalConductance UA_nominal
    "Thermal conductance at nominal flow, used to compute time constant"

  parameter Modelica.Units.SI.MassFlowRate m1_flow_nominal
    "Mass flow rate medim 1"
  parameter Modelica.Units.SI.MassFlowRate m2_flow_nominal
    "Mass flow rate medium 2"

  parameter Modelica.Units.SI.Time tau1=20
    "Time constant at nominal flow for medium 1"
  parameter Modelica.Units.SI.Time tau2=1
    "Time constant at nominal flow for medium 2"

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

  replaceable Buildings.Fluid.HeatExchangers.BaseClasses.HexElementSensible ele[nPipPar, nPipSeg]
    constrainedby Buildings.Fluid.HeatExchangers.BaseClasses.PartialHexElement(
    redeclare each package Medium1 = Medium1,
    redeclare each package Medium2 = Medium2,
    initialize_p1 = {{(i == 1 and j == 1 and initialize_p1) for i in 1:nPipSeg} for j in 1:nPipPar},
    initialize_p2 = {{(i == 1 and j == 1 and initialize_p2) for i in 1:nPipSeg} for j in 1:nPipPar},
    each allowFlowReversal1=allowFlowReversal1,
    each allowFlowReversal2=allowFlowReversal2,
    each tau1=tau1/nPipSeg,
    each m1_flow_nominal=m1_flow_nominal/nPipPar,
    each tau2=tau2,
    each m2_flow_nominal=m2_flow_nominal/nPipPar/nPipSeg,
    each tau_m=tau_m,
    each UA_nominal=UA_nominal/nPipPar/nPipSeg,
    each energyDynamics=energyDynamics,
    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=dp1_nominal,
    each dp2_nominal=dp2_nominal) "Element of a heat exchanger"

  Modelica.Fluid.Interfaces.FluidPort_a[nPipPar] port_a1(
        redeclare each package Medium = Medium1,
        each m_flow(start=0, min=if allowFlowReversal1 then -Constants.inf else 0))
    "Fluid connector a for medium 1 (positive design flow direction is from port_a1 to port_b1)"
  Modelica.Fluid.Interfaces.FluidPort_b[nPipPar] port_b1(
        redeclare each package Medium = Medium1,
        each m_flow(start=0, max=if allowFlowReversal1 then +Constants.inf else 0))
    "Fluid connector b for medium 1 (positive design flow direction is from port_a to port_b)"
  Modelica.Fluid.Interfaces.FluidPort_a[nPipPar,nPipSeg] port_a2(
        redeclare each package Medium = Medium2,
        each m_flow(start=0, min=if allowFlowReversal2 then -Constants.inf else 0))
    "Fluid connector a for medium 2 (positive design flow direction is from port_a2 to port_b2)"
  Modelica.Fluid.Interfaces.FluidPort_b[nPipPar,nPipSeg] port_b2(
        redeclare each package Medium = Medium2,
        each m_flow(start=0, max=if allowFlowReversal2 then +Constants.inf else 0))
    "Fluid connector b for medium 2 (positive design flow direction is from port_a to port_b)"

  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPor1[nPipPar, nPipSeg]
    "Heat port for heat exchange with the control volume 1"
  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPor2[nPipPar, nPipSeg]
    "Heat port for heat exchange with the control volume 2"

  Modelica.Units.SI.HeatFlowRate Q1_flow
    "Heat transferred from solid into medium 1";
  Modelica.Units.SI.HeatFlowRate Q2_flow
    "Heat transferred from solid into medium 2";
  parameter Modelica.Units.SI.Time tau_m=60
    "Time constant of metal at nominal UA value"

  parameter Modelica.Fluid.Types.Dynamics energyDynamics=
    Modelica.Fluid.Types.Dynamics.DynamicFreeInitial
    "Default formulation of energy balances"

  Modelica.Blocks.Interfaces.RealInput Gc_2
    "Signal representing the convective thermal conductance medium 2 in [W/K]"
  Modelica.Blocks.Interfaces.RealInput Gc_1
    "Signal representing the convective thermal conductance medium 1 in [W/K]"
protected
  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"

equation
  // As OpenModelica does not support multiple iterators as of August 2014, we
  // use here two sum(.) functions
  Q1_flow = sum(sum(ele[i,j].Q1_flow for i in 1:nPipPar) for j in 1:nPipSeg);
  Q2_flow = sum(sum(ele[i,j].Q2_flow for i in 1:nPipPar) for j in 1:nPipSeg);
  for i in 1:nPipPar loop
    connect(ele[i,1].port_a1,       port_a1[i])
    connect(ele[i,nPipSeg].port_b1, port_b1[i])
    for j in 1:nPipSeg-1 loop
      connect(ele[i,j].port_b1, ele[i,j+1].port_a1)
    end for;

    for j in 1:nPipSeg loop
      connect(ele[i,j].port_a2, port_a2[i,j])
      connect(ele[i,j].port_b2, port_b2[i,j])
    end for;
  end for;

  connect(Gc_1, gai_1.u)
  connect(Gc_2, gai_2.u)
  for i in 1:nPipPar loop

     for j in 1:nPipSeg loop
      connect(gai_1.y, ele[i,j].Gc_1)
      connect(gai_2.y, ele[i,j].Gc_2)
     end for;
  end for;

  connect(ele.heaPor1, heaPor1)
  connect(ele.heaPor2, heaPor2)
end CoilRegister;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model DuctManifoldFixedResistance
  "Manifold for a heat exchanger air duct connection"
  extends PartialDuctManifold;

  parameter Boolean use_dh = false "Set to true to specify hydraulic diameter"

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Mass flow rate at port_a"
  parameter Modelica.Units.SI.PressureDifference dp_nominal(min=0, displayUnit=
        "Pa") "Pressure drop"
  parameter Modelica.Units.SI.Length dh=1 "Hydraulic diameter of duct"
  parameter Real ReC=4000
    "Reynolds number where transition to turbulence starts"
  parameter Boolean linearized = false
    "= true, use linear relation between m_flow and dp for any flow rate"
  parameter Real deltaM(min=0) = 0.3
    "Fraction of nominal mass flow rate where transition to turbulent occurs"
  parameter Boolean from_dp = false
    "= true, use m_flow = f(dp) else dp = f(m_flow)"

  Buildings.Fluid.FixedResistances.PressureDrop fixRes(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal,
    from_dp=from_dp,
    deltaM=deltaM,
    linearized=linearized) "Fixed resistance for each duct"
protected
  DuctManifoldFlowDistributor floDis(
    redeclare package Medium = Medium,
    nPipPar=nPipPar,
    mStart_flow_a=mStart_flow_a,
    nPipSeg=nPipSeg,
    allowFlowReversal=allowFlowReversal,
    m_flow_nominal=m_flow_nominal)
    "Mass flow distributor to the individual segments of the coil"
equation
  connect(fixRes.port_a, port_a)
  connect(fixRes.port_b, floDis.port_a)
  connect(floDis.port_b, port_b)
end DuctManifoldFixedResistance;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model DuctManifoldFlowDistributor
  "Manifold for duct inlet that distributes the mass flow rate equally"
  extends PartialDuctManifold;

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Mass flow rate at port_a"

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

  Modelica.Blocks.Math.Gain gain(final k=1/nPipPar/nPipSeg)
    "Gain for mass flow distribution to manifold"

  Modelica.Blocks.Sources.Constant dpDis(final k=0)
    "Pressure drop of distribution"

  Movers.BaseClasses.IdealSource sou0(
    redeclare final package Medium = Medium,
    allowFlowReversal=allowFlowReversal,
    m_flow_small=1E-4*abs(m_flow_nominal/nPipPar/nPipSeg),
    final control_m_flow=false,
    m_flow_start=mStart_flow_a/nPipPar/nPipSeg,
    final dp_start=0,
    final show_V_flow=false,
    final show_T=false) "Mass flow rate source with fixed dp"

  Movers.BaseClasses.IdealSource sou1[nPipPar-1](
    redeclare each final package Medium = Medium,
    each final allowFlowReversal=allowFlowReversal,
    each final m_flow_small=1E-4*abs(m_flow_nominal/nPipPar/nPipSeg),
    each final control_m_flow=true,
    each m_flow_start=mStart_flow_a/nPipPar/nPipSeg,
    each final show_V_flow=false,
    each final show_T=false) "Mass flow rate source with fixed m_flow"

  Movers.BaseClasses.IdealSource sou2[nPipPar,nPipSeg-1](
    redeclare each final package Medium = Medium,
    each final allowFlowReversal=allowFlowReversal,
    each final m_flow_small=1E-4*abs(m_flow_nominal/nPipPar/nPipSeg),
    each final control_m_flow=true,
    each m_flow_start=mStart_flow_a/nPipPar/nPipSeg,
    each final show_V_flow=false,
    each final show_T=false) "Mass flow rate source with fixed m_flow"
equation
  connect(port_a, senMasFlo.port_a)
  connect(senMasFlo.m_flow, gain.u)

  // Connect the model that imposes zero pressure drop in the manifold
  connect(senMasFlo.port_b, sou0.port_a)
  connect(sou0.port_b, port_b[1, 1])
  connect(dpDis.y, sou0.dp_in)

  // Connect the models that impose the mass flow rates
  for i in 1:nPipPar-1 loop
      connect(senMasFlo.port_b, sou1[i].port_a)
      connect(sou1[i].port_b, port_b[i+1, 1])
      connect(gain.y, sou1[i].m_flow_in)
    end for;

  for i in 1:nPipPar loop
    for j in 1:nPipSeg-1 loop
      connect(senMasFlo.port_b, sou2[i, j].port_a)
      connect(sou2[i, j].port_b, port_b[i, j+1])
      connect(gain.y, sou2[i, j].m_flow_in)
    end for;
  end for;
end DuctManifoldFlowDistributor;


Duct manifold without resistance. Duct manifold without flow resistance.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model DuctManifoldNoResistance "Duct manifold without resistance"
  extends PartialDuctManifold;

equation
  for i in 1:nPipPar loop
    for j in 1:nPipSeg loop
    connect(port_a, port_b[i, j])
    end for;
  end for;
end DuctManifoldNoResistance;


Calculates the hA value for water inside a coil. Model for convective heat transfer coefficients inside a coil.
Optionally, the convective heat transfer coefficient can
be computed as a function of temperature and mass flow rate.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model HACoilInside "Calculates the hA value for water inside a coil"
  extends Modelica.Blocks.Icons.Block;

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Water mass flow rate"

  Modelica.Blocks.Interfaces.RealInput m_flow(unit="kg/s") "Mass flow rate"
  Modelica.Blocks.Interfaces.RealInput T(unit="K") "Temperature"

  Modelica.Blocks.Interfaces.RealOutput hA(unit="W/K")
    "Inside convective heat transfer"

  parameter Modelica.Units.SI.ThermalConductance hA_nominal(min=0)
    "Convective heat transfer coefficient"
  parameter Real n(min=0, max=1)=0.85
    "Water-side exponent for convective heat transfer coefficient, h proportional to m_flow^n";
  parameter Modelica.Units.SI.Temperature T_nominal=
      Modelica.Units.Conversions.from_degC(20) "Nominal water temperature"
  parameter Boolean flowDependent=true
    "Set to false to make hA independent of mass flow rate"
  parameter Boolean temperatureDependent = true
    "Set to false to make hA independent of temperature"

protected
  Real x(min=0)
    "Factor for temperature dependent variation of heat transfer coefficient";
  parameter Real s(min=0, fixed=false)
    "Coefficient for temperature dependence of heat transfer coefficient";
  Real fm "Fraction of actual to nominal mass flow rate";

initial equation
  s =if temperatureDependent then 0.014/(1 + 0.014*
    Modelica.Units.Conversions.to_degC(T_nominal)) else 1;
equation
  fm = if flowDependent then m_flow / m_flow_nominal else 1;
  x = if temperatureDependent then 1 + s * (T-T_nominal) else 1;
  if flowDependent and temperatureDependent then
    hA = x * hA_nominal * Buildings.Utilities.Math.Functions.regNonZeroPower(fm, n, 0.1);
  elseif flowDependent then
    hA = hA_nominal * Buildings.Utilities.Math.Functions.regNonZeroPower(fm, n, 0.1);
  elseif temperatureDependent then
    hA = x * hA_nominal;
  else
    hA = hA_nominal;
  end if;
end HACoilInside;


Sensible convective heat transfer model for air to water coil. Model for sensible convective heat transfer coefficients for an air to water coil.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model HADryCoil
  "Sensible convective heat transfer model for air to water coil"
  extends Modelica.Blocks.Icons.Block;

  parameter Modelica.Units.SI.ThermalConductance UA_nominal(min=0)
    "Thermal conductance at nominal flow"

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal_w
    "Water mass flow rate"
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal_a
    "Air mass flow rate"

  Modelica.Blocks.Interfaces.RealInput m1_flow(final unit="kg/s") "Mass flow rate medium 1"
  Modelica.Blocks.Interfaces.RealInput m2_flow(final unit="kg/s") "Mass flow rate medium 2"
  Modelica.Blocks.Interfaces.RealInput T_1(final unit="K") "Temperature medium 1"
  Modelica.Blocks.Interfaces.RealInput T_2(final unit="K") "Temperature medium 2"

  Modelica.Blocks.Interfaces.RealOutput hA_1(final unit="W/K")
    "Convective heat transfer medium 1"
  Modelica.Blocks.Interfaces.RealOutput hA_2(final unit="W/K")
    "Convective heat transfer medium 2"

  parameter Real r_nominal(min=0)=0.5
    "Ratio between air-side and water-side convective heat transfer coefficient"
  parameter Modelica.Units.SI.ThermalConductance hA_nominal_w(min=0) =
    UA_nominal*(r_nominal + 1)/r_nominal
    "Water side convective heat transfer coefficient"
  parameter Modelica.Units.SI.ThermalConductance hA_nominal_a(min=0) =
    r_nominal*hA_nominal_w
    "Air side convective heat transfer coefficient, including fin resistance"
  parameter Real n_w(min=0, max=1)=0.85
    "Water-side exponent for convective heat transfer coefficient, h~m_flow^n";
  parameter Real n_a(min=0, max=1)=0.8
    "Air-side exponent for convective heat transfer coefficient, h~m_flow^n";
  parameter Modelica.Units.SI.Temperature T0_w=
      Modelica.Units.Conversions.from_degC(20) "Water temperature"
  parameter Modelica.Units.SI.Temperature T0_a=
      Modelica.Units.Conversions.from_degC(20) "Air temperature"
  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 = true
    "Set to false to make water-side hA independent of temperature"
  parameter Boolean airSideTemperatureDependent = true
    "Set to false to make air-side hA independent of temperature"
protected
  Real x_a(min=0)
    "Factor for air side temperature dependent variation of heat transfer coefficient";
  Real x_w(min=0)
    "Factor for water side temperature dependent variation of heat transfer coefficient";
  parameter Real s_w(min=0, fixed=false)
    "Coefficient for temperature dependence of water side heat transfer coefficient";
  Real fm_w "Fraction of actual to nominal mass flow rate";
  Real fm_a "Fraction of actual to nominal mass flow rate";
initial equation
  s_w =if waterSideTemperatureDependent then 0.014/(1 + 0.014*
    Modelica.Units.Conversions.to_degC(T0_w)) else 1;
equation
  fm_w = if waterSideFlowDependent then
              m1_flow / m_flow_nominal_w else 1;
  fm_a = if airSideFlowDependent then
              m2_flow / m_flow_nominal_a else 1;
  x_w = if waterSideTemperatureDependent then
         1 + s_w * (T_1-T0_w) else
              1;
  x_a = if airSideTemperatureDependent then
         1 + 7.8532E-4 * (T_2-T0_a) else
              1;
  if waterSideFlowDependent then
    hA_1 = x_w * hA_nominal_w
               * Buildings.Utilities.Math.Functions.regNonZeroPower(fm_w, n_w, 0.1);
  else
    hA_1 = x_w * hA_nominal_w;
  end if;

  if airSideFlowDependent then
    hA_2 = x_a * hA_nominal_a
               * Buildings.Utilities.Math.Functions.regNonZeroPower(fm_a, n_a, 0.1);
  else
    hA_2 = x_a * hA_nominal_a;
  end if;
end HADryCoil;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model HANaturalCylinder
  "Calculates an hA value for natural convection around a cylinder"
  extends Modelica.Blocks.Icons.Block;
  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    "Medium inside the tank";
  parameter Modelica.Units.SI.Diameter ChaLen
    "Characteristic length of the cylinder";

  parameter Modelica.Units.SI.ThermalConductance hA_nominal(min=0)
    "Convective heat transfer coefficient"
  parameter Modelica.Units.SI.Temperature TFlu_nominal
    "Fluid temperature at hA_nominal"
  parameter Modelica.Units.SI.Temperature TSur_nominal
    "Surface temperature at hA_nominal"

  Modelica.Blocks.Interfaces.RealInput TSur(unit = "K")
    "Temperature of the external surface of the heat exchanger"
  Modelica.Blocks.Interfaces.RealInput TFlu(unit = "K")
    "Temperature of the fluid in the heat exchanger"
  Modelica.Blocks.Interfaces.RealOutput hA(unit = "W/K")
    "hA-value of the heat exchanger"
  Real h(unit="W/(m2.K)") "Convection coefficient";
protected
  parameter Real Gr_nominal(fixed=false) "Grashof number";
  parameter Real B_nominal(unit="1/K", fixed=false)
    "isobaricExpansionCoefficient";
  parameter Real nu_nominal(unit = "m2/s", fixed=false)
    "Kinematic viscosity of the medium";
  parameter Modelica.Units.SI.DynamicViscosity mu_nominal(fixed=false)
    "Dynamic viscosity of the medium";
  parameter Modelica.Units.SI.Density rho_nominal(fixed=false)
    "Density of the medium";

  parameter Modelica.Units.SI.ThermalConductivity k_nominal(fixed=false)
    "Thermal conductivity of the fluid";

  parameter Real Ra_nominal(fixed=false) "Rayleigh number";
  parameter Real Pr_nominal(fixed=false) "Prandlt number";
  parameter Real Nusselt_nominal(fixed=false) "Nusselt number";

  parameter Real h_nominal(unit="W/(m2.K)", fixed=false)
    "Convection coefficient";
  parameter Modelica.Units.SI.Area A(fixed=false)
    "Surface area, deduced from hA_nominal, fluid temperatures and characteristic length";

  Modelica.Units.SI.ThermalConductivity k "Thermal conductivity of the fluid";
  Real Gr "Grashof number";
  Real B(unit="1/K") "isobaricExpansionCoefficient";
  Real nu(unit = "m2/s") "Kinematic viscosity of the medium";
  Modelica.Units.SI.DynamicViscosity mu "Dynamic viscosity of the medium";
  Modelica.Units.SI.Density rho "Density of the medium";
  constant Modelica.Units.SI.Acceleration g=Modelica.Constants.g_n
    "Acceleration due to gravity";

  Medium.ThermodynamicState state
    "Thermodynamic state of the fluid around the cylinder";
  Real Ra "Rayleigh number";
  Real Pr "Prandlt number";
  Real Nusselt "Nusselt number";

function nusselt
    input Modelica.Units.SI.ThermalConductivity k "Thermal conductivity";
  input Real Pr "Prandlt number";
  input Real Ra "Rayleigh number";
  output Real Nu(min=0) "Nusselt number";
  protected
  Real num "Numerator";
  Real den "Denominator";
algorithm
  num := (0.387*Buildings.Utilities.Math.Functions.smoothMax(Ra,1,0.1)^(1/6));
  den := ((1+(0.559/Pr)^(9/16))^(8/27));
  Nu := (0.6+num/den)^2;
end nusselt;

initial equation

  // Fluid properties
  mu_nominal = Buildings.Fluid.HeatExchangers.BaseClasses.dynamicViscosityWater(
        T = 0.5 * (TSur_nominal+TFlu_nominal));
  rho_nominal = Medium.density(
        Medium.setState_pTX(
          p = Medium.p_default,
          T = 0.5*(TSur_nominal+TFlu_nominal),
          X = Medium.X_default));
  Pr_nominal = Buildings.Fluid.HeatExchangers.BaseClasses.prandtlNumberWater(
          T = 0.5*(TSur_nominal+TFlu_nominal));

  B_nominal = Buildings.Fluid.HeatExchangers.BaseClasses.isobaricExpansionCoefficientWater(
          T = 0.5*(TSur_nominal+TFlu_nominal));
  nu_nominal = mu_nominal/rho_nominal;

  Gr_nominal = Modelica.Constants.g_n * B_nominal * (TSur_nominal -
  TFlu_nominal)*ChaLen^3/nu_nominal^2;
  Ra_nominal = Gr_nominal*Pr_nominal;
  // Convection coefficient
  k_nominal = Medium.thermalConductivity(
    Medium.setState_pTX(
    p = Medium.p_default,
    T = 0.5*(TFlu_nominal+TSur_nominal),
    X = Medium.X_default));
  Nusselt_nominal = nusselt(k=k_nominal, Pr=Pr_nominal, Ra=Ra_nominal);
  h_nominal = Nusselt_nominal * k_nominal/ChaLen;
  A = hA_nominal / h_nominal;
equation
  // Fluid properties
  state = Medium.setState_pTX(
             p = Medium.p_default,
             T = 0.5*(TSur+TFlu),
             X = Medium.X_default);
  mu = Buildings.Fluid.HeatExchangers.BaseClasses.dynamicViscosityWater(
        T = 0.5 * (TSur+TFlu));
  rho = Medium.density(state);
  Pr = Buildings.Fluid.HeatExchangers.BaseClasses.prandtlNumberWater(
          T = 0.5*(TSur+TFlu));

  B = Buildings.Fluid.HeatExchangers.BaseClasses.isobaricExpansionCoefficientWater(
          T = 0.5*(TSur+TFlu));
  nu = mu/rho;

  Gr = Modelica.Constants.g_n * B * (TSur - TFlu)*ChaLen^3/nu^2;
  Ra = Gr*Pr;
  // Convection coefficient
  k = Medium.thermalConductivity(state);
  Nusselt = nusselt(k=k, Pr=Pr, Ra=Ra);
  h = Nusselt * k/ChaLen;
  hA = h*A;

            where NuD is the Nusselt number, RaD is the
            Rayleigh number and
            Pr is the Prandtl number.

            This correclation is accurate for RaD less than 1012.

            h is then calculated from the Nusselt number. The equation is

            h = NuD k/D

            where k is the thermal conductivity of the fluid and D is the diameter
            of the submerged cylinder.

References

            Fundamentals of Heat and Mass Transfer (Fourth Edition), Frank Incropera and David
            DeWitt, John Wiley and Sons, 1996

            ", revisions="

August 29, 2014 by Michael Wetter:

Refactored function calls for medium properties.

May 10, 2013 by Michael Wetter:

Revised implementation to use hA_nominal as a parameter, and compute the
associated surface area A. This revision was required to have a consistent
computation of the hA values inside and outside of the coil in the
heat exchanger model of the water tank.

February 26, 2013 by Peter Grant:

First implementation.

"),
    Icon(graphics={                       Text(
          extent={{-66,88},{60,-2}},
          textColor={0,0,0},
          fillColor={255,255,255},
          fillPattern=FillPattern.Solid,
          textString="hA"),
        Rectangle(
          extent={{-62,-26},{64,-72}},
          lineColor={0,0,0},
          fillColor={0,0,0},
          fillPattern=FillPattern.Solid),
        Ellipse(
          extent={{-56,-72},{-66,-26}},
          lineColor={0,0,0},
          fillColor={255,255,255},
          fillPattern=FillPattern.Solid),
        Ellipse(
          extent={{70,-72},{60,-26}},
          lineColor={0,0,0},
          fillColor={0,0,0},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{-38,-4},{-44,-18},{-32,-18},{-38,-4}},
          lineColor={175,175,175},
          fillColor={175,175,175},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{-12,-4},{-18,-18},{-6,-18},{-12,-4}},
          lineColor={175,175,175},
          fillColor={175,175,175},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{16,-4},{10,-18},{22,-18},{16,-4}},
          lineColor={175,175,175},
          fillColor={175,175,175},
          fillPattern=FillPattern.Solid),
        Polygon(
          points={{42,-4},{36,-18},{48,-18},{42,-4}},
          lineColor={175,175,175},
          fillColor={175,175,175},
          fillPattern=FillPattern.Solid),
        Line(
          points={{-38,-18},{-34,-38},{-44,-52},{-32,-72},{-40,-86}},
          color={175,175,175},
          smooth=Smooth.Bezier),
        Line(
          points={{-12,-18},{-8,-38},{-18,-52},{-6,-72},{-14,-86}},
          color={175,175,175},
          smooth=Smooth.Bezier),
        Line(
          points={{16,-18},{20,-38},{10,-52},{22,-72},{14,-86}},
          color={175,175,175},
          smooth=Smooth.Bezier),
        Line(
          points={{42,-18},{46,-38},{36,-52},{48,-72},{40,-86}},
          color={175,175,175},
          smooth=Smooth.Bezier)}));
end HANaturalCylinder;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model HexElementLatent "Element of a heat exchanger with humidity condensation of fluid 2"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialHexElement(
   redeclare final Buildings.Fluid.MixingVolumes.BaseClasses.MixingVolumeHeatPort vol1(
     final energyDynamics=energyDynamics,
     final massDynamics=energyDynamics,
     final initialize_p=initialize_p1,
     prescribedHeatFlowRate=false),
   redeclare final Buildings.Fluid.MixingVolumes.BaseClasses.MixingVolumeHeatMoisturePort vol2(
     final energyDynamics=energyDynamics,
     final massDynamics=energyDynamics,
     final initialize_p=initialize_p2,
     final simplify_mWat_flow=simplify_mWat_flow,
     prescribedHeatFlowRate=false));

  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"

  MassExchange masExc(
     redeclare final package Medium=Medium2) "Model for mass exchange"

  Modelica.Blocks.Math.Gain gain(final k=-1) "Change sign"
protected
  Modelica.Thermal.HeatTransfer.Sensors.TemperatureSensor temSen(
    T(final quantity="ThermodynamicTemperature",
      final unit = "K", displayUnit = "degC", min=0))
    "Temperature sensor of metal"
  Buildings.HeatTransfer.Sources.PrescribedHeatFlow heaConVapAir
    "Heat conductor for latent heat flow rate, accounting for latent heat removed with vapor"
  Modelica.Blocks.Math.Product pro
    "Product to compute the latent heat flow rate"
  Modelica.Blocks.Sources.RealExpression h_fg(final y=Buildings.Utilities.Psychrometrics.Constants.h_fg)
    "Enthalpy of vaporization"
  HeatTransfer.Sources.PrescribedHeatFlow heaConVapCoi
    "Heat conductor for latent heat flow rate, accounting for latent heat deposited with vapor on the coil"
equation
  connect(temSen.T, masExc.TSur)
  connect(vol2.X_w, masExc.XInf)
  connect(Gc_2, masExc.Gc)
  connect(temSen.port, con1.solid)
  connect(masExc.mWat_flow, pro.u2)
  connect(pro.u1, h_fg.y)
  connect(heaConVapAir.port, con2.fluid)
  connect(masExc.mWat_flow, vol2.mWat_flow)
  connect(pro.y, heaConVapAir.Q_flow)
  connect(heaConVapCoi.Q_flow, gain.y)
  connect(pro.y, gain.u)
  connect(heaConVapCoi.port, con2.solid)
end HexElementLatent;


Element of a heat exchanger with no humidity condensation. Element of a heat exchanger with humidity condensation and
with dynamics of the fluids and the solid.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model HexElementSensible
  "Element of a heat exchanger with no humidity condensation"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialHexElement(
   redeclare final Buildings.Fluid.MixingVolumes.BaseClasses.MixingVolumeHeatPort vol1(
     final energyDynamics=energyDynamics,
     final massDynamics=energyDynamics,
     final initialize_p=initialize_p1,
     prescribedHeatFlowRate=false),
   redeclare final Buildings.Fluid.MixingVolumes.BaseClasses.MixingVolumeHeatPort vol2(
     final energyDynamics=energyDynamics,
     final massDynamics=energyDynamics,
     final initialize_p=initialize_p2,
     prescribedHeatFlowRate=false));
end HexElementSensible;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model MassExchange
  "Block to compute the latent heat transfer based on the Lewis number"
  extends Buildings.BaseClasses.BaseIcon;
  replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    "Fluid medium model"

  Modelica.Blocks.Interfaces.RealInput XInf "Water mass fraction of medium"
  Modelica.Blocks.Interfaces.RealInput TSur(final quantity="ThermodynamicTemperature",
                                            final unit = "K", displayUnit = "degC", min=0)
    "Surface temperature"
  Modelica.Blocks.Interfaces.RealOutput mWat_flow(final unit = "kg/s")
    "Water flow rate"
  Modelica.Blocks.Interfaces.RealInput Gc
    "Signal representing the convective (sensible) thermal conductance in [W/K]"
  parameter Real Le = 1 "Lewis number (around 1 for water vapor in air)";
  parameter Real n = 1/3
    "Exponent in bondary layer ratio, delta/delta_t = Pr^n";
public
  Buildings.Utilities.Psychrometrics.X_pW humRatPre(use_p_in=false)
    "Model to convert water vapor pressure into humidity ratio"
  Buildings.Utilities.Psychrometrics.pW_TDewPoi TDewPoi
    "Model to compute the water vapor pressure at the dew point"
  Modelica.Blocks.Math.Gain gain(k=1/cpLe_default)
    "Constant to convert from heat transfer to mass transfer"
  Modelica.Blocks.Math.Product mWat "Water flow rate"
  Modelica.Blocks.Math.Min min
  Modelica.Blocks.Sources.Constant zero(k=0) "Constant for zero"
  Modelica.Blocks.Math.Add delX(k2=-1, k1=1) "Humidity difference"
protected
 parameter Medium.ThermodynamicState sta_default = Medium.setState_phX(h=Medium.h_default,
       p=Medium.p_default, X=Medium.X_default);
  parameter Modelica.Units.SI.SpecificHeatCapacity cp_default=
      Medium.specificHeatCapacityCp(sta_default)
    "Specific heat capacity";
 parameter Real cpLe_default(unit="J/(kg.K)") = cp_default * Le^(1-n);
equation
  connect(TSur, TDewPoi.T)
  connect(TDewPoi.p_w, humRatPre.p_w)
  connect(Gc, gain.u)
  connect(gain.y, mWat.u2)
  connect(mWat.y, mWat_flow)
  connect(zero.y,min. u1)
  connect(delX.u2,XInf)
  connect(humRatPre.X_w, delX.u1)
  connect(delX.y, min.u2)
  connect(min.y, mWat.u1)
end MassExchange;


Computes number of transfer units for given heat exchanger effectiveness and heat exchanger flow regime. This function computes the number of transfer units for a given heat exchanger effectiveness,
capacity flow ratio and heat exchanger flow regime.
The different options for the flow regime are declared in

Buildings.Fluid.Types.HeatExchangerFlowRegime.

within Buildings.Fluid.HeatExchangers.BaseClasses;
function ntu_epsilonZ
  "Computes number of transfer units for given heat exchanger effectiveness and heat exchanger flow regime"
  import f = Buildings.Fluid.Types.HeatExchangerFlowRegime;
  input Real eps(min=0, max=0.999) "Heat exchanger effectiveness";
  input Real Z(min=0, max=1) "Ratio of capacity flow rate (CMin/CMax)";
  input Integer flowRegime
    "Heat exchanger flow regime";
  output Real NTU "Number of transfer units";

protected
  Real a "Auxiliary variable";

  function epsilon_ntuZ_crossFlowUnmixed
    "Internal function to solve eps=f(NTU, Z) for NTU for cross flow unmixed"
    extends Modelica.Math.Nonlinear.Interfaces.partialScalarFunction;

    input Real eps(min=0, max=0.999) "Heat exchanger effectiveness";
    input Real Z(min=0, max=1) "Ratio of capacity flow rate (CMin/CMax)";

  protected
    Real NTUExp "Auxiliary variable";

  algorithm
    NTUExp := u^(-0.22);
    y := 1 - Modelica.Math.exp( ( Modelica.Math.exp( - u * Z * NTUExp)  - 1)  / (Z * NTUExp))-eps;
  end epsilon_ntuZ_crossFlowUnmixed;

algorithm
  if (flowRegime == Integer(f.ParallelFlow)) then // parallel flow
    a := Z+1;
    assert(eps < 1/a,
      "Invalid input data. eps > 1/(1+Z) is physically not possible for parallel flow.
  Received eps = " + String(eps) + "
             Z = " + String(Z) + "
       1/(Z+1) = " + String(1/a));
    NTU := -(Modelica.Math.log(1-eps*a))/(a);
  elseif (flowRegime == Integer(f.CounterFlow)) then// counter flow
   // a is constraining Z since eps is not defined for Z=1.
    a := smooth(1, if Z < 0.97 then Z else
      Buildings.Utilities.Math.Functions.smoothMin(
      x1=Z,
      x2=0.98,
      deltaX=0.01));
    NTU := Modelica.Math.log((1-eps)/(1-eps*a)) / (a-1);

  elseif (flowRegime == Integer(f.CrossFlowUnmixed)) then
    a := 0;
    // The function Internal.solve evaluates epsilon_ntuZ at NTU=x_min-1e-10 and NTU=x_max+1e-10
    // when it solves iteratively epsilon_ntuZ for ntu
    // Therefore, we set x_min=1.5*1e-10 to prevent computing NTU^(-0.22)=(-1e-10)^(-0.22).
    NTU :=Modelica.Math.Nonlinear.solveOneNonlinearEquation(
      f=function epsilon_ntuZ_crossFlowUnmixed(eps=eps, Z=Z),
      u_min=1.5*1e-10,
      u_max=1e6);
  elseif (flowRegime == Integer(f.CrossFlowCMinUnmixedCMaxMixed)) then
    // cross flow, (single pass), CMax mixed, CMin unmixed. (Coil with one row.)
   a := smooth(1, if Z > 0.03 then Z else
      Buildings.Utilities.Math.Functions.smoothMin(
      x1=0.02,
      x2=Z,
      deltaX=0.01));
    NTU := -Modelica.Math.log(1+(Modelica.Math.log(1-eps*a)/a));
  elseif (flowRegime == Integer(f.CrossFlowCMinMixedCMaxUnmixed)) then
    // cross flow, (single pass), CMin mixed, CMax unmixed.
   a := smooth(1, if Z > 0.03 then Z else
      Buildings.Utilities.Math.Functions.smoothMin(
      x1=0.02,
      x2=Z,
      deltaX=0.01));
    NTU := -Modelica.Math.log(1+Z*Modelica.Math.log(1-eps))/Z;
  elseif (flowRegime == Integer(f.ConstantTemperaturePhaseChange)) then
    // one side is experiencing constant temperature phase change
    // Z is unused
    a := 0;
    NTU := -Modelica.Math.log((1-eps));
  else
    a := 0;
    NTU := 0;
    assert(Integer(f.ParallelFlow) <= flowRegime and
           flowRegime <= Integer(f.ConstantTemperaturePhaseChange),
           "Flow regime is not implemented.");
  end if;
end ntu_epsilonZ;


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


within Buildings.Fluid.HeatExchangers.BaseClasses;
partial model PartialDuctManifold
  "Partial manifold for heat exchanger duct connection"
  extends PartialDuctPipeManifold;
  parameter Integer nPipSeg(min=1)
    "Number of pipe segments per register used for discretization";

  Modelica.Fluid.Interfaces.FluidPort_b[nPipPar,nPipSeg] port_b(
        redeclare each package Medium = Medium,
        each m_flow(start=-mStart_flow_a/nPipSeg/nPipPar,
             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)"
end PartialDuctManifold;


within Buildings.Fluid.HeatExchangers.BaseClasses;
partial model PartialDuctPipeManifold
  "Partial heat exchanger duct and pipe manifold"
  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";

  // The parameter mStart_flow_a is used by PartialDuctManifold and
  // by PartialPipeManifold to set the start value of each port.
  // It is no longer used to set port_a.m_flow.start as the arithmetics is not
  // expanded by Dymola when checking for consistent start values, and hence
  // the translation will stop with an error in Dymola 2016 when using the pedantic
  // mode.
  parameter Modelica.Units.SI.MassFlowRate mStart_flow_a
    "Guess value for mass flow rate at port_a"

  Modelica.Fluid.Interfaces.FluidPort_a port_a(
        redeclare package Medium = Medium,
        m_flow(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)"
end PartialDuctPipeManifold;


Partial model to implement heat exchangers based on effectiveness model. Partial model to implement heat exchanger models.

within Buildings.Fluid.HeatExchangers.BaseClasses;
partial model PartialEffectiveness
  "Partial model to implement heat exchangers based on effectiveness model"
  extends Fluid.Interfaces.StaticFourPortHeatMassExchanger(
    prescribedHeatFlowRate1=true,
    prescribedHeatFlowRate2=true,
    show_T=false);

  Medium1.Temperature T_in1 = if allowFlowReversal1 then
    fra_a1 * Medium1.temperature(state_a1_inflow) + fra_b1 * Medium1.temperature(state_b1_inflow) else
    Medium1.temperature(state_a1_inflow)
    "Inlet temperature medium 1";
  Medium2.Temperature T_in2 = if allowFlowReversal2 then
    fra_a2 * Medium2.temperature(state_a2_inflow) + fra_b2 * Medium2.temperature(state_b2_inflow) else
    Medium2.temperature(state_a2_inflow)
    "Inlet temperature medium 2";
  Modelica.Units.SI.ThermalConductance C1_flow(
    min=0,
    nominal=m1_flow_nominal*cp1_default)=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(
    min=0,
    nominal=m2_flow_nominal*cp2_default)=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";
  Modelica.Units.SI.ThermalConductance CMin_flow(min=0) = min(C1_flow, C2_flow)
    "Minimum heat capacity flow rate";
  Modelica.Units.SI.HeatFlowRate QMax_flow=CMin_flow*(T_in2 - T_in1)
    "Maximum heat flow rate into medium 1";
protected
  parameter Real delta=1E-3 "Parameter used for smoothing";

  parameter Modelica.Units.SI.SpecificHeatCapacity cp1_default =
    Medium1.specificHeatCapacityCp(Medium1.setState_pTX(
      Medium1.p_default,
      Medium1.T_default,
      Medium1.X_default))
    "Specific heat capacity of medium 1 at default medium state";
  parameter Modelica.Units.SI.SpecificHeatCapacity cp2_default =
    Medium2.specificHeatCapacityCp(Medium2.setState_pTX(
      Medium2.p_default,
      Medium2.T_default,
      Medium2.X_default))
    "Specific heat capacity of medium 2 at default medium state";
  parameter Modelica.Units.SI.ThermalConductance CMin_flow_small =
    min(m1_flow_small*cp1_default, m2_flow_small*cp2_default)
    "Small value for smoothing of minimum heat capacity flow rate";
  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 a1 (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 b1 (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)";
  mXi2_flow = zeros(Medium2.nXi);

Thus, if medium 1 is heated in this device, then Q1_flow > 0
and QMax_flow > 0.

", revisions="

April 27, 2023, by Michael Wetter:

Set nominal and min attributes for capacity flow rates.

Moved assignments from initial equation to parameter declaration.
This avoids a warning in Dymola 2023x about non-literal nominal values.

This is for
#3360.

February 21, 2019, by Filip Jorissen:

Revised implementation of all equations
such that a binding equation is used.
I.e. we set the variable value at the variable definition
instead of using the equation section.
This allows overwriting the equation
when extending the model.

See
#1102.

April 30, 2018, by Filip Jorissen:

Set prescribedHeatFlowRate1=true and
prescribedHeatFlowRate2=true.

See
#907.

June 9, 2015 by Michael Wetter:

Changed type of T_in1 and T_in2
to Medium1.Temperature and Medium2.Temperature
to avoid an error because of conflicting start values if

Buildings.Examples.ChillerPlant.BaseClasses.Controls.Examples.ChillerSetPointControl
is translated using pedantic mode in Dymola 2016.
This is for
#426.

October 8, 2011, by Michael Wetter:

Set show_T=false to avoid state events near zero flow.

August 31, 2011, by Michael Wetter:

Removed unused variables gai1 and gai2.

February 12, 2010, by Michael Wetter:

Changed model structure to implement effectiveness-NTU model.

January 28, 2010, by Michael Wetter:

Added regularization near zero flow.

October 2, 2009, by Michael Wetter:

Changed computation of inlet temperatures to use
state_*_inflow which is already known in base class.

April 28, 2008, by Michael Wetter:

First implementation.

"));
end PartialEffectiveness;


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

within Buildings.Fluid.HeatExchangers.BaseClasses;
model PartialEffectivenessNTU
  "Partial model for heat exchanger with effectiveness - NTU relation and no moisture condensation"
  extends Buildings.Fluid.HeatExchangers.BaseClasses.PartialEffectiveness(
      sensibleOnly1=true,
      sensibleOnly2=true,
      Q1_flow = eps*QMax_flow,
      Q2_flow = -Q1_flow,
      mWat1_flow = 0,
      mWat2_flow = 0);
  import con = Buildings.Fluid.Types.HeatExchangerConfiguration;
  import flo = Buildings.Fluid.Types.HeatExchangerFlowRegime;

  parameter con configuration "Heat exchanger configuration"

  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 Boolean use_Q_flow_nominal = true
    "Set to true to specify Q_flow_nominal and temperatures, or to false to specify effectiveness"

  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)
    "Nominal temperature at port a1"
  parameter Modelica.Units.SI.Temperature T_a2_nominal(fixed=use_Q_flow_nominal)
    "Nominal temperature at port a2"

  parameter Real eps_nominal(fixed=not use_Q_flow_nominal)
    "Nominal heat transfer effectiveness"

  input Modelica.Units.SI.ThermalConductance UA "UA value";

  Real eps(min=0, max=1) "Heat exchanger effectiveness";

  // NTU has been removed as NTU goes to infinity as CMin goes to zero.
  // This quantity is not good for modeling.
  //  Real NTU(min=0) "Number of transfer units";
  final parameter Modelica.Units.SI.ThermalConductance UA_nominal(fixed=false)
    "Nominal UA value";
  final parameter Real NTU_nominal(min=0, fixed=false)
    "Nominal number of transfer units";

protected
  final parameter Medium1.ThermodynamicState sta1_default = Medium1.setState_pTX(
     T=Medium1.T_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_pTX(
     T=Medium2.T_default,
     p=Medium2.p_default,
     X=Medium2.X_default[1:Medium2.nXi]) "Default state for 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";
  parameter Modelica.Units.SI.ThermalConductance CMin_flow_nominal(fixed=false)
    "Minimal capacity flow rate at nominal condition";
  parameter Modelica.Units.SI.ThermalConductance CMax_flow_nominal(fixed=false)
    "Maximum capacity flow rate at nominal condition";
  parameter Real Z_nominal(
    min=0,
    max=1,
    fixed=false) "Ratio of capacity flow rate at nominal condition";
  parameter Modelica.Units.SI.Temperature T_b1_nominal(fixed=false)
    "Nominal temperature at port b1";
  parameter Modelica.Units.SI.Temperature T_b2_nominal(fixed=false)
    "Nominal temperature at port b2";
  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";
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);

  // Heat transferred from fluid 1 to 2 at nominal condition
  C1_flow_nominal = m1_flow_nominal*cp1_nominal;
  C2_flow_nominal = m2_flow_nominal*cp2_nominal;
  CMin_flow_nominal = min(C1_flow_nominal, C2_flow_nominal);
  CMax_flow_nominal = max(C1_flow_nominal, C2_flow_nominal);
  Z_nominal = CMin_flow_nominal/CMax_flow_nominal;
  Q_flow_nominal = m1_flow_nominal*cp1_nominal*(T_a1_nominal - T_b1_nominal);
  if use_Q_flow_nominal then
    Q_flow_nominal = -m2_flow_nominal*cp2_nominal*(T_a2_nominal - T_b2_nominal);
    eps_nominal = abs(Q_flow_nominal/((T_a1_nominal - T_a2_nominal)*
      CMin_flow_nominal));
    assert(Q_flow_nominal / (T_a1_nominal - T_a2_nominal) >= 0,
    "In " + getInstanceName() + ": Q_flow_nominal is defined with the wrong sign. " +
    "By convention, a positive value describes a heat flow from Medium1 to Medium2. " +
    "The parameter T_a1_nominal should then be larger than T_a2_nominal." +
    "Future version of this library might enforce this convention and throw an error.",
    level = AssertionLevel.warning);
  else
    T_a1_nominal = Medium1.T_default;
    T_a2_nominal = Medium2.T_default;
    T_b1_nominal = Medium1.T_default;
    T_b2_nominal = Medium2.T_default;
  end if;
  assert(eps_nominal > 0 and eps_nominal < 1,
    "eps_nominal out of bounds, eps_nominal = " + String(eps_nominal) +
    "\n  To achieve the required heat transfer rate at epsilon=0.8, set |T_a1_nominal-T_a2_nominal| = "
     + String(abs(Q_flow_nominal/0.8/CMin_flow_nominal)) +
    "\n  or increase flow rates. The current parameters result in " +
    "\n  CMin_flow_nominal = " + String(CMin_flow_nominal) +
    "\n  CMax_flow_nominal = " + String(CMax_flow_nominal));
  // Assign the flow regime for the given heat exchanger configuration and capacity flow rates
  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;
  // The equation sorter of Dymola 7.3 does not guarantee that the above assert is tested prior to the
  // function call on the next line. Thus, we add the test on eps_nominal to avoid an error in ntu_epsilonZ
  // for invalid input arguments
  NTU_nominal = if (eps_nominal > 0 and eps_nominal < 1) then
    Buildings.Fluid.HeatExchangers.BaseClasses.ntu_epsilonZ(
    eps=eps_nominal,
    Z=Z_nominal,
    flowRegime=Integer(flowRegime_nominal)) else 0;
  UA_nominal = NTU_nominal*CMin_flow_nominal;
equation
  // Assign the flow regime for the given heat exchanger configuration and capacity 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;

  // Effectiveness
  eps = Buildings.Fluid.HeatExchangers.BaseClasses.epsilon_C(
    UA=UA,
    C1_flow=C1_flow,
    C2_flow=C2_flow,
    flowRegime=Integer(flowRegime),
    CMin_flow_nominal=CMin_flow_nominal,
    CMax_flow_nominal=CMax_flow_nominal,
    delta=delta);
end PartialEffectivenessNTU;


Element of a heat exchanger 2. Element of a heat exchanger
with dynamics of the fluids and the solid.
The <i>hA</i> value for both fluids is an input.
The driving force for the heat transfer is the temperature difference
between the fluid volumes and the solid.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model PartialHexElement "Element of a heat exchanger 2"
  extends Buildings.Fluid.Interfaces.FourPortHeatMassExchanger;

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

  parameter Modelica.Units.SI.ThermalConductance UA_nominal
    "Thermal conductance at nominal flow, used to compute time constant"
  parameter Modelica.Units.SI.Time tau_m(min=0) = 60
    "Time constant of metal at nominal UA value"
  parameter Modelica.Units.SI.HeatCapacity C=2*UA_nominal*tau_m
    "Heat capacity of metal (= cp*m)";

  Modelica.Blocks.Interfaces.RealInput Gc_1
    "Signal representing the convective thermal conductance medium 1 in [W/K]"
  Modelica.Blocks.Interfaces.RealInput Gc_2
    "Signal representing the convective thermal conductance medium 2 in [W/K]"

  Modelica.Thermal.HeatTransfer.Components.HeatCapacitor mas(
    C=C,
    T(stateSelect=StateSelect.always,
      fixed=(energyDynamics == Modelica.Fluid.Types.Dynamics.FixedInitial)),
    der_T( fixed=(energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyStateInitial)))
    if not (energyDynamics == Modelica.Fluid.Types.Dynamics.SteadyState)
    "Mass of metal"

  Modelica.Thermal.HeatTransfer.Components.Convection con1(dT(min=-200))
    "Convection (and conduction) on fluid side 1"
  Modelica.Thermal.HeatTransfer.Components.Convection con2(dT(min=-200))
    "Convection (and conduction) on fluid side 2"
  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPor1
    "Heat port for heat exchange with the control volume 1"
  Modelica.Thermal.HeatTransfer.Interfaces.HeatPort_a heaPor2
    "Heat port for heat exchange with the control volume 2"
equation
  connect(Gc_1, con1.Gc)
  connect(Gc_2, con2.Gc)
  connect(con1.solid,mas. port)
  connect(con1.fluid, vol1.heatPort)
  connect(con2.fluid, vol2.heatPort)
  connect(con2.solid, con1.solid)
  connect(vol1.heatPort, heaPor1)
  connect(vol2.heatPort, heaPor2)
end PartialHexElement;


within Buildings.Fluid.HeatExchangers.BaseClasses;
partial model PartialPipeManifold "Partial pipe manifold for a heat exchanger"
  extends PartialDuctPipeManifold;
  Modelica.Fluid.Interfaces.FluidPort_b[nPipPar] port_b(
        redeclare each package Medium = Medium,
        each m_flow(start=-mStart_flow_a/nPipPar, 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)"
end PartialPipeManifold;


within Buildings.Fluid.HeatExchangers.BaseClasses;
partial model PartialPrescribedOutlet
  "Ideal heater, cooler, humidifier or dehumidifier with prescribed outlet conditions"
  extends Buildings.Fluid.Interfaces.PartialTwoPortInterface;
  extends Buildings.Fluid.Interfaces.TwoPortFlowResistanceParameters(
    final computeFlowResistance=(abs(dp_nominal) > Modelica.Constants.eps));

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

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

  parameter Modelica.Units.SI.Time tau(min=0) = 10
    "Time constant at nominal flow rate (used if energyDynamics not equal Modelica.Fluid.Types.Dynamics.SteadyState)"

protected
  Buildings.Fluid.FixedResistances.PressureDrop preDro(
    redeclare final package Medium = Medium,
    final m_flow_nominal=m_flow_nominal,
    final deltaM=deltaM,
    final allowFlowReversal=allowFlowReversal,
    final show_T=false,
    final from_dp=from_dp,
    final linearized=linearizeFlowResistance,
    final homotopyInitialization=homotopyInitialization,
    final dp_nominal=dp_nominal) "Flow resistance"

  Buildings.Fluid.Interfaces.PrescribedOutlet outCon(
    redeclare final package Medium = Medium,
    final energyDynamics=energyDynamics,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_small=m_flow_small,
    final show_T=false,
    final m_flow_nominal=m_flow_nominal,
    final tau=tau) "Model to set outlet conditions"
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(port_a, preDro.port_a)
  connect(preDro.port_b,outCon. port_a)
  connect(outCon.port_b, port_b)
end PartialPrescribedOutlet;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model PipeManifoldFixedResistance
  "Pipe manifold for a heat exchanger connection"
  extends PartialPipeManifold;

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Mass flow rate at port_a"
  parameter Modelica.Units.SI.PressureDifference dp_nominal(min=0, displayUnit=
        "Pa") "Pressure drop"

  parameter Boolean use_dh = false "Set to true to specify hydraulic diameter"
  parameter Modelica.Units.SI.Length dh=0.025
    "Hydraulic diameter for each pipe"
  parameter Real ReC=4000
    "Reynolds number where transition to turbulence starts"
  parameter Boolean linearized = false
    "= true, use linear relation between m_flow and dp for any flow rate"
  parameter Real deltaM(min=0) = 0.3
    "Fraction of nominal mass flow rate where transition to turbulent occurs"
  parameter Boolean from_dp = false
    "= true, use m_flow = f(dp) else dp = f(m_flow)"

  Buildings.Fluid.FixedResistances.PressureDrop fixRes(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    dp_nominal=dp_nominal,
    from_dp=from_dp,
    deltaM=deltaM,
    linearized=linearized) "Fixed resistance for each duct"
protected
  PipeManifoldFlowDistributor floDis(
    redeclare package Medium = Medium,
    nPipPar=nPipPar,
    mStart_flow_a=mStart_flow_a,
    allowFlowReversal=allowFlowReversal)
    "Mass flow distributor to the individual pipes"
equation
  connect(fixRes.port_b, floDis.port_a)
  connect(floDis.port_b, port_b)
  connect(fixRes.port_a, port_a)
end PipeManifoldFixedResistance;


Manifold for heat exchanger register that distributes the mass flow rate equally. This model distributes the mass flow rates equally between all instances
of port_b.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model PipeManifoldFlowDistributor
  "Manifold for heat exchanger register that distributes the mass flow rate equally"
  extends PartialPipeManifold;

equation
    // mass and momentum balance
  port_b[1].m_flow = -port_a.m_flow/nPipPar;

  for i in 2:nPipPar loop
    port_b[i].m_flow = port_b[1].m_flow;
  end for;

  port_b[1].p = port_a.p;

  for i in 1:nPipPar loop
    inStream(port_a.h_outflow)  = port_b[i].h_outflow;
    inStream(port_a.Xi_outflow) = port_b[i].Xi_outflow;
    inStream(port_a.C_outflow)  = port_b[i].C_outflow;
  end for;
  port_a.h_outflow  = sum(inStream(port_b[i].h_outflow) for i in 1:nPipPar)/nPipPar;
  port_a.Xi_outflow = sum(inStream(port_b[i].Xi_outflow) for i in 1:nPipPar)/nPipPar;
  port_a.C_outflow  = sum(inStream(port_b[i].C_outflow) for i in 1:nPipPar)/nPipPar;
end PipeManifoldFlowDistributor;


Manifold for heat exchanger register

within Buildings.Fluid.HeatExchangers.BaseClasses;
model PipeManifoldNoResistance "Manifold for heat exchanger register"
  extends PartialPipeManifold;
 parameter Boolean connectAllPressures=true;
  Modelica.Fluid.Fittings.MultiPort mulPor(
      redeclare package Medium = Medium,
      final nPorts_b=nPipPar)
equation
  connect(port_a, mulPor.port_a)
  connect(mulPor.ports_b, port_b)
end PipeManifoldNoResistance;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model RayleighNumber
  "Calculates the Rayleigh number for a given fluid and characteristic length"
  extends Modelica.Blocks.Icons.Block;
   replaceable package Medium = Modelica.Media.Interfaces.PartialMedium
    "Fluid medium model";
  parameter Modelica.Units.SI.Diameter ChaLen "Characteristic length";
  Real Gr "Grashof number";
  Real B(unit="1/K") "isobaricExpansionCoefficient";
  Real nu(unit = "m2/s") "Kinematic viscosity of the medium";
  Modelica.Units.SI.DynamicViscosity mu "Dynamic viscosity of the medium";
  Modelica.Units.SI.Density rho "Density of the medium";

   Modelica.Blocks.Interfaces.RealInput TSur(unit = "K")
    "Surface temperature of the HX"

   Modelica.Blocks.Interfaces.RealOutput Ra "Rayleigh number"

   Modelica.Blocks.Interfaces.RealOutput Pr "Prandlt number"
   Modelica.Blocks.Interfaces.RealInput TFlu(unit="K")
    "Temperature of the surrounding fluid"
equation
    mu = Buildings.Fluid.HeatExchangers.BaseClasses.dynamicViscosityWater(
        T = 0.5 * (TSur+TFlu));
    rho = Medium.density(
        Medium.setState_pTX(
        p = Medium.p_default,
        T = 0.5*(TSur+TFlu),
        X = Medium.X_default));
    Pr = Buildings.Fluid.HeatExchangers.BaseClasses.prandtlNumberWater(
          T = 0.5*(TSur+TFlu));

    B = Buildings.Fluid.HeatExchangers.BaseClasses.isobaricExpansionCoefficientWater(
          T = 0.5*(TSur+TFlu));
    nu = mu/rho;

    Gr = Modelica.Constants.g_n * B * (TSur - TFlu)*ChaLen^3/nu^2;
    Ra = Gr*Pr;
end RayleighNumber;


Fully dry coil model. This model implements the calculation for a 100% dry coil.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model WetCoilDryRegime
  "Fully dry coil model"

  // - water
  input Modelica.Units.SI.ThermalConductance UAWat "UA for water side";
  input Modelica.Units.SI.MassFlowRate mWat_flow "Mass flow rate for water";
  input Modelica.Units.SI.MassFlowRate mWatNonZer_flow(min=Modelica.Constants.eps)
    "Mass flow rate for water, bounded away from zero";

  input Modelica.Units.SI.SpecificHeatCapacity cpWat
    "Specific heat capacity of water";
  input Modelica.Units.SI.Temperature TWatIn "Water temperature at inlet";
  // -- air
  input Modelica.Units.SI.ThermalConductance UAAir "UA for air side";
  input Modelica.Units.SI.MassFlowRate mAir_flow(min=Modelica.Constants.eps)
    "Mass flow rate of air";
  input Modelica.Units.SI.MassFlowRate mAirNonZer_flow(min=Modelica.Constants.eps)
    "Mass flow rate for air, bounded away from zero";
  input Modelica.Units.SI.SpecificHeatCapacity cpAir
    "Specific heat capacity of moist air at constant pressure";
  input Modelica.Units.SI.Temperature TAirIn "Temperature of air at inlet";
  // -- misc.
  input Buildings.Fluid.Types.HeatExchangerFlowRegime cfg
    "Flow regime of the heat exchanger";
  input Modelica.Units.SI.MassFlowRate mAir_flow_nominal
    "Nominal mass flow rate for air";
  input Modelica.Units.SI.MassFlowRate mWat_flow_nominal
    "Nominal mass flow rate for water";

  parameter Real delta = 1E-3 "Small value used for smoothing";

  output Modelica.Units.SI.HeatFlowRate QTot_flow
    "Heat transferred from water to air";
  output Modelica.Units.SI.Temperature TWatOut "Temperature of water at outlet";
  output Modelica.Units.SI.Temperature TAirOut
    "Temperature of air at the outlet";
  output Real eps(min=0, max=1, unit="1")
    "Effectiveness for heat exchanger";
  Modelica.Units.SI.ThermalConductance CWat_flow=mWat_flow*cpWat
    "Capacitance rate of water";
  Modelica.Units.SI.ThermalConductance CAir_flow=mAir_flow*cpAir
    "Capacitance rate of air";
  Modelica.Units.SI.ThermalConductance CMin_flow_nominal=min(mAir_flow_nominal*
      cpAir, mWat_flow_nominal*cpWat) "Minimum capacity rate";
  Modelica.Units.SI.ThermalConductance CMax_flow_nominal=max(mAir_flow_nominal*
      cpAir, mWat_flow_nominal*cpWat) "Maximum capacity rate";
  Modelica.Units.SI.ThermalConductance CMin_flow=
      Buildings.Utilities.Math.Functions.smoothMin(
      x1=CAir_flow,
      x2=CWat_flow,
      deltaX=1E-3*(CMax_flow_nominal - CMin_flow_nominal))
    "Minimum capacity rate";
  Modelica.Units.SI.ThermalConductance UA "Overall heat transfer coefficient";
  output Modelica.Units.SI.Temperature TSurAirOut
    "Surface Temperature at air outlet";
equation
  UA = 1/ (1 / UAAir + 1 / UAWat);

  eps=epsilon_C(
    UA=UA,
    C1_flow=CWat_flow,
    C2_flow=CAir_flow,
    flowRegime=Integer(cfg),
    CMin_flow_nominal= CMin_flow_nominal,
    CMax_flow_nominal=CMax_flow_nominal,
    delta= delta);

  QTot_flow = eps*CMin_flow*(TAirIn-TWatIn);
  TAirOut=TAirIn-QTot_flow/(mAirNonZer_flow*cpAir);
  TWatOut=TWatIn+QTot_flow/(mWatNonZer_flow*cpWat);
  TSurAirOut = (TAirOut * UAAir + TWatIn * UAWat) / (UAAir + UAWat);
end WetCoilDryRegime;


Model implementing the switching algorithm of the TK-fuzzy model for cooling coil application. This model implements the switching algorithm for the dry and wet regime.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model WetCoilDryWetRegime
  "Model implementing the switching algorithm of the TK-fuzzy model for cooling coil application"

  parameter Modelica.Units.SI.MassFlowRate mWat_flow_nominal(min=0)
    "Nominal mass flow rate for water"
  parameter Modelica.Units.SI.MassFlowRate mAir_flow_nominal(min=0)
    "Nominal mass flow rate for air"

  input Real Qfac(final unit="1")
    "Smoothing factor to prevent division-by-zero";

  input Buildings.Fluid.Types.HeatExchangerFlowRegime cfg=
    Buildings.Fluid.Types.HeatExchangerFlowRegime.CounterFlow
    "Heat exchanger configuration";

  // -- Water
  Modelica.Blocks.Interfaces.RealInput UAWat(
    final quantity="ThermalConductance",
    final unit="W/K")
    "Product of heat transfer coefficient times area for water side"
  Modelica.Blocks.Interfaces.RealInput mWat_flow(
    quantity="MassFlowRate",
    min = 0,
    final unit="kg/s")
    "Mass flow rate for water"
  Modelica.Blocks.Interfaces.RealInput cpWat(
    final quantity="SpecificHeatCapacity",
    final unit="J/(kg.K)")
    "Inlet water temperature"
  Modelica.Blocks.Interfaces.RealInput TWatIn(
    final quantity="ThermodynamicTemperature",
    final unit="K",
    min = 200,
    start = 288.15,
    nominal = 300,
    displayUnit="degC")
    "Inlet water temperature"
  // -- Air
  Modelica.Blocks.Interfaces.RealInput UAAir(
    final quantity="ThermalConductance",
    final unit="W/K")
    "Product of heat transfer coefficient times area for air side"
  Modelica.Blocks.Interfaces.RealInput mAir_flow(
    quantity="MassFlowRate",
    min = 0,
    final unit="kg/s")
    "Mass flow rate for air"
  Modelica.Blocks.Interfaces.RealInput cpAir(
    final quantity="SpecificHeatCapacity",
    final unit="J/(kg.K)")
    "Inlet specific heat capacity (at constant pressure)"
  Modelica.Blocks.Interfaces.RealInput TAirIn(
    final quantity="ThermodynamicTemperature",
    final unit="K",
    min = 200,
    start = 288.15,
    nominal = 300,
    displayUnit="degC")
    "Inlet air temperature"
  Modelica.Blocks.Interfaces.RealInput hAirIn(
    final quantity="SpecificEnergy",
    final unit="J/kg")
    "Inlet air enthalpy"
  Modelica.Blocks.Interfaces.RealInput pAir(
    final quantity="Pressure",
    final unit="Pa",
    displayUnit="bar",
    min=70000,
    nominal = 1e5)
    "Inlet air absolute pressure"
  Modelica.Blocks.Interfaces.RealInput X_wAirIn(
    min=0,
    max=1,
    unit="1")
    "Mass fraction of water in inlet air (kg water/kg total air)"

  Modelica.Blocks.Interfaces.RealOutput QTot_flow(
    final quantity="Power",
    final unit="W")
    "Total heat transfer from water into air, negative for cooling"
  Modelica.Blocks.Interfaces.RealOutput QSen_flow(
    final quantity="Power",
    final unit="W")
    "Sensible heat transfer from water into air, negative for cooling"
  Modelica.Units.SI.HeatFlowRate QLat_flow "Latent heat transfer rate";

  Modelica.Blocks.Interfaces.RealOutput mCon_flow(
    quantity="MassFlowRate",
    final unit="kg/s")
    "Mass flow of the condensate, negative for dehumidification"
  Buildings.Fluid.HeatExchangers.BaseClasses.WetCoilDryRegime fullyDry(
    final UAWat=UAWat,
    final mWat_flow=mWat_flow,
    final cpWat=cpWat,
    final TWatIn=TWatIn,
    final UAAir=UAAir,
    final mAir_flow=mAir_flow,
    final mWatNonZer_flow=mWatNonZer_flow,
    final mAirNonZer_flow=mAirNonZer_flow,
    final cpAir=cpAir,
    final TAirIn=TAirIn,
    final cfg=cfg,
    final mAir_flow_nominal=mAir_flow_nominal,
    final mWat_flow_nominal=mWat_flow_nominal) "Fully-dry coil model";

  Buildings.Fluid.HeatExchangers.BaseClasses.WetCoilWetRegime fullyWet(
    final UAWat=UAWat,
    final mWat_flow=mWat_flow,
    final cpWat=cpWat,
    final TWatIn=TWatIn,
    final UAAir=UAAir,
    final mAir_flow=mAir_flow,
    final mWatNonZer_flow=mWatNonZer_flow,
    final mAirNonZer_flow=mAirNonZer_flow,
    final cpAir=cpAir,
    final TAirIn=TAirIn,
    final cfg=cfg,
    final mAir_flow_nominal=mAir_flow_nominal,
    final mWat_flow_nominal=mWat_flow_nominal,
    final pAir=pAir,
    final X_wAirIn=X_wAirIn) "Fully-wet coil model";

  Real dryFra(final unit="1", min=0, max=1)
    "Dry fraction, 0.3 means condensation occurs at 30% heat exchange length from air inlet";
protected
  Modelica.Units.SI.MassFlowRate mAirNonZer_flow(min=Modelica.Constants.eps) =
    Buildings.Utilities.Math.Functions.smoothMax(
    x1=mAir_flow,
    x2=1E-3*mAir_flow_nominal,
    deltaX=0.25E-3*mAir_flow_nominal) "Mass flow rate of air";
  Modelica.Units.SI.MassFlowRate mWatNonZer_flow(min=Modelica.Constants.eps) =
    Buildings.Utilities.Math.Functions.smoothMax(
    x1=mWat_flow,
    x2=1E-3*mWat_flow_nominal,
    deltaX=0.25E-3*mWat_flow_nominal) "Mass flow rate of water";

  Modelica.Units.SI.Temperature TAirInDewPoi
    "Dew point temperature of incoming air";

  Buildings.Utilities.Psychrometrics.pW_X pWIn(
    final X_w=X_wAirIn,
    final p_in=pAir);
  Buildings.Utilities.Psychrometrics.TDewPoi_pW TDewIn(
    final p_w=pWIn.p_w);

  //-- Values for fuzzy logics
  Real mu_FW(final unit="1", min=0, max=1), mu_FD(unit="1",min=0, max=1)
    "Membership functions for Fully-Wet and Fully-Dry conditions";
  Real w_FW(final unit="1", min=0, max=1),  w_FD(unit="1",min=0, max=1)
    "Normalized weight functions for Fully-Wet and Fully-Dry conditions";

equation

  TAirInDewPoi=TDewIn.T;

  mu_FW= Buildings.Utilities.Math.Functions.spliceFunction(
    pos=0,neg=1,x=fullyWet.TSurAirIn-TAirInDewPoi,
    deltax=Buildings.Utilities.Math.Functions.smoothMax(
      abs(fullyDry.TSurAirOut-fullyWet.TSurAirIn), 1e-2,1e-3));

  mu_FD= Buildings.Utilities.Math.Functions.spliceFunction(
    pos=1,neg=0,x=fullyDry.TSurAirOut-TAirInDewPoi,
    deltax=Buildings.Utilities.Math.Functions.smoothMax(
      abs(fullyDry.TSurAirOut-fullyWet.TSurAirIn), 1e-2,1e-3));

  w_FW=mu_FW/(mu_FW+mu_FD);
  w_FD=mu_FD/(mu_FW+mu_FD);

  QTot_flow= -(w_FW*fullyWet.QTot_flow+w_FD*fullyDry.QTot_flow)*Qfac;
  QSen_flow= -(w_FW*fullyWet.QSen_flow+w_FD*fullyDry.QTot_flow)*Qfac;
  dryFra= w_FD;

  QLat_flow=QTot_flow-QSen_flow;
  mCon_flow=QLat_flow/Buildings.Utilities.Psychrometrics.Constants.h_fg*Qfac;
end WetCoilDryWetRegime;


within Buildings.Fluid.HeatExchangers.BaseClasses;
model WetCoilUARated
  "Model that calculates the UA-value from cooling coil data at rated conditions."

  replaceable package MediumA=Buildings.Media.Air
    constrainedby Modelica.Media.Interfaces.PartialMedium
    "Air-side medium";
  replaceable package MediumW=Buildings.Media.Water
    constrainedby Modelica.Media.Interfaces.PartialMedium
    "Water-side medium";

  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 QTot_flow
    "Nominal heat flow rate (positive for heat transfer from 1 to 2)";
  parameter Modelica.Units.SI.Temperature TAirIn
    "Air inlet temperature at a rated condition";

  parameter Modelica.Units.SI.MassFraction X_wAirIn
    "Mass fraction of water in inlet air at a rated condition";
  parameter Modelica.Units.SI.Temperature TWatIn
    "Water inlet temperature at a rated condition";

  parameter Modelica.Units.SI.MassFlowRate mAir_flow
    "Air mass flow rate at a rated condition";
  parameter Modelica.Units.SI.MassFlowRate mWat_flow
    "Water mass flow rate at a rated condition";
  parameter Modelica.Units.SI.ThermalConductance UA
    "Overall heat transfer coefficient for a fully dry condition";
  parameter Real r_nominal(min=0, max=1)
    "Ratio between air-side and water-side convective heat transfer at nominal condition";

protected
  constant Modelica.Units.SI.SpecificEnthalpy hfg=Buildings.Utilities.Psychrometrics.Constants.h_fg
    "Enthapy of vaporization of water";
  constant Modelica.Units.SI.SpecificEnthalpy hUnit=1
    "Physical dimension of specific enthalpy used for a unit conversion";
  constant Modelica.Units.SI.Temperature TUnit=1
    "Physical dimension of temperature used for a unit conversion";
  constant Modelica.Units.SI.SpecificHeatCapacity cpUnit=1
    "Physical dimension of specific heat capacity used for a unit conversion";
  parameter Modelica.Units.SI.Temperature TAirOut(fixed=false)
    "Air outlet temperature  at a rated condition";
  parameter Modelica.Units.SI.Temperature TWatOut=TWatIn - QTot_flow/cpWat/
      mWat_flow "Water outlet temperature at a rated condition";
  parameter Modelica.Units.SI.SpecificEnthalpy hAirIn=
      MediumA.specificEnthalpy_pTX(
      p=MediumA.p_default,
      T=TAirIn,
      X={X_wAirIn,1 - X_wAirIn})
    "Enthalpy of incoming moist air at a rated condition";
  parameter MediumA.ThermodynamicState staAir=MediumA.setState_phX(
    p=MediumA.p_default, h=hAirIn, X={X_wAirIn, 1-X_wAirIn})
    "Inlet air thermodynamic state";
  parameter Modelica.Units.SI.SpecificHeatCapacity cpAir=
      MediumA.specificHeatCapacityCp(staAir)
    "Isobaric specific heat capacity of air";
  parameter Modelica.Units.SI.SpecificEnthalpy hAirOut=hAirIn + QTot_flow/
      mAir_flow "Enthalpy of outgoing moist air at a rated condition";
  parameter Modelica.Units.SI.SpecificEnthalpy hWatIn=
      MediumW.specificEnthalpy_pTX(
      p=MediumW.p_default,
      T=TWatIn,
      X=MediumW.X_default)
    "Enthalpy of incoming moist air at a rated condition";
  parameter MediumW.ThermodynamicState staWat=MediumW.setState_phX(
    p=MediumW.p_default, h=hWatIn, X=MediumW.X_default)
    "Inlet water thermodynamic state";
  parameter Modelica.Units.SI.SpecificHeatCapacity cpWat=
      MediumW.specificHeatCapacityCp(staWat)
    "Isobaric specific heat capacity of water";
  parameter Modelica.Units.SI.SpecificHeatCapacity cpEff(fixed=false, min=0) "Effective specific heat: change in saturated moist air enthalpy with respect to
    temperature along the saturation line between inlet and outlet water temperatures";
  parameter Modelica.Units.SI.SpecificEnthalpy LMED(fixed=false)
    "Log mean enthalpy difference";
  parameter Modelica.Units.SI.MassFlowRate UASta(
    fixed=false,
    min=0,
    start=1/(1/10 + 1/20))
    "Overall heat transfer coefficient for enthalpy difference";
  parameter Modelica.Units.SI.ThermalConductance UAAir(
    min=0,
    start=10,
    fixed=false)
    "Air side convective heat transfer coefficient, including fin resistance";
  parameter Modelica.Units.SI.ThermalConductance UAWat(
    min=0,
    start=20,
    fixed=false) "Water side convective heat transfer coefficient";
  parameter Boolean isFulDry(fixed=false)
    "Indicator of the fully-dry coil regime";
  parameter Boolean isFulWet(fixed=false)
    "Indicator of the fully-wet coil regime";
  parameter Modelica.Units.SI.AbsolutePressure pSatTWatIn=
      Buildings.Utilities.Psychrometrics.Functions.saturationPressure(TWatIn)
    "Saturation pressure of water at the water inlet temperature";
  parameter Modelica.Units.SI.MassFraction X_wSatTWatIn=
      Buildings.Utilities.Psychrometrics.Functions.X_pSatpphi(
      pSat=pSatTWatIn,
      p=MediumA.p_default,
      phi=1)
    "Mass fraction of water in saturated moist air at the water inlet temperature";
  parameter Modelica.Units.SI.SpecificEnthalpy hSatTWatIn=
      Buildings.Media.Air.specificEnthalpy_pTX(
      p=MediumA.p_default,
      T=TWatIn,
      X={X_wSatTWatIn,1 - X_wSatTWatIn})
    "Enthalpy of saturated moist air at the water inlet temperature";
  parameter Modelica.Units.SI.AbsolutePressure pSatTWatOut=
      Buildings.Utilities.Psychrometrics.Functions.saturationPressure(TWatOut)
    "Saturation pressure of water at the water oulet temperature";
  parameter Modelica.Units.SI.MassFraction X_wSatTWatOut=
      Buildings.Utilities.Psychrometrics.Functions.X_pSatpphi(
      pSat=pSatTWatOut,
      p=MediumA.p_default,
      phi=1)
    "Mass fraction of water in saturated moist air at the water outlet temperature";
  parameter Modelica.Units.SI.SpecificEnthalpy hSatTWatOut=
      Buildings.Media.Air.specificEnthalpy_pTX(
      p=MediumA.p_default,
      T=TWatOut,
      X={X_wSatTWatOut,1 - X_wSatTWatOut})
    "Enthalpy of saturated moist air at the water oulet temperature";
initial equation
  isFulDry = if use_Q_flow_nominal then (X_wSatTWatIn >= X_wAirIn) else true;
  isFulWet = if use_Q_flow_nominal then (X_wSatTWatOut <= X_wAirIn) else true;
  assert(
    not use_Q_flow_nominal or
      hAirOut >= hSatTWatIn and hAirIn >= hSatTWatOut or
      hAirOut <= hSatTWatIn and hAirIn <= hSatTWatOut,
    "In " + getInstanceName() +
    ": The moist air enthalpy at the coil inlet or outlet is unrealistically low. " +
    "Check the rated conditions.");
  assert(
    isFulDry or isFulWet,
    "In " + getInstanceName() +
    ": The nominal conditions correspond to a partially-wet coil regime. " +
    "The modeling uncertainty under such conditions has not been assessed. " +
    "Rather specify nominal conditions in fully-dry or fully-wet regime.",
    level=AssertionLevel.warning);
  if use_Q_flow_nominal then
    if isFulDry then
      TAirOut = TAirIn + QTot_flow / mAir_flow / cpAir;
      LMED = (if abs(TWatIn - TAirOut - (TWatOut - TAirIn)) < Modelica.Constants.small then
        TWatIn - TAirOut else Buildings.Fluid.HeatExchangers.BaseClasses.lmtd(
          TWatIn,
          TWatOut,
          TAirIn,
          TAirOut)) / TUnit * hUnit;
      QTot_flow=LMED*UASta;
      cpEff = 0;
      UA = UASta*cpUnit;
    else //fully wet
      // calculation of overall UAsta based on log mean enthalpy difference
      LMED = (if abs(hSatTWatIn - hAirOut - (hSatTWatOut - hAirIn)) < Modelica.Constants.small then
        (hSatTWatIn - hAirOut)/hUnit*TUnit else
        Buildings.Fluid.HeatExchangers.BaseClasses.lmtd(
          hSatTWatIn/hUnit*TUnit,
          hSatTWatOut/hUnit*TUnit,
          hAirIn/hUnit*TUnit,
          hAirOut/hUnit*TUnit)) / TUnit * hUnit;
      QTot_flow=LMED*UASta;
      cpEff= (hSatTWatOut-hSatTWatIn)/(TWatOut-TWatIn);
      UASta = (UAAir/cpAir)/(1 + (cpEff*UAAir)/(cpAir*UAWat));
      // Dummy value.
      TAirOut=MediumA.T_default;
    end if;
  else
    // Dummy values.
    TAirIn=MediumA.T_default;
    TAirOut=MediumA.T_default;
    X_wAirIn=MediumA.X_default[1];
    TWatIn=MediumA.T_default;
    TWatOut=MediumA.T_default;
    hAirIn=MediumA.h_default;
    hAirOut=MediumA.h_default;
    LMED=hUnit;
    -QTot_flow=LMED*UASta;
    cpEff= 0;
  end if;

  UAWat = UAAir / r_nominal;
  UA = 1/ (1/UAAir  + 1/UAWat);
end WetCoilUARated;


Fully wet coil model using esilon_C.mo function. This model implements the calculation for a 100% wet coil.

within Buildings.Fluid.HeatExchangers.BaseClasses;
model WetCoilWetRegime
  "Fully wet coil model using esilon_C.mo function"
  constant Real cpEff0=2050 "Used for scaling";
  constant Real cpWat0=4200 "Used for scaling";
  parameter Real delta = 1E-3 "Small value used for smoothing";
  constant Modelica.Units.SI.SpecificHeatCapacity cpDum=1
    "Dummy cp to eliminate the warning message of the unit mismatch when using the eps-NTU model for the wet coil";
  constant Modelica.Units.SI.TemperatureDifference dTWat=0.1
    "Temperature differential used to compute the finite difference of the saturated specific enthalpy";
  parameter Real tau=6*60
    "Time constant for state estimation: introduced to avoid the algebraic loop of the wet coil equations";

  // - water
  input Modelica.Units.SI.ThermalConductance UAWat "UA for water side";
  input Modelica.Units.SI.MassFlowRate mWat_flow "Mass flow rate of water";
  input Modelica.Units.SI.MassFlowRate mWatNonZer_flow
    "Non-zero mass flow rate of water";
  input Modelica.Units.SI.SpecificHeatCapacity cpWat
    "Specific heat capacity of water";
  input Modelica.Units.SI.Temperature TWatIn "Water temperature at inlet";
  input Modelica.Units.SI.MassFlowRate mWat_flow_nominal
    "Mass flow rate of water at nominal conditions";
  // -- air
  input Modelica.Units.SI.Pressure pAir "Pressure on air-side of coil";
  input Modelica.Units.SI.ThermalConductance UAAir "UA for air side";
  input Modelica.Units.SI.MassFlowRate mAir_flow "Mass flow rate of air";
  input Modelica.Units.SI.MassFlowRate mAirNonZer_flow
    "Non-zero mass flow rate of water";
  input Modelica.Units.SI.SpecificHeatCapacity cpAir
    "Specific heat capacity of moist air at constant pressure";
  input Modelica.Units.SI.Temperature TAirIn "Temperature of air at inlet";
  input Modelica.Units.SI.MassFraction X_wAirIn
    "Mass fraction of water in moist air at inlet";
  input Buildings.Fluid.Types.HeatExchangerFlowRegime cfg
    "Configuration of the heat exchanger";
  input Modelica.Units.SI.MassFlowRate mAir_flow_nominal
    "Mass flow rate of air at nominal conditions";

  Modelica.Units.SI.SpecificEnthalpy hAirIn
    "Specific enthalpy of air at inlet conditions";
  Buildings.Utilities.Psychrometrics.hSat_pTSat hSatWatInM(p=pAir,TSat=TWatIn)
    "Model to calculate saturated specific enthalpy of air at water inlet temperature";
  Modelica.Units.SI.SpecificEnthalpy hSatWatIn
    "Saturated specific enthalpy of air at water inlet temperature";
  Buildings.Utilities.Psychrometrics.hSat_pTSat hSatWatIn_dT_M(p=pAir,TSat=TWatIn+dTWat)
    "Model to calculate derivative of saturated specific enthalpy of air at water inlet temperature";
  Modelica.Units.SI.SpecificHeatCapacity dhSatdTWatIn
    "Finite difference of saturated moist air enthalpy at water inlet temperature";
  Real NonZerDelWatTem
    "Regularization water temperature difference between inlet and outlet";

  Modelica.Units.SI.SpecificEnthalpy hAirOut
    "Specific enthalpy of moist air at the air outlet";
  Buildings.Utilities.Psychrometrics.hSat_pTSat hSatWatOutM(p=pAir,TSat=TWatOutEst)
    "Model to calculate saturated specific enthalpy of air at water outlet temperature";
  Modelica.Units.SI.SpecificEnthalpy hSatWatOut
    "Saturated specific enthalpy of air at water outlet temperature";

  Modelica.Units.SI.Temperature TSurEff
    "Effective surface temperature of the coil to split sensible and latent heat from total heat transfer rate";

  Modelica.Units.SI.SpecificEnthalpy hSatSurEff
    "Enthalpy of saturated moist air at the effective surface temperature";

  Buildings.Utilities.Psychrometrics.hSat_pTSat hSatSurEffM(p=pAir,TSat=TSurEff)
    "An object to calculate the saturated enthalpy of moist air at the coil surface temperature";
  Buildings.Utilities.Psychrometrics.hSat_pTSat hSatSurEffMinM(p=pAir,TSat=273.15+1)
    "An object to calculate a lower bound of the saturated enthalpy of moist
    air at the coil surface temperature";

  Modelica.Units.SI.SpecificHeatCapacity cpEff "Effective specific heat: change in enthalpy with respect to temperature
     along the saturation line at the local water temperature";

  Modelica.Units.SI.MassFlowRate UASta
    "Overall mass transfer coefficient for dry coil";

  // Set start value, otherwise OpenModelica has a division by zero in the expression
  // (1 - exp(-NTUAirSta)) of hSatSurEff
  Real NTUAirSta(
    start=1,
    unit="1")
    "Number of transfer units for air-side only (NTU_a*)";

  Real epsSta(start=0.66, unit="1")
    "Effectiveness for heat exchanger (e*)";

  Modelica.Units.SI.MassFlowRate CStaMin
    "Min of product of mass flow rates and specific heats; analogous to Cmin";

  Modelica.Units.SI.MassFlowRate CStaMin_flow_nominal=min(mAir_flow_nominal,
      mWat_flow_nominal*cpEff0/cpWat0)
    "Analogus to CMin_flow_nominal, only for a regularization";
  Modelica.Units.SI.MassFlowRate CStaMax_flow_nominal=max(mAir_flow_nominal,
      mWat_flow_nominal*cpEff0/cpWat0)
    "Analogus to CMax_flow_nominal, only for a regularization";
  Modelica.Units.SI.MassFlowRate deltaCStaMin=delta*min(mAir_flow_nominal,
      mWat_flow_nominal*cpEff0/cpWat0)
    "Min of product of mass flow rates and specific heats, analogous to Cmin";
  Modelica.Units.SI.Temperature TWatOutEst
    "State estimation of temperature of water at outlet";

  output Modelica.Units.SI.HeatFlowRate QTot_flow
    "Total heat flow from water to air stream";
  output Modelica.Units.SI.HeatFlowRate QSen_flow
    "Sensible heat flow from water to air stream";
  output Modelica.Units.SI.Temperature TWatOut
    "Temperature at the water outlet";
  output Modelica.Units.SI.Temperature TSurAirIn
    "Coil surface temperature at the air inlet";
  output Modelica.Units.SI.Temperature TAirOut "Temperature at the air outlet";
initial equation
  TWatOutEst=0.5*(TWatIn+ TAirIn);

equation

  hAirIn=Buildings.Media.Air.specificEnthalpy_pTX(
    p=pAir,T=TAirIn,X={X_wAirIn,1-X_wAirIn});
  hSatWatIn=hSatWatInM.hSat;
  dhSatdTWatIn=(hSatWatIn_dT_M.hSat-hSatWatInM.hSat)/dTWat;
  hSatWatOut= hSatWatOutM.hSat;
  NonZerDelWatTem=Buildings.Utilities.Math.Functions.regNonZeroPower(
    x=TWatOutEst-TWatIn,n=1,delta=0.1);
  cpEff = Buildings.Utilities.Math.Functions.smoothMax(
    (hSatWatOut - hSatWatIn)/NonZerDelWatTem,
    dhSatdTWatIn,
    cpEff0*delta);

  CStaMin=Buildings.Utilities.Math.Functions.smoothMin(
    mAir_flow,
    mWat_flow*cpWat/cpEff,
    deltaCStaMin/4);

  UASta = (UAAir/cpAir)/(1 + (cpEff*UAAir)/(cpAir*UAWat));

  epsSta=epsilon_C(
    UA=UASta*cpDum,
    C1_flow=mWat_flow*cpWat/cpEff*cpDum,
    C2_flow=mAir_flow*cpDum,
    flowRegime=Integer(cfg),
    CMin_flow_nominal=CStaMin_flow_nominal*cpDum,
    CMax_flow_nominal=CStaMax_flow_nominal*cpDum,
    delta=delta);

  QTot_flow = epsSta*CStaMin*(hAirIn  - hSatWatIn);

  QTot_flow = mAir_flow*(hAirIn- hAirOut);
  TWatOut=TWatIn+QTot_flow/(mWatNonZer_flow*cpWat);

  NTUAirSta = UAAir/(mAir_flow*cpAir);

  hSatSurEff = Buildings.Utilities.Math.Functions.smoothMax(
    x1 = hSatSurEffMinM.hSat,
    x2 = hAirIn  +(hAirOut - hAirIn) / (1 - exp(-NTUAirSta)),
    deltaX = delta*1E4); // NTUAirSta is bounded as long as UAAir>0 due to the regularization of mAir_flow

  hSatSurEffM.hSat=hSatSurEff;
  TAirOut = TSurEff +(TAirIn  - TSurEff)*exp(-NTUAirSta);
  QSen_flow= Buildings.Utilities.Math.Functions.smoothMin(
    x1 = mAir_flow*cpAir*(TAirIn-TAirOut),
    x2 = QTot_flow,
    deltaX = delta*mWatNonZer_flow*cpWat0*5); // the last term is only for regularization with DTWater=5oC

  TSurAirIn = (TAirIn * UAAir + TWatOut * UAWat) / (UAWat + UAAir);
  der(TWatOutEst)=-1/tau*TWatOutEst+1/tau*TWatOut;
end WetCoilWetRegime;


Test model for the functions epsilon_ntuZ and ntu_epsilonZ. Model to test the implementation of the epsilon-NTU functions and their inverse functions.

within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model EpsilonNTUZ "Test model for the functions epsilon_ntuZ and ntu_epsilonZ"
  extends Modelica.Icons.Example;
  import f = Buildings.Fluid.Types.HeatExchangerFlowRegime;
  Real Z[6] "Ratio of capacity flow rates";
  Real epsilon[6] "Heat exchanger effectiveness";
  Real eps[6] "Heat exchanger effectiveness";
  Real ntu[6] "Number of transfer units";
  Real diff[6] "Difference in results";

protected
  constant Real uniCon(final unit="1/s") = 1
    "Unit conversion to satisfy unit check";

equation
  for conf in {Integer(f.ParallelFlow),
               Integer(f.CounterFlow),
               Integer(f.CrossFlowUnmixed),
               Integer(f.CrossFlowCMinMixedCMaxUnmixed),
               Integer(f.CrossFlowCMinUnmixedCMaxMixed),
               Integer(f.ConstantTemperaturePhaseChange)} loop
    Z[conf]       = abs(cos(uniCon*time));
    epsilon[conf] = 0.01 + 0.98*abs(sin(uniCon*time)) * 1/(1+Z[conf]);
    ntu[conf]     = ntu_epsilonZ(epsilon[conf], Z[conf], conf);
    eps[conf]     = epsilon_ntuZ(ntu[conf],     Z[conf], conf);
    diff[conf]    = epsilon[conf] - eps[conf];
  end for;
end EpsilonNTUZ;


Test model for HACoilInside. Test model for
        Buildings.Fluid.HeatExchanger.BaseClassess.HACoilInside.

within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model HACoilInside "Test model for HACoilInside"
  extends Modelica.Icons.Example;

  Modelica.Blocks.Sources.Sine sine(
    f=0.1,
    amplitude=0.063,
    offset=0.063) "Mass flow rate"
  Modelica.Blocks.Sources.Sine sine1(
    amplitude=10,
    f=0.1,
    offset=273.15 + 50) "Temperature"
  Buildings.Fluid.HeatExchangers.BaseClasses.HACoilInside hASin(
    hA_nominal=13*(0.5+1)/0.5,
    m_flow_nominal=0.063) "Model for convection coefficient"
equation
  connect(sine1.y, hASin.T)
  connect(sine.y, hASin.m_flow)
end HACoilInside;


Test model for dry coil convection coefficient. Test model for
Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil.

within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model HADryCoil "Test model for dry coil convection coefficient"
  extends Modelica.Icons.Example;
  Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil  hADryCoi(
    m_flow_nominal_w=0.1,
    UA_nominal=10000,
    m_flow_nominal_a=2.5) "Calculates an hA value for a dry coil"
  Modelica.Blocks.Sources.Ramp TWat(
    offset=293.15,
    duration=20,
    startTime=40,
    height=40) "Temperature of the water"
  Modelica.Blocks.Sources.Ramp TAir(
    height=-20,
    offset=293.15,
    duration=20,
    startTime=60) "Temperature of the air"
  Modelica.Blocks.Sources.Ramp mWat_flow(
    height=0.1,
    duration=20,
    offset=0) "Water mass flow rate"
  Modelica.Blocks.Sources.Ramp mAir_flow(
    duration=20,
    offset=0,
    height=2.5,
    startTime=20) "Air mass flow rate"
  Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil hADryCoiAir(
    m_flow_nominal_w=0.1,
    UA_nominal=10000,
    m_flow_nominal_a=2.5,
    waterSideFlowDependent=false,
    waterSideTemperatureDependent=false)
    "Calculates an hA value for a dry coil"
  Buildings.Fluid.HeatExchangers.BaseClasses.HADryCoil hADryCoiWat(
    m_flow_nominal_w=0.1,
    UA_nominal=10000,
    m_flow_nominal_a=2.5,
    airSideFlowDependent=false,
    airSideTemperatureDependent=false) "Calculates an hA value for a dry coil"
equation
  connect(TWat.y, hADryCoi.T_1)
  connect(hADryCoi.T_2, TAir.y)
  connect(mAir_flow.y, hADryCoi.m2_flow)
  connect(mWat_flow.y, hADryCoi.m1_flow)
  connect(TWat.y, hADryCoiAir.T_1)
  connect(hADryCoiAir.T_2, TAir.y)
  connect(mAir_flow.y, hADryCoiAir.m2_flow)
  connect(mWat_flow.y, hADryCoiAir.m1_flow)
  connect(TWat.y, hADryCoiWat.T_1)
  connect(hADryCoiWat.T_2, TAir.y)
  connect(mAir_flow.y, hADryCoiWat.m2_flow)
  connect(mWat_flow.y, hADryCoiWat.m1_flow)
end HADryCoil;


within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model HANaturalCylinder "Test model for natural convection outside of a coil"
  extends Modelica.Icons.Example;
  Buildings.Fluid.HeatExchangers.BaseClasses.HANaturalCylinder hANatCyl(redeclare
      package Medium = Buildings.Media.Water,
      ChaLen=0.1,
    hA_nominal=10,
    TFlu_nominal=293.15,
    TSur_nominal=313.15)
    "Calculates an hA value for natural convection around a cylinder"
  Modelica.Blocks.Sources.Ramp TSur(
    duration=100,
    height=50,
    offset=293.15) "Temperature of the pipe surface"
  Modelica.Blocks.Sources.Ramp TFlu(
    duration=100,
    startTime=150,
    height=50,
    offset=293.15) "Temperature of the surrounding fluid"
equation
  connect(TSur.y, hANatCyl.TSur)
  connect(TFlu.y, hANatCyl.TFlu)
end HANaturalCylinder;


within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model HexElementLatent
  "Model that tests the basic element that is used to built heat exchanger models"
  extends Modelica.Icons.Example;
 package Medium_W = Buildings.Media.Water;
 package Medium_A = Buildings.Media.Air;
  Buildings.Fluid.Sources.Boundary_pT sin_2(
    redeclare package Medium = Medium_A,
    use_p_in=true,
    use_T_in=true,
    T=288.15,
    nPorts=1)
    Modelica.Blocks.Sources.Ramp PIn(
    height=20,
    duration=300,
    startTime=300,
    offset=101324.95)
  Buildings.Fluid.Sources.Boundary_pT sou_2(
    redeclare package Medium = Medium_A,
    use_p_in=true,
    use_T_in=true,
    T=283.15,
    nPorts=1)
    Modelica.Blocks.Sources.Ramp TWat(
    startTime=1,
    height=4,
    duration=300,
    offset=303.15) "Water temperature"
  Modelica.Blocks.Sources.Constant TDb(k=278.15) "Drybulb temperature"
    Modelica.Blocks.Sources.Constant POut(k=101325)
  Buildings.Fluid.Sources.Boundary_pT sin_1(
   redeclare package Medium = Medium_W,
    use_p_in=true,
    T=288.15,
    nPorts=1)
  Buildings.Fluid.Sources.Boundary_pT sou_1(
    redeclare package Medium = Medium_W,
    use_T_in=true,
    p=101340,
    T=293.15,
    nPorts=1)
  Buildings.Fluid.FixedResistances.PressureDrop res_22(
    m_flow_nominal=5,
    dp_nominal=5,
    redeclare package Medium = Medium_A)
  Buildings.Fluid.FixedResistances.PressureDrop res_12(
    m_flow_nominal=5,
    dp_nominal=5,
    redeclare package Medium = Medium_W)
  Buildings.Fluid.HeatExchangers.BaseClasses.HexElementLatent hex(
    m1_flow_nominal=5,
    m2_flow_nominal=5,
    UA_nominal=9999,
    redeclare package Medium1 = Medium_W,
    redeclare package Medium2 = Medium_A,
    dp1_nominal=10,
    dp2_nominal=10,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
  Modelica.Blocks.Sources.Constant TDb1(k=303.15) "Drybulb temperature"
  Modelica.Blocks.Sources.Constant hACon(k=10000) "Convective heat transfer"
  Sensors.EnthalpyFlowRate senEntFlo(
    redeclare package Medium = Medium_W,
    m_flow_nominal=5,
    tau=0,
    initType=Modelica.Blocks.Types.Init.SteadyState)
    "Enthalpy flow rate sensor"
  Sensors.EnthalpyFlowRate senEntFlo1(
    redeclare package Medium = Medium_W,
    m_flow_nominal=5,
    tau=0,
    initType=Modelica.Blocks.Types.Init.SteadyState)
    "Enthalpy flow rate sensor"
  Sensors.EnthalpyFlowRate senEntFlo2(
    m_flow_nominal=5,
    tau=0,
    initType=Modelica.Blocks.Types.Init.SteadyState,
    redeclare package Medium = Medium_A) "Enthalpy flow rate sensor"
  Sensors.EnthalpyFlowRate senEntFlo3(
    m_flow_nominal=5,
    tau=0,
    initType=Modelica.Blocks.Types.Init.SteadyState,
    redeclare package Medium = Medium_A) "Enthalpy flow rate sensor"
  Modelica.Blocks.Math.MultiSum netEne(nu=4, k={1,-1,1,-1})
    "Net energy balance (needs to be zero)"
equation
  connect(TDb.y, sou_1.T_in)
  connect(POut.y, sin_1.p_in)
  connect(PIn.y, sou_2.p_in)
  connect(TWat.y, sou_2.T_in)
  connect(POut.y, sin_2.p_in)
  connect(TDb1.y, sin_2.T_in)
  connect(hACon.y, hex.Gc_1)
  connect(hACon.y, hex.Gc_2)
  connect(sin_1.ports[1], res_12.port_b)
  connect(sin_2.ports[1], res_22.port_b)
  connect(senEntFlo.port_b, hex.port_a1)
  connect(senEntFlo.port_a, sou_1.ports[1])
  connect(res_12.port_a, senEntFlo1.port_b)
  connect(hex.port_b1, senEntFlo1.port_a)
  connect(res_22.port_a, senEntFlo2.port_a)
  connect(senEntFlo2.port_b, hex.port_b2)
  connect(hex.port_a2, senEntFlo3.port_a)
  connect(senEntFlo3.port_b, sou_2.ports[1])
  connect(netEne.u[1], senEntFlo.H_flow)
  connect(senEntFlo1.H_flow, netEne.u[2])
  connect(senEntFlo2.H_flow, netEne.u[3])
  connect(senEntFlo3.H_flow, netEne.u[4])
end HexElementLatent;


within Buildings.Fluid.HeatExchangers.BaseClasses.Examples;
model HexElementLatentLoop
  "Model that tests the basic element that is used to built heat exchanger models"
  extends Modelica.Icons.Example;
 package Medium_W = Buildings.Media.Water;
 package Medium_A = Buildings.Media.Air;

  parameter Modelica.Units.SI.MassFlowRate mW_flow_nominal=0.1
    "Water mass flow rate";
  parameter Modelica.Units.SI.MassFlowRate mA_flow_nominal=0.14
    "Air mass flow rate";

  Buildings.Fluid.Sources.Boundary_pT sin_2(
    redeclare package Medium = Medium_A,
    use_p_in=false,
    use_T_in=false,
    nPorts=1)
    "Pressure boundary condition"
  Buildings.Fluid.Sources.Boundary_pT sin_1(
   redeclare package Medium = Medium_W, nPorts=1) "Pressure boundary condition"
  Sources.MassFlowSource_T sou(
    redeclare package Medium = Medium_W,
    m_flow=5,
    nPorts=1,
    use_T_in=true)
              "Mass flow source"
  Buildings.Fluid.HeatExchangers.BaseClasses.HexElementLatent hex(
    redeclare package Medium1 = Medium_W,
    redeclare package Medium2 = Medium_A,
    dp2_nominal=5,
    UA_nominal=1E3,
    m1_flow_nominal=mW_flow_nominal,
    m2_flow_nominal=mA_flow_nominal,
    dp1_nominal=0,
    show_T=true,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
                    "Hex element"
  Modelica.Blocks.Sources.Constant hACon(k=70)    "Convective heat transfer"
  Movers.FlowControlled_m_flow fan(
    redeclare package Medium = Medium_A,
    m_flow_nominal=mA_flow_nominal,
    inputType=Buildings.Fluid.Types.InputType.Constant,
    addPowerToMedium=false,
    nominalValuesDefineDefaultPressureCurve=true,
    dp_nominal=5,
    use_riseTime=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial) "Fan"
  Buildings.Utilities.Psychrometrics.pW_TDewPoi TDewPoi
    "Model to compute the water vapor pressure at the dew point"
public
  Buildings.Utilities.Psychrometrics.X_pW humRatPre(use_p_in=false)
    "Model to convert water vapor pressure into humidity ratio"
  Modelica.Blocks.Sources.Constant TWat(k=273.15 + 5) "Water inlet temperature"
  Buildings.Fluid.HeatExchangers.PrescribedOutlet hea(
    redeclare package Medium = Medium_A,
    m_flow_nominal=mA_flow_nominal,
    dp_nominal=0,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState,
    use_X_wSet=false)                                         "Heater"
  Modelica.Blocks.Sources.Constant TAir(k=273.15 + 20) "Air inlet temperature"
  Modelica.Blocks.Sources.RealExpression TSur(y=hex.masExc.TSur)
    "Surface temperature of heat exchanger"
equation
  connect(hACon.y, hex.Gc_1)
  connect(hACon.y, hex.Gc_2)
  connect(sou.ports[1], hex.port_a1)
  connect(sin_1.ports[1], hex.port_b1)
  connect(fan.port_a, hex.port_b2)
  connect(sin_2.ports[1], fan.port_a)
  connect(TDewPoi.p_w, humRatPre.p_w)
  connect(TWat.y, sou.T_in)
  connect(fan.port_b, hea.port_a)
  connect(hea.port_b, hex.port_a2)
  connect(TAir.y, hea.TSet)
  connect(TSur.y, TDewPoi.T)
end HexElementLatentLoop;