within Modelica.Fluid;
package Fittings
  "Adaptors for connections of fluid components and the regulation of fluid flow"
  package Bends "Flow models for bends"
    extends Modelica.Icons.VariantsPackage;
    model CurvedBend "Curved bend flow model"
      extends Modelica.Fluid.Dissipation.Utilities.Icons.PressureLoss.Bend_i;
      extends Modelica.Fluid.Interfaces.PartialPressureLoss;

      parameter Modelica.Fluid.Fittings.BaseClasses.Bends.CurvedBend.Geometry geometry
        "Geometry of curved bend"

    protected
      parameter Medium.AbsolutePressure dp_small(min=0)=
                 Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_DP(
                    geometry,
                    Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_IN_var(
                      rho=Medium.density(state_dp_small),
                      eta=Medium.dynamicViscosity(state_dp_small)),
                    m_flow_small)
        "Default small pressure drop for regularization of laminar and zero flow (calculated from m_flow_small)";

    equation
      if allowFlowReversal then
         m_flow = Modelica.Fluid.Fittings.BaseClasses.Bends.CurvedBend.massFlowRate(
                    dp, geometry, d_a, d_b, eta_a, eta_b, dp_small, m_flow_small);
      else
         m_flow = Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_MFLOW(
                    geometry,
                    Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_IN_var(rho=d_a, eta=eta_a), dp);
      end if;
    end CurvedBend;

    model EdgedBend "Edged bend flow model"
      extends Modelica.Fluid.Dissipation.Utilities.Icons.PressureLoss.Bend_i;
      extends Modelica.Fluid.Interfaces.PartialPressureLoss;

      parameter Modelica.Fluid.Fittings.BaseClasses.Bends.EdgedBend.Geometry geometry
        "Geometry of curved bend"

    protected
      parameter Medium.AbsolutePressure dp_small(min=0)=
                 Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_DP(
                   Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_con(
                       d_hyd=geometry.d_hyd,
                       delta=geometry.delta,
                       K=geometry.K),
                    Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_var(
                      rho=Medium.density(state_dp_small),
                      eta=Medium.dynamicViscosity(state_dp_small)),
                    m_flow_small)
        "Default small pressure drop for regularization of laminar and zero flow (calculated from m_flow_small)";

    equation
      if allowFlowReversal then
         m_flow = Modelica.Fluid.Fittings.BaseClasses.Bends.EdgedBend.massFlowRate(
                    dp, geometry, d_a, d_b, eta_a, eta_b, dp_small, m_flow_small);
      else
         m_flow = Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_MFLOW(
                   Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_con(
                       d_hyd=geometry.d_hyd,
                       delta=geometry.delta,
                       K=geometry.K),
                    Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_var(rho=d_a, eta=eta_a), dp);
      end if;
    end EdgedBend;
  end Bends;

  package Orifices "Flow models for orifices"
    extends Modelica.Icons.VariantsPackage;

    model ThickEdgedOrifice "Thicked edged orifice flow model"
      extends Modelica.Fluid.Dissipation.Utilities.Icons.PressureLoss.Orifice_i;
      extends Modelica.Fluid.Interfaces.PartialPressureLoss;

      parameter
        Modelica.Fluid.Fittings.BaseClasses.Orifices.ThickEdgedOrifice.Geometry geometry
        "Geometry of thick edged orifice"

    protected
      parameter Medium.AbsolutePressure dp_small(min=0)=
                 Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_DP(
                 Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_con(
                       A_0=geometry.venaCrossArea,
                       A_1=geometry.crossArea,
                       C_0=geometry.venaPerimeter,
                       C_1=geometry.perimeter,
                       L=geometry.venaLength,
                       dp_smooth=1e-10),
                    Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_var(
                      rho=Medium.density(state_dp_small),
                      eta=Medium.dynamicViscosity(state_dp_small)),
                    m_flow_small)
        "Default small pressure drop for regularization of laminar and zero flow (calculated from m_flow_small)";
    equation
      if allowFlowReversal then
         m_flow = Modelica.Fluid.Fittings.BaseClasses.Orifices.ThickEdgedOrifice.massFlowRate(
                    dp, geometry, d_a, d_b, eta_a, eta_b, dp_small, m_flow_small);
      else
         m_flow = Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_MFLOW(
                     Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_con(
                       A_0=geometry.venaCrossArea,
                       A_1=geometry.crossArea,
                       C_0=geometry.venaPerimeter,
                       C_1=geometry.perimeter,
                       L=geometry.venaLength,
                       dp_smooth=dp_small),
                    Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_var(rho=d_a, eta=eta_a), dp);
      end if;
    end ThickEdgedOrifice;

  end Orifices;

  package GenericResistances "Flow models for generic resistances"
  extends Modelica.Icons.VariantsPackage;

    model VolumeFlowRate
      "Flow model for generic resistance parameterized with the volume flow rate"

      extends Modelica.Fluid.Dissipation.Utilities.Icons.PressureLoss.General_i;
      extends Modelica.Fluid.Interfaces.PartialTwoPortTransport;

      parameter Real a(unit="(Pa.s2)/m6") "Coefficient for quadratic term"
      parameter Real b(unit="(Pa.s)/m3") "Coefficient for linear term"

    protected
      parameter Medium.ThermodynamicState state_dp_small=Medium.setState_pTX(
                           Medium.reference_p,
                           Medium.reference_T,
                           Medium.reference_X)
        "Medium state to compute dp_small";
      parameter Medium.AbsolutePressure dp_small(min=0)=
                 Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_DP(
                   Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_con(
                       a=a,
                       b=b,
                       dp_min=1e-10),
                    Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_var(
                      rho=Medium.density(state_dp_small)),
                    m_flow_small)
        "Default small pressure drop for regularization of laminar and zero flow (calculated from m_flow_small)";
      Medium.Density d_a
        "Density at port_a when fluid is flowing from port_a to port_b";
      Medium.Density d_b
        "If allowFlowReversal=true then density at port_b when fluid is flowing from port_b to port_a else d_a";

    equation
      // Isenthalpic state transformation (no storage and no loss of energy)
      port_a.h_outflow = inStream(port_b.h_outflow);
      port_b.h_outflow = inStream(port_a.h_outflow);

      // Medium properties
      d_a = Medium.density(state_a);
      if allowFlowReversal then
        d_b = Medium.density(state_b);
      else
        d_b = d_a;
      end if;

      if allowFlowReversal then
         m_flow = Modelica.Fluid.Fittings.BaseClasses.GenericResistances.VolumeFlowRate.massFlowRate(
                    dp, a, b, d_a, d_b, dp_small, m_flow_small);
      else
         m_flow = Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_MFLOW(
                   Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_con(
                       a=a,
                       b=b,
                       dp_min=dp_small),
                    Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_var(rho=d_a), dp);
      end if;
    end VolumeFlowRate;
  end GenericResistances;

     extends Modelica.Icons.VariantsPackage;

model SimpleGenericOrifice
    "Simple generic orifice defined by pressure loss coefficient and diameter (only for flow from port_a to port_b)"

  extends Modelica.Fluid.Interfaces.PartialTwoPortTransport(
    dp_start = dp_nominal,
    m_flow_small = if system.use_eps_Re then system.eps_m_flow*m_flow_nominal else system.m_flow_small,
    m_flow(stateSelect = if momentumDynamics == Types.Dynamics.SteadyState then StateSelect.default
                         else StateSelect.prefer));

  extends Modelica.Fluid.Interfaces.PartialLumpedFlow(
    final pathLength = 0,
    final momentumDynamics = Types.Dynamics.SteadyState);

  parameter SI.Diameter diameter "Diameter of orifice";
  parameter Real zeta "Loss factor for flow of port_a -> port_b"
  parameter Boolean use_zeta = true
      "= false to obtain zeta from dp_nominal and m_flow_nominal";

  // Operational conditions
  parameter SI.MassFlowRate m_flow_nominal = if system.use_eps_Re then system.m_flow_nominal else 1e2*system.m_flow_small
      "Mass flow rate for dp_nominal"
  parameter SI.Pressure dp_nominal = if not system.use_eps_Re then 1e3 else
   BaseClasses.lossConstant_D_zeta(diameter, zeta)/Medium.density_pTX(Medium.p_default, Medium.T_default, Medium.X_default)*m_flow_nominal^2
      "Nominal pressure drop"

  parameter Boolean use_Re = system.use_eps_Re
      "= true, if turbulent region is defined by Re, otherwise by m_flow_small"

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

  protected
  parameter Medium.AbsolutePressure dp_small(min=0) = if system.use_eps_Re then dp_nominal/m_flow_nominal*m_flow_small else system.dp_small
      "Regularization of zero flow if |dp| < dp_small"

  // Variables
  public
  Real zeta_nominal;
  Medium.Density d = 0.5*(Medium.density(state_a) + Medium.density(state_b));
  SI.Pressure dp_fg(start=dp_start)
      "Pressure loss due to friction and gravity";
  SI.Area A_mean = Modelica.Constants.pi/4*diameter^2
      "Mean cross flow area";

  constant SI.ReynoldsNumber Re_turbulent = 10000 "cf. sharpEdgedOrifice";
  SI.MassFlowRate m_flow_turbulent=if not use_Re then m_flow_small else
    max(m_flow_small,
        (Modelica.Constants.pi/8)*diameter*(Medium.dynamicViscosity(state_a) + Medium.dynamicViscosity(state_b))*Re_turbulent);
  SI.AbsolutePressure dp_turbulent=if not use_Re then dp_small else
    max(dp_small, BaseClasses.lossConstant_D_zeta(diameter, zeta_nominal)/d*m_flow_turbulent^2);
equation
  if use_zeta then
    zeta_nominal = zeta;
  else
    zeta_nominal = 2*A_mean^2*d*dp_nominal/m_flow_nominal^2;
  end if;

  Ib_flow = 0;
  F_p = A_mean*(Medium.pressure(state_b) - Medium.pressure(state_a));
  F_fg = A_mean*dp_fg;

  /*
   dp = 0.5*zeta*d*v*|v|
      = 0.5*zeta*d*1/(d*A)^2 * m_flow * |m_flow|
      = 0.5*zeta/A^2 *1/d * m_flow * |m_flow|
      = k/d * m_flow * |m_flow|
   k  = 0.5*zeta/A^2
      = 0.5*zeta/(pi*(D/2)^2)^2
      = 8*zeta/(pi*D^2)^2
  */
  if from_dp then
    m_flow = homotopy(Utilities.regRoot2(
                         dp_fg,
                         dp_turbulent,
                         Medium.density(state_a)/BaseClasses.lossConstant_D_zeta(diameter, zeta_nominal),
                         Medium.density(state_b)/BaseClasses.lossConstant_D_zeta(diameter, zeta_nominal)),
                       m_flow_nominal*dp_fg/dp_nominal);
  else
    dp_fg = homotopy(Utilities.regSquare2(
                         m_flow,
                         m_flow_turbulent,
                         BaseClasses.lossConstant_D_zeta(diameter, zeta_nominal)/Medium.density(state_a),
                         BaseClasses.lossConstant_D_zeta(diameter, zeta_nominal)/Medium.density(state_b)),
                     dp_nominal*m_flow/m_flow_nominal);
  end if;

  // Isenthalpic state transformation (no storage and no loss of energy)
  port_a.h_outflow = inStream(port_b.h_outflow);
  port_b.h_outflow = inStream(port_a.h_outflow);
end SimpleGenericOrifice;

model SharpEdgedOrifice
    "Pressure drop due to sharp edged orifice (for both flow directions)"
    import Modelica.Units.NonSI;
  extends BaseClasses.QuadraticTurbulent.BaseModel(final data=
          BaseClasses.QuadraticTurbulent.LossFactorData.sharpEdgedOrifice(
          diameter,
          leastDiameter,
          length,
          alpha));
  parameter SI.Length length "Length of orifice";
  parameter SI.Diameter diameter
      "Inner diameter of pipe (= same at port_a and port_b)";
  parameter SI.Diameter leastDiameter "Smallest diameter of orifice";
  parameter NonSI.Angle_deg alpha "Angle of orifice";

end SharpEdgedOrifice;

model AbruptAdaptor
    "Pressure drop in pipe due to suddenly expanding or reducing area (for both flow directions)"
  extends BaseClasses.QuadraticTurbulent.BaseModelNonconstantCrossSectionArea(final data=
          BaseClasses.QuadraticTurbulent.LossFactorData.suddenExpansion(
          diameter_a, diameter_b));
  parameter SI.Diameter diameter_a "Inner diameter of pipe at port_a";
  parameter SI.Diameter diameter_b "Inner diameter of pipe at port_b";
end AbruptAdaptor;

  model MultiPort
    "Multiply a port; useful if multiple connections shall be made to a port exposing a state"

    function positiveMax
      extends Modelica.Icons.Function;
      input Real x;
      output Real y;
    algorithm
      y :=max(x, 1e-10);
    end positiveMax;

    import Modelica.Constants;

    replaceable package Medium=Modelica.Media.Interfaces.PartialMedium

    // Ports
    parameter Integer nPorts_b=0
      "Number of outlet ports (mass is distributed evenly between the outlet ports"

    Modelica.Fluid.Interfaces.FluidPort_a port_a(
      redeclare package Medium=Medium)
    Modelica.Fluid.Interfaces.FluidPorts_b ports_b[nPorts_b](
      redeclare each package Medium=Medium)

    Medium.MassFraction ports_b_Xi_inStream[nPorts_b,Medium.nXi]
      "inStream mass fractions at ports_b";
    Medium.ExtraProperty ports_b_C_inStream[nPorts_b,Medium.nC]
      "inStream extra properties at ports_b";

  equation
    // Only one connection allowed to a port to avoid unwanted ideal mixing
    for i in 1:nPorts_b loop
      assert(cardinality(ports_b[i]) <= 1,"
each ports_b[i] of boundary shall at most be connected to one component.
If two or more connections are present, ideal mixing takes
place with these connections, which is usually not the intention
of the modeller. Increase nPorts_b to add an additional port.
");
    end for;

    // mass and momentum balance
    0 = port_a.m_flow + sum(ports_b.m_flow);
    ports_b.p = fill(port_a.p, nPorts_b);

    // mixing at port_a
    port_a.h_outflow = sum({positiveMax(ports_b[j].m_flow)*inStream(ports_b[j].h_outflow) for j in 1:nPorts_b})
                         / sum({positiveMax(ports_b[j].m_flow) for j in 1:nPorts_b});
    for j in 1:nPorts_b loop
       // expose stream values from port_a to ports_b
       ports_b[j].h_outflow  = inStream(port_a.h_outflow);
       ports_b[j].Xi_outflow = inStream(port_a.Xi_outflow);
       ports_b[j].C_outflow  = inStream(port_a.C_outflow);

       ports_b_Xi_inStream[j,:] = inStream(ports_b[j].Xi_outflow);
       ports_b_C_inStream[j,:] = inStream(ports_b[j].C_outflow);
    end for;
    for i in 1:Medium.nXi loop
      port_a.Xi_outflow[i] = (positiveMax(ports_b.m_flow)*ports_b_Xi_inStream[:,i])
                           / sum(positiveMax(ports_b.m_flow));
    end for;
    for i in 1:Medium.nC loop
      port_a.C_outflow[i] = (positiveMax(ports_b.m_flow)*ports_b_C_inStream[:,i])
                           / sum(positiveMax(ports_b.m_flow));
    end for;
  end MultiPort;

  model TeeJunctionIdeal
    "Splitting/joining component with static balances for an infinitesimal control volume"
    extends Modelica.Fluid.Fittings.BaseClasses.PartialTeeJunction;

  equation
    connect(port_1, port_2)
    connect(port_1, port_3)
  end TeeJunctionIdeal;

  model TeeJunctionVolume
    "Splitting/joining component with static balances for a dynamic control volume"
    extends Modelica.Fluid.Fittings.BaseClasses.PartialTeeJunction;
    extends Modelica.Fluid.Interfaces.PartialLumpedVolume(
      final fluidVolume = V);

    parameter SI.Volume V "Mixing volume inside junction";

  equation
    // Only one connection allowed to a port to avoid unwanted ideal mixing
    assert(cardinality(port_1) <= 1,"
port_1 of volume can at most be connected to one component.
If two or more connections are present, ideal mixing takes
place with these connections which is usually not the intention
of the modeller.
");
    assert(cardinality(port_2) <= 1,"
port_2 of volume can at most be connected to one component.
If two or more connections are present, ideal mixing takes
place with these connections which is usually not the intention
of the modeller.
");
    assert(cardinality(port_3) <= 1,"
port_3 of volume can at most be connected to one component.
If two or more connections are present, ideal mixing takes
place with these connections which is usually not the intention
of the modeller.
");

    // Boundary conditions
    port_1.h_outflow = medium.h;
    port_2.h_outflow = medium.h;
    port_3.h_outflow = medium.h;

    port_1.Xi_outflow = medium.Xi;
    port_2.Xi_outflow = medium.Xi;
    port_3.Xi_outflow = medium.Xi;

    port_1.C_outflow = C;
    port_2.C_outflow = C;
    port_3.C_outflow = C;

    // Mass balances
    mb_flow = port_1.m_flow + port_2.m_flow + port_3.m_flow "Mass balance";
    mbXi_flow = port_1.m_flow*actualStream(port_1.Xi_outflow)
                + port_2.m_flow*actualStream(port_2.Xi_outflow)
                + port_3.m_flow*actualStream(port_3.Xi_outflow)
      "Component mass balances";

    mbC_flow  = port_1.m_flow*actualStream(port_1.C_outflow)
              + port_2.m_flow*actualStream(port_2.C_outflow)
              + port_3.m_flow*actualStream(port_3.C_outflow)
      "Trace substance mass balances";

    // Momentum balance (suitable for compressible media)
    port_1.p = medium.p;
    port_2.p = medium.p;
    port_3.p = medium.p;

    // Energy balance
    Hb_flow = port_1.m_flow*actualStream(port_1.h_outflow)
              + port_2.m_flow*actualStream(port_2.h_outflow)
              + port_3.m_flow*actualStream(port_3.h_outflow);
    Qb_flow = 0;
    Wb_flow = 0;
  end TeeJunctionVolume;

  package BaseClasses
    "Base classes used in the Fittings package (only of interest to build new component models)"
    extends Modelica.Icons.BasesPackage;

    function lossConstant_D_zeta "Return the loss constant 8*zeta/(pi^2*D^4)"
          extends Modelica.Icons.Function;

      input SI.Diameter D "Diameter at port_a or port_b";
      input Real zeta
        "Constant pressure loss factor with respect to D (i.e., either port_a or port_b)";
      output Real k "Loss constant (= 8*zeta/(pi^2*D^4))";

    algorithm
      k :=8*zeta/(Modelica.Constants.pi*Modelica.Constants.pi*D*D*D*D);
    end lossConstant_D_zeta;

    package QuadraticTurbulent
      "Pressure loss components that are mainly defined by a quadratic turbulent regime with constant loss factor data"
      extends Modelica.Icons.Package;
     record LossFactorData
        "Data structure defining constant loss factor data for dp = zeta*rho*v*|v|/2 and functions providing the data for some loss types"

            extends Modelica.Icons.Record;

      SI.Diameter diameter_a "Diameter at port_a"
      SI.Diameter diameter_b "Diameter at port_b"
      Real zeta1 "Loss factor for flow port_a -> port_b"
      Real zeta2 "Loss factor for flow port_b -> port_a"
      SI.ReynoldsNumber Re_turbulent
          "Loss factors suited for Re >= Re_turbulent"
      SI.Diameter D_Re "Diameter used to compute Re"
      Boolean zeta1_at_a = true
          "dp = zeta1*(if zeta1_at_a then rho_a*v_a^2/2 else rho_b*v_b^2/2)"
      Boolean zeta2_at_a = false
          "dp = -zeta2*(if zeta2_at_a then rho_a*v_a^2/2 else rho_b*v_b^2/2)"
      Boolean zetaLaminarKnown = false
          "= true, if zeta = c0/Re in laminar region"
      Real c0 = 1
          "zeta = c0/Re; dp = zeta*rho_Re*v_Re^2/2, Re=v_Re*D_Re*rho_Re/mu_Re)"

       encapsulated function wallFriction
          "Return pressure loss data due to friction in a straight pipe with walls of nonuniform roughness (not useful for smooth pipes, since zeta is no function of Re)"
          import Modelica.Units.SI;
          import Modelica.Fluid.Fittings.BaseClasses.QuadraticTurbulent.LossFactorData;
          import Modelica.Fluid.Types.Roughness;
          import lg = Modelica.Math.log10;

         input SI.Length length "Length of pipe"
         input SI.Diameter diameter "Inner diameter of pipe"
         input Roughness roughness(min=1e-10)
            "Absolute roughness of pipe (> 0 required, details see info layer)"
         output LossFactorData data
            "Pressure loss factors for both flow directions";
        protected
         Real Delta(min=0) = roughness/diameter "Relative roughness";
       algorithm
         data.diameter_a          := diameter;
         data.diameter_b          := diameter;
         data.zeta1        := (length/diameter)/(2*lg(3.7 /Delta))^2;
         data.zeta2        := data.zeta1;
         data.Re_turbulent := 4000
            ">= 560/Delta flow does not depend on Re, but interpolation is bad";
         data.D_Re         := diameter;
         data.zeta1_at_a   := true;
         data.zeta2_at_a   := false;
         data.zetaLaminarKnown := true;
         data.c0               := 64*(length/diameter);
       end wallFriction;

       encapsulated function suddenExpansion
          "Return pressure loss data for sudden expansion or contraction in a pipe (for both flow directions)"
          import Modelica.Units.SI;
          import
            Modelica.Fluid.Fittings.BaseClasses.QuadraticTurbulent.LossFactorData;

         input SI.Diameter diameter_a "Inner diameter of pipe at port_a"
         input SI.Diameter diameter_b "Inner diameter of pipe at port_b"
         output LossFactorData data
            "Pressure loss factors for both flow directions";
        protected
         Real A_rel;
       algorithm
         data.diameter_a          := diameter_a;
         data.diameter_b          := diameter_b;
         data.Re_turbulent := 100;
         data.zetaLaminarKnown := true;
         data.c0 := 30;

         if diameter_a <= diameter_b then
            A_rel :=(diameter_a/diameter_b)^2;
            data.zeta1 :=(1 - A_rel)^2;
            data.zeta2 :=0.5*(1 - A_rel)^0.75;
            data.zeta1_at_a :=true;
            data.zeta2_at_a :=true;
            data.D_Re := diameter_a;
         else
            A_rel :=(diameter_b/diameter_a)^2;
            data.zeta1 :=0.5*(1 - A_rel)^0.75;
            data.zeta2 :=(1 - A_rel)^2;
            data.zeta1_at_a :=false;
            data.zeta2_at_a :=false;
            data.D_Re := diameter_b;
         end if;
       end suddenExpansion;

       encapsulated function sharpEdgedOrifice
          "Return pressure loss data for sharp edged orifice (for both flow directions)"
          import Modelica.Units.SI;
          import Modelica.Units.NonSI;
          import Modelica.Fluid.Fittings.BaseClasses.QuadraticTurbulent.LossFactorData;

          input SI.Diameter diameter
            "Inner diameter of pipe (= same at port_a and port_b)"
          input SI.Diameter leastDiameter "Smallest diameter of orifice"
          input SI.Diameter length "Length of orifice"
          input NonSI.Angle_deg alpha "Angle of orifice"
          output LossFactorData data
            "Pressure loss factors for both flow directions";
        protected
          Real A_rel=(leastDiameter/diameter)^2;
          Real LD=length/leastDiameter;
          Real k=0.13 + 0.34*10^(-(3.4*LD + 88.4*LD^2.3));
       algorithm
          data.diameter_a := diameter;
          data.diameter_b := diameter;
          data.zeta1 := ((1 - A_rel) + 0.707*(1 - A_rel)^0.375)^2*(1/A_rel)^2;
          data.zeta2 := k*(1 - A_rel)^0.75 + (1 - A_rel)^2 + 2*sqrt(k*(1 -
            A_rel)^0.375) + (1 - A_rel);
          data.Re_turbulent := 1e4;
          data.D_Re := leastDiameter;
          data.zeta1_at_a := true;
          data.zeta2_at_a := false;
          data.zetaLaminarKnown := false;
          data.c0 := 0;
       end sharpEdgedOrifice;
     end LossFactorData;

      function massFlowRate_dp
        "Return mass flow rate from constant loss factor data and pressure drop (m_flow = f(dp))"
              //import Modelica.Fluid.PressureLosses.BaseClasses.lossConstant_D_zeta;
        extends Modelica.Icons.Function;

        input SI.Pressure dp "Pressure drop (dp = port_a.p - port_b.p)";
        input SI.Density rho_a "Density at port_a";
        input SI.Density rho_b "Density at port_b";
        input LossFactorData data
          "Constant loss factors for both flow directions"
        input SI.AbsolutePressure dp_small = 1
          "Turbulent flow if |dp| >= dp_small";
        output SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";

      protected
        Real k1 = lossConstant_D_zeta(if data.zeta1_at_a then data.diameter_a else data.diameter_b,data.zeta1);
        Real k2 = lossConstant_D_zeta(if data.zeta2_at_a then data.diameter_a else data.diameter_b,data.zeta2);
      algorithm
        /*
   dp = 0.5*zeta*rho*v*|v|
      = 0.5*zeta*rho*1/(rho*A)^2 * m_flow * |m_flow|
      = 0.5*zeta/A^2 *1/rho * m_flow * |m_flow|
      = k/rho * m_flow * |m_flow|
   k  = 0.5*zeta/A^2
      = 0.5*zeta/(pi*(D/2)^2)^2
      = 8*zeta/(pi*D^2)^2
  */
        m_flow :=Utilities.regRoot2(dp, dp_small, rho_a/k1, rho_b/k2);
      end massFlowRate_dp;

      function massFlowRate_dp_and_Re
        "Return mass flow rate from constant loss factor data, pressure drop and Re (m_flow = f(dp))"
              extends Modelica.Icons.Function;
        import Modelica.Constants.pi;
        input SI.Pressure dp "Pressure drop (dp = port_a.p - port_b.p)";
        input SI.Density rho_a "Density at port_a";
        input SI.Density rho_b "Density at port_b";
        input SI.DynamicViscosity mu_a "Dynamic viscosity at port_a";
        input SI.DynamicViscosity mu_b "Dynamic viscosity at port_b";
        input LossFactorData data
          "Constant loss factors for both flow directions"
        output SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";

      protected
        Real k0=2*data.c0/(pi*data.D_Re^3);
        Real k1 = lossConstant_D_zeta(if data.zeta1_at_a then data.diameter_a else data.diameter_b,data.zeta1);
        Real k2 = lossConstant_D_zeta(if data.zeta2_at_a then data.diameter_a else data.diameter_b,data.zeta2);
        Real yd0
          "Derivative of m_flow=m_flow(dp) at zero, if data.zetaLaminarKnown";
        SI.AbsolutePressure dp_turbulent
          "The turbulent region is: |dp| >= dp_turbulent";
      algorithm
      /*
Turbulent region:
   Re = m_flow*(4/pi)/(D_Re*mu)
   dp = 0.5*zeta*rho*v*|v|
      = 0.5*zeta*rho*1/(rho*A)^2 * m_flow * |m_flow|
      = 0.5*zeta/A^2 *1/rho * m_flow * |m_flow|
      = k/rho * m_flow * |m_flow|
   k  = 0.5*zeta/A^2
      = 0.5*zeta/(pi*(D/2)^2)^2
      = 8*zeta/(pi*D^2)^2
   m_flow_turbulent = (pi/4)*D_Re*mu*Re_turbulent
   dp_turbulent     =  k/rho *(D_Re*mu*pi/4)^2 * Re_turbulent^2

   The start of the turbulent region is computed with mean values
   of dynamic viscosity mu and density rho. Otherwise, one has
   to introduce different "delta" values for both flow directions.
   In order to simplify the approach, only one delta is used.

Laminar region:
   dp = 0.5*zeta/(A^2*d) * m_flow * |m_flow|
      = 0.5 * c0/(|m_flow|*(4/pi)/(D_Re*mu)) / ((pi*(D_Re/2)^2)^2*d) * m_flow*|m_flow|
      = 0.5 * c0*(pi/4)*(D_Re*mu) * 16/(pi^2*D_Re^4*d) * m_flow*|m_flow|
      = 2*c0/(pi*D_Re^3) * mu/rho * m_flow
      = k0 * mu/rho * m_flow
   k0 = 2*c0/(pi*D_Re^3)

   In order that the derivative of dp=f(m_flow) is continuous
   at m_flow=0, the mean values of mu and d are used in the
   laminar region: mu/rho = (mu_a + mu_b)/(rho_a + rho_b)
   If data.zetaLaminarKnown = false then mu_a and mu_b are potentially zero
   (because dummy values) and therefore the division is only performed
   if zetaLaminarKnown = true.
*/
         dp_turbulent :=(k1 + k2)/(rho_a + rho_b)*
                        ((mu_a + mu_b)*data.D_Re*pi/8)^2*data.Re_turbulent^2;
         yd0 :=if data.zetaLaminarKnown then
                  (rho_a + rho_b)/(k0*(mu_a + mu_b)) else 0;
         m_flow := Utilities.regRoot2(dp, dp_turbulent, rho_a/k1, rho_b/k2,
                                                     data.zetaLaminarKnown, yd0);
      end massFlowRate_dp_and_Re;

      function pressureLoss_m_flow
        "Return pressure drop from constant loss factor and mass flow rate (dp = f(m_flow))"
              extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
        input SI.Density rho_a "Density at port_a";
        input SI.Density rho_b "Density at port_b";
        input LossFactorData data
          "Constant loss factors for both flow directions"
        input SI.MassFlowRate m_flow_small = 0.01
          "Turbulent flow if |m_flow| >= m_flow_small";
        output SI.Pressure dp "Pressure drop (dp = port_a.p - port_b.p)";

      protected
        Real k1 = lossConstant_D_zeta(if data.zeta1_at_a then data.diameter_a else data.diameter_b,data.zeta1);
        Real k2 = lossConstant_D_zeta(if data.zeta2_at_a then data.diameter_a else data.diameter_b,data.zeta2);
      algorithm
        /*
   dp = 0.5*zeta*rho*v*|v|
      = 0.5*zeta*rho*1/(rho*A)^2 * m_flow * |m_flow|
      = 0.5*zeta/A^2 *1/rho * m_flow * |m_flow|
      = k/rho * m_flow * |m_flow|
   k  = 0.5*zeta/A^2
      = 0.5*zeta/(pi*(D/2)^2)^2
      = 8*zeta/(pi*D^2)^2
  */
        dp :=Utilities.regSquare2(m_flow, m_flow_small, k1/rho_a, k2/rho_b);
      end pressureLoss_m_flow;

      function pressureLoss_m_flow_and_Re
        "Return pressure drop from constant loss factor, mass flow rate and Re (dp = f(m_flow))"
              extends Modelica.Icons.Function;
        import Modelica.Constants.pi;
        input SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
        input SI.Density rho_a "Density at port_a";
        input SI.Density rho_b "Density at port_b";
        input SI.DynamicViscosity mu_a "Dynamic viscosity at port_a";
        input SI.DynamicViscosity mu_b "Dynamic viscosity at port_b";
        input LossFactorData data
          "Constant loss factors for both flow directions"
        output SI.Pressure dp "Pressure drop (dp = port_a.p - port_b.p)";

      protected
        Real k0 = 2*data.c0/(pi*data.D_Re^3);
        Real k1 = lossConstant_D_zeta(if data.zeta1_at_a then data.diameter_a else data.diameter_b,data.zeta1);
        Real k2 = lossConstant_D_zeta(if data.zeta2_at_a then data.diameter_a else data.diameter_b,data.zeta2);
        Real yd0
          "Derivative of dp = f(m_flow) at zero, if data.zetaLaminarKnown";
        SI.MassFlowRate m_flow_turbulent
          "The turbulent region is: |m_flow| >= m_flow_turbulent";
      algorithm
      /*
Turbulent region:
   Re = m_flow*(4/pi)/(D_Re*mu)
   dp = 0.5*zeta*rho*v*|v|
      = 0.5*zeta*rho*1/(rho*A)^2 * m_flow * |m_flow|
      = 0.5*zeta/A^2 *1/rho * m_flow * |m_flow|
      = k/rho * m_flow * |m_flow|
   k  = 0.5*zeta/A^2
      = 0.5*zeta/(pi*(D/2)^2)^2
      = 8*zeta/(pi*D^2)^2
   m_flow_turbulent = (pi/4)*D_Re*mu*Re_turbulent
   dp_turbulent     =  k/rho *(D_Re*mu*pi/4)^2 * Re_turbulent^2

   The start of the turbulent region is computed with mean values
   of dynamic viscosity mu and density rho. Otherwise, one has
   to introduce different "delta" values for both flow directions.
   In order to simplify the approach, only one delta is used.

Laminar region:
   dp = 0.5*zeta/(A^2*d) * m_flow * |m_flow|
      = 0.5 * c0/(|m_flow|*(4/pi)/(D_Re*mu)) / ((pi*(D_Re/2)^2)^2*d) * m_flow*|m_flow|
      = 0.5 * c0*(pi/4)*(D_Re*mu) * 16/(pi^2*D_Re^4*d) * m_flow*|m_flow|
      = 2*c0/(pi*D_Re^3) * mu/rho * m_flow
      = k0 * mu/rho * m_flow
   k0 = 2*c0/(pi*D_Re^3)

   In order that the derivative of dp=f(m_flow) is continuous
   at m_flow=0, the mean values of mu and d are used in the
   laminar region: mu/rho = (mu_a + mu_b)/(rho_a + rho_b)
   If data.zetaLaminarKnown = false then mu_a and mu_b are potentially zero
   (because dummy values) and therefore the division is only performed
   if zetaLaminarKnown = true.
*/
        m_flow_turbulent :=(pi/8)*data.D_Re*(mu_a + mu_b)*data.Re_turbulent;
        yd0 :=if data.zetaLaminarKnown then k0*(mu_a + mu_b)/(rho_a + rho_b) else 0;
        dp :=Utilities.regSquare2(m_flow, m_flow_turbulent, k1/rho_a, k2/rho_b,
                                                 data.zetaLaminarKnown, yd0);
      end pressureLoss_m_flow_and_Re;

      partial model BaseModel
        "Generic pressure drop component with constant turbulent loss factor data and without an icon"

        extends Modelica.Fluid.Interfaces.PartialTwoPortTransport(
          dp_start = dp_nominal,
          m_flow_small = if system.use_eps_Re then system.eps_m_flow*m_flow_nominal else system.m_flow_small,
          m_flow(stateSelect = if momentumDynamics == Types.Dynamics.SteadyState then StateSelect.default
                               else StateSelect.prefer));
        extends Modelica.Fluid.Interfaces.PartialLumpedFlow(
          final pathLength = 0,
          final momentumDynamics = Types.Dynamics.SteadyState);

        parameter LossFactorData data "Loss factor data";
        parameter SI.MassFlowRate m_flow_nominal=if system.use_eps_Re then system.m_flow_nominal else 1e2*system.m_flow_small
          "Nominal mass flow rate"

        // Advanced
        parameter Boolean use_Re = system.use_eps_Re
          "= true, if turbulent region is defined by Re, otherwise by m_flow_small"
        parameter Boolean from_dp = true
          "= true, use m_flow = f(dp) else dp = f(m_flow)"
      protected
        parameter Medium.ThermodynamicState state_nominal=Medium.setState_pTX(
                             Medium.reference_p,
                             Medium.reference_T,
                             Medium.reference_X)
          "Medium state to compute nominal pressure drop";
        parameter SI.Pressure dp_nominal=
          pressureLoss_m_flow(m_flow_nominal, Medium.density(state_nominal), Medium.density(state_nominal), data, m_flow_small)
          "Nominal pressure loss";
        parameter Medium.AbsolutePressure dp_small(min=0) = if system.use_eps_Re then dp_nominal/m_flow_nominal*m_flow_small else system.dp_small
          "Regularization of zero flow if |dp| < dp_small"
        //parameter Medium.MassFlowRate m_flow_small = system.m_flow_small
        //  "Turbulent flow if |m_flow| >= m_flow_small"
        //

        // Diagnostics
      public
        parameter Boolean show_Re = false
          "= true, if Reynolds number is included for plotting"
        SI.ReynoldsNumber Re = Modelica.Fluid.Pipes.BaseClasses.CharacteristicNumbers.ReynoldsNumber_m_flow(
              m_flow,
              noEvent(if m_flow>0 then Medium.dynamicViscosity(state_a) else Medium.dynamicViscosity(state_b)),
              data.D_Re) if show_Re "Reynolds number at diameter data.D_Re";

        // Variables
        SI.Pressure dp_fg
          "Pressure loss due to friction and gravity";
        SI.Area A_mean = Modelica.Constants.pi/4*(data.diameter_a^2+data.diameter_b^2)/2
          "Mean cross flow area";

      equation
        Ib_flow = 0;
        F_p = A_mean*(Medium.pressure(state_b) - Medium.pressure(state_a));
        F_fg = A_mean*dp_fg;
        if from_dp then
           m_flow = homotopy(if use_Re then
                               massFlowRate_dp_and_Re(
                                 dp_fg, Medium.density(state_a), Medium.density(state_b),
                                 Medium.dynamicViscosity(state_a),
                                 Medium.dynamicViscosity(state_b),
                                 data) else
                               massFlowRate_dp(dp_fg, Medium.density(state_a), Medium.density(state_b), data, dp_small),
                             m_flow_nominal*dp_fg/dp_nominal);
        else
           dp_fg = homotopy(if use_Re then
                              pressureLoss_m_flow_and_Re(
                                m_flow, Medium.density(state_a), Medium.density(state_b),
                                Medium.dynamicViscosity(state_a),
                                Medium.dynamicViscosity(state_b),
                              data) else
                              pressureLoss_m_flow(m_flow, Medium.density(state_a), Medium.density(state_b), data, m_flow_small),
                            dp_nominal*m_flow/m_flow_nominal);
        end if;

        // Isenthalpic state transformation (no storage and no loss of energy)
        port_a.h_outflow = inStream(port_b.h_outflow);
        port_b.h_outflow = inStream(port_a.h_outflow);
      end BaseModel;

      partial model BaseModelNonconstantCrossSectionArea
        "Generic pressure drop component with constant turbulent loss factor data and without an icon, for non-constant cross section area"

        extends Modelica.Fluid.Interfaces.PartialTwoPortTransport(
          final dp_start = dp_nominal,
          m_flow_small = if system.use_eps_Re then system.eps_m_flow*m_flow_nominal else system.m_flow_small,
          m_flow(stateSelect = if momentumDynamics == Types.Dynamics.SteadyState then StateSelect.default
                               else StateSelect.prefer));
        extends Modelica.Fluid.Interfaces.PartialLumpedFlow(
          final pathLength = 0,
          final momentumDynamics = Types.Dynamics.SteadyState);

        parameter LossFactorData data "Loss factor data";
        parameter SI.MassFlowRate m_flow_nominal=if system.use_eps_Re then system.m_flow_nominal else 1e2*system.m_flow_small
          "Nominal mass flow rate"

        // Advanced
        /// Other settings than the final values are not yet implemented ///
        final parameter Boolean use_Re = false
          "= true, if turbulent region is defined by Re, otherwise by m_flow_small"
        final parameter Boolean from_dp = false
          "= true, use m_flow = f(dp) else dp = f(m_flow)"
        // End not yet implemented /////////////////////////////////////////
      protected
        parameter Medium.ThermodynamicState state_nominal=Medium.setState_pTX(
                             Medium.reference_p,
                             Medium.reference_T,
                             Medium.reference_X)
          "Medium state to compute nominal pressure drop"
        parameter SI.Pressure dp_nominal=
          pressureLoss_m_flow(m_flow_nominal, Medium.density(state_nominal), Medium.density(state_nominal), data, m_flow_small)
          "Nominal pressure loss";
        parameter Medium.AbsolutePressure dp_small(min=0) = if system.use_eps_Re then dp_nominal/m_flow_nominal*m_flow_small else system.dp_small
          "Regularization of zero flow if |dp| < dp_small"
        //parameter Medium.MassFlowRate m_flow_small = system.m_flow_small
        //  "Turbulent flow if |m_flow| >= m_flow_small"
        //

        // Diagnostics
      public
        parameter Boolean show_Re = false
          "= true, if Reynolds number is included for plotting"
        SI.ReynoldsNumber Re = Modelica.Fluid.Pipes.BaseClasses.CharacteristicNumbers.ReynoldsNumber_m_flow(
              m_flow,
              noEvent(if m_flow>0 then Medium.dynamicViscosity(state_a) else Medium.dynamicViscosity(state_b)),
              data.D_Re) if show_Re "Reynolds number at diameter data.D_Re";
        parameter Boolean show_totalPressures = false
          "= true, if total pressures are included for plotting"
        SI.AbsolutePressure p_total_a = port_a.p + 0.5 * m_flow^2 /((Modelica.Constants.pi/4 * data.diameter_a^2)^2 * noEvent(if port_a.m_flow > 0 then Medium.density(state_a) else Medium.density(state_b))) if
          show_totalPressures "Total pressure at port_a";
        SI.AbsolutePressure p_total_b = port_b.p + 0.5 * m_flow^2 /((Modelica.Constants.pi/4 * data.diameter_b^2)^2 * noEvent(if port_b.m_flow > 0 then Medium.density(state_b) else Medium.density(state_a))) if
          show_totalPressures "Total pressure at port_a";
        parameter Boolean show_portVelocities = false
          "= true, if port velocities are included for plotting"
        SI.Velocity v_a = port_a.m_flow /(Modelica.Constants.pi/4 * data.diameter_a^2 * noEvent(if port_a.m_flow > 0 then Medium.density(state_a) else Medium.density(state_b))) if
          show_portVelocities "Fluid velocity into port_a";
        SI.Velocity v_b = port_b.m_flow /(Modelica.Constants.pi/4 * data.diameter_b^2 * noEvent(if port_b.m_flow > 0 then Medium.density(state_b) else Medium.density(state_a))) if
          show_portVelocities "Fluid velocity into port_b";

        // Variables
        SI.Pressure dp_fg
          "Pressure loss due to friction and gravity";
        SI.Area A_mean = Modelica.Constants.pi/4*(data.diameter_a^2+data.diameter_b^2)/2
          "Mean cross flow area";

        Medium.ThermodynamicState state_b_des
          "Thermodynamic state at port b for flow a -> b";
        Medium.ThermodynamicState state_a_nondes
          "Thermodynamic state at port a for flow a <- b";

      equation
        Ib_flow = 0;
        F_p = A_mean*(Medium.pressure(state_b) - Medium.pressure(state_a));
        F_fg = A_mean*dp_fg;
        if from_dp then
           m_flow = if use_Re then
                       massFlowRate_dp_and_Re(
                          dp_fg, Medium.density(state_a), Medium.density(state_b),
                          Medium.dynamicViscosity(state_a),
                          Medium.dynamicViscosity(state_b),
                          data) else
                       massFlowRate_dp(dp_fg, Medium.density(state_a), Medium.density(state_b), data, dp_small);
        else
           dp_fg = if use_Re then
                   pressureLoss_m_flow_and_Re(
                       m_flow, Medium.density(state_a), Medium.density(state_b),
                       Medium.dynamicViscosity(state_a),
                       Medium.dynamicViscosity(state_b),
                       data) else
                   pressureLoss_m_flow_totalPressure(m_flow,
                     Medium.density(state_a),
                     Medium.density(state_b_des),
                     Medium.density(state_b),
                     Medium.density(state_a_nondes),
                     data, m_flow_small);
        end if;

        // Isenthalpic state transformation (no storage and no loss of energy)
        port_a.h_outflow = inStream(port_b.h_outflow);
        port_b.h_outflow = inStream(port_a.h_outflow);

        // medium states for downstream properties, may want to change this neglecting the only difference from state_a, state_b, which is in pressure
        // This will remove the extra interaction variables
        state_b_des = Medium.setState_phX(port_b.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow));
        state_a_nondes = Medium.setState_phX(port_a.p, inStream(port_b.h_outflow), inStream(port_b.Xi_outflow));
      end BaseModelNonconstantCrossSectionArea;

      function pressureLoss_m_flow_totalPressure
        "Return pressure drop from constant loss factor and mass flow rate (dp = f(m_flow))"
              extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
        input SI.Density rho_a_des
          "Density at port_a, mass flow in design direction a -> b";
        input SI.Density rho_b_des
          "Density at port_b, mass flow in design direction a -> b";
        input SI.Density rho_b_nondes
          "Density at port_b, mass flow against design direction a <- b";
        input SI.Density rho_a_nondes
          "Density at port_a, mass flow against design direction a <- b";
        input LossFactorData data
          "Constant loss factors for both flow directions"
        input SI.MassFlowRate m_flow_small = 0.01
          "Turbulent flow if |m_flow| >= m_flow_small";
        output SI.Pressure dp "Pressure drop (dp = port_a.p - port_b.p)";

      protected
        SI.Area A_a = Modelica.Constants.pi * data.diameter_a^2/4
          "Cross section area at port_a";
        SI.Area A_b = Modelica.Constants.pi * data.diameter_b^2/4
          "Cross section area at port_b";
      algorithm
          dp := 1/2 * m_flow^2 *( if m_flow > 0 then
            data.zeta1/(if data.zeta1_at_a then rho_a_des    * A_a^2 else    rho_b_des * A_b^2) - 1/(rho_a_des    * A_a^2) + 1/(rho_b_des    * A_b^2) else
            -data.zeta2/(if data.zeta2_at_a then rho_a_nondes * A_a^2 else rho_b_nondes * A_b^2) - 1/(rho_a_nondes * A_a^2) + 1/(rho_b_nondes * A_b^2));
      end pressureLoss_m_flow_totalPressure;
    end QuadraticTurbulent;

    partial model PartialTeeJunction
      "Base class for a splitting/joining component with three ports"
      import Modelica.Fluid.Types;
      import Modelica.Fluid.Types.PortFlowDirection;

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

      Modelica.Fluid.Interfaces.FluidPort_a port_1(redeclare package Medium =
            Medium, m_flow(min=if (portFlowDirection_1 == PortFlowDirection.Entering) then
                    0.0 else -Modelica.Constants.inf, max=if (portFlowDirection_1
               == PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
      Modelica.Fluid.Interfaces.FluidPort_b port_2(redeclare package Medium =
            Medium, m_flow(min=if (portFlowDirection_2 == PortFlowDirection.Entering) then
                    0.0 else -Modelica.Constants.inf, max=if (portFlowDirection_2
               == PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))
      Modelica.Fluid.Interfaces.FluidPort_a port_3(
        redeclare package Medium=Medium,
        m_flow(min=if (portFlowDirection_3==PortFlowDirection.Entering) then 0.0 else -Modelica.Constants.inf,
        max=if (portFlowDirection_3==PortFlowDirection.Leaving) then 0.0 else Modelica.Constants.inf))

    protected
      parameter PortFlowDirection portFlowDirection_1=PortFlowDirection.Bidirectional
        "Flow direction for port_1"
      parameter PortFlowDirection portFlowDirection_2=PortFlowDirection.Bidirectional
        "Flow direction for port_2"
      parameter PortFlowDirection portFlowDirection_3=PortFlowDirection.Bidirectional
        "Flow direction for port_3"
    end PartialTeeJunction;

    package Bends "Pressure loss functions for bends"
      extends Modelica.Icons.VariantsPackage;

      package CurvedBend "Pressure loss functions for curved bends"
          extends Modelica.Icons.Package;

      function massFlowRate
          "Return mass flow rate m_flow as function of pressure loss dp for a curved bend"
        extends Modelica.Icons.Function;
        input SI.Pressure dp "Pressure loss";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
      algorithm
         m_flow := Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_MFLOW(
                     geometry,
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(dp, d_a, d_b, dp_small),
                         eta=Modelica.Fluid.Utilities.regStep(dp, eta_a, eta_b, dp_small)),
                     dp);
      end massFlowRate;

      function pressureLoss
          "Return pressure loss dp as function of mass flow rate m_flow for a curved bend"
        extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.Pressure dp "Pressure loss";
      algorithm
         dp := Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_DP(
                     geometry,
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_curvedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(m_flow, d_a, d_b, m_flow_small),
                         eta=Modelica.Fluid.Utilities.regStep(m_flow, eta_a, eta_b, m_flow_small)),
                     m_flow);
      end pressureLoss;

        record Geometry "Geometric data for a curved bend"
          extends Modelica.Icons.Record;

          SI.Diameter d_hyd "Hydraulic diameter"
          SI.Radius R_0 "Curvature radius"
          SI.Angle delta=1.5707963267949 "Angle of turning"
          Modelica.Fluid.Types.Roughness K=2.5e-5
            "Absolute roughness, with a default for a smooth steel pipe"
        end Geometry;
      end CurvedBend;

      package EdgedBend "Pressure loss functions for edged bends"
          extends Modelica.Icons.Package;

      function massFlowRate
          "Return mass flow rate m_flow as function of pressure loss dp for a curved bend"
        extends Modelica.Icons.Function;

        input SI.Pressure dp "Pressure loss";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
      algorithm
         m_flow := Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_MFLOW(
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_con(
                         d_hyd=geometry.d_hyd,
                         delta=geometry.delta,
                         K=geometry.K),
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(dp, d_a, d_b, dp_small),
                         eta=Modelica.Fluid.Utilities.regStep(dp, eta_a, eta_b, dp_small)),
                     dp);
      end massFlowRate;

      function pressureLoss
          "Return pressure loss dp as function of mass flow rate m_flow for a curved bend"
        extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.Pressure dp "Pressure loss";
      algorithm
         dp := Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_DP(
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_con(
                         d_hyd=geometry.d_hyd,
                         delta=geometry.delta,
                         K=geometry.K),
                     Modelica.Fluid.Dissipation.PressureLoss.Bend.dp_edgedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(m_flow, d_a, d_b, m_flow_small),
                         eta=Modelica.Fluid.Utilities.regStep(m_flow, eta_a, eta_b, m_flow_small)),
                     m_flow);
      end pressureLoss;

        record Geometry "Geometric data for a curved bend"
          extends Modelica.Icons.Record;

          SI.Diameter d_hyd "Hydraulic diameter"
          SI.Angle delta "Angle of turning"
          Modelica.Fluid.Types.Roughness K=2.5e-5
            "Absolute roughness, with a default for a smooth steel pipe"
        end Geometry;
      end EdgedBend;
    end Bends;

    package Orifices "Pressure loss functions for orifices"
        extends Modelica.Icons.VariantsPackage;
      package ThickEdgedOrifice
        "Pressure loss functions for thick edged orifices"
          extends Modelica.Icons.Package;

      function massFlowRate
          "Return mass flow rate m_flow as function of pressure loss dp for a thick edged orifice"
        extends Modelica.Icons.Function;

        input SI.Pressure dp "Pressure loss";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
      algorithm
         m_flow := Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_MFLOW(
                     Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_con(
                         A_0=geometry.venaCrossArea,
                         A_1=geometry.crossArea,
                         C_0=geometry.venaPerimeter,
                         C_1=geometry.perimeter,
                         L=geometry.venaLength,
                         dp_smooth=dp_small),
                     Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(dp, d_a, d_b, dp_small),
                         eta=Modelica.Fluid.Utilities.regStep(dp, eta_a, eta_b, dp_small)),
                     dp);
      end massFlowRate;

      function pressureLoss
          "Return pressure loss dp as function of mass flow rate m_flow for a thick edged orifice"
        extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
        input Geometry geometry "Geometry of bend";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.DynamicViscosity eta_a
            "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
        input SI.DynamicViscosity eta_b
            "Dynamic viscosity at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.Pressure dp "Pressure loss";
      algorithm
         dp := Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_DP(
                 Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_con(
                         A_0=geometry.venaCrossArea,
                         A_1=geometry.crossArea,
                         C_0=geometry.venaPerimeter,
                         C_1=geometry.perimeter,
                         L=geometry.venaLength,
                         dp_smooth=dp_small),
                     Modelica.Fluid.Dissipation.PressureLoss.Orifice.dp_thickEdgedOverall_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(m_flow, d_a, d_b, m_flow_small),
                         eta=Modelica.Fluid.Utilities.regStep(m_flow, eta_a, eta_b, m_flow_small)),
                     m_flow);
      end pressureLoss;

        record Geometry "Geometric data for a thick edged orifice"
          extends Modelica.Icons.Record;

          SI.Area crossArea "Inner cross sectional area"
          SI.Length perimeter "Inner perimeter"

          SI.Area venaCrossArea "Cross sectional area of vena contraction"
          SI.Length venaPerimeter "Perimeter of vena contraction"
          SI.Length venaLength "Length of vena contraction"
        end Geometry;

      package Choices "Choices for Geometry"
        extends Modelica.Icons.Package;

        function circular "Circular cross section"
            import Modelica.Constants.pi;

          input SI.Diameter diameter "Inner diameter of circular orifice"
          input SI.Diameter venaDiameter "Diameter of vena contraction"
          input SI.Length venaLength "Length of vena contraction"

           output ThickEdgedOrifice.Geometry geometry
              "Geometry of circular thick edged orifice";
        algorithm
           geometry.crossArea := diameter^2*pi/4;
           geometry.perimeter := pi*diameter;
           geometry.venaCrossArea := venaDiameter^2*pi/4;
           geometry.venaPerimeter := pi*venaDiameter;
           geometry.venaLength := venaLength;
        end circular;

        function rectangular "Rectangular cross section"
            import Modelica.Constants.pi;

          input SI.Length width "Inner width of rectangular orifice"
          input SI.Length height "Inner height of rectangular orifice"
          input SI.Length venaWidth "Width of vena contraction"
          input SI.Length venaHeight "Height of vena contraction"
          input SI.Length venaLength "Length of vena contraction"

           output ThickEdgedOrifice.Geometry geometry
              "Geometry of circular thick edged orifice";
        algorithm
           geometry.crossArea := width*height;
           geometry.perimeter := 2*width + 2*height;
           geometry.venaCrossArea := venaWidth*venaHeight;
           geometry.venaPerimeter := 2*venaWidth + 2*venaHeight;
           geometry.venaLength := venaLength;
        end rectangular;

        function general "General cross section"
            import Modelica.Constants.pi;

          input SI.Area crossArea "Inner cross sectional area"
          input SI.Length perimeter "Inner perimeter"

          input SI.Area venaCrossArea
              "Cross sectional area of vena contraction"
          input SI.Length venaPerimeter "Perimeter of vena contraction"
          input SI.Length venaLength "Length of vena contraction"

           output ThickEdgedOrifice.Geometry geometry
              "Geometry of circular thick edged orifice";
        algorithm
           geometry.crossArea := crossArea;
           geometry.perimeter := perimeter;
           geometry.venaCrossArea := venaCrossArea;
           geometry.venaPerimeter := venaPerimeter;
           geometry.venaLength := venaLength;
        end general;
      end Choices;
      end ThickEdgedOrifice;
    end Orifices;

    package GenericResistances
      "Pressure loss functions for generic, geometry independent flow resistances"
        extends Modelica.Icons.VariantsPackage;
      package VolumeFlowRate
        "Pressure loss functions for generic resistances parameterized with the volume flow rate"
          extends Modelica.Icons.Package;

      function massFlowRate
          "Return mass flow rate m_flow as function of pressure loss dp for a curved bend"
        extends Modelica.Icons.Function;

        input SI.Pressure dp "Pressure loss";
        input Real a(unit="(Pa.s2)/m6")
            "Coefficient for quadratic term (dp = a*V_flow^2 + b*V_flow)";
        input Real b(unit="(Pa.s)/m3")
            "Coefficient for linear term (dp = a*V_flow^2 + b*V_flow)";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
      algorithm
         m_flow := Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_MFLOW(
                     Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_con(
                         a=a,
                         b=b,
                         dp_min=dp_small),
                     Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(dp, d_a, d_b, dp_small)),
                     dp);
      end massFlowRate;

      function pressureLoss
          "Return pressure loss dp as function of mass flow rate m_flow for a curved bend"
        extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate (= port_a.m_flow)";
        input Real a(unit="(Pa.s2)/m6")
            "Coefficient for quadratic term (dp = a*V_flow^2 + b*V_flow)";
        input Real b(unit="(Pa.s)/m3")
            "Coefficient for linear term (dp = a*V_flow^2 + b*V_flow)";
        input SI.Density d_a
            "Density at port_a when fluid is flowing from port_a to port_b";
        input SI.Density d_b
            "Density at port_b when fluid is flowing from port_b to port_a";
        input SI.AbsolutePressure dp_small
            "Small pressure drop used for regularization if m_flow=f(...,dp_small,..,dp)";
        input SI.MassFlowRate m_flow_small
            "Small mass flow rate used for regularization if dp=f_inv(...,m_flow_small,m_flow)";
        output SI.Pressure dp "Pressure loss";
      algorithm
         dp := Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_DP(
                     Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_con(
                         a=a,
                         b=b,
                         dp_min=dp_small),
                     Modelica.Fluid.Dissipation.PressureLoss.General.dp_volumeFlowRate_IN_var(
                         rho=Modelica.Fluid.Utilities.regStep(m_flow, d_a, d_b, m_flow_small)),
                     m_flow);
      end pressureLoss;
      end VolumeFlowRate;
    end GenericResistances;
  end BaseClasses;
end Fittings;


within Modelica.Fluid;
package Interfaces
  "Interfaces for steady state and unsteady, mixed-phase, multi-substance, incompressible and compressible flow"

  extends Modelica.Icons.InterfacesPackage;

  connector FluidPort
    "Interface for quasi one-dimensional fluid flow in a piping network (incompressible or compressible, one or more phases, one or more substances)"

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

    flow Medium.MassFlowRate m_flow
      "Mass flow rate from the connection point into the component";
    Medium.AbsolutePressure p "Thermodynamic pressure in the connection point";
    stream Medium.SpecificEnthalpy h_outflow
      "Specific thermodynamic enthalpy close to the connection point if m_flow < 0";
    stream Medium.MassFraction Xi_outflow[Medium.nXi]
      "Independent mixture mass fractions m_i/m close to the connection point if m_flow < 0";
    stream Medium.ExtraProperty C_outflow[Medium.nC]
      "Properties c_i/m close to the connection point if m_flow < 0";
  end FluidPort;

  connector FluidPort_a "Generic fluid connector at design inlet"
    extends FluidPort;
  end FluidPort_a;

  connector FluidPort_b "Generic fluid connector at design outlet"
    extends FluidPort;
  end FluidPort_b;

  connector FluidPorts_a
    "Fluid connector with filled, large icon to be used for vectors of FluidPorts (vector dimensions must be added after dragging)"
    extends FluidPort;
  end FluidPorts_a;

  connector FluidPorts_b
    "Fluid connector with outlined, large icon to be used for vectors of FluidPorts (vector dimensions must be added after dragging)"
    extends FluidPort;
  end FluidPorts_b;

  partial model PartialTwoPort "Partial component with two ports"
    import Modelica.Constants;
    outer Modelica.Fluid.System system "System wide properties";

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

    parameter Boolean allowFlowReversal = system.allowFlowReversal
      "= true to allow flow reversal, false restricts to design direction (port_a -> port_b)"

    Modelica.Fluid.Interfaces.FluidPort_a port_a(
                                  redeclare package Medium = Medium,
                       m_flow(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_b(
                                  redeclare package Medium = Medium,
                       m_flow(max=if allowFlowReversal then +Constants.inf else 0))
      "Fluid connector b (positive design flow direction is from port_a to port_b)"
    // Model structure, e.g., used for visualization
  protected
    parameter Boolean port_a_exposesState = false
      "= true if port_a exposes the state of a fluid volume";
    parameter Boolean port_b_exposesState = false
      "= true if port_b.p exposes the state of a fluid volume";
    parameter Boolean showDesignFlowDirection = true
      "= false to hide the arrow in the model icon";
  end PartialTwoPort;

partial model PartialTwoPortTransport
    "Partial element transporting fluid between two ports without storage of mass or energy"

  extends PartialTwoPort(
    final port_a_exposesState=false,
    final port_b_exposesState=false);

  // Advanced
  // Note: value of dp_start shall be refined by derived model, basing on local dp_nominal
  parameter Medium.AbsolutePressure dp_start(min=-Modelica.Constants.inf) = 0.01*system.p_start
      "Guess value of dp = port_a.p - port_b.p"
  parameter Medium.MassFlowRate m_flow_start = system.m_flow_start
      "Guess value of m_flow = port_a.m_flow"
  // Note: value of m_flow_small shall be refined by derived model, basing on local m_flow_nominal
  parameter Medium.MassFlowRate m_flow_small = if system.use_eps_Re then system.eps_m_flow*system.m_flow_nominal else system.m_flow_small
      "Small mass flow rate for regularization of zero flow"

  // Diagnostics
  parameter Boolean show_T = true
      "= true, if temperatures at port_a and port_b are computed"
  parameter Boolean show_V_flow = true
      "= true, if volume flow rate at inflowing port is computed"

  // Variables
  Medium.MassFlowRate m_flow(
     min=if allowFlowReversal then -Modelica.Constants.inf else 0,
     start = m_flow_start) "Mass flow rate in design flow direction";
  SI.Pressure dp(start=dp_start)
      "Pressure difference between port_a and port_b (= port_a.p - port_b.p)";

  SI.VolumeFlowRate V_flow=
      m_flow/Modelica.Fluid.Utilities.regStep(m_flow,
                  Medium.density(state_a),
                  Medium.density(state_b),
                  m_flow_small) if show_V_flow
      "Volume flow rate at inflowing port (positive when flow from port_a to port_b)";

  Medium.Temperature port_a_T=
      Modelica.Fluid.Utilities.regStep(port_a.m_flow,
                  Medium.temperature(state_a),
                  Medium.temperature(Medium.setState_phX(port_a.p, port_a.h_outflow, port_a.Xi_outflow)),
                  m_flow_small) if show_T
      "Temperature close to port_a, if show_T = true";
  Medium.Temperature port_b_T=
      Modelica.Fluid.Utilities.regStep(port_b.m_flow,
                  Medium.temperature(state_b),
                  Medium.temperature(Medium.setState_phX(port_b.p, port_b.h_outflow, port_b.Xi_outflow)),
                  m_flow_small) if show_T
      "Temperature close to port_b, if show_T = true";
  protected
  Medium.ThermodynamicState state_a "State for medium inflowing through port_a";
  Medium.ThermodynamicState state_b "State for medium inflowing through port_b";
equation
  // medium states
  state_a = Medium.setState_phX(port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow));
  state_b = Medium.setState_phX(port_b.p, inStream(port_b.h_outflow), inStream(port_b.Xi_outflow));

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

  // Design direction of mass flow rate
  m_flow = port_a.m_flow;
  assert(m_flow > -m_flow_small or allowFlowReversal, "Reversing flow occurs even though allowFlowReversal is false");

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

  // Transport of substances
  port_a.Xi_outflow = inStream(port_b.Xi_outflow);
  port_b.Xi_outflow = inStream(port_a.Xi_outflow);

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

  connector HeatPorts_a
    "HeatPort connector with filled, large icon to be used for vectors of HeatPorts (vector dimensions must be added after dragging)"
    extends Modelica.Thermal.HeatTransfer.Interfaces.HeatPort;
  end HeatPorts_a;

  connector HeatPorts_b
    "HeatPort connector with filled, large icon to be used for vectors of HeatPorts (vector dimensions must be added after dragging)"
    extends Modelica.Thermal.HeatTransfer.Interfaces.HeatPort;
  end HeatPorts_b;

  partial model PartialHeatTransfer "Common interface for heat transfer models"

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

    parameter Integer n=1 "Number of heat transfer segments"

    // Inputs provided to heat transfer model
    input Medium.ThermodynamicState[n] states
      "Thermodynamic states of flow segments";

    input SI.Area[n] surfaceAreas "Heat transfer areas";

    // Outputs defined by heat transfer model
    output SI.HeatFlowRate[n] Q_flows "Heat flow rates";

    // Parameters
    parameter Boolean use_k = false
      "= true to use k value for thermal isolation"
    parameter SI.CoefficientOfHeatTransfer k = 0
      "Heat transfer coefficient to ambient"
    parameter SI.Temperature T_ambient = system.T_ambient "Ambient temperature"
    outer Modelica.Fluid.System system "System wide properties";

    // Heat ports
    Modelica.Fluid.Interfaces.HeatPorts_a[n] heatPorts
      "Heat port to component boundary"

    // Variables
    Medium.Temperature[n] Ts = Medium.temperature(states)
      "Temperatures defined by fluid states";

  equation
    if use_k then
      Q_flows = heatPorts.Q_flow + {k*surfaceAreas[i]*(T_ambient - heatPorts[i].T) for i in 1:n};
    else
      Q_flows = heatPorts.Q_flow;
    end if;
  end PartialHeatTransfer;

    partial model PartialLumpedVolume
    "Lumped volume with mass and energy balance"
    import Modelica.Fluid.Types;
    import Modelica.Fluid.Types.Dynamics;
    import Modelica.Media.Interfaces.Choices.IndependentVariables;

      outer Modelica.Fluid.System system "System properties";
      replaceable package Medium =
        Modelica.Media.Interfaces.PartialMedium "Medium in the component"

      // Inputs provided to the volume model
      input SI.Volume fluidVolume "Volume";

      // Assumptions
      parameter Types.Dynamics energyDynamics=system.energyDynamics
      "Formulation of energy balance"
      parameter Types.Dynamics massDynamics=system.massDynamics
      "Formulation of mass balance"
      final parameter Types.Dynamics substanceDynamics=massDynamics
      "Formulation of substance balance"
      final parameter Types.Dynamics traceDynamics=massDynamics
      "Formulation of trace substance balance"

      // Initialization
      parameter Medium.AbsolutePressure p_start = system.p_start
      "Start value of pressure"
      parameter Boolean use_T_start = true
      "= true, use T_start, otherwise h_start"
      parameter Medium.Temperature T_start=
        if use_T_start then system.T_start else Medium.temperature_phX(p_start,h_start,X_start)
      "Start value of temperature"
      parameter Medium.SpecificEnthalpy h_start=
        if use_T_start then Medium.specificEnthalpy_pTX(p_start, T_start, X_start) else Medium.h_default
      "Start value of specific enthalpy"
      parameter Medium.MassFraction X_start[Medium.nX] = Medium.X_default
      "Start value of mass fractions m_i/m"
      parameter Medium.ExtraProperty C_start[Medium.nC](
           quantity=Medium.extraPropertiesNames) = Medium.C_default
      "Start value of trace substances"

      Medium.BaseProperties medium(
        preferredMediumStates = (if energyDynamics == Dynamics.SteadyState and
                                    massDynamics   == Dynamics.SteadyState then false else true),
        p(start=p_start),
        h(start=h_start),
        T(start=T_start),
        Xi(start=X_start[1:Medium.nXi]));
      SI.Energy U "Internal energy of fluid";
      SI.Mass m "Mass of fluid";
      SI.Mass[Medium.nXi] mXi "Masses of independent components in the fluid";
      SI.Mass[Medium.nC] mC "Masses of trace substances in the fluid";
      // C need to be added here because unlike for Xi, which has medium.Xi,
      // there is no variable medium.C
      Medium.ExtraProperty C[Medium.nC] "Trace substance mixture content";

      // variables that need to be defined by an extending class
      SI.MassFlowRate mb_flow "Mass flows across boundaries";
      SI.MassFlowRate[Medium.nXi] mbXi_flow
      "Substance mass flows across boundaries";
      Medium.ExtraPropertyFlowRate[Medium.nC] mbC_flow
      "Trace substance mass flows across boundaries";
      SI.EnthalpyFlowRate Hb_flow
      "Enthalpy flow across boundaries or energy source/sink";
      SI.HeatFlowRate Qb_flow
      "Heat flow across boundaries or energy source/sink";
      SI.Power Wb_flow "Work flow across boundaries or source term";
  protected
      parameter Boolean initialize_p = not Medium.singleState
      "= true to set up initial equations for pressure";
      Real[Medium.nC] mC_scaled(min=fill(Modelica.Constants.eps, Medium.nC))
      "Scaled masses of trace substances in the fluid";
    equation
      assert(not (energyDynamics<>Dynamics.SteadyState and massDynamics==Dynamics.SteadyState) or Medium.singleState,
             "Bad combination of dynamics options and Medium not conserving mass if fluidVolume is fixed.");

      // Total quantities
      m = fluidVolume*medium.d;
      mXi = m*medium.Xi;
      U = m*medium.u;
      mC = m*C;

      // Energy and mass balances
      if energyDynamics == Dynamics.SteadyState then
        0 = Hb_flow + Qb_flow + Wb_flow;
      else
        der(U) = Hb_flow + Qb_flow + Wb_flow;
      end if;

      if massDynamics == Dynamics.SteadyState then
        0 = mb_flow;
      else
        der(m) = mb_flow;
      end if;

      if substanceDynamics == Dynamics.SteadyState then
        zeros(Medium.nXi) = mbXi_flow;
      else
        der(mXi) = mbXi_flow;
      end if;

      if traceDynamics == Dynamics.SteadyState then
        zeros(Medium.nC)  = mbC_flow;
      else
        der(mC_scaled) = mbC_flow./Medium.C_nominal;
      end if;
        mC = mC_scaled.*Medium.C_nominal;

    initial equation
      // initialization of balances
      if energyDynamics == Dynamics.FixedInitial then
        /*
    if use_T_start then
      medium.T = T_start;
    else
      medium.h = h_start;
    end if;
    */
        if Medium.ThermoStates == IndependentVariables.ph or
           Medium.ThermoStates == IndependentVariables.phX then
           medium.h = h_start;
        else
           medium.T = T_start;
        end if;
      elseif energyDynamics == Dynamics.SteadyStateInitial then
        /*
    if use_T_start then
      der(medium.T) = 0;
    else
      der(medium.h) = 0;
    end if;
    */
        if Medium.ThermoStates == IndependentVariables.ph or
           Medium.ThermoStates == IndependentVariables.phX then
           der(medium.h) = 0;
        else
           der(medium.T) = 0;
        end if;
      end if;

      if massDynamics == Dynamics.FixedInitial then
        if initialize_p then
          medium.p = p_start;
        end if;
      elseif massDynamics == Dynamics.SteadyStateInitial then
        if initialize_p then
          der(medium.p) = 0;
        end if;
      end if;

      if substanceDynamics == Dynamics.FixedInitial then
        medium.Xi = X_start[1:Medium.nXi];
      elseif substanceDynamics == Dynamics.SteadyStateInitial then
        der(medium.Xi) = zeros(Medium.nXi);
      end if;

      if traceDynamics == Dynamics.FixedInitial then
        mC_scaled = m*C_start[1:Medium.nC]./Medium.C_nominal;
      elseif traceDynamics == Dynamics.SteadyStateInitial then
        der(mC_scaled) = zeros(Medium.nC);
      end if;
    end PartialLumpedVolume;

      partial model PartialLumpedFlow
    "Base class for a lumped momentum balance"

        outer Modelica.Fluid.System system "System properties";

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

        parameter Boolean allowFlowReversal = system.allowFlowReversal
      "= true to allow flow reversal, false restricts to design direction (m_flow >= 0)"

        // Inputs provided to the flow model
        input SI.Length pathLength "Length flow path";

        // Variables defined by the flow model
        Medium.MassFlowRate m_flow(
           min=if allowFlowReversal then -Modelica.Constants.inf else 0,
           start = m_flow_start,
           stateSelect = if momentumDynamics == Types.Dynamics.SteadyState then StateSelect.default else
                                     StateSelect.prefer)
      "Mass flow rates between states";

        // Parameters
        parameter Modelica.Fluid.Types.Dynamics momentumDynamics=system.momentumDynamics
      "Formulation of momentum balance"

        parameter Medium.MassFlowRate m_flow_start=system.m_flow_start
      "Start value of mass flow rates"

        // Total quantities
        SI.Momentum I "Momenta of flow segments";

        // Source terms and forces to be defined by an extending model (zero if not used)
        SI.Force Ib_flow "Flow of momentum across boundaries";
        SI.Force F_p "Pressure force";
        SI.Force F_fg "Friction and gravity force";

      equation
        // Total quantities
        I = m_flow*pathLength;

        // Momentum balances
        if momentumDynamics == Types.Dynamics.SteadyState then
          0 = Ib_flow - F_p - F_fg;
        else
          der(I) = Ib_flow - F_p - F_fg;
        end if;

      initial equation
        if momentumDynamics == Types.Dynamics.FixedInitial then
          m_flow = m_flow_start;
        elseif momentumDynamics == Types.Dynamics.SteadyStateInitial then
          der(m_flow) = 0;
        end if;
      end PartialLumpedFlow;

partial model PartialDistributedVolume
    "Base class for distributed volume models"
    import Modelica.Fluid.Types;
    import Modelica.Fluid.Types.Dynamics;
    import Modelica.Media.Interfaces.Choices.IndependentVariables;
  outer Modelica.Fluid.System system "System properties";

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

  // Discretization
  parameter Integer n=2 "Number of discrete volumes";

  // Inputs provided to the volume model
  input SI.Volume[n] fluidVolumes
      "Discretized volume, determine in inheriting class";

  // Assumptions
  parameter Types.Dynamics energyDynamics=system.energyDynamics
      "Formulation of energy balances"
  parameter Types.Dynamics massDynamics=system.massDynamics
      "Formulation of mass balances"
  final parameter Types.Dynamics substanceDynamics=massDynamics
      "Formulation of substance balances"
  final parameter Types.Dynamics traceDynamics=massDynamics
      "Formulation of trace substance balances"

  //Initialization
  parameter Medium.AbsolutePressure p_a_start=system.p_start
      "Start value of pressure at port a"
  parameter Medium.AbsolutePressure p_b_start=p_a_start
      "Start value of pressure at port b"
  final parameter Medium.AbsolutePressure[n] ps_start=if n > 1 then linspace(
        p_a_start, p_b_start, n) else {(p_a_start + p_b_start)/2}
      "Start value of pressure";

  parameter Boolean use_T_start=true "Use T_start if true, otherwise h_start"

  parameter Medium.Temperature T_start=if use_T_start then system.T_start else
              Medium.temperature_phX(
        (p_a_start + p_b_start)/2,
        h_start,
        X_start) "Start value of temperature"
  parameter Medium.SpecificEnthalpy h_start=if use_T_start then
        Medium.specificEnthalpy_pTX(
        (p_a_start + p_b_start)/2,
        T_start,
        X_start) else Medium.h_default "Start value of specific enthalpy"
  parameter Medium.MassFraction X_start[Medium.nX] = Medium.X_default
      "Start value of mass fractions m_i/m"
  parameter Medium.ExtraProperty C_start[Medium.nC](
       quantity=Medium.extraPropertiesNames) = Medium.C_default
      "Start value of trace substances"

  // Total quantities
  SI.Energy[n] Us "Internal energy of fluid";
  SI.Mass[n] ms "Fluid mass";
  SI.Mass[n,Medium.nXi] mXis "Substance mass";
  SI.Mass[n,Medium.nC] mCs "Trace substance mass";
  // C need to be added here because unlike for Xi, which has medium[:].Xi,
  // there is no variable medium[:].C
  SI.Mass[n,Medium.nC] mCs_scaled "Scaled trace substance mass";
  Medium.ExtraProperty Cs[n, Medium.nC] "Trace substance mixture content";

  Medium.BaseProperties[n] mediums(
    each preferredMediumStates=true,
    p(start=ps_start),
    each h(start=h_start),
    each T(start=T_start),
    each Xi(start=X_start[1:Medium.nXi]));

  //Source terms, have to be defined by an extending model (to zero if not used)
  Medium.MassFlowRate[n] mb_flows "Mass flow rate, source or sink";
  Medium.MassFlowRate[n,Medium.nXi] mbXi_flows
      "Independent mass flow rates, source or sink";
  Medium.ExtraPropertyFlowRate[n,Medium.nC] mbC_flows
      "Trace substance mass flow rates, source or sink";
  SI.EnthalpyFlowRate[n] Hb_flows "Enthalpy flow rate, source or sink";
  SI.HeatFlowRate[n] Qb_flows "Heat flow rate, source or sink";
  SI.Power[n] Wb_flows "Mechanical power, p*der(V) etc.";

  protected
  parameter Boolean initialize_p = not Medium.singleState
      "= true to set up initial equations for pressure";

equation
  assert(not (energyDynamics<>Dynamics.SteadyState and massDynamics==Dynamics.SteadyState) or Medium.singleState,
         "Bad combination of dynamics options and Medium not conserving mass if fluidVolumes are fixed.");

  // Total quantities
  for i in 1:n loop
    ms[i] =fluidVolumes[i]*mediums[i].d;
    mXis[i, :] = ms[i]*mediums[i].Xi;
    mCs[i, :]  = ms[i]*Cs[i, :];
    Us[i] = ms[i]*mediums[i].u;
  end for;

  // Energy and mass balances
  if energyDynamics == Dynamics.SteadyState then
    for i in 1:n loop
      0 = Hb_flows[i] + Wb_flows[i] + Qb_flows[i];
    end for;
  else
    for i in 1:n loop
      der(Us[i]) = Hb_flows[i] + Wb_flows[i] + Qb_flows[i];
    end for;
  end if;
  if massDynamics == Dynamics.SteadyState then
    for i in 1:n loop
      0 = mb_flows[i];
    end for;
  else
    for i in 1:n loop
      der(ms[i]) = mb_flows[i];
    end for;
  end if;
  if substanceDynamics == Dynamics.SteadyState then
    for i in 1:n loop
      zeros(Medium.nXi) = mbXi_flows[i, :];
    end for;
  else
    for i in 1:n loop
      der(mXis[i, :]) = mbXi_flows[i, :];
    end for;
  end if;
  if traceDynamics == Dynamics.SteadyState then
    for i in 1:n loop
      zeros(Medium.nC)  = mbC_flows[i, :];
    end for;
  else
    for i in 1:n loop
      der(mCs_scaled[i, :])  = mbC_flows[i, :]./Medium.C_nominal;
      mCs[i, :] = mCs_scaled[i, :].*Medium.C_nominal;
    end for;
  end if;

initial equation
  // initialization of balances
  if energyDynamics == Dynamics.FixedInitial then
    /*
    if use_T_start then
      mediums.T = fill(T_start, n);
    else
      mediums.h = fill(h_start, n);
    end if;
    */
    if Medium.ThermoStates == IndependentVariables.ph or
       Medium.ThermoStates == IndependentVariables.phX then
       mediums.h = fill(h_start, n);
    else
       mediums.T = fill(T_start, n);
    end if;

  elseif energyDynamics == Dynamics.SteadyStateInitial then
    /*
    if use_T_start then
      der(mediums.T) = zeros(n);
    else
      der(mediums.h) = zeros(n);
    end if;
    */
    if Medium.ThermoStates == IndependentVariables.ph or
       Medium.ThermoStates == IndependentVariables.phX then
       der(mediums.h) = zeros(n);
    else
       der(mediums.T) = zeros(n);
    end if;
  end if;

  if massDynamics == Dynamics.FixedInitial then
    if initialize_p then
      mediums.p = ps_start;
    end if;
  elseif massDynamics == Dynamics.SteadyStateInitial then
    if initialize_p then
      der(mediums.p) = zeros(n);
    end if;
  end if;

  if substanceDynamics == Dynamics.FixedInitial then
    mediums.Xi = fill(X_start[1:Medium.nXi], n);
  elseif substanceDynamics == Dynamics.SteadyStateInitial then
    for i in 1:n loop
      der(mediums[i].Xi) = zeros(Medium.nXi);
    end for;
  end if;

  if traceDynamics == Dynamics.FixedInitial then
    Cs = fill(C_start[1:Medium.nC], n);
  elseif traceDynamics == Dynamics.SteadyStateInitial then
    for i in 1:n loop
      der(mCs[i,:])      = zeros(Medium.nC);
    end for;
  end if;
end PartialDistributedVolume;

      partial model PartialDistributedFlow
    "Base class for a distributed momentum balance"

        outer Modelica.Fluid.System system "System properties";

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

        parameter Boolean allowFlowReversal = system.allowFlowReversal
      "= true to allow flow reversal, false restricts to design direction (m_flows >= zeros(m))"

        // Discretization
        parameter Integer m=1 "Number of flow segments";

        // Inputs provided to the flow model
        input SI.Length[m] pathLengths "Lengths along flow path";

        // Variables defined by momentum model
        Medium.MassFlowRate[m] m_flows(
           each min=if allowFlowReversal then -Modelica.Constants.inf else 0,
           each start = m_flow_start,
           each stateSelect = if momentumDynamics == Types.Dynamics.SteadyState then StateSelect.default else
                                     StateSelect.prefer)
      "Mass flow rates between states";

        // Parameters
        parameter Modelica.Fluid.Types.Dynamics momentumDynamics=system.momentumDynamics
      "Formulation of momentum balance"

        parameter Medium.MassFlowRate m_flow_start=system.m_flow_start
      "Start value of mass flow rates"

        // Total quantities
        SI.Momentum[m] Is "Momenta of flow segments";

        // Source terms and forces to be defined by an extending model (zero if not used)
        SI.Force[m] Ib_flows "Flow of momentum across boundaries";
        SI.Force[m] Fs_p "Pressure forces";
        SI.Force[m] Fs_fg "Friction and gravity forces";

      equation
        // Total quantities
        Is = {m_flows[i]*pathLengths[i] for i in 1:m};

        // Momentum balances
        if momentumDynamics == Types.Dynamics.SteadyState then
          zeros(m) = Ib_flows - Fs_p - Fs_fg;
        else
          der(Is) = Ib_flows - Fs_p - Fs_fg;
        end if;

      initial equation
        if momentumDynamics == Types.Dynamics.FixedInitial then
          m_flows = fill(m_flow_start, m);
        elseif momentumDynamics == Types.Dynamics.SteadyStateInitial then
          der(m_flows) = zeros(m);
        end if;
      end PartialDistributedFlow;

  partial model PartialPressureLoss
    "Base flow model for pressure loss functions with the same area at port_a and at port_b"
    extends Modelica.Fluid.Interfaces.PartialTwoPortTransport;
  protected
    parameter Medium.ThermodynamicState state_dp_small=Medium.setState_pTX(
                         Medium.reference_p,
                         Medium.reference_T,
                         Medium.reference_X) "Medium state to compute dp_small";
    Medium.Density d_a
      "Density at port_a when fluid is flowing from port_a to port_b";
    Medium.Density d_b
      "If allowFlowReversal=true then density at port_b when fluid is flowing from port_b to port_a else d_a";
    Medium.DynamicViscosity eta_a
      "Dynamic viscosity at port_a when fluid is flowing from port_a to port_b";
    Medium.DynamicViscosity eta_b
      "If allowFlowReversal=true then dynamic viscosity at port_b when fluid is flowing from port_b to port_a else eta_a";
  equation
    // Isenthalpic state transformation (no storage and no loss of energy)
    port_a.h_outflow = inStream(port_b.h_outflow);
    port_b.h_outflow = inStream(port_a.h_outflow);

    // Medium properties
    d_a   = Medium.density(state_a);
    eta_a = Medium.dynamicViscosity(state_a);
    if allowFlowReversal then
      d_b   = Medium.density(state_b);
      eta_b = Medium.dynamicViscosity(state_b);
    else
      d_b   = d_a;
      eta_b = eta_a;
    end if;

  end PartialPressureLoss;

end Interfaces;