within AixLib.Fluid;
package Actuators "Package with actuator models such as valves and dampers"
  extends Modelica.Icons.VariantsPackage;

package UsersGuide "User's Guide"
  extends Modelica.Icons.Information;
  res(dp_nominal=10000,                m_flow_nominal=0.1);

Instead of this arrangement, the model res can be deleted
and the valve configured as

  val(dpValve_nominal=6000, dpFixed=10000, m_flow_nominal=0.1);

This yields the same simulation results, but a nonlinear equation can be avoided
in some cases. Although lumping the pressure drop of other components into the
valve model violates the intent that in component-based modeling, each component
should only model its own behavior, having the option of eliminating
a nonlinear equation can be worthwhile.

For three way valves, similar parameters exist for the controlled ports of the valve. For example,
consider the configuration below.

Suppose the parameters are

  val(dpValve_nominal=6000, dpFixed={0, 0}, m_flow_nominal=0.1);
  res1(dp_nominal=10000,                    m_flow_nominal=0.1);
  res3(dp_nominal=100,                      m_flow_nominal=0.1);

An equivalent model could be created by deleting the two resistance models
res1 and res3, and configuring the valve as

  val(dpValve_nominal=6000, dpFixed={10000, 100}, m_flow_nominal=0.1);

Leakage flow rate

Valves and air dampers should for numerical reasons have a small leakage flow rate.
This leakage l is a non-dimensional number, defined as
l=Kv(y=0) ⁄ =Kv(y=1).
A typical default value is l=0.0001.

If l=0, models will issue an error message as
this can in some situations lead to numerical problems if
a flow leg becomes decoupled from a reference pressure source.

Transients of actuators

This section describes how valves and dampers can be configured
to approximate the travel time of an actuator.
Such an approximation can also lead to faster simulation because
discrete or fast changes in controllers are damped before they
influence the flow network.

Approximation using a 2nd order filter

The valves and dampers in the package

AixLib.Fluid.Actuators
all have a parameter use_inputFilter.
This parameter is used as follows:

If use_inputFilter=false, then the actual valve
or damper position is equal to the input signal y.

If use_inputFilter=true, then the actual valve or damper position
is computed in such a way that it approximates a valve motor.
This approximation is implemented using a 2nd order low-pass filter.
The filter has a parameter riseTime, which by default is
set to 120 seconds.
The rise time is the time required to reach 99.6% of the opening.

Using a filter often leads to a more robust simulation,
because a step change
in the input signal is \"smoothened\" by the filter, and
hence the flow network is only exposed to a continuously differentiable change
in the input signal.
However, if the filter is part of a closed loop control, then the transient
response gets changed. Therefore, if the parameter use_inputFilter
is changed, control gains may need to be retuned.

For example, suppose there is a closed loop control with a PI-controller

AixLib.Controls.Continuous.LimPID
and a valve, configured with use_inputFilter=true and riseTime=120 seconds.
Assume that the transient response of the other dynamic elements in the control loop is fast
compared to the rise time of the filter.
Then, a proportional gain of k=0.1 and an integrator time constant of
Ti=120 seconds often yields satisfactory closed loop control performance.
These values may need to be changed for different applications as they are also a function
of the loop gain.
If the control loop shows oscillatory behavior, then reduce k and/or increase Ti.
If the control loop reacts too slow, do the opposite.

We will now show how the parameter riseTime affects the actual position of a
control valve.
The figure below shows a model with two control valves.
The valve val1 is configured with use_inputFilter=true
and a rise time riseTime=120 seconds.
The grey motor symbol above the control valve val1
indicates that use_inputFilter=true.

If these valves both have a step input signal at 10 seconds, then the
actual opening of the valves are as follows:

Thus, in the valve val1, the mass flow rate will slowly increase,
whereas in val2, the mass flow rate changes instantaneously.

If use_inputFilter=true, then the parameter
y_start can be used to set the initial position of the
actuator, and the parameter
init can be used to configure how the position
should be initialized.

For most applications, the default values are appropriate.
Although adding a filter increases the number of equations,
it can reduce computing time because the equations are
easier to solve when a controller switches.

Approximation using a motor model with hysteresis

The model

AixLib.Fluid.Actuators.Motors.IdealMotor
models a motor with hysteresis.
It is more detailed than the above approximation.
However, it can significantly increase computing time
because it generates a state event whenever the valve position changes.

"));

end UsersGuide;
end Actuators;


Partial model that implements the filtered opening for valves and dampers. This model implements the filter that is used to approximate the travel
time of the actuator.
Models that extend this model use the signal
y_actual to obtain the
current position of the actuator.

within AixLib.Fluid.Actuators.BaseClasses;
model ActuatorSignal
  "Partial model that implements the filtered opening for valves and dampers"

  constant Integer order(min=1) = 2 "Order of filter";

  parameter Boolean use_inputFilter=true
    "= true, if opening is filtered with a 2nd order CriticalDamping filter"
  parameter Modelica.Units.SI.Time riseTime=120
    "Rise time of the filter (time to reach 99.6 % of an opening step)"
  parameter Modelica.Blocks.Types.Init init=Modelica.Blocks.Types.Init.InitialOutput
    "Type of initialization (no init/steady state/initial state/initial output)"
  parameter Real y_start=1 "Initial position of actuator"

  Modelica.Blocks.Interfaces.RealInput y(min=0, max=1)
    "Actuator position (0: closed, 1: open)"

  Modelica.Blocks.Interfaces.RealOutput y_actual
    "Actual actuator position"

  // Classes used to implement the filtered opening
protected
  final parameter Modelica.Units.SI.Frequency fCut=5/(2*Modelica.Constants.pi*
      riseTime) "Cut-off frequency of filter";

  parameter Boolean casePreInd = false
    "In case of PressureIndependent the model I/O is modified"
  Modelica.Blocks.Interfaces.RealOutput y_internal(unit="1")
    "Output connector for internal use (= y_actual if not casePreInd)";
  Modelica.Blocks.Interfaces.RealOutput y_filtered if use_inputFilter
    "Filtered valve position in the range 0..1"

  AixLib.Fluid.BaseClasses.ActuatorFilter filter(
    final n=order,
    final f=fCut,
    final normalized=true,
    final initType=init,
    final y_start=y_start) if use_inputFilter
    "Second order filter to approximate actuator opening time, and to improve numerics"

equation
  connect(filter.y, y_filtered)
  if use_inputFilter then
    connect(y, filter.u)
    connect(filter.y, y_internal)
  else
    connect(y, y_internal)
  end if;
  if not casePreInd then
    connect(y_internal, y_actual);
  end if;
end ActuatorSignal;


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


Partial model for air dampers with exponential opening characteristics. Partial model for air dampers with exponential opening characteristics.
This is the base model for air dampers.
The model implements the functions that relate the opening signal and the
flow coefficient.
The model also defines parameters that are used by different air damper
models.

within AixLib.Fluid.Actuators.BaseClasses;
partial model PartialDamperExponential
  "Partial model for air dampers with exponential opening characteristics"
  extends AixLib.Fluid.BaseClasses.PartialResistance(
    final dp_nominal=dpDamper_nominal+dpFixed_nominal,
    final m_flow_turbulent=if use_deltaM then deltaM * m_flow_nominal else
      eta_default*ReC*sqrt(A)*facRouDuc);
  extends AixLib.Fluid.Actuators.BaseClasses.ActuatorSignal;
  parameter Modelica.Units.SI.PressureDifference dpDamper_nominal(displayUnit=
        "Pa") "Pressure drop of fully open damper at nominal mass flow rate"
  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal(displayUnit=
        "Pa") = 0
    "Pressure drop of duct and resistances other than the damper in series, at nominal mass flow rate"
  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"
  final parameter Modelica.Units.SI.Velocity v_nominal=(2/rho_default/k1*
      dpDamper_nominal)^0.5 "Nominal face velocity";
  final parameter Modelica.Units.SI.Area A=m_flow_nominal/rho_default/v_nominal
    "Face area";
  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 Real a(unit="1")=-1.51 "Coefficient a for damper characteristics"
  parameter Real b(unit="1")=0.105*90 "Coefficient b for damper characteristics"
  parameter Real yL(unit="1") = 15/90 "Lower value for damper curve"
  parameter Real yU(unit="1") = 55/90 "Upper value for damper curve"
  final parameter Real k0(min=0, unit="1") = 2 * rho_default * (A / kDamMin)^2
    "Loss coefficient for y=0 (pressure drop divided by dynamic pressure)"
  parameter Real k1(min=0, unit="1") = 0.45
    "Loss coefficient for y=1 (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)"
  parameter Boolean use_constant_density=true
    "Set to true to use constant density for flow friction"
  Medium.Density rho "Medium density";
  final parameter Real kFixed = if dpFixed_nominal > Modelica.Constants.eps then
    m_flow_nominal / sqrt(dpFixed_nominal) else Modelica.Constants.inf
    "Flow coefficient of fixed resistance that may be in series with damper, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real kDam
    "Flow coefficient of damper, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
  Real k
    "Flow coefficient of damper plus fixed resistance, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
protected
  parameter Medium.Density rho_default=Medium.density(sta_default)
    "Density, used to compute fluid volume";
  parameter Real facRouDuc= if roundDuct then sqrt(Modelica.Constants.pi)/2 else 1
    "Shape factor used to compute the hydraulic diameter for round ducts";
  parameter Real kL = AixLib.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=yL, a=a, b=b, cL=cL, cU=cU, yL=yL, yU=yU)^2
    "Loss coefficient at the lower limit of the exponential characteristics";
  parameter Real kU = AixLib.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=yU, a=a, b=b, cL=cL, cU=cU, yL=yL, yU=yU)^2
    "Loss coefficient at the upper limit of the exponential characteristics";
  parameter Real[3] cL={
    (Modelica.Math.log(k0) - b - a)/yL^2,
    (-b*yL - 2*Modelica.Math.log(k0) + 2*b + 2*a)/yL,
    Modelica.Math.log(k0)} "Polynomial coefficients for curve fit for y < yl";
  parameter Real[3] cU={
    (Modelica.Math.log(k1) - a)/(yU^2 - 2*yU + 1),
    (-b*yU^2 - 2*Modelica.Math.log(k1)*yU - (-2*b - 2*a)*yU - b)/(yU^2 - 2*yU + 1),
    (Modelica.Math.log(k1)*yU^2 + b*yU^2 + (-2*b - 2*a)*yU + b + a)/(yU^2 - 2*yU + 1)}
    "Polynomial coefficients for curve fit for y > yu";
  parameter Real kDamMax =  (2 * rho_default / k1)^0.5 * A
    "Flow coefficient of damper fully open, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2)";
  parameter Real kTotMax = if dpFixed_nominal > Modelica.Constants.eps then
    sqrt(1 / (1 / kFixed^2 + 1 / kDamMax^2)) else kDamMax
    "Flow coefficient of damper fully open plus fixed resistance, with unit=(kg.m)^(1/2)";
  parameter Real kDamMin = l * kDamMax
    "Flow coefficient of damper fully closed, with unit=(kg.m)^(1/2)";
  parameter Real kTotMin = if dpFixed_nominal > Modelica.Constants.eps then
    sqrt(1 / (1 / kFixed^2 + 1 / kDamMin^2)) else kDamMin
    "Flow coefficient of damper fully closed + fixed resistance, with unit=(kg.m)^(1/2)";
initial equation
  assert(dpDamper_nominal > Modelica.Constants.eps,
    "In " + getInstanceName() + ": dpDamper_nominal must be strictly greater than zero.");
  assert(dpFixed_nominal >= 0,
    "In " + getInstanceName() + ": dpFixed_nominal must be greater than zero.");
  assert(yL < yU,
    "In " + getInstanceName() + ": yL must be strictly lower than yU.");
  assert(m_flow_turbulent > 0,
    "In " + getInstanceName() + ": m_flow_turbulent must be strictly greater than zero.");
  assert(k1 >= 0.2,
    "In " + getInstanceName() + ": k1 must be greater than 0.2. k1=" + String(k1));
  assert(k1 < kU,
    "In " + getInstanceName() + ": k1 must be strictly lower than exp(a + b * (1 - yU)). k1=" +
    String(k1) + ", exp(...) = " + String(kU));
  assert(k0 <= 1e10,
    "In " + getInstanceName() + ": k0 must be lower than 1e10. k0=" + String(k0));
  assert(k0 > kL,
    "In " + getInstanceName() + ": k0 must be strictly higher than exp(a + b * (1 - yL)). k0=" +
    String(k0) + ", exp(...) = " + String(kL));
equation
  rho = if use_constant_density then
      rho_default
    else
      Medium.density(Medium.setState_phX(
        port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow)));
  // flow coefficient, k = m_flow/sqrt(dp)
  kDam=sqrt(2*rho)*A/AixLib.Fluid.Actuators.BaseClasses.exponentialDamper(
    y=y_actual,
    a=a,
    b=b,
    cL=cL,
    cU=cU,
    yL=yL,
    yU=yU);
  k = if dpFixed_nominal > Modelica.Constants.eps then sqrt(1/(1/kFixed^2 + 1/kDam^2)) else kDam;
end PartialDamperExponential;


Partial three way valve

within AixLib.Fluid.Actuators.BaseClasses;
partial model PartialThreeWayValve "Partial three way valve"
  extends AixLib.Fluid.BaseClasses.PartialThreeWayResistance(
    m_flow_small = m_flow_nominal*1e-4,
    final mDyn_flow_nominal = m_flow_nominal,
      redeclare replaceable
      AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve res1
        constrainedby AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve(
          deltaM=deltaM,
          from_dp=from_dp,
          final linearized=linearized[1],
          final homotopyInitialization=homotopyInitialization,
          final CvData=AixLib.Fluid.Types.CvTypes.OpPoint,
          final m_flow_nominal=m_flow_nominal,
          final dpValve_nominal=dpValve_nominal,
          final dpFixed_nominal=dpFixed_nominal[1],
          final use_inputFilter=false,
          final riseTime=riseTime),
      redeclare FixedResistances.LosslessPipe res2(
        m_flow_nominal=m_flow_nominal),
      redeclare replaceable
      AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve res3
        constrainedby AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve(
          deltaM=deltaM,
          from_dp=from_dp,
          final linearized=linearized[2],
          final homotopyInitialization=homotopyInitialization,
          final CvData=AixLib.Fluid.Types.CvTypes.OpPoint,
          final m_flow_nominal=m_flow_nominal,
          final dpValve_nominal=dpValve_nominal/fraK^2,
          final dpFixed_nominal=dpFixed_nominal[2],
          final use_inputFilter=false,
          final riseTime=riseTime));
    extends AixLib.Fluid.Actuators.BaseClasses.ActuatorSignal;
    extends AixLib.Fluid.Actuators.BaseClasses.ValveParameters(
      rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

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

  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal[2](
    each displayUnit="Pa",
    each min=0) = {0,0}
    "Nominal pressure drop of pipes and other equipment in flow legs at port_1 and port_3"

  parameter Real fraK(min=0, max=1) = 0.7
    "Fraction Kv(port_3→port_2)/Kv(port_1→port_2)";
  parameter Real[2] l(each min=0, each max=1) = {0.0001, 0.0001}
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";
  parameter Real deltaM = 0.02
    "Fraction of nominal flow rate where linearization starts, if y=1"

  parameter Boolean[2] linearized = {false, false}
    "= true, use linear relation between m_flow and dp for any flow rate"

protected
  Modelica.Blocks.Math.Feedback inv "Inversion of control signal"
  Modelica.Blocks.Sources.Constant uni(final k=1)
    "Outputs one for bypass valve"

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

equation
  connect(uni.y, inv.u1)
end PartialThreeWayValve;


Partial model for a two way valve. Partial model for a two way valve. This is the base model for valves
with different opening characteristics, such as linear, equal percentage,
quick opening or pressure-independent.

within AixLib.Fluid.Actuators.BaseClasses;
partial model PartialTwoWayValve "Partial model for a two way valve"

  extends AixLib.Fluid.BaseClasses.PartialResistance(
       final dp_nominal=dpValve_nominal + dpFixed_nominal,
       dp(nominal=6000),
       final m_flow_turbulent = deltaM * abs(m_flow_nominal));

  extends AixLib.Fluid.Actuators.BaseClasses.ValveParameters(
      rhoStd=Medium.density_pTX(101325, 273.15+4, Medium.X_default));

  extends AixLib.Fluid.Actuators.BaseClasses.ActuatorSignal;
  parameter Modelica.Units.SI.PressureDifference dpFixed_nominal(
    displayUnit="Pa",
    min=0) = 0 "Pressure drop of pipe and other resistances that are in series"

  parameter Real l(min=1e-10, max=1) = 0.0001
    "Valve leakage, l=Kv(y=0)/Kv(y=1)";
  input Real phi
    "Ratio actual to nominal mass flow rate of valve, phi=Kv(y)/Kv(y=1)";
  parameter Real kFixed(unit="", min=0) = if dpFixed_nominal > Modelica.Constants.eps
    then m_flow_nominal / sqrt(dpFixed_nominal) else 0
    "Flow coefficient of fixed resistance that may be in series with valve, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real kVal(unit="", min=Modelica.Constants.small)
    "Flow coefficient of valve, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
  Real k(unit="", min=Modelica.Constants.small)
    "Flow coefficient of valve and pipe in series, k=m_flow/sqrt(dp), with unit=(kg.m)^(1/2).";
initial equation
  assert(dpFixed_nominal > -Modelica.Constants.eps, "In " + getInstanceName() +
  ": Model requires dpFixed_nominal >= 0 but received dpFixed_nominal = "
        + String(dpFixed_nominal) + " Pa.");
end PartialTwoWayValve;


Partial model for a two way valve using a Kv characteristic. Partial model for valves with different opening characteristics,
such as linear, equal percentage or quick opening. This partial extends from

AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve
and also contains the governing equations for these three two way valve models.

within AixLib.Fluid.Actuators.BaseClasses;
partial model PartialTwoWayValveKv
  "Partial model for a two way valve using a Kv characteristic"
  extends AixLib.Fluid.Actuators.BaseClasses.PartialTwoWayValve;

equation
 kVal = phi*Kv_SI;
 if (dpFixed_nominal > Modelica.Constants.eps) then
   k = sqrt(1/(1/kFixed^2 + 1/kVal^2));
 else
   k = kVal;
 end if;

 if linearized then
   // This implementation yields m_flow_nominal = phi*kv_SI * sqrt(dp_nominal)
   // if m_flow = m_flow_nominal and dp = dp_nominal
   m_flow*m_flow_nominal_pos = k^2 * dp;
 else
   if homotopyInitialization then
     if from_dp then
         m_flow=homotopy(actual=AixLib.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(dp=dp, k=k,
                                m_flow_turbulent=m_flow_turbulent),
                                simplified=m_flow_nominal_pos*dp/dp_nominal_pos);
      else
         dp=homotopy(actual=AixLib.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;
   else // do not use homotopy
     if from_dp then
       m_flow=AixLib.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(dp=dp, k=k,
                                m_flow_turbulent=m_flow_turbulent);
      else
        dp=AixLib.Fluid.BaseClasses.FlowModels.basicFlowFunction_m_flow(m_flow=m_flow, k=k,
                                m_flow_turbulent=m_flow_turbulent);
      end if;
    end if; // homotopyInitialization
 end if; // linearized
end PartialTwoWayValveKv;


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 AixLib.Fluid.Actuators.BaseClasses;
partial model ValveParameters "Model with parameters for valves"

  parameter AixLib.Fluid.Types.CvTypes CvData=AixLib.Fluid.Types.CvTypes.OpPoint
    "Selection of flow coefficient"
  parameter Real Kv(
    fixed= if CvData==AixLib.Fluid.Types.CvTypes.Kv then true else false)
    "Kv (metric) flow coefficient [m3/h/(bar)^(1/2)]"
  parameter Real Cv(
    fixed= if CvData==AixLib.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 == AixLib.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 == AixLib.Fluid.Types.CvTypes.OpPoint then true else false)
    "Nominal pressure drop of fully open valve, used if CvData=AixLib.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 == AixLib.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 == AixLib.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 == AixLib.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 == AixLib.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 AixLib.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=AixLib.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 AixLib.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 AixLib.Fluid.Actuators.Dampers;
model Exponential
  "Air damper with exponential opening characteristics"
  extends AixLib.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=AixLib.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=AixLib.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=AixLib.Fluid.BaseClasses.FlowModels.basicFlowFunction_dp(
                  dp=dp, k=k, m_flow_turbulent=m_flow_turbulent);
      else
        dp=AixLib.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 AixLib.Fluid.Actuators.Dampers;
model MixingBox "Outside air mixing box with interlocked air dampers"
  extends AixLib.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)"
  AixLib.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_inputFilter=false)
    "Outdoor air damper"
  AixLib.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_inputFilter=false)
    "Exhaust air damper"
  AixLib.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_inputFilter=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;


Package with air damper models

within AixLib.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 AixLib.Fluid.Actuators.Dampers;
model PressureIndependent
  "Model for an air damper whose mass flow is proportional to the input signal"
  extends AixLib.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 = AixLib.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=
        AixLib.Utilities.Math.Functions.smoothMax(
        dp,
        -dp,
        dp_small) "Regularized absolute value of pressure drop";
  Real mSqu_flow = AixLib.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 := AixLib.Utilities.Math.Functions.smoothLimit(
    AixLib.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 = AixLib.Utilities.Math.Functions.splineDerivatives(x=kSupSpl_raw, y=ySupSpl_raw);
equation
  // From TwoWayPressureIndependent valve model
  m_flow_set = m_flow_nominal*phi;
  dp_min = AixLib.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 = AixLib.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 AixLib.Utilities.Math.Functions.quinticHermite(
                 x=dp_x,
                 x1=dp_x1,
                 x2=dp_x2,
                 y1=m_flow_y1,
                 y2=m_flow_y2,
                 y1d= AixLib.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=AixLib.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 = AixLib.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 AixLib.Utilities.Math.Functions.quinticHermite(
                 x=m_flow_x,
                 x1=m_flow_x1,
                 x2=m_flow_x2,
                 y1=dp_y1,
                 y2=dp_y2,
                 y1d=AixLib.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=AixLib.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 = AixLib.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

AixLib.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

AixLib.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})}),
   __Dymola_LockedEditing="Model from IBPSA");
end PressureIndependent;


VAV box with a fixed resistance plus a damper model withe exponential characteristics. Model of two resistances in series. One resistance has a fixed flow
  coefficient, the other resistance is an air damper whose flow
  coefficient is an exponential function of the opening angle.

within AixLib.Fluid.Actuators.Dampers;
model VAVBoxExponential
  "VAV box with a fixed resistance plus a damper model withe exponential characteristics"
  extends AixLib.Fluid.Actuators.BaseClasses.PartialDamperExponential(
  dp(nominal=dp_nominal),
  final kFixed=sqrt(kResSqu));
  parameter Boolean dp_nominalIncludesDamper = true
    "set to true if dp_nominal includes the pressure loss of the open damper"
protected
  parameter Modelica.Units.SI.PressureDifference dpDamOpe_nominal(displayUnit=
        "Pa") = k1*m_flow_nominal^2/2/Medium.density(sta_default)/A^2
    "Pressure drop of fully open damper at nominal flow rate";
  parameter Real kResSqu(unit="kg.m", fixed=false)
    "Resistance coefficient for fixed resistance element";
initial equation
  kResSqu = if dp_nominalIncludesDamper then
       m_flow_nominal^2 / (dp_nominal-dpDamOpe_nominal) else
       m_flow_nominal^2 / dp_nominal;
  assert(kResSqu > 0,
         "Wrong parameters in damper model: dp_nominal < dpDamOpe_nominal"
          + "\n  dp_nominal = "       + String(dp_nominal)
          + "\n  dpDamOpe_nominal = " + String(dpDamOpe_nominal));
end VAVBoxExponential;


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

  AixLib.Fluid.Actuators.Dampers.Exponential res(
    redeclare package Medium = Medium,
    use_inputFilter=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)

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

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

  AixLib.Fluid.Actuators.Dampers.PressureIndependent preIndDpFixed_nominal(
    use_inputFilter=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"

  AixLib.Fluid.Actuators.Dampers.PressureIndependent preIndFrom_dp(
    use_inputFilter=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"

  AixLib.Fluid.Actuators.Dampers.PressureIndependent preInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    dpDamper_nominal=10,
    use_inputFilter=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 AixLib.Fluid.Actuators.Dampers.Examples;
model MixingBox
  "Mixing box with constant pressure difference and varying control signal"
  extends Modelica.Icons.Example;

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

  AixLib.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"
    AixLib.Fluid.Sources.Boundary_pT bouIn(             redeclare package
      Medium = Medium, T=273.15 + 10,
    use_p_in=true,
    nPorts=2)
    AixLib.Fluid.Sources.Boundary_pT bouSup(             redeclare package
      Medium = Medium, T=273.15 + 26,
    use_p_in=true,
    nPorts=1)
    AixLib.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 AixLib.Fluid.Actuators.Dampers;
package Examples "Collection of models that illustrate model use and test models"
  extends Modelica.Icons.ExamplesPackage;
end Examples;


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

 package Medium = AixLib.Media.Air;

  AixLib.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)
  AixLib.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
               Medium, T=273.15 + 20,
    nPorts=2,
    use_p_in=true)
  AixLib.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)
  AixLib.Fluid.Actuators.Dampers.Exponential vav(
    redeclare package Medium = Medium,
    dpDamper_nominal=5,
    dpFixed_nominal=5,
    m_flow_nominal=2)
  AixLib.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 AixLib.Fluid.Actuators.Dampers;
package Validation "Collection of validation models"
  extends Modelica.Icons.ExamplesPackage;
end Validation;


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

  package Medium = AixLib.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";
  AixLib.Fluid.Actuators.Dampers.Exponential damExp(
    redeclare final package Medium = Medium,
    use_inputFilter=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"
  AixLib.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"
  AixLib.Fluid.Sources.Boundary_pT sin(
    redeclare final package Medium = Medium,
    nPorts=4) "Pressure boundary condition"
  AixLib.Fluid.Actuators.Dampers.PressureIndependent damPreInd(
    redeclare final package Medium = Medium,
    final m_flow_nominal=m_flow_nominal,
    final dpDamper_nominal=dp_nominal,
    use_inputFilter=false)
    "Pressure independent damper"
  Exponential damExpPI(
    redeclare final package Medium = Medium,
    use_inputFilter=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 AixLib.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 AixLib.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 AixLib.Fluid.Actuators.Motors.Examples;
model IdealMotor "Test model for IdealMotor"
  extends Modelica.Icons.Example;
  AixLib.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 AixLib.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 AixLib.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 AixLib.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 AixLib.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 AixLib.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 AixLib.Fluid.Actuators.Valves;
model TwoWayButterfly
  "Two way valve with the flow characteristic of a typical butterfly valve"
  extends AixLib.Fluid.Actuators.Valves.TwoWayPolynomial(
    final CvData = AixLib.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 AixLib.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=AixLib.Fluid.Actuators.BaseClasses.equalPercentage(
        y_actual,
        R,
        l,
        delta0), simplified=l + y_actual*(1 - l)) else
        AixLib.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 AixLib.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 AixLib.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 AixLib.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 AixLib.Fluid.Actuators.Valves;
model TwoWayPolynomial "Two way valve with polynomial characteristic"
  extends AixLib.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 AixLib.Fluid.Actuators.Valves;
model TwoWayPressureIndependent "Model of a pressure-independent two way valve"
  extends AixLib.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 = AixLib.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 = AixLib.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 AixLib.Utilities.Math.Functions.quinticHermite(
                 x=dp_x,
                 x1=dp_x1,
                 x2=dp_x2,
                 y1=m_flow_y1,
                 y2=m_flow_y2,
                 y1d= AixLib.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=AixLib.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 = AixLib.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 AixLib.Utilities.Math.Functions.quinticHermite(
                 x=m_flow_x,
                 x1=m_flow_x1,
                 x2=m_flow_x2,
                 y1=dp_y1,
                 y2=dp_y2,
                 y1d=AixLib.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=AixLib.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 AixLib.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 AixLib.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 AixLib.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 AixLib.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(AixLib.Utilities.Math.Functions.isMonotonic(
           x=flowCharacteristics.y,
           strict=true),
         "The values for y in flowCharacteristics must be strictly increasing.");
  assert(AixLib.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 AixLib.Fluid.Actuators.Valves;
package Data "Data records for table-based valves"
  extends Modelica.Icons.MaterialPropertiesPackage;
end Data;


  This model shows the usage of a MixingValve against its
  design-direction. The results show that it is possible to use the
  MixingValve against its design-direction. The valve will then split
  the incoming flow into two fractions according to the opening of the
  valve.

  26.11.2014, by Roozbeh Sangi: implemented

within AixLib.Fluid.Actuators.Valves.Examples;
model MixingValveBackwardDirection
   extends Modelica.Icons.Example;
   package Medium = Modelica.Media.Water.ConstantPropertyLiquidWater;

  inner AixLib.Utilities.Sources.BaseParameters baseParameters
  AixLib.Fluid.Sources.Boundary_pT boundary_ph(
    redeclare package Medium = Medium,
    p=150000,
    nPorts=1)
  AixLib.Fluid.Sources.Boundary_pT boundary_ph1(nPorts=1, redeclare package Medium =
               Medium)
  AixLib.Fluid.Sources.Boundary_pT boundary_ph2(nPorts=1, redeclare package Medium =
               Medium)
  AixLib.Obsolete.Year2021.Fluid.Actuators.Valves.MixingValve mixingValveFiltered(
    filteredOpening=true,
    riseTime=100,
    redeclare package Medium = Medium)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensorMixed(redeclare
      package                                                                    Medium =
                       Medium, m_flow_nominal=1)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensorMixed(redeclare package Medium =
               Medium)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensor1(redeclare package Medium =
               Medium, m_flow_nominal=1)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensor1(redeclare package Medium =
        Medium)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensor2(redeclare package Medium =
               Medium, m_flow_nominal=1)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensor2(redeclare package Medium =
        Medium)
  Modelica.Blocks.Sources.Step step(startTime=100, height=0.7)
equation
  connect(temperatureSensorMixed.port_b, massFlowSensorMixed.port_a)
  connect(massFlowSensorMixed.port_b, mixingValveFiltered.port_2)
  connect(temperatureSensor1.port_b, massFlowSensor1.port_a)
  connect(temperatureSensor2.port_b, massFlowSensor2.port_a)
  connect(mixingValveFiltered.port_3, temperatureSensor1.port_a)
  connect(mixingValveFiltered.port_1, temperatureSensor2.port_a)
  connect(mixingValveFiltered.opening, step.y)
  connect(massFlowSensor1.port_b, boundary_ph1.ports[1])
  connect(massFlowSensor2.port_b, boundary_ph2.ports[1])
  connect(temperatureSensorMixed.port_a, boundary_ph.ports[1])
end MixingValveBackwardDirection;


<html><p>
  This model shows the usage of a MixingValve in its design-direction.
  The results are consistent with ones expectation of the function of a
  mixing valve. A step on the opening of the valve is applied after
  some time, which changes the the ratio between the two inflowing
  streams.
</p>
</html>

within AixLib.Fluid.Actuators.Valves.Examples;
model MixingValveForwardDirection
    extends Modelica.Icons.Example;
    package Medium = Modelica.Media.Water.ConstantPropertyLiquidWater;

  inner AixLib.Utilities.Sources.BaseParameters baseParameters
  AixLib.Fluid.Sources.Boundary_pT boundary_ph(
              nPorts=1,
    redeclare package Medium = Medium,
    p=150000)
  AixLib.Fluid.Sources.Boundary_pT boundary_ph1(nPorts=1, redeclare package Medium =
               Medium)
  AixLib.Fluid.Sources.Boundary_pT boundary_ph2(         nPorts=1,
    redeclare package Medium = Medium,
    p=150000)
  AixLib.Obsolete.Year2021.Fluid.Actuators.Valves.MixingValve mixingValveFiltered(
    filteredOpening=true,
    riseTime=100,
    redeclare package Medium = Medium)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensor1(redeclare package Medium =
               Medium, m_flow_nominal=1)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensor2(redeclare package Medium =
               Medium,
    tau=1,
    m_flow_nominal=1)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensor1(redeclare package Medium =
        Medium)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensor2(redeclare package Medium =
        Medium)
  Modelica.Blocks.Sources.Step step(startTime=100, height=0.7)
  AixLib.Fluid.Sensors.TemperatureTwoPort
                                        temperatureSensorMixed(redeclare
      package                                                                    Medium =
                       Medium, m_flow_nominal=1)
  AixLib.Fluid.Sensors.MassFlowRate  massFlowSensorMixed(redeclare package Medium =
               Medium)
equation
  connect(temperatureSensor2.port_b, massFlowSensor2.port_a)
  connect(temperatureSensor1.port_b, massFlowSensor1.port_a)
  connect(massFlowSensor1.port_b, mixingValveFiltered.port_3)
  connect(massFlowSensor2.port_b, mixingValveFiltered.port_1)
  connect(step.y, mixingValveFiltered.opening)
  connect(mixingValveFiltered.port_2, temperatureSensorMixed.port_a)
  connect(temperatureSensorMixed.port_b, massFlowSensorMixed.port_a)
  connect(temperatureSensor1.port_a, boundary_ph.ports[1])
  connect(massFlowSensorMixed.port_b, boundary_ph1.ports[1])
  connect(temperatureSensor2.port_a, boundary_ph2.ports[1])
end MixingValveForwardDirection;


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


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

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

  AixLib.Fluid.Actuators.Valves.ThreeWayLinear valLin(
    redeclare package Medium = Medium,
    l={0.05,0.05},
    m_flow_nominal=2,
    use_inputFilter=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"
  AixLib.Fluid.Sources.Boundary_pT sou(
    redeclare package Medium = Medium,
    nPorts=3,
    use_p_in=true,
    T=313.15) "Boundary condition for flow source"
  AixLib.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_inputFilter=false,
    dpValve_nominal=6000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial)
  AixLib.Fluid.Sources.Boundary_pT ret(
    redeclare package Medium = Medium,
    nPorts=3,
    use_p_in=true,
    T=303.15) "Boundary condition for flow sink"

  AixLib.Fluid.Actuators.Valves.ThreeWayTable valTab(
    redeclare package Medium = Medium,
    m_flow_nominal=2,
    use_inputFilter=false,
    dpValve_nominal=6000,
    energyDynamics=Modelica.Fluid.Types.Dynamics.FixedInitial,
    flowCharacteristics1=AixLib.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 AixLib.Fluid.Actuators.Valves.Examples;
model TwoWayValvePressureIndependent
  "Two way valves with pressure independent opening characteristic"
  extends Modelica.Icons.Example;
  package Medium = AixLib.Media.Water;

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

  AixLib.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
        Medium,
    use_p_in=true,
    T=293.15,
    nPorts=3) "Boundary condition for flow source"
  AixLib.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=AixLib.Fluid.Types.CvTypes.OpPoint,
    use_inputFilter=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=AixLib.Fluid.Types.CvTypes.OpPoint,
    use_inputFilter=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=AixLib.Fluid.Types.CvTypes.OpPoint,
    use_inputFilter=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 AixLib.Fluid.Actuators.Valves.Examples;
model TwoWayValves "Two way valves with different opening characteristics"
  extends Modelica.Icons.Example;
  package Medium = AixLib.Media.Water;

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

  TwoWayPressureIndependent valInd(
    redeclare package Medium = Medium,
    m_flow_nominal=1,
    CvData=AixLib.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal=10000,
    use_inputFilter=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_inputFilter=false,
    redeclare package Medium = Medium,
    CvData=AixLib.Fluid.Types.CvTypes.OpPoint)
    "Valve with polynomial opening characteristic"
  TwoWayButterfly valBut(
    redeclare package Medium = Medium,
    Kvs=1492,
    use_inputFilter=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 AixLib.Fluid.Actuators.Valves.Examples;
model TwoWayValvesMotor
  "Two way valves with different opening characteristics and motor"
  extends Modelica.Icons.Example;
  package Medium = AixLib.Media.Water;

  AixLib.Fluid.Actuators.Valves.TwoWayLinear valLin(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_inputFilter=false,
    dpValve_nominal=6000) "Valve model, linear opening characteristics"
  AixLib.Fluid.Sources.Boundary_pT sou(             redeclare package Medium =
        Medium,
    nPorts=4,
    use_p_in=true,
    T=293.15) "Boundary condition for flow source"
  AixLib.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)
  AixLib.Fluid.Actuators.Valves.TwoWayQuickOpening valQui(
    redeclare package Medium = Medium,
    l=0.05,
    m_flow_nominal=2,
    use_inputFilter=false,
    dpValve_nominal=6000) "Valve model, quick opening characteristics"
  AixLib.Fluid.Actuators.Valves.TwoWayEqualPercentage valEqu(
    redeclare package Medium = Medium,
    l=0.05,
    R=10,
    delta0=0.1,
    m_flow_nominal=2,
    use_inputFilter=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=AixLib.Fluid.Types.CvTypes.OpPoint,
    dpValve_nominal=10000,
    use_inputFilter=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 AixLib.Fluid.Actuators.Valves.Examples;
model TwoWayValvesTable "Two way valve with linear opening characteristics"
  extends Modelica.Icons.Example;
  package Medium = AixLib.Media.Water "Medium";

    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  AixLib.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"
  AixLib.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_inputFilter=false,
    dpValve_nominal=6000,
    flowCharacteristics=AixLib.Fluid.Actuators.Valves.Data.Linear(),
    from_dp=true) "Valve model with opening characteristics based on a table"
  TwoWayLinear valLin(
    use_inputFilter=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 AixLib.Fluid.Actuators.Valves.Examples;
model TwoWayValveTable
  "Two way valve with nonlinear opening characteristics based on a table"
  extends Modelica.Icons.Example;
  package Medium = AixLib.Media.Water "Medium";

    Modelica.Blocks.Sources.Ramp y(
    height=1,
    duration=1,
    offset=0) "Control signal"
  AixLib.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"
  AixLib.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_inputFilter=false,
    from_dp=true,
    flowCharacteristics=datVal,
    CvData=AixLib.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;


within AixLib.Fluid.Actuators.Valves;
package ExpansionValves "Package that contains models of expansion valves"
  extends Modelica.Icons.Package;
end ExpansionValves;


within AixLib.Fluid.Actuators.Valves.ExpansionValves;
package BaseClasses "Package that contains base classes used for the expansion valve models"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;