Model with parameters for valves. Model that computes the flow coefficients of valves. This base class allows the following modeling options,
which have been adapted from the valve implementation
in
Modelica.Fluid
to specify the valve flow coefficient in fully open conditions:

within Buildings.Fluid.Actuators.BaseClasses;
partial model ValveParameters "Model with parameters for valves"

  parameter Buildings.Fluid.Types.CvTypes CvData=Buildings.Fluid.Types.CvTypes.OpPoint
    "Selection of flow coefficient"
  parameter Real Kv(
    fixed= if CvData==Buildings.Fluid.Types.CvTypes.Kv then true else false)
    "Kv (metric) flow coefficient [m3/h/(bar)^(1/2)]"
  parameter Real Cv(
    fixed= if CvData==Buildings.Fluid.Types.CvTypes.Cv then true else false)
    "Cv (US) flow coefficient [USG/min/(psi)^(1/2)]"
  parameter Modelica.Units.SI.Area Av(fixed=if CvData == Buildings.Fluid.Types.CvTypes.Av
         then true else false) "Av (metric) flow coefficient"

  parameter Real deltaM = 0.02
    "Fraction of nominal flow rate where linearization starts, if y=1"
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal
    "Nominal mass flow rate"
  parameter Modelica.Units.SI.PressureDifference dpValve_nominal(
    displayUnit="Pa",
    min=0,
    fixed=if CvData == Buildings.Fluid.Types.CvTypes.OpPoint then true else false)
    "Nominal pressure drop of fully open valve, used if CvData=Buildings.Fluid.Types.CvTypes.OpPoint"

  parameter Modelica.Units.SI.Density rhoStd
    "Inlet density for which valve coefficients are defined"

protected
  parameter Real Kv_SI(
    min=0,
    fixed= false)
    "Flow coefficient for fully open valve in SI units, Kv=m_flow/sqrt(dp) [kg/s/(Pa)^(1/2)]"
initial equation
  if  CvData == Buildings.Fluid.Types.CvTypes.OpPoint then
    Kv_SI =           m_flow_nominal/sqrt(dpValve_nominal);
    Kv    =           Kv_SI/(rhoStd/3600/sqrt(1E5));
    Cv    =           Kv_SI/(rhoStd*0.0631/1000/sqrt(6895));
    Av    =           Kv_SI/sqrt(rhoStd);
  elseif CvData == Buildings.Fluid.Types.CvTypes.Kv then
    Kv_SI =           Kv*rhoStd/3600/sqrt(1E5)
      "Unit conversion m3/(h*sqrt(bar)) to kg/(s*sqrt(Pa))";
    Cv    =           Kv_SI/(rhoStd*0.0631/1000/sqrt(6895));
    Av    =           Kv_SI/sqrt(rhoStd);
    dpValve_nominal =  (m_flow_nominal/Kv_SI)^2;
  elseif CvData == Buildings.Fluid.Types.CvTypes.Cv then
    Kv_SI =           Cv*rhoStd*0.0631/1000/sqrt(6895)
      "Unit conversion USG/(min*sqrt(psi)) to kg/(s*sqrt(Pa))";
    Kv    =           Kv_SI/(rhoStd/3600/sqrt(1E5));
    Av    =           Kv_SI/sqrt(rhoStd);
    dpValve_nominal =  (m_flow_nominal/Kv_SI)^2;
  else
    assert(CvData == Buildings.Fluid.Types.CvTypes.Av, "Invalid value for CvData.
Obtained CvData = " + String(CvData) + ".");
    Kv_SI =           Av*sqrt(rhoStd);
    Kv    =           Kv_SI/(rhoStd/3600/sqrt(1E5));
    Cv    =           Kv_SI/(rhoStd*0.0631/1000/sqrt(6895));
    dpValve_nominal =  (m_flow_nominal/Kv_SI)^2;
  end if;
end ValveParameters;


Rangeability. This example checks whether the function derivative
is implemented correctly. If the derivative implementation
is not correct, the model will stop with an assert statement.

within Buildings.Fluid.Actuators.BaseClasses.Examples;
model EqualPercentageDerivativeCheck
  extends Modelica.Icons.Example;

 parameter Real R = 50 "Rangeability";
 parameter Real delta = 0.01 "Value where transition occurs";
 parameter Real l = 0.001 "Leakage";
  Real x;
  Real y;
initial equation
   y=x;
equation
  x=Buildings.Fluid.Actuators.BaseClasses.equalPercentage(time, R, l, delta);
  der(y)=der(x);
  assert(abs(x-y) < 1E-2, "Model has an error");
end EqualPercentageDerivativeCheck;


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


Air damper with exponential opening characteristics. Model of two flow resistances in series:

within Buildings.Fluid.Actuators.Dampers;
model Exponential
  "Air damper with exponential opening characteristics"
  extends Buildings.Fluid.Actuators.BaseClasses.PartialDamperExponential;
equation
  // Pressure drop calculation
  if linearized then
    m_flow*m_flow_nominal_pos = k^2*dp;
  else
    if homotopyInitialization then
      if from_dp then
        m_flow=homotopy(
            actual=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
                  dp=dp, k=k,
                  m_flow_turbulent=m_flow_turbulent),
            simplified= m_flow_nominal_pos*dp/max(Modelica.Constants.eps, dp_nominal_pos));
      else
        dp=homotopy(
            actual=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
                  m_flow=m_flow, k=k,
                  m_flow_turbulent=m_flow_turbulent),
            simplified=dp_nominal_pos*m_flow/m_flow_nominal_pos);
        end if;  // from_dp
    else // do not use homotopy
      if from_dp then
        m_flow=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
                  dp=dp, k=k, m_flow_turbulent=m_flow_turbulent);
      else
        dp=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
                  m_flow=m_flow, k=k, m_flow_turbulent=m_flow_turbulent);
      end if;  // from_dp
    end if; // homotopyInitialization
  end if; // linearized
end Exponential;


within Buildings.Fluid.Actuators.Dampers;
model MixingBox "Outside air mixing box with interlocked air dampers"
  extends Buildings.Fluid.Actuators.BaseClasses.ActuatorSignal;

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

  parameter Boolean allowFlowReversal = true
    "= false to simplify equations, assuming, but not enforcing, no flow reversal"
  parameter Boolean use_deltaM = true
    "Set to true to use deltaM for turbulent transition, else ReC is used";
  parameter Real deltaM = 0.3
    "Fraction of nominal mass flow rate where transition to turbulent occurs"
  parameter Boolean roundDuct = false
    "Set to true for round duct, false for square cross section"
  parameter Real ReC=4000
    "Reynolds number where transition to turbulence starts"
  parameter Modelica.Units.SI.MassFlowRate mOut_flow_nominal
    "Mass flow rate outside air damper"
  parameter Modelica.Units.SI.PressureDifference dpDamOut_nominal(min=0,
      displayUnit="Pa") "Pressure drop of damper in outside air leg"
  parameter Modelica.Units.SI.PressureDifference dpFixOut_nominal(
    min=0,
    displayUnit="Pa") = 0
    "Pressure drop of duct and other resistances in outside air leg"

  parameter Modelica.Units.SI.MassFlowRate mRec_flow_nominal
    "Mass flow rate recirculation air damper"
  parameter Modelica.Units.SI.PressureDifference dpDamRec_nominal(min=0,
      displayUnit="Pa") "Pressure drop of damper in recirculation air leg"
  parameter Modelica.Units.SI.PressureDifference dpFixRec_nominal(
    min=0,
    displayUnit="Pa") = 0
    "Pressure drop of duct and other resistances in recirculation air leg"

  parameter Modelica.Units.SI.MassFlowRate mExh_flow_nominal
    "Mass flow rate exhaust air damper"
  parameter Modelica.Units.SI.PressureDifference dpDamExh_nominal(min=0,
      displayUnit="Pa") "Pressure drop of damper in exhaust air leg"
  parameter Modelica.Units.SI.PressureDifference dpFixExh_nominal(
    min=0,
    displayUnit="Pa") = 0
    "Pressure drop of duct and other resistances in exhaust air leg"

  parameter Boolean from_dp=true
    "= true, use m_flow = f(dp) else dp = f(m_flow)"
  parameter Boolean linearized=false
    "= true, use linear relation between m_flow and dp for any flow rate"
  parameter Boolean use_constant_density=true
    "Set to true to use constant density for flow friction"
  parameter Real a=-1.51 "Coefficient a for damper characteristics"
  parameter Real b=0.105*90 "Coefficient b for damper characteristics"
  parameter Real yL=15/90 "Lower value for damper curve"
  parameter Real yU=55/90 "Upper value for damper curve"
  parameter Real k1=0.45
    "Flow coefficient for y=1, k1 = pressure drop divided by dynamic pressure"
  parameter Real l(min=1e-10, max=1) = 0.0001
    "Damper leakage, ratio of flow coefficients k(y=0)/k(y=1)"

  Modelica.Fluid.Interfaces.FluidPort_a port_Out(redeclare package Medium =
        Medium, m_flow(start=0, min=if allowFlowReversal then -Constants.inf else
                0))
    "Fluid connector a (positive design flow direction is from port_a to port_b)"
  Modelica.Fluid.Interfaces.FluidPort_b port_Exh(redeclare package Medium =
        Medium, m_flow(start=0, max=if allowFlowReversal then +Constants.inf else
                0))
    "Fluid connector b (positive design flow direction is from port_a to port_b)"
  Modelica.Fluid.Interfaces.FluidPort_a port_Ret(redeclare package Medium =
        Medium, m_flow(start=0, min=if allowFlowReversal then -Constants.inf else
                0))
    "Fluid connector a (positive design flow direction is from port_a to port_b)"
  Modelica.Fluid.Interfaces.FluidPort_b port_Sup(redeclare package Medium =
        Medium, m_flow(start=0, max=if allowFlowReversal then +Constants.inf else
                0))
    "Fluid connector b (positive design flow direction is from port_a to port_b)"
  Buildings.Fluid.Actuators.Dampers.Exponential damOA(
    redeclare final package Medium = Medium,
    final m_flow_nominal=mOut_flow_nominal,
    final dpDamper_nominal=dpDamOut_nominal,
    final dpFixed_nominal=dpFixOut_nominal,
    final from_dp=from_dp,
    final linearized=linearized,
    final use_deltaM=use_deltaM,
    final deltaM=deltaM,
    final roundDuct=roundDuct,
    final ReC=ReC,
    final a=a,
    final b=b,
    final yL=yL,
    final yU=yU,
    final l=l,
    final k1=k1,
    final use_constant_density=use_constant_density,
    final allowFlowReversal=allowFlowReversal,
    final use_strokeTime=false)
    "Outdoor air damper"
  Buildings.Fluid.Actuators.Dampers.Exponential damExh(
    redeclare final package Medium = Medium,
    final m_flow_nominal=mExh_flow_nominal,
    final dpDamper_nominal=dpDamExh_nominal,
    final dpFixed_nominal=dpFixExh_nominal,
    final from_dp=from_dp,
    final linearized=linearized,
    final use_deltaM=use_deltaM,
    final deltaM=deltaM,
    final roundDuct=roundDuct,
    final ReC=ReC,
    final a=a,
    final b=b,
    final yL=yL,
    final yU=yU,
    final l=l,
    final k1=k1,
    final use_constant_density=use_constant_density,
    final allowFlowReversal=allowFlowReversal,
    final use_strokeTime=false)
    "Exhaust air damper"
  Buildings.Fluid.Actuators.Dampers.Exponential damRec(
    redeclare final package Medium = Medium,
    final m_flow_nominal=mRec_flow_nominal,
    final dpDamper_nominal=dpDamRec_nominal,
    final dpFixed_nominal=dpFixRec_nominal,
    final from_dp=from_dp,
    final linearized=linearized,
    final use_deltaM=use_deltaM,
    final deltaM=deltaM,
    final roundDuct=roundDuct,
    final ReC=ReC,
    final a=a,
    final b=b,
    final yL=yL,
    final yU=yU,
    final l=l,
    final k1=k1,
    final use_constant_density=use_constant_density,
    final allowFlowReversal=allowFlowReversal,
    final use_strokeTime=false)
    "Recirculation air damper"
  Modelica.Blocks.Sources.Constant uni(k=1)
    "Unity signal"
  Modelica.Blocks.Math.Add add(k2=-1)
    "Adder"
protected
  parameter Medium.Density rho_default=Medium.density(sta_default)
    "Density, used to compute fluid volume";
  parameter Medium.ThermodynamicState sta_default=
     Medium.setState_pTX(T=Medium.T_default, p=Medium.p_default, X=Medium.X_default)
    "Default medium state";
equation
  connect(uni.y, add.u1)
  connect(add.y, damRec.y)
  connect(damOA.port_a, port_Out)
  connect(damExh.port_b, port_Exh)
  connect(port_Sup, damOA.port_b)
  connect(damRec.port_b, port_Sup)
  connect(port_Ret, damExh.port_a)
  connect(port_Ret, damRec.port_a)
  connect(y_actual, add.u2)

  connect(y_actual, damOA.y)
  connect(y_actual, damExh.y)
end MixingBox;


within Buildings.Fluid.Actuators.Dampers;
model MixingBoxMinimumFlow
  "Outside air mixing box with parallel damper for minimum outside air flow rate"
 extends Buildings.Fluid.Actuators.Dampers.MixingBox;
  import Modelica.Constants;

  parameter Modelica.Units.SI.MassFlowRate mOutMin_flow_nominal
    "Mass flow rate minimum outside air damper"
  parameter Modelica.Units.SI.PressureDifference dpDamOutMin_nominal(min=0,
      displayUnit="Pa") "Pressure drop of damper in minimum outside air leg"
  parameter Modelica.Units.SI.PressureDifference dpFixOutMin_nominal(
    min=0,
    displayUnit="Pa") = 0
    "Pressure drop of duct and other resistances in minimum outside air leg"
  parameter Real yOutMin_start=y_start
    "Initial value of signal for minimum outside air damper"

  Modelica.Fluid.Interfaces.FluidPort_a port_OutMin(redeclare package Medium =
        Medium, m_flow(start=0, min=if allowFlowReversal then -Constants.inf else
                0))
    "Fluid connector a (positive design flow direction is from port_a to port_b)"
  Modelica.Blocks.Interfaces.RealInput yOutMin
    "Damper position minimum outside air (0: closed, 1: open)"
  Modelica.Blocks.Interfaces.RealOutput yOutMin_actual "Actual valve position"

  Buildings.Fluid.Actuators.Dampers.Exponential damOAMin(
    redeclare final package Medium = Medium,
    final from_dp=from_dp,
    final linearized=linearized,
    final use_deltaM=use_deltaM,
    final deltaM=deltaM,
    final roundDuct=roundDuct,
    final ReC=ReC,
    final a=a,
    final b=b,
    final yL=yL,
    final yU=yU,
    final k1=k1,
    final l=l,
    final use_constant_density=use_constant_density,
    final allowFlowReversal=allowFlowReversal,
    final m_flow_nominal=mOutMin_flow_nominal,
    final dpDamper_nominal=dpDamOutMin_nominal,
    final dpFixed_nominal=dpFixOutMin_nominal,
    final use_strokeTime=false) "Damper for minimum outside air intake"
protected
  Modelica.Blocks.Interfaces.RealOutput yOutMin_filtered if use_strokeTime
    "Filtered damper position in the range 0..1"

  Modelica.Blocks.Nonlinear.SlewRateLimiter actPosOutMin(
    Rising=1/strokeTime,
    Falling=-1/strokeTime,
    Td=0.001*strokeTime,
    initType=init,
    y_start=y_start,
    strict=true) if use_strokeTime
    "Actuator position for minimum outdoor air flow"
equation
 connect(actPosOutMin.y, yOutMin_filtered)
  if use_strokeTime then
  connect(yOutMin,actPosOutMin. u)
  connect(actPosOutMin.y, yOutMin_actual)
  else
    connect(yOutMin, yOutMin_actual)
  end if;
  //////
  connect(port_OutMin, damOAMin.port_a)
  connect(damOAMin.port_b, port_Sup)
  connect(yOutMin_actual, damOAMin.y)
end MixingBoxMinimumFlow;


Package with air damper models

within Buildings.Fluid.Actuators;
package Dampers "Package with air damper models"
  extends Modelica.Icons.VariantsPackage;
end Dampers;


Model for an air damper whose mass flow is proportional to the input signal. Model for an air damper whose airflow is proportional to the input signal, assuming
that at y = 1, m_flow = m_flow_nominal. This is unless the pressure difference
dp is too low,
in which case a kDam = m_flow_nominal/sqrt(dp_nominal) characteristic is used.

within Buildings.Fluid.Actuators.Dampers;
model PressureIndependent
  "Model for an air damper whose mass flow is proportional to the input signal"
  extends Buildings.Fluid.Actuators.BaseClasses.PartialDamperExponential(
    final linearized=false,
    final casePreInd=true,
    from_dp=true);
  input Real phi = l + y_internal*(1 - l)
    "Ratio actual to nominal mass flow rate of damper, phi=kDam(y)/kDam(y=1)";
  parameter Real l2(unit="1", min=1e-10) = 0.01
    "Gain for mass flow increase if pressure is above nominal pressure"
  parameter Real deltax(unit="1", min=1E-5) = 0.02 "Transition interval for flow rate"
protected
  constant Real y_min = 2E-2
    "Minimum value of control signal before zeroing of the opening";
  constant Integer sizeSupSplBnd = 5
    "Number of support points on each quadratic domain for spline interpolation";
  constant Integer sizeSupSpl = 2 * sizeSupSplBnd + 3
    "Total number of support points for spline interpolation";
  constant Real y2dd = 0
    "Second derivative at second support point";

  parameter Real[sizeSupSpl] ySupSpl_raw = cat(
    1,
    linspace(1, yU, sizeSupSplBnd),
    {yU-1/3*(yU-yL), (yU+yL)/2, yU-2/3*(yU-yL)},
    linspace(yL, 0, sizeSupSplBnd))
    "y values of unsorted support points for spline interpolation";
  parameter Real[sizeSupSpl] kSupSpl_raw = Buildings.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=ySupSpl_raw, a=a, b=b, cL=cL, cU=cU, yL=yL, yU=yU) .^ 2
    "k values of unsorted support points for spline interpolation";
  parameter Real[sizeSupSpl] ySupSpl(each fixed=false)
    "y values of sorted support points for spline interpolation";
  parameter Real[sizeSupSpl] kSupSpl(each fixed=false)
    "k values of sorted support points for spline interpolation";
  parameter Integer[sizeSupSpl] idx_sorted(each fixed=false)
    "Indices of sorted support points";
  parameter Real[sizeSupSpl] invSplDer(each fixed=false)
    "Derivatives at support points for spline interpolation";
  parameter Real coeff1 = l2/dpDamper_nominal*m_flow_nominal
    "Parameter for avoiding unnecessary computations";
  parameter Real coeff2 = 1/coeff1
    "Parameter for avoiding unnecessary computations";

  Real kSquInv
    "Square inverse of flow coefficient (damper plus fixed resistance)";
  Real kDamSquInv
    "Square inverse of flow coefficient (damper only)";

  Modelica.Units.SI.MassFlowRate m_flow_set "Requested mass flow rate";
  Modelica.Units.SI.PressureDifference dp_min(displayUnit="Pa")
    "Minimum pressure difference required for delivering requested mass flow rate";
  Modelica.Units.SI.PressureDifference dp_x;
  Modelica.Units.SI.PressureDifference dp_x1;
  Modelica.Units.SI.PressureDifference dp_x2;
  Modelica.Units.SI.PressureDifference dp_y2;
  Modelica.Units.SI.PressureDifference dp_y1
    "Support points for interpolation flow functions";
  Modelica.Units.SI.MassFlowRate m_flow_x;
  Modelica.Units.SI.MassFlowRate m_flow_x1;
  Modelica.Units.SI.MassFlowRate m_flow_x2;
  Modelica.Units.SI.MassFlowRate m_flow_y2;
  Modelica.Units.SI.MassFlowRate m_flow_y1
    "Support points for interpolation flow functions";
  Modelica.Units.SI.MassFlowRate m_flow_smooth
    "Smooth interpolation result between two flow regimes";
  Modelica.Units.SI.PressureDifference dp_smooth
    "Smooth interpolation result between two flow regimes";
  Real y_actual_smooth(final unit="1")
    "Fractional opening computed based on m_flow_smooth and dp";

function basicFlowFunction_dp_m_flow
  "Inverse of flow function that computes that computes the square inverse of flow coefficient"
  extends Modelica.Icons.Function;
    input Modelica.Units.SI.MassFlowRate m_flow
      "Mass flow rate in design flow direction";
    input Modelica.Units.SI.PressureDifference dp
      "Pressure difference between port_a and port_b (= port_a.p - port_b.p)";
    input Modelica.Units.SI.MassFlowRate m_flow_small
      "Minimum value of mass flow rate guarding against k=(0)/sqrt(dp)";
    input Modelica.Units.SI.PressureDifference dp_small
      "Minimum value of pressure drop guarding against k=m_flow/(0)";
  output Real kSquInv
    "Square inverse of flow coefficient";
  protected
    Modelica.Units.SI.PressureDifference dpPos=
        Buildings.Utilities.Math.Functions.smoothMax(
        dp,
        -dp,
        dp_small) "Regularized absolute value of pressure drop";
  Real mSqu_flow = Buildings.Utilities.Math.Functions.smoothMax(
    m_flow^2, m_flow_small^2, m_flow_small^2)
    "Regularized square value of mass flow rate";
algorithm
  kSquInv := dpPos / mSqu_flow;
end basicFlowFunction_dp_m_flow;

function exponentialDamper_inv
  "Inverse function of the exponential damper characteristics"
  extends Modelica.Icons.Function;
  input Real kTheta "Loss coefficient";
  input Real[:] kSupSpl "k values of support points";
  input Real[:] ySupSpl "y values of support points";
  input Real[:] invSplDer "Derivatives at support points";
  output Real y "Fractional opening";
  protected
  parameter Integer sizeSupSpl = size(kSupSpl, 1) "Number of spline support points";
  Integer i "Integer to select data interval";
algorithm
  i := 1;
  for j in 2:sizeSupSpl loop
    if kTheta <= kSupSpl[j] then
      i := j;
      break;
    end if;
  end for;
  y := Buildings.Utilities.Math.Functions.smoothLimit(
    Buildings.Utilities.Math.Functions.cubicHermiteLinearExtrapolation(
      x=kTheta,
      x1=kSupSpl[i - 1],
      x2=kSupSpl[i],
      y1=ySupSpl[i - 1],
      y2=ySupSpl[i],
      y1d=invSplDer[i - 1],
      y2d=invSplDer[i]),
    0,
    1,
    1E-3);
end exponentialDamper_inv;

initial equation
  (kSupSpl, idx_sorted) = Modelica.Math.Vectors.sort(kSupSpl_raw, ascending=true);
  // The sum below is a trick to avoid in OPTIMICA the warning
  // Variable array index in equation can result in slow simulation time.
  // This warning was issued for the formulation ySupSpl = ySupSpl_raw[idx_sorted];
  for i in 1:sizeSupSpl loop
    ySupSpl[i] = sum((if k == idx_sorted[i] then ySupSpl_raw[k] else 0) for k in 1:sizeSupSpl);
  end for;
  invSplDer = Buildings.Utilities.Math.Functions.splineDerivatives(x=kSupSpl_raw, y=ySupSpl_raw);
equation
  // From TwoWayPressureIndependent valve model
  m_flow_set = m_flow_nominal*phi;
  dp_min = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
              m_flow=m_flow_set,
              k=kTotMax,
              m_flow_turbulent=m_flow_turbulent);

  if from_dp then
    m_flow_x=0;
    m_flow_x1=0;
    m_flow_x2=0;
    dp_y1=0;
    dp_y2=0;
    dp_smooth=0;

    dp_x = dp-dp_min;
    dp_x1 = -dp_x2;
    dp_x2 = deltax*dp_min;
    // min function ensures that m_flow_y1 does not increase further for dp_x > dp_x1
    m_flow_y1 = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
                                  dp=min(dp, dp_min+dp_x1),
                                  k=kTotMax,
                                  m_flow_turbulent=m_flow_turbulent);
    // max function ensures that m_flow_y2 does not decrease further for dp_x < dp_x2
    m_flow_y2 = m_flow_set + coeff1*max(dp_x,dp_x2);

    m_flow_smooth = noEvent(smooth(2,
        if dp_x <= dp_x1
        then m_flow_y1
        elseif dp_x >=dp_x2
        then m_flow_y2
        else Buildings.Utilities.Math.Functions.quinticHermite(
                 x=dp_x,
                 x1=dp_x1,
                 x2=dp_x2,
                 y1=m_flow_y1,
                 y2=m_flow_y2,
                 y1d= Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp_der(
                                     dp=dp_min + dp_x1,
                                     k=kTotMax,
                                     m_flow_turbulent=m_flow_turbulent,
                                     dp_der=1),
                 y2d=coeff1,
                 y1dd=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp_der2(
                                     dp=dp_min + dp_x1,
                                     k=kTotMax,
                                     m_flow_turbulent=m_flow_turbulent,
                                     dp_der=1,
                                     dp_der2=0),
                 y2dd=y2dd)));
  else
    dp_x=0;
    dp_x1=0;
    dp_x2=0;
    m_flow_y1=0;
    m_flow_y2=0;
    m_flow_smooth=0;

    m_flow_x = m_flow-m_flow_set;
    m_flow_x1 = -m_flow_x2;
    m_flow_x2 = deltax*m_flow_set;
    // min function ensures that dp_y1 does not increase further for m_flow_x > m_flow_x1
    dp_y1 = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
                                     m_flow=min(m_flow, m_flow_set + m_flow_x1),
                                     k=kTotMax,
                                     m_flow_turbulent=m_flow_turbulent);
    // max function ensures that dp_y2 does not decrease further for m_flow_x < m_flow_x2
    dp_y2 = dp_min + coeff2*max(m_flow_x, m_flow_x2);

    dp_smooth = noEvent(smooth(2,
        if m_flow_x <= m_flow_x1
        then dp_y1
        elseif m_flow_x >=m_flow_x2
        then dp_y2
        else Buildings.Utilities.Math.Functions.quinticHermite(
                 x=m_flow_x,
                 x1=m_flow_x1,
                 x2=m_flow_x2,
                 y1=dp_y1,
                 y2=dp_y2,
                 y1d=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow_der(
                                     m_flow=m_flow_set + m_flow_x1,
                                     k=kTotMax,
                                     m_flow_turbulent=m_flow_turbulent,
                                     m_flow_der=1),
                 y2d=coeff2,
                 y1dd=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow_der2(
                                     m_flow=m_flow_set + m_flow_x1,
                                     k=kTotMax,
                                     m_flow_turbulent=m_flow_turbulent,
                                     m_flow_der=1,
                                     m_flow_der2=0),
                 y2dd=y2dd)));
  end if;
  // Computation of damper opening
  kSquInv = basicFlowFunction_dp_m_flow(
    m_flow=m_flow,
    dp=dp,
    m_flow_small=1E-3*abs(m_flow_nominal),
    dp_small=1E-4*dp_nominal_pos);
  kDamSquInv = if dpFixed_nominal > Modelica.Constants.eps then
    kSquInv - 1 / kFixed^2 else kSquInv;
  // Use of regStep might no longer be needed when the leakage flow modeling is updated.
  y_actual_smooth = Buildings.Utilities.Math.Functions.regStep(
    x=y_internal - y_min,
    y1=exponentialDamper_inv(
      kTheta=kDamSquInv*2*rho*A^2, kSupSpl=kSupSpl, ySupSpl=ySupSpl, invSplDer=invSplDer),
    y2=0,
    x_small=1E-3);
  // Homotopy transformation
  if homotopyInitialization then
    if from_dp then
      m_flow=homotopy(actual=m_flow_smooth,
                      simplified=m_flow_nominal_pos*dp/dp_nominal_pos);
    else
      dp=homotopy(actual=dp_smooth,
                  simplified=dp_nominal_pos*m_flow/m_flow_nominal_pos);
    end if;
    y_actual = homotopy(
      actual=y_actual_smooth,
      simplified=y);
  else
    if from_dp then
      m_flow=m_flow_smooth;
    else
      dp=dp_smooth;
    end if;
    y_actual = y_actual_smooth;
  end if;

inverting the exponential characteristics to compute the fractional opening
from the loss coefficient value (directly derived from the flow coefficient).

The quadratic interpolation used outside the exponential domain in the function

Buildings.Fluid.Actuators.BaseClasses.exponentialDamper
yields a local extremum.
Therefore, the formal inversion of the function is not possible.
A cubic spline is used instead to fit the inverse of the damper characteristics.
The central domain of the characteritics having a monotonous exponential profile, its
inverse can be properly approximated with three equidistant support points.
However, the quadratic functions used outside of the exponential domain can have
various profiles depending on the damper coefficients.
Therefore, five linearly distributed support points are used on each side domain to
ensure a good fit of the inverse.

Note that below a threshold value of the input control signal (fixed at 0.02),
the fractional opening is forced to zero and no more related to the actual
flow coefficient of the damper.
This avoids steep transients of the computed opening while transitioning from reverse flow.
This is to be considered as a modeling workaround (avoiding the introduction of
an additional state variable) to prevent control chattering during
shut off operation where the pressure difference at the damper boundaries
can vary between slightly positive and negative values due to outdoor pressure
variations.

",
revisions="

August 11, 2021, by Michael Wetter:

Reformulated initial equation section to avoid warning in OPTIMICA about
variable array index.

This is for
IBPSA #1513.

June 10, 2021, by Michael Wetter:

Changed implementation of the filter and changed the parameter order to a constant
as most users need not change this value.

This is for
IBPSA #1498.

April 6, 2020, by Antoine Gautier:

Added the computation of the damper opening.

December 23, 2019 by Antoine Gautier:

Refactored as the model can now extend directly

Buildings.Fluid.Actuators.BaseClasses.PartialDamperExponential.

This is for
IBPSA #1188.

March 21, 2017 by David Blum:

First implementation.

"),
   Icon(graphics={Line(
         points={{0,100},{0,-24}}),
        Rectangle(
          extent={{-100,40},{100,-42}},
          lineColor={0,0,0},
          fillPattern=FillPattern.HorizontalCylinder,
          fillColor={192,192,192}),
        Rectangle(
          extent={{-100,22},{100,-24}},
          lineColor={0,0,0},
          fillPattern=FillPattern.HorizontalCylinder,
          fillColor={0,127,255})}));
end PressureIndependent;


within Buildings.Fluid.Actuators.Dampers.Examples;
model Damper
  "Dampers with constant pressure difference and varying control signal."
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Air "Medium model for air";

  Buildings.Fluid.Actuators.Dampers.Exponential res(
    redeclare package Medium = Medium,
    use_strokeTime=false,
    dpDamper_nominal=10,
    m_flow_nominal=1,
    k1=0.45) "A damper with quadratic relationship between m_flow and dp"

    Modelica.Blocks.Sources.Ramp yRam(
    duration=0.3,
    offset=0,
    startTime=0.2,
    height=1)

  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    p(displayUnit="Pa") = 101335,
    T=293.15,
    nPorts=4) "Pressure boundary condition"

  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=4) "Pressure boundary condition"

  Buildings.Fluid.Actuators.Dampers.PressureIndependent preIndDpFixed_nominal(
    use_strokeTime=false,
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dpFixed_nominal=5,
    dpDamper_nominal=10)
    "A damper with a mass flow proportional to the input signal and using dpFixed_nominal"

  Buildings.Fluid.Actuators.Dampers.PressureIndependent preIndFrom_dp(
    use_strokeTime=false,
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dpFixed_nominal=0,
    dpDamper_nominal=10,
    from_dp=false)
    "A damper with a mass flow proportional to the input signal and using from_dp = false"

  Buildings.Fluid.Actuators.Dampers.PressureIndependent preInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dpDamper_nominal=10,
    use_strokeTime=false)
    "A damper with a mass flow proportional to the input signal"

equation
  connect(yRam.y, res.y)
  connect(yRam.y, preInd.y)
  connect(res.port_a, sou.ports[1])
  connect(preInd.port_a, sou.ports[2])
  connect(preIndFrom_dp.port_a, sou.ports[3])
  connect(res.port_b, sin.ports[1])
  connect(preInd.port_b, sin.ports[2])
  connect(sou.ports[4], preIndDpFixed_nominal.port_a)
  connect(preIndFrom_dp.port_b, sin.ports[3])
  connect(preIndDpFixed_nominal.port_b, sin.ports[4])
  connect(preIndFrom_dp.y, yRam.y)
  connect(preIndDpFixed_nominal.y, yRam.y)
end Damper;


within Buildings.Fluid.Actuators.Dampers.Examples;
model MixingBox
  "Mixing box with constant pressure difference and varying control signal"
  extends Modelica.Icons.Example;

 package Medium = Buildings.Media.Air
    "Medium in the component"

  Buildings.Fluid.Actuators.Dampers.MixingBox mixBox(
    mOut_flow_nominal=1,
    mRec_flow_nominal=1,
    mExh_flow_nominal=1,
    redeclare package Medium = Medium,
    dpDamExh_nominal=10,
    dpDamOut_nominal=10,
    dpDamRec_nominal=10,
    dpFixExh_nominal=20,
    dpFixOut_nominal=20,
    dpFixRec_nominal=20)
             "mixing box"
    Buildings.Fluid.Sources.Boundary_pT bouIn(             redeclare package
      Medium = Medium, T=273.15 + 10,
    use_p_in=true,
    nPorts=2)
    Buildings.Fluid.Sources.Boundary_pT bouSup(             redeclare package
      Medium = Medium, T=273.15 + 26,
    use_p_in=true,
    nPorts=1)
    Buildings.Fluid.Sources.Boundary_pT bouRet(             redeclare package
      Medium = Medium, T=273.15 + 20,
    use_p_in=true,
    nPorts=1)
    Modelica.Blocks.Sources.Constant PAtm(k=101325)
    Modelica.Blocks.Sources.Ramp PSup(
    offset=101320,
    height=-10,
    startTime=0,
    duration=20)
    Modelica.Blocks.Sources.Ramp PRet(
    height=10,
    offset=101330,
    duration=20,
    startTime=20)
    Modelica.Blocks.Sources.Step yDam(
    height=1,
    offset=0,
    startTime=60)

equation
  connect(yDam.y, mixBox.y)
  connect(bouIn.p_in, PAtm.y)
  connect(PRet.y, bouRet.p_in)
  connect(bouSup.p_in, PSup.y)
  connect(bouIn.ports[1], mixBox.port_Out)
  connect(bouIn.ports[2], mixBox.port_Exh)
  connect(bouSup.ports[1], mixBox.port_Sup)
  connect(bouRet.ports[1], mixBox.port_Ret)
end MixingBox;


within Buildings.Fluid.Actuators.Dampers.Examples;
model MixingBoxMinimumFlow
  "Mixing box with minimum flow rate, with constant pressure difference and varying control signal"
  extends Modelica.Icons.Example;

 package Medium = Buildings.Media.Air "Medium in the component"

  Buildings.Fluid.Actuators.Dampers.MixingBoxMinimumFlow mixBox(
    mOutMin_flow_nominal=0.3,
    dpDamOutMin_nominal=5,
    dpFixOutMin_nominal=20,
    mOut_flow_nominal=1,
    mRec_flow_nominal=1,
    mExh_flow_nominal=1,
    redeclare package Medium = Medium,
    dpDamExh_nominal=10,
    dpDamOut_nominal=10,
    dpDamRec_nominal=10,
    dpFixExh_nominal=20,
    dpFixOut_nominal=20,
    dpFixRec_nominal=20) "mixing box"
    Buildings.Fluid.Sources.Boundary_pT bouIn(             redeclare package
      Medium = Medium, T=273.15 + 10,
    use_p_in=true,
    nPorts=3)
    Buildings.Fluid.Sources.Boundary_pT bouSup(             redeclare package
      Medium = Medium, T=273.15 + 26,
    use_p_in=true,
    nPorts=1)
    Buildings.Fluid.Sources.Boundary_pT bouRet(             redeclare package
      Medium = Medium, T=273.15 + 20,
    use_p_in=true,
    nPorts=1)
    Modelica.Blocks.Sources.Constant PAtm(k=101325)
    Modelica.Blocks.Sources.Constant yDamMin(k=0.5)
    Modelica.Blocks.Sources.Ramp PSup(
    offset=101320,
    height=-60,
    startTime=0,
    duration=20)
    Modelica.Blocks.Sources.Ramp PRet(
    height=60,
    offset=101330,
    duration=20,
    startTime=20)
    Modelica.Blocks.Sources.Step yDam(
    height=0.1,
    offset=0.45,
    startTime=60)

equation
  connect(yDamMin.y, mixBox.yOutMin)
  connect(yDam.y, mixBox.y)
  connect(bouIn.p_in, PAtm.y)
  connect(PRet.y, bouRet.p_in)
  connect(bouSup.p_in, PSup.y)
  connect(bouIn.ports[1], mixBox.port_OutMin)
  connect(bouIn.ports[2], mixBox.port_Out)
  connect(bouIn.ports[3], mixBox.port_Exh)
  connect(bouSup.ports[1], mixBox.port_Sup)
  connect(bouRet.ports[1], mixBox.port_Ret)
end MixingBoxMinimumFlow;


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


within Buildings.Fluid.Actuators.Dampers.Examples;
model VAVBoxExponential
  "VAV box with constant pressure difference and varying control signal"
  extends Modelica.Icons.Example;

 package Medium = Buildings.Media.Air;

  Buildings.Fluid.Actuators.Dampers.Exponential dam(
    redeclare package Medium = Medium,
    dpDamper_nominal=5,
    m_flow_nominal=2)
    Modelica.Blocks.Sources.Step yDam(
    height=-1,
    offset=1,
    startTime=60)
    Modelica.Blocks.Sources.Ramp P(
    height=-20,
    offset=101335,
    startTime=0,
    duration=60)
  Buildings.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
               Medium, T=273.15 + 20,
    nPorts=2,
    use_p_in=true)
  Buildings.Fluid.Sources.Boundary_pT sin(             redeclare package Medium =
               Medium, T=273.15 + 20,
    nPorts=2,
    use_p_in=true)
    Modelica.Blocks.Sources.Constant PAtm(k=101325)
  Buildings.Fluid.Actuators.Dampers.Exponential vav(
    redeclare package Medium = Medium,
    dpDamper_nominal=5,
    dpFixed_nominal=5,
    m_flow_nominal=2)
  Buildings.Fluid.FixedResistances.PressureDrop res(
    from_dp=true,
    m_flow_nominal=2,
    redeclare package Medium = Medium,
    dp_nominal=5)

equation
  connect(yDam.y,dam. y)
  connect(P.y, sou.p_in)
  connect(PAtm.y, sin.p_in)
  connect(yDam.y, vav.y)
  connect(res.port_b, dam.port_a)
  connect(sou.ports[1], res.port_a)
  connect(sou.ports[2], vav.port_a)
  connect(dam.port_b, sin.ports[1])
  connect(sin.ports[2], vav.port_b)
end VAVBoxExponential;


Collection of validation models

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


within Buildings.Fluid.Actuators.Dampers.Validation;
model PressureIndependent
  "Test model for the pressure independent damper model"
  extends Modelica.Icons.Example;

  package Medium = Buildings.Media.Air "Medium model for air";
  parameter Modelica.Units.SI.PressureDifference dp_nominal(displayUnit="Pa")
     = 10 "Damper nominal pressure drop";
  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=1
    "Damper nominal mass flow rate";
  Buildings.Fluid.Actuators.Dampers.Exponential damExp(
    redeclare final package Medium = Medium,
    use_strokeTime=false,
    final dpDamper_nominal=dp_nominal,
    final m_flow_nominal=m_flow_nominal)
    "Damper with exponential opening characteristics"
  Modelica.Blocks.Sources.Ramp yRam(
    duration=0.3,
    offset=0,
    startTime=0.3,
    height=1) "Ramp up damper control signal"
  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare final package Medium = Medium,
    use_p_in=true,
    p(displayUnit="Pa") = 101335,
    T=293.15,
    nPorts=4) "Pressure boundary condition"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare final package Medium = Medium,
    nPorts=4) "Pressure boundary condition"
  Buildings.Fluid.Actuators.Dampers.PressureIndependent damPreInd(
    redeclare final package Medium = Medium,
    final m_flow_nominal=m_flow_nominal,
    final dpDamper_nominal=dp_nominal,
    use_strokeTime=false)
    "Pressure independent damper"
  Exponential damExpPI(
    redeclare final package Medium = Medium,
    use_strokeTime=false,
    final dpDamper_nominal=dp_nominal,
    final m_flow_nominal=m_flow_nominal)
    "Damper with exponential opening characteristics"
  Controls.Continuous.LimPID conPID(
    k=10,
    Ti=0.001,
    controllerType=Modelica.Blocks.Types.SimpleController.PID,
    initType=Modelica.Blocks.Types.Init.InitialState)
    "Discharge flow rate controller"
  Sensors.MassFlowRate senMasFlo(
    redeclare final package Medium = Medium)
    "Discharge flow rate sensor"
  Modelica.Blocks.Sources.Ramp yRam1(
    duration=0.3,
    offset=Medium.p_default - 20,
    startTime=0,
    height=40) "Ram up supply pressure"
  Modelica.Blocks.Sources.Ramp yRam2(
    duration=0.3,
    offset=0,
    startTime=0.7,
    height=-40) "Ramp down supply pressure"
  Modelica.Blocks.Math.Add add
  Sensors.RelativePressure senRelPre(
    redeclare final package Medium = Medium)
    "Pressure drop sensor"
  Modelica.Blocks.Math.Gain gain(final k=1/m_flow_nominal)
    "Normalize discharge flow rate"
equation
  connect(damExp.port_a, sou.ports[1])
  connect(damExp.port_b, sin.ports[1])
  connect(sou.ports[2], damPreInd.port_a)
  connect(damPreInd.port_b, sin.ports[2])
  connect(yRam.y, damPreInd.y)
  connect(damPreInd.y_actual, damExp.y)
  connect(damExpPI.port_b, senMasFlo.port_a)
  connect(senMasFlo.port_b, sin.ports[3])
  connect(sou.ports[3], damExpPI.port_a)
  connect(conPID.y, damExpPI.y)
  connect(yRam.y, conPID.u_s)
  connect(yRam1.y, add.u1)
  connect(yRam2.y, add.u2)
  connect(add.y, sou.p_in)
  connect(sou.ports[4], senRelPre.port_a)
  connect(senRelPre.port_b, sin.ports[4])
  connect(senMasFlo.m_flow, gain.u)
  connect(gain.y, conPID.u_m)
end PressureIndependent;


Ideal motor model with hysteresis. Ideal actuator motor model with hysteresis and finite actuation speed.
If the current actuator position y is below (or above) the
input signal u by an amount bigger than the hysteresis
delta, then the position y is increased (decreased)
until it reaches u.
The output y is bounded between 0 and 1.

within Buildings.Fluid.Actuators.Motors;
model IdealMotor "Ideal motor model with hysteresis"
  extends Modelica.Blocks.Interfaces.SISO;

  parameter Real delta(min=0, max=0.5) = 0.05 "Hysteresis";
  parameter Modelica.Units.SI.Time tOpe(min=0) = 120 "Opening time";
  parameter Modelica.Units.SI.Time tClo(min=0) = tOpe "Closing time";
  parameter Real y_start(min=0, max=1) = 0.5 "Start position";

  Modelica.Blocks.Logical.Hysteresis uppHys(final uLow=0, uHigh=delta,
    final pre_y_start=false)
  Modelica.Blocks.Logical.Hysteresis lowHys(uLow=-delta, final uHigh=0,
    final pre_y_start=true) "Lower hysteresis"
  Modelica.Blocks.Logical.Switch uppSwi
  Modelica.Blocks.Continuous.LimIntegrator int(
    final y_start=y_start,
    final k=1,
    outMax=1,
    outMin=0,
    initType=Modelica.Blocks.Types.Init.InitialState,
    limitsAtInit=true,
    y(stateSelect=StateSelect.always)) "Integrator for valve opening position"

protected
  final Modelica.Blocks.Sources.Constant zer(final k=0) "Zero signal"
  Modelica.Blocks.Sources.Constant vOpe(final k=1/tOpe) "Opening speed"
  Modelica.Blocks.Sources.Constant vClo(final k=-1/tClo) "Closing speed"
  Modelica.Blocks.Logical.Switch lowSwi
  Modelica.Blocks.Math.Add add
  Modelica.Blocks.Math.Feedback feeBac "Feedback to compute position error"
equation
  connect(zer.y, uppSwi.u3)
  connect(uppHys.y, uppSwi.u2)
  connect(vOpe.y, uppSwi.u1)
  connect(lowHys.y, lowSwi.u2)
  connect(vClo.y, lowSwi.u3)
  connect(zer.y, lowSwi.u1)
  connect(add.y, int.u)
  connect(uppSwi.y, add.u1)
  connect(u, feeBac.u1)
  connect(feeBac.y, uppHys.u)
  connect(feeBac.y, lowHys.u)
  connect(lowSwi.y, add.u2)
  connect(int.y, y)
  connect(int.y, feeBac.u2)
end IdealMotor;


within Buildings.Fluid.Actuators;
package Motors "Package with motor models for valves and dampers"
  extends Modelica.Icons.VariantsPackage;
end Motors;


Test model for IdealMotor. Test model for the ideal motor with hysteresis model.
The motor receives at its control input a time-varying signal.
It adjusts its position whenever the input signal differs
from the current position by more than the hysteresis.

within Buildings.Fluid.Actuators.Motors.Examples;
model IdealMotor "Test model for IdealMotor"
  extends Modelica.Icons.Example;
  Buildings.Fluid.Actuators.Motors.IdealMotor mot(tOpe=60) "Motor model"
  Modelica.Blocks.Sources.TimeTable ySet(table=[0,0; 60,0; 60,1; 120,1; 180,0.5;
        240,0.5; 300,0; 360,0; 360,0.25; 420,0.25; 480,1; 540,1.5; 600,-0.25])
    "Set point for actuator"
equation
  connect(ySet.y, mot.u)
end IdealMotor;


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


Package with valve models

within Buildings.Fluid.Actuators;
package Valves "Package with valve models"
  extends Modelica.Icons.VariantsPackage;
end Valves;


Three way valve with equal percentage and linear characteristics. Three way valve with equal percentage characteristics
between port_1 and port_2
and linear opening characteristic between port_3 and port_2.
Such opening characteristics were typical for valves from Landis & Gyr (now
Siemens).

within Buildings.Fluid.Actuators.Valves;
model ThreeWayEqualPercentageLinear
  "Three way valve with equal percentage and linear characteristics"
    extends BaseClasses.PartialThreeWayValve(
      redeclare TwoWayEqualPercentage res1(
        R=R,
        delta0=delta0,
        final l=l[1]),
      redeclare TwoWayLinear res3(
        final l=l[2]));
  parameter Real R = 50 "Rangeability, R=50...100 typically";
  parameter Real delta0 = 0.01
    "Range of significant deviation from equal percentage law";

equation
  connect(inv.y, res3.y)
  connect(y_actual, inv.u2)
  connect(y_actual, res1.y)
end ThreeWayEqualPercentageLinear;


Three way valve with linear characteristics. Three way valve with linear opening characteristic.

within Buildings.Fluid.Actuators.Valves;
model ThreeWayLinear "Three way valve with linear characteristics"
    extends BaseClasses.PartialThreeWayValve(
      redeclare TwoWayLinear res1(
        final l=l[1]),
      redeclare TwoWayLinear res3(
        final l=l[2]));

equation
  connect(inv.y, res3.y)
  connect(y_actual, inv.u2)
  connect(y_actual, res1.y)
end ThreeWayLinear;


Three way valve with table-specified characteristics. Three way valve with table-specified opening characteristics.
A separate characteristic for each flow path is used.

within Buildings.Fluid.Actuators.Valves;
model ThreeWayTable
  "Three way valve with table-specified characteristics"
    extends BaseClasses.PartialThreeWayValve(final l={0,0},
      redeclare TwoWayTable res1(final flowCharacteristics=flowCharacteristics1),
      redeclare TwoWayTable res3(final flowCharacteristics=flowCharacteristics3));

  parameter Data.Generic flowCharacteristics1
    "Table with flow characteristics for direct flow path at port_1"
  parameter Data.Generic flowCharacteristics3
    "Table with flow characteristics for bypass flow path at port_3"
equation
  connect(inv.y, res3.y)
  connect(y_actual, inv.u2)
  connect(y_actual, res1.y)
end ThreeWayTable;


Two way valve with the flow characteristic of a typical butterfly valve. Two way valve with the flow characteristic of a typical butterfly valve as listed below.

within Buildings.Fluid.Actuators.Valves;
model TwoWayButterfly
  "Two way valve with the flow characteristic of a typical butterfly valve"
  extends Buildings.Fluid.Actuators.Valves.TwoWayPolynomial(
    final CvData = Buildings.Fluid.Types.CvTypes.Kv,
    final Kv = Kvs,
    final c={0,1.101898284705380E-01, 2.217227395456580,  -7.483401207660790, 1.277617623360130E+01, -6.618045307070130});

  parameter Real Kvs "Kv value at fully open valve"
end TwoWayButterfly;


Two way valve with equal percentage flow characteristics. Two way valve with an equal percentage valve opening characteristic.

within Buildings.Fluid.Actuators.Valves;
model TwoWayEqualPercentage
  "Two way valve with equal percentage flow characteristics"
  extends BaseClasses.PartialTwoWayValveKv(
    phi=max(0.1*l, if homotopyInitialization then
        homotopy(actual=Buildings.Fluid.Actuators.BaseClasses.equalPercentage(
        y_actual,
        R,
        l,
        delta0), simplified=l + y_actual*(1 - l)) else
        Buildings.Fluid.Actuators.BaseClasses.equalPercentage(
        y_actual,
        R,
        l,
        delta0)));
  parameter Real R=50 "Rangeability, R=50...100 typically";
  parameter Real delta0=0.01
    "Range of significant deviation from equal percentage law";

initial equation
  // Since the flow model Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow computes
  // 1/k^2, the parameter l must not be zero.
  assert(l > 0, "Valve leakage parameter l must be bigger than zero.");
  assert(l < 1/R, "Wrong parameters in valve model.\n"
                + "  Rangeability R = " + String(R) + "\n"
                + "  Leakage flow l = " + String(l) + "\n"
                + "  Must have l < 1/R = " + String(1/R));
end TwoWayEqualPercentage;


Two way valve with linear flow characteristics. Two way valve with linear opening characteristic.

within Buildings.Fluid.Actuators.Valves;
model TwoWayLinear "Two way valve with linear flow characteristics"
  extends BaseClasses.PartialTwoWayValveKv(phi=max(0.1*l, l + y_actual*(1 - l)));

initial equation
  // Since the flow model Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow computes
  // 1/k^2, the parameter l must not be zero.
  assert(l > 0, "Valve leakage parameter l must be bigger than zero.");
end TwoWayLinear;


Two way valve with polynomial characteristic. Two way valve with polynomial opening characteristic.
The polynomial coefficients are defined using parameter c.
The elements of c are coefficients for increasing powers of y,
starting with the power 0, which corresponds to a fixed offset.
This valve model can be used to implement valves with a custom
opening characteristic, such as a combination
of a linear and an equal percentage characteristic.

within Buildings.Fluid.Actuators.Valves;
model TwoWayPolynomial "Two way valve with polynomial characteristic"
  extends Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValveKv(
    phi=max(0.1*l, l + pol_y*(1 - l)));

  parameter Real[:] c
    "Polynomial coefficients, starting with fixed offset";

protected
  constant Integer nP = 100
    "Number of points for initial algorithm test";
  Real pol_y = sum(c.*{y_actual^i for i in 0:size(c,1)-1})
    "Polynomial of valve control signal";

  // Test the validity of the provided valve coefficients
initial equation
  // We compute the analytic derivative of the polynomial for y=(1/points),
  // where the derivative of y^j = j*y^(j-1).
  if size(c, 1)>=2 then
    for i in 0:nP/2 loop
      assert(sum({c[j+1]*j*(i/nP)^(j-1) for j in 1:size(c, 1) - 1})>=0,
      "The provided valve polynomial coefficients
      do not lead to a strictly increasing characteristic for y=" + String(i/nP)+ ". This is not allowed.");
    end for;
  end if;
  assert(c[1]>=0, "The provided valve polynomial coefficients do not lead to
  a valve opening that is larger than or equal to zero for a control signal of zero.");
  assert(sum(c)<=1.1, "The provided valve polynomial coefficients do not lead to
  a valve opening that is smaller than or equal to one for a control signal of one.");
end TwoWayPolynomial;


Model of a pressure-independent two way valve. Two way valve with a pressure-independent valve opening characteristic.
The mass flow rate is controlled such that it is nearly equal to its
set point y*m_flow_nominal, unless the pressure
dp is too low, in which case a regular Kv
characteristic is used.

within Buildings.Fluid.Actuators.Valves;
model TwoWayPressureIndependent "Model of a pressure-independent two way valve"
  extends Buildings.Fluid.Actuators.BaseClasses.PartialTwoWayValve(
            final linearized = false,
            from_dp=true,
            phi=max(0.1*l, l + y_actual*(1 - l)));

  parameter Real l2(min=1e-10) = 0.01
    "Gain for mass flow increase if pressure is above nominal pressure"
  parameter Real deltax = 0.02 "Transition interval for flow rate"

protected
  parameter Real coeff1 = l2/dp_nominal*m_flow_nominal
    "Parameter for avoiding unnecessary computations";
  parameter Real coeff2 = 1/coeff1
    "Parameter for avoiding unnecessary computations";
  constant Real y2dd = 0
    "Second derivative at second support point";
  Modelica.Units.SI.MassFlowRate m_flow_set "Requested mass flow rate";
  Modelica.Units.SI.PressureDifference dp_min(displayUnit="Pa")
    "Minimum pressure difference required for delivering requested mass flow rate";
  Modelica.Units.SI.PressureDifference dp_x;
  Modelica.Units.SI.PressureDifference dp_x1;
  Modelica.Units.SI.PressureDifference dp_x2;
  Modelica.Units.SI.PressureDifference dp_y2;
  Modelica.Units.SI.PressureDifference dp_y1
    "Support points for interpolation flow functions";
  Modelica.Units.SI.MassFlowRate m_flow_x;
  Modelica.Units.SI.MassFlowRate m_flow_x1;
  Modelica.Units.SI.MassFlowRate m_flow_x2;
  Modelica.Units.SI.MassFlowRate m_flow_y2;
  Modelica.Units.SI.MassFlowRate m_flow_y1
    "Support points for interpolation flow functions";
  Modelica.Units.SI.MassFlowRate m_flow_smooth
    "Smooth interpolation result between two flow regimes";
  Modelica.Units.SI.PressureDifference dp_smooth
    "Smooth interpolation result between two flow regimes";

equation
  m_flow_set = m_flow_nominal*phi;
  kVal = Kv_SI;
  if (dpFixed_nominal > Modelica.Constants.eps) then
    k = sqrt(1/(1/kFixed^2 + 1/kVal^2));
  else
    k = kVal;
  end if;
  dp_min = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
              m_flow=m_flow_set,
              k=k,
              m_flow_turbulent=m_flow_turbulent);

  if from_dp then
    m_flow_x=0;
    m_flow_x1=0;
    m_flow_x2=0;
    dp_y1=0;
    dp_y2=0;
    dp_smooth=0;

    dp_x = dp-dp_min;
    dp_x1 = -dp_x2;
    dp_x2 = deltax*dp_min;
    // min function ensures that m_flow_y1 does not increase further for dp_x > dp_x1
    m_flow_y1 = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
                                  dp=min(dp, dp_min+dp_x1),
                                  k=k,
                                  m_flow_turbulent=m_flow_turbulent);
    // max function ensures that m_flow_y2 does not decrease further for dp_x < dp_x2
    m_flow_y2 = m_flow_set + coeff1*max(dp_x,dp_x2);

    m_flow_smooth = noEvent(smooth(2,
        if dp_x <= dp_x1
        then m_flow_y1
        elseif dp_x >=dp_x2
        then m_flow_y2
        else Buildings.Utilities.Math.Functions.quinticHermite(
                 x=dp_x,
                 x1=dp_x1,
                 x2=dp_x2,
                 y1=m_flow_y1,
                 y2=m_flow_y2,
                 y1d= Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp_der(
                                     dp=dp_min + dp_x1,
                                     k=k,
                                     m_flow_turbulent=m_flow_turbulent,
                                     dp_der=1),
                 y2d=coeff1,
                 y1dd=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp_der2(
                                     dp=dp_min + dp_x1,
                                     k=k,
                                     m_flow_turbulent=m_flow_turbulent,
                                     dp_der=1,
                                     dp_der2=0),
                 y2dd=y2dd)));
  else
    dp_x=0;
    dp_x1=0;
    dp_x2=0;
    m_flow_y1=0;
    m_flow_y2=0;
    m_flow_smooth=0;

    m_flow_x = m_flow-m_flow_set;
    m_flow_x1 = -m_flow_x2;
    m_flow_x2 = deltax*m_flow_set;
    // min function ensures that dp_y1 does not increase further for m_flow_x > m_flow_x1
    dp_y1 = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(
                                     m_flow=min(m_flow, m_flow_set + m_flow_x1),
                                     k=k,
                                     m_flow_turbulent=m_flow_turbulent);
    // max function ensures that dp_y2 does not decrease further for m_flow_x < m_flow_x2
    dp_y2 = dp_min + coeff2*max(m_flow_x, m_flow_x2);

    dp_smooth = noEvent(smooth(2,
        if m_flow_x <= m_flow_x1
        then dp_y1
        elseif m_flow_x >=m_flow_x2
        then dp_y2
        else Buildings.Utilities.Math.Functions.quinticHermite(
                 x=m_flow_x,
                 x1=m_flow_x1,
                 x2=m_flow_x2,
                 y1=dp_y1,
                 y2=dp_y2,
                 y1d=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow_der(
                                     m_flow=m_flow_set + m_flow_x1,
                                     k=k,
                                     m_flow_turbulent=m_flow_turbulent,
                                     m_flow_der=1),
                 y2d=coeff2,
                 y1dd=Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow_der2(
                                     m_flow=m_flow_set + m_flow_x1,
                                     k=k,
                                     m_flow_turbulent=m_flow_turbulent,
                                     m_flow_der=1,
                                     m_flow_der2=0),
                 y2dd=y2dd)));
  end if;

  if homotopyInitialization then
    if from_dp then
      m_flow=homotopy(actual=m_flow_smooth,
                      simplified=m_flow_nominal_pos*dp/dp_nominal_pos);
    else
        dp=homotopy(
           actual=dp_smooth,
           simplified=dp_nominal_pos*m_flow/m_flow_nominal_pos);
    end if;
  else
    if from_dp then
      m_flow=m_flow_smooth;
    else
      dp=dp_smooth;
    end if;
  end if;
end TwoWayPressureIndependent;


Two way valve with quick opening flow characteristics. Two way valve with a power function for the valve opening characteristic.
Valves that need to open quickly typically have such a valve characteristics.

within Buildings.Fluid.Actuators.Valves;
model TwoWayQuickOpening
  "Two way valve with quick opening flow characteristics"
  extends BaseClasses.PartialTwoWayValveKv(
    phi=max(0.1*l,
         if homotopyInitialization then
           homotopy(
             actual=l + Modelica.Fluid.Utilities.regPow(
               y_actual,
               alpInv,
               delta0)*(1 - l),
             simplified=l + y_actual*(1 - l))
          else
            l + Modelica.Fluid.Utilities.regPow(y_actual, alpInv, delta0)*(1 - l)));

  parameter Real alp = 2 "Parameter for valve characteristics, alp>0";
  parameter Real delta0 = 0.01 "Range of significant deviation from power law";
protected
   parameter Real alpInv = 1/alp "Inverse of alpha";

initial equation
  // Since the flow model Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow computes
  // 1/k^2, the parameter l must not be zero.
  assert(l > 0, "Valve leakage parameter l must be bigger than zero.");
end TwoWayQuickOpening;


Two way valve with table-specified flow characteristics. Two way valve with opening characteristic that is configured through
a table.

within Buildings.Fluid.Actuators.Valves;
model TwoWayTable "Two way valve with table-specified flow characteristics"
  extends BaseClasses.PartialTwoWayValveKv(
    phi=max(0.1*l, phiLooUp.y[1]),
    final l = phiLooUp.table[1, 2]);
  parameter Data.Generic flowCharacteristics "Table with flow characteristics"

  // Since the flow model Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow computes
  // 1/k^2, the flowCharacteristics.phi[1] must not be zero.
  // We therefore set a lower bound.
protected
  Modelica.Blocks.Tables.CombiTable1Dv phiLooUp(
    final tableOnFile=false,
    final table=[flowCharacteristics.y,cat(
        1,
        {max(flowCharacteristics.phi[1], 1E-8)},
        {flowCharacteristics.phi[i] for i in 2:size(flowCharacteristics.phi, 1)})],

    final columns=2:2,
    final smoothness=Modelica.Blocks.Types.Smoothness.ContinuousDerivative)
    "Normalized mass flow rate for the given valve position under the assumption of a constant pressure"

initial equation
  assert(abs(flowCharacteristics.y[1]) < Modelica.Constants.eps,
    "flowCharateristics.y[1] must be 0.");
  assert(abs(flowCharacteristics.y[size(flowCharacteristics.y, 1)] - 1) <
    Modelica.Constants.eps,
    "flowCharateristics.y[end] must be 1.");
  assert(abs(flowCharacteristics.phi[size(flowCharacteristics.phi, 1)] - 1) <
    Modelica.Constants.eps,
    "flowCharateristics.phi[end] must be 1.");

  // Assert that the sequences are strictly increasing
  assert(Buildings.Utilities.Math.Functions.isMonotonic(
           x=flowCharacteristics.y,
           strict=true),
         "The values for y in flowCharacteristics must be strictly increasing.");
  assert(Buildings.Utilities.Math.Functions.isMonotonic(
           x=flowCharacteristics.phi,
           strict=true),
         "The values for phi in flowCharacteristics must be strictly increasing.");

equation
  connect(phiLooUp.u[1], y_actual)
end TwoWayTable;


Data records for table-based valves

within Buildings.Fluid.Actuators.Valves;
package Data "Data records for table-based valves"
  extends Modelica.Icons.MaterialPropertiesPackage;
end Data;


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


within Buildings.Fluid.Actuators.Valves.Examples;
model ThreeWayValves "Three way valves with different opening characteristics"
  extends Modelica.Icons.Example;

  package Medium = Buildings.Media.Water "Medium in the component";

  Buildings.Fluid.Actuators.Valves.ThreeWayLinear valLin(
    redeclare package Medium = Medium,
    l={0.05,0.05},
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
    "Valve model, linear opening characteristics"
  Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    nPorts=3,
    use_p_in=true,
    T=313.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=3,
    use_p_in=true,
    T=313.15) "Boundary condition for flow sink"
  Modelica.Blocks.Sources.Constant PSin(k=3E5)
  Modelica.Blocks.Sources.Constant PSou(k=306000)
  Actuators.Valves.ThreeWayEqualPercentageLinear valEquPerLin(
    l={0.05,0.05},
    redeclare package Medium = Medium,
    R=10,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
  Buildings.Fluid.Sources.Boundary_pT ret(
    redeclare package Medium = Medium,
    nPorts=3,
    use_p_in=true,
    T=303.15) "Boundary condition for flow sink"

  Buildings.Fluid.Actuators.Valves.ThreeWayTable valTab(
    redeclare package Medium = Medium,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    flowCharacteristics1=Buildings.Fluid.Actuators.Valves.Data.Linear(),
    flowCharacteristics3(y={0,0.5,1}, phi={0.001,0.3,1}))
    "Valve model, linear opening characteristics"
equation
  connect(y.y, valLin.y)
  connect(PSin.y, sin.p_in)
  connect(y.y, valEquPerLin.y)
  connect(sou.ports[1], valLin.port_1)
  connect(sou.ports[2], valEquPerLin.port_1)
  connect(valLin.port_2, sin.ports[1])
  connect(valEquPerLin.port_2, sin.ports[2])
  connect(PSou.y, ret.p_in)
  connect(ret.ports[1], valLin.port_3)
  connect(ret.ports[2], valEquPerLin.port_3)
  connect(PSou.y, sou.p_in)
  connect(valTab.port_1, sou.ports[3])
  connect(valTab.port_2, sin.ports[3])
  connect(valTab.port_3, ret.ports[3])
  connect(valTab.y, y.y)
end ThreeWayValves;


within Buildings.Fluid.Actuators.Valves.Examples;
model TwoWayValvePressureIndependent
  "Two way valves with pressure independent opening characteristic"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water;

    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"

  Buildings.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
        Medium,
    use_p_in=true,
    T=293.15,
    nPorts=3) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(             redeclare package Medium =
        Medium,
    p(displayUnit="Pa") = 3E5,
    T=293.15,
    nPorts=3) "Boundary condition for flow sink"

  TwoWayPressureIndependent valInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    use_strokeTime=false,
    l=0.05,
    from_dp=true,
    dpFixed_nominal=0,
    l2=0.1,
    dpValve_nominal=10000) "Pressure independent valve"
    Modelica.Blocks.Sources.Ramp dp(
    duration=1,
    startTime=1,
    offset=303000,
    height=12000) "Pressure ramp"
  TwoWayPressureIndependent valIndDpFix(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    use_strokeTime=false,
    l=0.05,
    dpFixed_nominal=5000,
    from_dp=true,
    l2=0.1,
    dpValve_nominal=10000) "Pressure independent valve using dp_Fixed_nominal"
  TwoWayPressureIndependent valIndFromMflow(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    use_strokeTime=false,
    l=0.05,
    from_dp=false,
    dpFixed_nominal=0,
    l2=0.1,
    dpValve_nominal=10000) "Pressure independent valve using from_dp = false"
equation
  connect(valInd.y, y.y)
  connect(sou.ports[1], valInd.port_a)
  connect(valInd.port_b, sin.ports[1])
  connect(dp.y, sou.p_in)
  connect(valIndDpFix.port_a, sou.ports[2])
  connect(valIndDpFix.port_b, sin.ports[2])
  connect(valIndFromMflow.port_a, sou.ports[3])
  connect(valIndFromMflow.port_b, sin.ports[3])
  connect(y.y, valIndDpFix.y)
  connect(y.y, valIndFromMflow.y)
end TwoWayValvePressureIndependent;


within Buildings.Fluid.Actuators.Valves.Examples;
model TwoWayValves "Two way valves with different opening characteristics"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water;

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valLin(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000) "Valve model, linear opening characteristics"
    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    nPorts=6,
    use_p_in=false,
    p(displayUnit="Pa") = 306000,
    T=293.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=6,
    p(displayUnit="Pa") = 3E5,
    T=293.15) "Boundary condition for flow sink"
  Buildings.Fluid.Actuators.Valves.TwoWayQuickOpening valQui(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000) "Valve model, quick opening characteristics"
  Buildings.Fluid.Actuators.Valves.TwoWayEqualPercentage valEqu(
    redeclare package Medium = Medium,
    l=0.05,
    R=10,
    delta0=0.1,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000)
    "Valve model, equal percentage opening characteristics"

  TwoWayPressureIndependent valInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal=10000,
    use_strokeTime=false,
    l=0.05,
    l2=0.01)
  TwoWayPolynomial valPol(
    c={0,0.5304,-0.7698,1.2278},
    l=0.05,
    m_flow_nominal=1,
    dpValve_nominal=10000,
    use_strokeTime=false,
    redeclare package Medium = Medium,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint)
    "Valve with polynomial opening characteristic"
  TwoWayButterfly valBut(
    redeclare package Medium = Medium,
    Kvs=1492,
    use_strokeTime=false,
    m_flow_nominal=1,
    l=0.05) "Butterfly valve"
equation
  connect(y.y, valLin.y)
  connect(y.y, valQui.y)
  connect(y.y, valEqu.y)
  connect(sou.ports[1], valLin.port_a)
  connect(valQui.port_a, sou.ports[2])
  connect(valEqu.port_a, sou.ports[3])
  connect(valLin.port_b, sin.ports[1])
  connect(valQui.port_b, sin.ports[2])
  connect(valEqu.port_b, sin.ports[3])
  connect(valInd.y, y.y)
  connect(valInd.port_b, sin.ports[4])
  connect(valInd.port_a, sou.ports[4])
  connect(valPol.port_b, sin.ports[5])
  connect(valPol.port_a, sou.ports[5])
  connect(valPol.y, y.y)
  connect(valBut.port_b, sin.ports[6])
  connect(valBut.port_a, sou.ports[6])
  connect(valBut.y, y.y)
end TwoWayValves;


within Buildings.Fluid.Actuators.Valves.Examples;
model TwoWayValvesMotor
  "Two way valves with different opening characteristics and motor"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water;

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valLin(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000) "Valve model, linear opening characteristics"
  Buildings.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
        Medium,
    nPorts=4,
    use_p_in=true,
    T=293.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(             redeclare package Medium =
        Medium,
    nPorts=4,
    use_p_in=true,
    T=293.15) "Boundary condition for flow sink"
    Modelica.Blocks.Sources.Constant PSin(k=3E5)
    Modelica.Blocks.Sources.Constant PSou(k=306000)
  Buildings.Fluid.Actuators.Valves.TwoWayQuickOpening valQui(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000) "Valve model, quick opening characteristics"
  Buildings.Fluid.Actuators.Valves.TwoWayEqualPercentage valEqu(
    redeclare package Medium = Medium,
    l=0.05,
    R=10,
    delta0=0.1,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000)
    "Valve model, equal percentage opening characteristics"
  Modelica.Blocks.Sources.TimeTable ySet(table=[0,0; 60,0; 60,1; 120,1; 180,0.5;
        240,0.5; 300,0; 360,0; 360,0.25; 420,0.25; 480,1; 540,1.5; 600,-0.25])
    "Set point for actuator"
  Actuators.Motors.IdealMotor mot(                 tOpe=60) "Motor model"

  TwoWayPressureIndependent valInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal=10000,
    use_strokeTime=false,
    l=0.05,
    l2=0.01)
equation
  connect(PSin.y, sin.p_in)
  connect(PSou.y, sou.p_in)
  connect(ySet.y, mot.u)
  connect(mot.y, valEqu.y)
  connect(mot.y, valQui.y)
  connect(mot.y, valLin.y)
  connect(sou.ports[1], valLin.port_a)
  connect(sou.ports[2], valQui.port_a)
  connect(sou.ports[3], valEqu.port_a)
  connect(valLin.port_b, sin.ports[1])
  connect(valQui.port_b, sin.ports[2])
  connect(valEqu.port_b, sin.ports[3])
  connect(valInd.port_a, sou.ports[4])
  connect(valInd.port_b, sin.ports[4])
  connect(valInd.y, valEqu.y)
end TwoWayValvesMotor;


within Buildings.Fluid.Actuators.Valves.Examples;
model TwoWayValvesTable "Two way valve with linear opening characteristics"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water "Medium";

    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    nPorts=2,
    use_p_in=false,
    p(displayUnit="Pa") = 306000,
    T=293.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=2,
    p(displayUnit="Pa") = 3E5,
    T=293.15) "Boundary condition for flow sink"
  Valves.TwoWayTable valTab(
    redeclare package Medium = Medium,
    m_flow_nominal=2,
    use_strokeTime=false,
    dpValve_nominal=6000,
    flowCharacteristics=Buildings.Fluid.Actuators.Valves.Data.Linear(),
    from_dp=true) "Valve model with opening characteristics based on a table"
  TwoWayLinear valLin(
    use_strokeTime=false,
    redeclare package Medium = Medium,
    l=0.0001,
    m_flow_nominal=2,
    dpValve_nominal=6000,
    from_dp=true) "Valve model with linear opening characteristics"

equation
  connect(y.y,valTab. y)
  connect(valTab.port_a, sou.ports[1])
  connect(valTab.port_b, sin.ports[1])
  connect(valLin.y, y.y)
  connect(sou.ports[2], valLin.port_a)
  connect(valLin.port_b, sin.ports[2])
end TwoWayValvesTable;


within Buildings.Fluid.Actuators.Valves.Examples;
model TwoWayValveTable
  "Two way valve with nonlinear opening characteristics based on a table"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water "Medium";

    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    use_p_in=false,
    p(displayUnit="Pa") = 306000,
    T=293.15,
    nPorts=1) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    p(displayUnit="Pa") = 3E5,
    T=293.15,
    nPorts=1) "Boundary condition for flow sink"
  Valves.TwoWayTable valTab(
    redeclare package Medium = Medium,
    use_strokeTime=false,
    from_dp=true,
    flowCharacteristics=datVal,
    CvData=Buildings.Fluid.Types.CvTypes.Kv,
    Kv=0.65,
    m_flow_nominal=0.04)
    "Valve model with opening characteristics based on a table"

  parameter Data.Generic datVal(
    y={0,0.1667,0.3333,0.5,0.6667,1},
    phi={0, 0.19, 0.35, 0.45, 0.5, 0.65}/0.65) "Valve characteristics"
  Modelica.Blocks.Math.UnitConversions.To_bar to_bar
  Sensors.RelativePressure senRelPre(redeclare package Medium = Medium)
    "Pressure differential sensor"
  Sensors.VolumeFlowRate senVolFlo(redeclare package Medium = Medium,
      m_flow_nominal=0.04) "Volume flow rate sensor"
  Modelica.Blocks.Math.Sqrt sqrt1
  Modelica.Blocks.Math.Gain to_m3_h(k=3600) "Conversion to m3/h"
  Modelica.Blocks.Math.Division kv "Kv-value"
equation
  connect(y.y,valTab. y)
  connect(sou.ports[1], senVolFlo.port_a)
  connect(senVolFlo.port_b, valTab.port_a)
  connect(valTab.port_a, senRelPre.port_a)
  connect(valTab.port_b, senRelPre.port_b)
  connect(valTab.port_b, sin.ports[1])
  connect(to_bar.u, senRelPre.p_rel)
  connect(sqrt1.u, to_bar.y)
  connect(senVolFlo.V_flow, to_m3_h.u)
  connect(to_m3_h.y,kv. u1)
  connect(sqrt1.y,kv. u2)
end TwoWayValveTable;


Collection of validation models

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


within Buildings.Fluid.Actuators.Valves.Validation;
model ThreeWayValveParameterization
  "Test model for parameterization of three-way valves"
  extends Modelica.Icons.Example;
  package Medium = Buildings.Media.Water "Medium in the component";

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=0.4
    "Design mass flow rate";
  parameter Modelica.Units.SI.PressureDifference dp_nominal=4500
    "Design pressure drop";

  parameter Real Kv_SI = m_flow_nominal/sqrt(dp_nominal)
    "Flow coefficient for fully open valve in SI units, Kv=m_flow/sqrt(dp) [kg/s/(Pa)^(1/2)]";

  parameter Real Kv = Kv_SI/(rhoStd/3600/sqrt(1E5))
    "Kv (metric) flow coefficient [m3/h/(bar)^(1/2)]";
  parameter Real Cv = Kv_SI/(rhoStd*0.0631/1000/sqrt(6895))
    "Cv (US) flow coefficient [USG/min/(psi)^(1/2)]";
  parameter Modelica.Units.SI.Area Av=Kv_SI/sqrt(rhoStd)
    "Av (metric) flow coefficient";

  parameter Modelica.Units.SI.Density rhoStd=Medium.density_pTX(
      101325,
      273.15 + 4,
      Medium.X_default) "Standard density";

  Buildings.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear valOPPoi(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal(displayUnit="kPa") = 4500,
    use_strokeTime=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Three way valve with operating point as parameter"

  Buildings.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear valKv(
    redeclare package Medium = Medium,
    CvData=Buildings.Fluid.Types.CvTypes.Kv,
    m_flow_nominal=m_flow_nominal,
    Kv=Kv,
    use_strokeTime=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Three way valve with Kv-value as parameter"

  Buildings.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear valCv(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    CvData=Buildings.Fluid.Types.CvTypes.Cv,
    Cv=Cv,
    use_strokeTime=false,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Three way valve with Cv-value as parameter"

  Buildings.Fluid.Actuators.Valves.ThreeWayEqualPercentageLinear valAv(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    Av=Av,
    use_strokeTime=false,
    CvData=Buildings.Fluid.Types.CvTypes.Av,
    energyDynamics=Modelica.Fluid.Types.Dynamics.SteadyState)
    "Three way valve with Av-value as parameter"

  Buildings.Fluid.Sources.Boundary_pT sou1(
    redeclare package Medium = Medium,
    nPorts=4,
    use_p_in=false,
    p(displayUnit="Pa") = 300000 + 4500,
    T=293.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=4,
    use_p_in=false,
    p=300000,
    T=293.15) "Boundary condition for flow sink"
    Modelica.Blocks.Sources.Ramp y(
    duration=1,
    height=1,
    offset=0) "Control signal"

  Buildings.Fluid.Sensors.MassFlowRate senM_flowOpPoi(
    redeclare package Medium = Medium) "Mass flow rate sensor"
  Buildings.Fluid.Sensors.MassFlowRate senM_flowKv(
    redeclare package Medium = Medium) "Mass flow rate sensor"
  Buildings.Fluid.Sensors.MassFlowRate senM_flowCv(
    redeclare package Medium = Medium) "Mass flow rate sensor"

  Buildings.Fluid.Sensors.MassFlowRate senM_flowAv(redeclare package Medium =
        Medium) "Mass flow rate sensor"
  Buildings.Fluid.Sources.Boundary_pT sou3(
    redeclare package Medium = Medium,
    nPorts=4,
    use_p_in=false,
    p(displayUnit="Pa") = 300000 + 4500,
    T=293.15) "Boundary condition for flow source"
equation
  connect(sou1.ports[1], valOPPoi.port_1)
  connect(sou1.ports[2], valKv.port_1)
  connect(sou1.ports[3], valCv.port_1)
  connect(sou1.ports[4], valAv.port_1)
  connect(sou3.ports[1], valOPPoi.port_3)
  connect(sou3.ports[2], valKv.port_3)
  connect(sou3.ports[3], valCv.port_3)
  connect(sou3.ports[4], valAv.port_3)
  connect(valOPPoi.port_2, senM_flowOpPoi.port_a)
  connect(valKv.port_2, senM_flowKv.port_a)
  connect(valCv.port_2, senM_flowCv.port_a)
  connect(valAv.port_2, senM_flowAv.port_a)
  connect(senM_flowOpPoi.port_b, sin.ports[1])
  connect(senM_flowKv.port_b, sin.ports[2])
  connect(senM_flowCv.port_b, sin.ports[3])
  connect(senM_flowAv.port_b, sin.ports[4])
  connect(y.y, valOPPoi.y)
  connect(y.y, valKv.y)
  connect(y.y, valCv.y)
  connect(y.y, valAv.y)
end ThreeWayValveParameterization;


within Buildings.Fluid.Actuators.Valves.Validation;
model TwoWayValveParameterization
  "Model to test and illustrate different parameterization for valves"
  extends Modelica.Icons.Example;

 package Medium = Buildings.Media.Water;

  parameter Modelica.Units.SI.MassFlowRate m_flow_nominal=0.4
    "Design mass flow rate";
  parameter Modelica.Units.SI.PressureDifference dp_nominal=4500
    "Design pressure drop";

  parameter Real Kv_SI = m_flow_nominal/sqrt(dp_nominal)
    "Flow coefficient for fully open valve in SI units, Kv=m_flow/sqrt(dp) [kg/s/(Pa)^(1/2)]";

  parameter Real Kv = Kv_SI/(rhoStd/3600/sqrt(1E5))
    "Kv (metric) flow coefficient [m3/h/(bar)^(1/2)]";
  parameter Real Cv = Kv_SI/(rhoStd*0.0631/1000/sqrt(6895))
    "Cv (US) flow coefficient [USG/min/(psi)^(1/2)]";
  parameter Modelica.Units.SI.Area Av=Kv_SI/sqrt(rhoStd)
    "Av (metric) flow coefficient";

  parameter Modelica.Units.SI.Density rhoStd=Medium.density_pTX(
      101325,
      273.15 + 4,
      Medium.X_default) "Standard density";

  Buildings.Fluid.Actuators.Valves.TwoWayLinear valOPPoi(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    CvData=Buildings.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal(displayUnit="kPa") = dp_nominal,
    use_strokeTime=false) "Valve model, linear opening characteristics"
    Modelica.Blocks.Sources.Ramp     y(duration=1)
                                            "Control signal"
  Valves.TwoWayLinear valKv(
    redeclare package Medium = Medium,
    CvData=Buildings.Fluid.Types.CvTypes.Kv,
    m_flow_nominal=m_flow_nominal,
    Kv=Kv,
    use_strokeTime=false) "Valve model, linear opening characteristics"

  Valves.TwoWayLinear valCv(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    CvData=Buildings.Fluid.Types.CvTypes.Cv,
    Cv=Cv,
    use_strokeTime=false) "Valve model, linear opening characteristics"

  Buildings.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    nPorts=4,
    p(displayUnit="Pa") = 300000 + 4500,
    T=293.15) "Boundary condition for flow source"
  Buildings.Fluid.Sources.Boundary_pT sin(
    redeclare package Medium = Medium,
    nPorts=4,
    use_p_in=false,
    p=300000,
    T=293.15) "Boundary condition for flow sink"

  Buildings.Fluid.Sensors.MassFlowRate senM_flowOpPoi(
    redeclare package Medium = Medium) "Mass flow rate sensor"
  Buildings.Fluid.Sensors.MassFlowRate senM_flowKv(
    redeclare package Medium = Medium) "Mass flow rate sensor"
  Buildings.Fluid.Sensors.MassFlowRate senM_flowCv(
    redeclare package Medium = Medium) "Mass flow rate sensor"
  Valves.TwoWayLinear valAv(
    redeclare package Medium = Medium,
    m_flow_nominal=m_flow_nominal,
    use_strokeTime=false,
    CvData=Buildings.Fluid.Types.CvTypes.Av,
    Av=Av) "Valve model, linear opening characteristics"
  Buildings.Fluid.Sensors.MassFlowRate senM_flowAv(redeclare package Medium =
        Medium) "Mass flow rate sensor"
equation
  connect(y.y, valOPPoi.y)
  connect(y.y, valKv.y)
  connect(valKv.port_a, sou.ports[2])
  connect(sou.ports[3], valCv.port_a)
  connect(y.y, valCv.y)
  connect(sou.ports[1], valOPPoi.port_a)
  connect(valOPPoi.port_b, senM_flowOpPoi.port_a)
  connect(valKv.port_b, senM_flowKv.port_a)
  connect(valCv.port_b, senM_flowCv.port_a)
  connect(senM_flowCv.port_b, sin.ports[3])
  connect(senM_flowKv.port_b, sin.ports[2])
  connect(senM_flowOpPoi.port_b, sin.ports[1])
  connect(sou.ports[4], valAv.port_a)
  connect(valAv.port_b, senM_flowAv.port_a)
  connect(senM_flowAv.port_b, sin.ports[4])
  connect(valAv.y, y.y)
end TwoWayValveParameterization;


Filter used for actuators of valves, dampers and movers. This block implements a filter that is used to approximate the actuators
of valves, dampers and fans.

within Buildings.Fluid.BaseClasses;
block ActuatorFilter
  "Filter used for actuators of valves, dampers and movers"
  import Modelica.Blocks.Types.Init;
  extends Modelica.Blocks.Interfaces.SISO;

  constant Integer n=2 "Order of filter";
  parameter Modelica.Units.SI.Frequency f(start=1) "Cut-off frequency";
  parameter Boolean normalized = true
    "= true, if amplitude at f_cut is 3 dB, otherwise unmodified filter";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit
    "Type of initialization (1: no init, 2: steady state, 3: initial state, 4: initial output)"
  parameter Real x_start[n]=zeros(n) "Initial or guess values of states"
  parameter Real y_start=0.0
    "Initial value of output (remaining states are in steady state)"

  parameter Real u_nominal = 1 "Magnitude of input";

  Real x[n](each final stateSelect=StateSelect.never) = u_nom*s
    "Transformed filter states";

protected
  final parameter Real u_nom = if abs(u_nominal-1) < 1E-12 then 1-1E-12 else u_nominal
    "Magnitude of input (set to a value different from 1 to avoid elimination by symbolic processing)";

  parameter Real alpha=if normalized then sqrt(2^(1/n) - 1) else 1.0
    "Frequency correction factor for normalized filter";
  parameter Real w_u=2*Modelica.Constants.pi*f/alpha/u_nom;

  Real s[n](start=x_start/u_nom) "Filter states";

initial equation
  if initType == Init.SteadyState then
    der(s) = zeros(n);
  elseif initType == Init.InitialState then
    s = x_start/u_nom;
  elseif initType == Init.InitialOutput then
    y = y_start;
    der(s[1:n - 1]) = zeros(n - 1);
  end if;

equation
  der(s[1]) = (u - u_nom*s[1])*w_u;
  for i in 2:n loop
    der(s[i]) = (u_nom*s[i - 1] - u_nom*s[i])*w_u;
  end for;
  y =u_nom*s[n];
end ActuatorFilter;


within Buildings.Fluid.BaseClasses;
block IndexMassFraction
  "Computes the index of a substance in the mass fraction vector Xi"
  replaceable package Medium =
      Modelica.Media.Interfaces.PartialCondensingGases "Medium model"
  parameter String substanceName="" "Name of species substance";

protected
  parameter Integer i_x = sum(
    if Modelica.Utilities.Strings.isEqual(string1=Medium.substanceNames[i],
                                          string2=substanceName,
                                          caseSensitive=false) then i else 0
                                          for i in 1:Medium.nXi) "Index of substance"
initial equation
  assert(i_x > 0, "Substance '" + substanceName + "' is not present in medium '"
                  + Medium.mediumName + "'.\n"
                  + "Change medium model to one that has '" + substanceName + "' as a substance.");
end IndexMassFraction;


Model that multiplies the mass flow rate. This model multiplies the mass flow rate so that
0 = port_b.m_flow + gain * port_a.m_flow
where gain > 0 is either equal to
the input variable u if use_input
is set to true, or equal to
the parameter k if use_input
is set to false.

within Buildings.Fluid.BaseClasses;
model MassFlowRateMultiplier "Model that multiplies the mass flow rate"
  extends Buildings.Fluid.Interfaces.PartialTwoPort;

  parameter Boolean use_input=false
    "Set to true for multiplier factor provided as an input instead of a parameter"
  parameter Real k=1
    "Gain for mass flow rate"

  Modelica.Blocks.Interfaces.RealInput u(
    final unit="1",
    final min=Modelica.Constants.small) if use_input
    "Multiplier factor"
  Modelica.Blocks.Interfaces.RealOutput uInv if use_input
    "Inverse of the multiplier factor"
protected
  Modelica.Blocks.Interfaces.RealInput u_internal
    "Connector for multiplier factor for internal use only"

  Modelica.Blocks.Sources.Constant cst(
    final k=k) if not use_input
    "Constant gain value"
  Modelica.Blocks.Math.Division div1 if use_input
    "Compute the inverse"
  Modelica.Blocks.Sources.Constant one(
    final k=1) if use_input
    "Constant 1"

initial equation
  assert(k > Modelica.Constants.small,
    "Gain must be strictly positive. Received k = " + String(k));
equation
  assert(u_internal > Modelica.Constants.small,
    "Gain must be strictly positive. Received u = " + String(u_internal));

  // Pressure drop in design flow direction
  port_a.p = port_b.p;

  // Mass balance (mass is not conserved by this model!)
  port_b.m_flow = -u_internal * port_a.m_flow;

  // Specific enthalpy flow rate
  port_a.h_outflow = inStream(port_b.h_outflow);
  port_b.h_outflow = inStream(port_a.h_outflow);
  // Transport of substances
  port_a.Xi_outflow = inStream(port_b.Xi_outflow);
  port_b.Xi_outflow = inStream(port_a.Xi_outflow);

  port_a.C_outflow = inStream(port_b.C_outflow);
  port_b.C_outflow = inStream(port_a.C_outflow);

  connect(cst.y, u_internal)
  connect(u_internal, div1.u2)
  connect(one.y, div1.u1)
  connect(div1.y, uInv)
  connect(u, u_internal)
end MassFlowRateMultiplier;


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


Partial model for a hydraulic resistance. Partial model for a flow resistance, possible with variable flow coefficient.
Models that extend this class need to implement an equation that relates
m_flow and dp, and they need to assign the parameter
m_flow_turbulent.

within Buildings.Fluid.BaseClasses;
partial model PartialResistance "Partial model for a hydraulic resistance"
    extends Buildings.Fluid.Interfaces.PartialTwoPortInterface(
     show_T=false,
     dp(nominal=if dp_nominal_pos > Modelica.Constants.eps
          then dp_nominal_pos else 1),
     m_flow(
        nominal=if m_flow_nominal_pos > Modelica.Constants.eps
          then m_flow_nominal_pos else 1),
     final m_flow_small = 1E-4*abs(m_flow_nominal));

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

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

  parameter Modelica.Units.SI.PressureDifference dp_nominal(displayUnit="Pa")
    "Pressure drop at nominal mass flow rate"

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

  parameter Modelica.Units.SI.MassFlowRate m_flow_turbulent(min=0)
    "Turbulent flow if |m_flow| >= m_flow_turbulent";

protected
  parameter Medium.ThermodynamicState sta_default=
     Medium.setState_pTX(T=Medium.T_default, p=Medium.p_default, X=Medium.X_default);
  parameter Modelica.Units.SI.DynamicViscosity eta_default=
      Medium.dynamicViscosity(sta_default)
    "Dynamic viscosity, used to compute transition to turbulent flow regime";

  final parameter Modelica.Units.SI.MassFlowRate m_flow_nominal_pos=abs(
      m_flow_nominal) "Absolute value of nominal flow rate";
  final parameter Modelica.Units.SI.PressureDifference dp_nominal_pos(
      displayUnit="Pa") = abs(dp_nominal)
    "Absolute value of nominal pressure difference";
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
  // Isenthalpic state transformation (no storage and no loss of energy)
  port_a.h_outflow = if allowFlowReversal then inStream(port_b.h_outflow) else Medium.h_default;
  port_b.h_outflow = inStream(port_a.h_outflow);

  // Mass balance (no storage)
  port_a.m_flow + port_b.m_flow = 0;

  // Transport of substances
  port_a.Xi_outflow = if allowFlowReversal then inStream(port_b.Xi_outflow) else Medium.X_default[1:Medium.nXi];
  port_b.Xi_outflow = inStream(port_a.Xi_outflow);

  port_a.C_outflow = if allowFlowReversal then inStream(port_b.C_outflow) else zeros(Medium.nC);
  port_b.C_outflow = inStream(port_a.C_outflow);
end PartialResistance;


within Buildings.Fluid.BaseClasses;
partial model PartialThreeWayResistance
  "Flow splitter with partial resistance model at each port"
  extends Buildings.Fluid.Interfaces.LumpedVolumeDeclarations(
    final massDynamics=energyDynamics,
    final mSenFac=1);

  Modelica.Fluid.Interfaces.FluidPort_a port_1(
    redeclare package Medium = Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_1 == Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_1== Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "First port, typically inlet"
  Modelica.Fluid.Interfaces.FluidPort_b port_2(
    redeclare package Medium = Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_2 == Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_2 == Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "Second port, typically outlet"
  Modelica.Fluid.Interfaces.FluidPort_a port_3(
    redeclare package Medium=Medium,
    h_outflow(start=Medium.h_default, nominal=Medium.h_default),
    m_flow(min=if (portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
           max=if (portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
    "Third port, can be either inlet or outlet"

  parameter Modelica.Units.SI.Time tau=10
    "Time constant at nominal flow for dynamic energy and momentum balance"
  parameter Modelica.Units.SI.MassFlowRate mDyn_flow_nominal
    "Nominal mass flow rate for dynamic momentum and energy balance"

  parameter Boolean from_dp = true
    "= true, use m_flow = f(dp) else dp = f(m_flow)"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_1=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_1"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_2=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_2"
  parameter Modelica.Fluid.Types.PortFlowDirection portFlowDirection_3=Modelica.Fluid.Types.PortFlowDirection.Bidirectional
    "Flow direction for port_3"
  parameter Boolean verifyFlowReversal = false
    "=true, to assert that the flow does not reverse when portFlowDirection_* does not equal Bidirectional"
  parameter Modelica.Units.SI.MassFlowRate m_flow_small
    "Small mass flow rate for checking flow reversal"

  replaceable Buildings.Fluid.Interfaces.PartialTwoPortInterface res1
    constrainedby Buildings.Fluid.Interfaces.PartialTwoPortInterface(
      redeclare final package Medium = Medium,
      allowFlowReversal=portFlowDirection_1 == Modelica.Fluid.Types.PortFlowDirection.Bidirectional)
    "Partial model, to be replaced with a fluid component"
  replaceable Buildings.Fluid.Interfaces.PartialTwoPortInterface res2
    constrainedby Buildings.Fluid.Interfaces.PartialTwoPortInterface(
      redeclare final package Medium = Medium,
      allowFlowReversal=portFlowDirection_2 == Modelica.Fluid.Types.PortFlowDirection.Bidirectional)
    "Partial model, to be replaced with a fluid component"
  replaceable Buildings.Fluid.Interfaces.PartialTwoPortInterface res3
    constrainedby Buildings.Fluid.Interfaces.PartialTwoPortInterface(
      redeclare final package Medium = Medium,
      allowFlowReversal=portFlowDirection_3 == Modelica.Fluid.Types.PortFlowDirection.Bidirectional)
    "Partial model, to be replaced with a fluid component"

  Buildings.Fluid.Delays.DelayFirstOrder vol(
    redeclare final package Medium = Medium,
    final nPorts=3,
    final tau=tau,
    final m_flow_nominal=mDyn_flow_nominal,
    final energyDynamics=energyDynamics,
    final p_start=p_start,
    final T_start=T_start,
    final X_start=X_start,
    final C_start=C_start,
    final allowFlowReversal=true,
    final prescribedHeatFlowRate=false)
    if have_controlVolume "Fluid volume to break algebraic loop"

protected
  parameter Boolean have_controlVolume=
      energyDynamics <> Modelica.Fluid.Types.Dynamics.SteadyState
    "Boolean flag used to remove conditional components";

  Modelica.Fluid.Interfaces.FluidPort_a port_internal(
    redeclare package Medium = Medium) if not have_controlVolume
    "Internal dummy port for easier connection of conditional connections"
initial equation
  assert(portFlowDirection_1<>Modelica.Fluid.Types.PortFlowDirection.Leaving or
         portFlowDirection_2<>Modelica.Fluid.Types.PortFlowDirection.Leaving or
         portFlowDirection_3<>Modelica.Fluid.Types.PortFlowDirection.Leaving,
         "In " + getInstanceName() + ": All ports are configured to
         Modelica.Fluid.Types.PortFlowDirection.Leaving, which is non-physical.");
  assert(portFlowDirection_1<>Modelica.Fluid.Types.PortFlowDirection.Entering or
         portFlowDirection_2<>Modelica.Fluid.Types.PortFlowDirection.Entering or
         portFlowDirection_3<>Modelica.Fluid.Types.PortFlowDirection.Entering,
         "In " + getInstanceName() + ": All ports are configured to
         Modelica.Fluid.Types.PortFlowDirection.Entering, which is non-physical.");

equation
  if verifyFlowReversal then
    if portFlowDirection_1==Modelica.Fluid.Types.PortFlowDirection.Entering then
      assert(port_1.m_flow> -m_flow_small,
      "In " + getInstanceName() + ":
      Flow is leaving port_1 despite portFlowDirection_1=PortFlowDirection.Entering, since m_flow=" +
      String(port_1.m_flow) + "<-"+String(m_flow_small));
    end if;
    if portFlowDirection_1==Modelica.Fluid.Types.PortFlowDirection.Leaving then
      assert(port_1.m_flow< m_flow_small,
      "In " + getInstanceName() + ":
      Flow is entering port_1 despite portFlowDirection_1=PortFlowDirection.Leaving, since m_flow=" +
      String(port_1.m_flow) + ">"+String(m_flow_small));
    end if;
    if portFlowDirection_2==Modelica.Fluid.Types.PortFlowDirection.Entering then
      assert(port_2.m_flow> -m_flow_small,
      "In " + getInstanceName() + ":
      Flow is leaving port_2 despite portFlowDirection_2=PortFlowDirection.Entering, since m_flow=" +
      String(port_2.m_flow) + "<-"+String(m_flow_small));
    end if;
    if portFlowDirection_2==Modelica.Fluid.Types.PortFlowDirection.Leaving then
      assert(port_2.m_flow< m_flow_small,
      "In " + getInstanceName() + ":
      Flow is entering port_2 despite portFlowDirection_2=PortFlowDirection.Leaving, since m_flow=" +
      String(port_2.m_flow) + ">"+String(m_flow_small));
    end if;
    if portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Entering then
      assert(port_3.m_flow> -m_flow_small,
      "In " + getInstanceName() + ":
      Flow is leaving port_3 despite portFlowDirection_3=PortFlowDirection.Entering, since m_flow=" +
      String(port_3.m_flow) + "<-"+String(m_flow_small));
    end if;
    if portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Leaving then
      assert(port_3.m_flow< m_flow_small,
      "In " + getInstanceName() + ":
      Flow is entering port_3 despite portFlowDirection_3=PortFlowDirection.Leaving, since m_flow=" +
      String(port_3.m_flow) + ">"+String(m_flow_small));
    end if;
  end if;

  if portFlowDirection_1==Modelica.Fluid.Types.PortFlowDirection.Leaving then
    if not have_controlVolume then
       connect(res1.port_a, port_internal)
    else
       connect(res1.port_a, vol.ports[1])
    end if;
    connect(port_1, res1.port_b)
  else
    if not have_controlVolume then
       connect(res1.port_b, port_internal)
    else
       connect(res1.port_b, vol.ports[1])
    end if;
    connect(port_1, res1.port_a)
  end if;

  if portFlowDirection_2==Modelica.Fluid.Types.PortFlowDirection.Leaving then
    if not have_controlVolume then
       connect(res2.port_a, port_internal)
    else
       connect(res2.port_a, vol.ports[2])
    end if;
    connect(port_2, res2.port_b)
  else
    if not have_controlVolume then
       connect(res2.port_b, port_internal)
    else
       connect(res2.port_b, vol.ports[2])
    end if;
    connect(port_2, res2.port_a)
  end if;

  if portFlowDirection_3==Modelica.Fluid.Types.PortFlowDirection.Leaving then
    if not have_controlVolume then
       connect(res3.port_a, port_internal)
    else
       connect(res3.port_a, vol.ports[3])
    end if;
    connect(port_3, res3.port_b)
  else
    if not have_controlVolume then
       connect(res3.port_b, port_internal)
    else
       connect(res3.port_b, vol.ports[3])
    end if;
    connect(port_3, res3.port_a)
  end if;
end PartialThreeWayResistance;


within Buildings.Fluid.BaseClasses;
package FlowModels "Flow models for pressure drop calculations"
  extends Modelica.Icons.BasesPackage;
end FlowModels;


Model that checks the correct implementation of the 1st order derivative of the flow function. This model validates the implementation of

Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp
and its first order derivative

Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp_der.
If the derivative implementation is wrong, the simulation will stop with an error.

within Buildings.Fluid.BaseClasses.FlowModels.Validation;
model BasicFlowFunction_dp_DerivativeCheck
  "Model that checks the correct implementation of the 1st order derivative of the flow function"
  extends Modelica.Icons.Example;

  constant Real gain = 2 "Gain for computing the mass flow rate";

  parameter Real k = 0.35 "Flow coefficient";
  parameter Modelica.Units.SI.MassFlowRate m_flow_turbulent=0.36
    "Mass flow rate where transition to turbulent flow occurs";
  Modelica.Units.SI.MassFlowRate m_flow "Mass flow rate";
  Modelica.Units.SI.MassFlowRate m_flow_comp "Comparison value for m_flow";
  Modelica.Units.SI.PressureDifference dp "Pressure drop";
  Modelica.Units.SI.MassFlowRate err "Integration error";
initial equation
 m_flow = m_flow_comp;
equation
  dp = time*gain;
  m_flow = Buildings.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
    dp=dp,
    k = k,
    m_flow_turbulent=m_flow_turbulent);
  der(m_flow) = der(m_flow_comp);
  err = m_flow-m_flow_comp;
  assert(abs(err) < 1E-3, "Error in implementation.");
end BasicFlowFunction_dp_DerivativeCheck;