Devices for conveying fluid

within Modelica.Fluid;
package Pipes "Devices for conveying fluid"
    extends Modelica.Icons.VariantsPackage;

  model StaticPipe "Basic pipe flow model without storage of mass or energy"

    // extending PartialStraightPipe
    extends Modelica.Fluid.Pipes.BaseClasses.PartialStraightPipe;

    // 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"
    parameter Medium.MassFlowRate m_flow_start = system.m_flow_start
      "Start value for mass flow rate"

    FlowModel flowModel(
            redeclare package Medium = Medium,
            final n=2,
            states={Medium.setState_phX(port_a.p, inStream(port_a.h_outflow), inStream(port_a.Xi_outflow)),
                   Medium.setState_phX(port_b.p, inStream(port_b.h_outflow), inStream(port_b.Xi_outflow))},
            vs={port_a.m_flow/Medium.density(flowModel.states[1])/flowModel.crossAreas[1],
                -port_b.m_flow/Medium.density(flowModel.states[2])/flowModel.crossAreas[2]}/nParallel,
            final momentumDynamics=Types.Dynamics.SteadyState,
            final allowFlowReversal=allowFlowReversal,
            final p_a_start=p_a_start,
            final p_b_start=p_b_start,
            final m_flow_start=m_flow_start,
            final nParallel=nParallel,
            final pathLengths={length},
            final crossAreas={crossArea, crossArea},
            final dimensions={4*crossArea/perimeter, 4*crossArea/perimeter},
            final roughnesses={roughness, roughness},
            final dheights={height_ab},
            final g=system.g) "Flow model"
  equation
    // Mass balance
    port_a.m_flow = flowModel.m_flows[1];
    0 = port_a.m_flow + port_b.m_flow;
    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);

    // Energy balance, considering change of potential energy
    // Wb_flow = v*A*dpdx + v*F_fric
    //         = m_flow/d/A * (A*dpdx + A*pressureLoss.dp_fg - F_grav)
    //         = m_flow/d/A * (-A*g*height_ab*d)
    //         = -m_flow*g*height_ab
    port_b.h_outflow = inStream(port_a.h_outflow) - system.g*height_ab;
    port_a.h_outflow = inStream(port_b.h_outflow) + system.g*height_ab;
  end StaticPipe;

  model DynamicPipe "Dynamic pipe model with storage of mass and energy"

    import Modelica.Fluid.Types.ModelStructure;

    // extending PartialStraightPipe
    extends Modelica.Fluid.Pipes.BaseClasses.PartialStraightPipe(
      final port_a_exposesState = (modelStructure == ModelStructure.av_b) or (modelStructure == ModelStructure.av_vb),
      final port_b_exposesState = (modelStructure == ModelStructure.a_vb) or (modelStructure == ModelStructure.av_vb));

    // extending PartialTwoPortFlow
    extends BaseClasses.PartialTwoPortFlow(
      final lengths=fill(length/n, n),
      final crossAreas=fill(crossArea, n),
      final dimensions=fill(4*crossArea/perimeter, n),
      final roughnesses=fill(roughness, n),
      final dheights=height_ab*dxs);

    // Wall heat transfer
    parameter Boolean use_HeatTransfer = false
      "= true to use the HeatTransfer model"
    replaceable model HeatTransfer =
        Modelica.Fluid.Pipes.BaseClasses.HeatTransfer.IdealFlowHeatTransfer
      constrainedby
      Modelica.Fluid.Pipes.BaseClasses.HeatTransfer.PartialFlowHeatTransfer
      "Wall heat transfer"
    Interfaces.HeatPorts_a[nNodes] heatPorts if use_HeatTransfer

    HeatTransfer heatTransfer(
      redeclare package Medium = Medium,
      final n=n,
      final nParallel=nParallel,
      final surfaceAreas=perimeter*lengths,
      final lengths=lengths,
      final dimensions=dimensions,
      final roughnesses=roughnesses,
      final states=mediums.state,
      final vs = vs,
      final use_k = use_HeatTransfer) "Heat transfer model"
    final parameter Real[n] dxs = lengths/sum(lengths) "Normalized lengths";
  equation
    Qb_flows = heatTransfer.Q_flows;
    // Wb_flow = v*A*dpdx + v*F_fric
    //         = v*A*dpdx + v*A*flowModel.dp_fg - v*A*dp_grav
    if n == 1 or useLumpedPressure then
      Wb_flows = dxs * ((vs*dxs)*(crossAreas*dxs)*((port_b.p - port_a.p) + sum(flowModel.dps_fg) - system.g*(dheights*mediums.d)))*nParallel;
    else
      if modelStructure == ModelStructure.av_vb then
        Wb_flows[1] = vs[1]*crossAreas[1]*((mediums[2].p - mediums[1].p)/(if n==2 then 2 else 1.5) + flowModel.dps_fg[1]/(if n==2 then 2 else 1.5) - system.g*dheights[1]*mediums[1].d)*nParallel;
        Wb_flows[2:n-1] = {vs[i]*crossAreas[i]*((mediums[i].p - mediums[i-1].p)/(if i==2 then 3 else 2) + (mediums[i+1].p - mediums[i].p)/(if i==n-1 then 3 else 2) + flowModel.dps_fg[i-1]/(if i==2 then 3 else 2) + flowModel.dps_fg[i]/(if i==n-1 then 3 else 2) - system.g*dheights[i]*mediums[i].d) for i in 2:n-1}*nParallel;
        Wb_flows[n] = vs[n]*crossAreas[n]*((mediums[n].p - mediums[n-1].p)/(if n==2 then 2 else 1.5) + flowModel.dps_fg[n-1]/(if n==2 then 2 else 1.5) - system.g*dheights[n]*mediums[n].d)*nParallel;
      elseif modelStructure == ModelStructure.av_b then
        Wb_flows[1:n-1] = {vs[i]*crossAreas[i]*((mediums[i+1].p - mediums[i].p) + flowModel.dps_fg[i] - system.g*dheights[i]*mediums[i].d) for i in 1:n-1}*nParallel;
        Wb_flows[n] = vs[n]*crossAreas[n]*((port_b.p - mediums[n].p) + flowModel.dps_fg[n] - system.g*dheights[n]*mediums[n].d)*nParallel;
      elseif modelStructure == ModelStructure.a_vb then
        Wb_flows[1] = vs[1]*crossAreas[1]*((mediums[1].p - port_a.p) + flowModel.dps_fg[1] - system.g*dheights[1]*mediums[1].d)*nParallel;
        Wb_flows[2:n] = {vs[i]*crossAreas[i]*((mediums[i].p - mediums[i-1].p) + flowModel.dps_fg[i] - system.g*dheights[i]*mediums[i].d) for i in 2:n}*nParallel;
      elseif modelStructure == ModelStructure.a_v_b then
        Wb_flows[1] = vs[1]*crossAreas[1]*(((mediums[1].p + mediums[2].p)/2 - port_a.p) + flowModel.dps_fg[1] + flowModel.dps_fg[2]/2 - system.g*dheights[1]*mediums[1].d)*nParallel;
        Wb_flows[2:n-1] = {vs[i]*crossAreas[i]*((mediums[i+1].p - mediums[i-1].p)/2 + (flowModel.dps_fg[i] + flowModel.dps_fg[i+1])/2 - system.g*dheights[i]*mediums[i].d) for i in 2:n-1}*nParallel;
        Wb_flows[n] = vs[n]*crossAreas[n]*((port_b.p - (mediums[n-1].p + mediums[n].p)/2) + flowModel.dps_fg[n]/2 + flowModel.dps_fg[n+1] - system.g*dheights[n]*mediums[n].d)*nParallel;
      else
        assert(false, "Unknown model structure");
      end if;
    end if;

    connect(heatPorts, heatTransfer.heatPorts)
  end DynamicPipe;

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

    partial model PartialStraightPipe "Base class for straight pipe models"
      extends Modelica.Fluid.Interfaces.PartialTwoPort;

      // Geometry

      // Note: define nParallel as Real to support inverse calculations
      parameter Real nParallel(min=1)=1 "Number of identical parallel pipes"
      parameter SI.Length length "Length"
      parameter Boolean isCircular=true
        "= true, if cross sectional area is circular"
      parameter SI.Diameter diameter "Diameter of circular pipe"
      parameter SI.Area crossArea=Modelica.Constants.pi*diameter*diameter/4
        "Inner cross section area"
      parameter SI.Length perimeter(min=0)=Modelica.Constants.pi*diameter
        "Inner perimeter"
      parameter Modelica.Fluid.Types.Roughness roughness=2.5e-5
        "Average height of surface asperities (default: smooth steel pipe)"
      final parameter SI.Volume V=crossArea*length*nParallel "Volume size";

      // Static head
      parameter SI.Length height_ab=0 "Height(port_b) - Height(port_a)"

      // Pressure loss
      replaceable model FlowModel =
        Modelica.Fluid.Pipes.BaseClasses.FlowModels.DetailedPipeFlow
        constrainedby
        Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialStaggeredFlowModel
        "Wall friction, gravity, momentum flow"

    equation
      assert(length >= height_ab, "Parameter length must be greater or equal height_ab.");
    end PartialStraightPipe;

    partial model PartialTwoPortFlow "Base class for distributed flow models"

      import Modelica.Fluid.Types.ModelStructure;

      // extending PartialTwoPort
      extends Modelica.Fluid.Interfaces.PartialTwoPort(
        final port_a_exposesState = (modelStructure == ModelStructure.av_b) or (modelStructure == ModelStructure.av_vb),
        final port_b_exposesState = (modelStructure == ModelStructure.a_vb) or (modelStructure == ModelStructure.av_vb));

      // distributed volume model
      extends Modelica.Fluid.Interfaces.PartialDistributedVolume(
        final n = nNodes,
        final fluidVolumes = {crossAreas[i]*lengths[i] for i in 1:n}*nParallel);

      // Geometry parameters
      parameter Real nParallel(min=1)=1
        "Number of identical parallel flow devices"
      parameter SI.Length[n] lengths "Lengths of flow segments"
      parameter SI.Area[n] crossAreas "Cross flow areas of flow segments"
      parameter SI.Length[n] dimensions "Hydraulic diameters of flow segments"
      parameter Modelica.Fluid.Types.Roughness[n] roughnesses
        "Average heights of surface asperities"

      // Static head
      parameter SI.Length[n] dheights=zeros(n)
        "Differences in heights of flow segments"

      // Assumptions
      parameter Types.Dynamics momentumDynamics=system.momentumDynamics
        "Formulation of momentum balances"

      // Initialization
      parameter Medium.MassFlowRate m_flow_start = system.m_flow_start
        "Start value for mass flow rate"

      // Discretization
      parameter Integer nNodes(min=1)=2 "Number of discrete flow volumes"

      parameter Types.ModelStructure modelStructure=Types.ModelStructure.av_vb
        "Determines whether flow or volume models are present at the ports"

      parameter Boolean useLumpedPressure=false
        "= true to lump pressure states together"
      final parameter Integer nFM=if useLumpedPressure then nFMLumped else nFMDistributed
        "Number of flow models in flowModel";
      final parameter Integer nFMDistributed=if modelStructure==Types.ModelStructure.a_v_b then n+1 else if (modelStructure==Types.ModelStructure.a_vb or modelStructure==Types.ModelStructure.av_b) then n else n-1 "Number of distributed flow models";
      final parameter Integer nFMLumped=if modelStructure==Types.ModelStructure.a_v_b then 2 else 1 "Number of lumped flow models";
      final parameter Integer iLumped=integer(n/2)+1
        "Index of control volume with representative state if useLumpedPressure"

      // Advanced model options
      parameter Boolean useInnerPortProperties=false
        "= true to take port properties for flow models from internal control volumes"
      Medium.ThermodynamicState state_a
        "State defined by volume outside port_a";
      Medium.ThermodynamicState state_b
        "State defined by volume outside port_b";
      Medium.ThermodynamicState[nFM+1] statesFM
        "State vector for flowModel model";

      // Pressure loss model
      replaceable model FlowModel =
        Modelica.Fluid.Pipes.BaseClasses.FlowModels.DetailedPipeFlow
        constrainedby
        Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialStaggeredFlowModel
        "Wall friction, gravity, momentum flow"
      FlowModel flowModel(
              redeclare package Medium = Medium,
              final n=nFM+1,
              final states=statesFM,
              final vs=vsFM,
              final momentumDynamics=momentumDynamics,
              final allowFlowReversal=allowFlowReversal,
              final p_a_start=p_a_start,
              final p_b_start=p_b_start,
              final m_flow_start=m_flow_start,
              final nParallel=nParallel,
              final pathLengths=pathLengths,
              final crossAreas=crossAreasFM,
              final dimensions=dimensionsFM,
              final roughnesses=roughnessesFM,
              final dheights=dheightsFM,
              final g=system.g) "Flow model"

      // Flow quantities
      Medium.MassFlowRate[n+1] m_flows(
         each min=if allowFlowReversal then -Modelica.Constants.inf else 0,
         each start=m_flow_start)
        "Mass flow rates of fluid across segment boundaries";
      Medium.MassFlowRate[n+1, Medium.nXi] mXi_flows
        "Independent mass flow rates across segment boundaries";
      Medium.MassFlowRate[n+1, Medium.nC] mC_flows
        "Trace substance mass flow rates across segment boundaries";
      Medium.EnthalpyFlowRate[n+1] H_flows
        "Enthalpy flow rates of fluid across segment boundaries";

      SI.Velocity[n] vs = {0.5*(m_flows[i] + m_flows[i+1])/mediums[i].d/crossAreas[i] for i in 1:n}/nParallel
        "Mean velocities in flow segments";

      // Model structure dependent flow geometry
    protected
      SI.Length[nFM] pathLengths "Lengths along flow path";
      SI.Length[nFM] dheightsFM "Differences in heights between flow segments";
      SI.Area[nFM+1] crossAreasFM "Cross flow areas of flow segments";
      SI.Velocity[nFM+1] vsFM "Mean velocities in flow segments";
      SI.Length[nFM+1] dimensionsFM "Hydraulic diameters of flow segments";
      Modelica.Fluid.Types.Roughness[nFM+1] roughnessesFM "Average heights of surface asperities";

    equation
      assert(nNodes > 1 or modelStructure <> ModelStructure.av_vb,
         "nNodes needs to be at least 2 for modelStructure av_vb, as flow model disappears otherwise!");
      // staggered grid discretization of geometry for flowModel, depending on modelStructure
      if useLumpedPressure then
        if modelStructure <> ModelStructure.a_v_b then
          pathLengths[1] = sum(lengths);
          dheightsFM[1] = sum(dheights);
          if n == 1 then
            crossAreasFM[1:2] = {crossAreas[1], crossAreas[1]};
            dimensionsFM[1:2] = {dimensions[1], dimensions[1]};
            roughnessesFM[1:2] = {roughnesses[1], roughnesses[1]};
          else // n > 1
            crossAreasFM[1:2] = {sum(crossAreas[1:iLumped-1])/(iLumped-1), sum(crossAreas[iLumped:n])/(n-iLumped+1)};
            dimensionsFM[1:2] = {sum(dimensions[1:iLumped-1])/(iLumped-1), sum(dimensions[iLumped:n])/(n-iLumped+1)};
            roughnessesFM[1:2] = {sum(roughnesses[1:iLumped-1])/(iLumped-1), sum(roughnesses[iLumped:n])/(n-iLumped+1)};
          end if;
        else
          if n == 1 then
            pathLengths[1:2] = {lengths[1]/2, lengths[1]/2};
            dheightsFM[1:2] = {dheights[1]/2, dheights[1]/2};
            crossAreasFM[1:3] = {crossAreas[1], crossAreas[1], crossAreas[1]};
            dimensionsFM[1:3] = {dimensions[1], dimensions[1], dimensions[1]};
            roughnessesFM[1:3] = {roughnesses[1], roughnesses[1], roughnesses[1]};
          else // n > 1
            pathLengths[1:2] = {sum(lengths[1:iLumped-1]), sum(lengths[iLumped:n])};
            dheightsFM[1:2] = {sum(dheights[1:iLumped-1]), sum(dheights[iLumped:n])};
            crossAreasFM[1:3] = {sum(crossAreas[1:iLumped-1])/(iLumped-1), sum(crossAreas)/n, sum(crossAreas[iLumped:n])/(n-iLumped+1)};
            dimensionsFM[1:3] = {sum(dimensions[1:iLumped-1])/(iLumped-1), sum(dimensions)/n, sum(dimensions[iLumped:n])/(n-iLumped+1)};
            roughnessesFM[1:3] = {sum(roughnesses[1:iLumped-1])/(iLumped-1), sum(roughnesses)/n, sum(roughnesses[iLumped:n])/(n-iLumped+1)};
          end if;
        end if;
      else
        if modelStructure == ModelStructure.av_vb then
          //nFM = n-1
          if n == 2 then
            pathLengths[1] = lengths[1] + lengths[2];
            dheightsFM[1] = dheights[1] + dheights[2];
          else
            pathLengths[1:n-1] = cat(1, {lengths[1] + 0.5*lengths[2]}, 0.5*(lengths[2:n-2] + lengths[3:n-1]), {0.5*lengths[n-1] + lengths[n]});
            dheightsFM[1:n-1] = cat(1, {dheights[1] + 0.5*dheights[2]}, 0.5*(dheights[2:n-2] + dheights[3:n-1]), {0.5*dheights[n-1] + dheights[n]});
          end if;
          crossAreasFM[1:n] = crossAreas;
          dimensionsFM[1:n] = dimensions;
          roughnessesFM[1:n] = roughnesses;
        elseif modelStructure == ModelStructure.av_b then
          //nFM = n
          pathLengths[1:n] = lengths;
          dheightsFM[1:n] = dheights;
          crossAreasFM[1:n+1] = cat(1, crossAreas[1:n], {crossAreas[n]});
          dimensionsFM[1:n+1] = cat(1, dimensions[1:n], {dimensions[n]});
          roughnessesFM[1:n+1] = cat(1, roughnesses[1:n], {roughnesses[n]});
        elseif modelStructure == ModelStructure.a_vb then
          //nFM = n
          pathLengths[1:n] = lengths;
          dheightsFM[1:n] = dheights;
          crossAreasFM[1:n+1] = cat(1, {crossAreas[1]}, crossAreas[1:n]);
          dimensionsFM[1:n+1] = cat(1, {dimensions[1]}, dimensions[1:n]);
          roughnessesFM[1:n+1] = cat(1, {roughnesses[1]}, roughnesses[1:n]);
        elseif modelStructure == ModelStructure.a_v_b then
          //nFM = n+1;
          pathLengths[1:n+1] = cat(1, {0.5*lengths[1]}, 0.5*(lengths[1:n-1] + lengths[2:n]), {0.5*lengths[n]});
          dheightsFM[1:n+1] = cat(1, {0.5*dheights[1]}, 0.5*(dheights[1:n-1] + dheights[2:n]), {0.5*dheights[n]});
          crossAreasFM[1:n+2] = cat(1, {crossAreas[1]}, crossAreas[1:n], {crossAreas[n]});
          dimensionsFM[1:n+2] = cat(1, {dimensions[1]}, dimensions[1:n], {dimensions[n]});
          roughnessesFM[1:n+2] = cat(1, {roughnesses[1]}, roughnesses[1:n], {roughnesses[n]});
        else
          assert(false, "Unknown model structure");
        end if;
      end if;

      // Source/sink terms for mass and energy balances
      for i in 1:n loop
        mb_flows[i] = m_flows[i] - m_flows[i + 1];
        mbXi_flows[i, :] = mXi_flows[i, :] - mXi_flows[i + 1, :];
        mbC_flows[i, :]  = mC_flows[i, :]  - mC_flows[i + 1, :];
        Hb_flows[i] = H_flows[i] - H_flows[i + 1];
      end for;

      // Distributed flow quantities, upwind discretization
      for i in 2:n loop
        H_flows[i] = semiLinear(m_flows[i], mediums[i - 1].h, mediums[i].h);
        mXi_flows[i, :] = semiLinear(m_flows[i], mediums[i - 1].Xi, mediums[i].Xi);
        mC_flows[i, :]  = semiLinear(m_flows[i], Cs[i - 1, :],         Cs[i, :]);
      end for;
      H_flows[1] = semiLinear(port_a.m_flow, inStream(port_a.h_outflow), mediums[1].h);
      H_flows[n + 1] = -semiLinear(port_b.m_flow, inStream(port_b.h_outflow), mediums[n].h);
      mXi_flows[1, :] = semiLinear(port_a.m_flow, inStream(port_a.Xi_outflow), mediums[1].Xi);
      mXi_flows[n + 1, :] = -semiLinear(port_b.m_flow, inStream(port_b.Xi_outflow), mediums[n].Xi);
      mC_flows[1, :] = semiLinear(port_a.m_flow, inStream(port_a.C_outflow), Cs[1, :]);
      mC_flows[n + 1, :] = -semiLinear(port_b.m_flow, inStream(port_b.C_outflow), Cs[n, :]);

      // Boundary conditions
      port_a.m_flow    = m_flows[1];
      port_b.m_flow    = -m_flows[n + 1];
      port_a.h_outflow = mediums[1].h;
      port_b.h_outflow = mediums[n].h;
      port_a.Xi_outflow = mediums[1].Xi;
      port_b.Xi_outflow = mediums[n].Xi;
      port_a.C_outflow = Cs[1, :];
      port_b.C_outflow = Cs[n, :];
      // The two equations below are not correct if C is stored in volumes.
      // C should be treated the same way as Xi.
      //port_a.C_outflow = inStream(port_b.C_outflow);
      //port_b.C_outflow = inStream(port_a.C_outflow);

      if useInnerPortProperties and n > 0 then
        state_a = Medium.setState_phX(port_a.p, mediums[1].h, mediums[1].Xi);
        state_b = Medium.setState_phX(port_b.p, mediums[n].h, mediums[n].Xi);
      else
        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));
      end if;

      // staggered grid discretization for flowModel, depending on modelStructure
      if useLumpedPressure then
        if modelStructure <> ModelStructure.av_vb then
          // all pressures are equal
          fill(mediums[1].p, n-1) = mediums[2:n].p;
        elseif n > 2 then
          // need two pressures
          fill(mediums[1].p, iLumped-2) = mediums[2:iLumped-1].p;
          fill(mediums[n].p, n-iLumped) = mediums[iLumped:n-1].p;
        end if;
        if modelStructure == ModelStructure.av_vb then
          port_a.p = mediums[1].p;
          statesFM[1] = mediums[1].state;
          m_flows[iLumped] = flowModel.m_flows[1];
          statesFM[2] = mediums[n].state;
          port_b.p = mediums[n].p;
          vsFM[1] = vs[1:iLumped-1]*lengths[1:iLumped-1]/sum(lengths[1:iLumped-1]);
          vsFM[2] = vs[iLumped:n]*lengths[iLumped:n]/sum(lengths[iLumped:n]);
        elseif modelStructure == ModelStructure.av_b then
          port_a.p = mediums[1].p;
          statesFM[1] = mediums[iLumped].state;
          statesFM[2] = state_b;
          m_flows[n+1] = flowModel.m_flows[1];
          vsFM[1] = vs*lengths/sum(lengths);
          vsFM[2] = m_flows[n+1]/Medium.density(state_b)/crossAreas[n]/nParallel;
        elseif modelStructure == ModelStructure.a_vb then
          m_flows[1] = flowModel.m_flows[1];
          statesFM[1] = state_a;
          statesFM[2] = mediums[iLumped].state;
          port_b.p = mediums[n].p;
          vsFM[1] = m_flows[1]/Medium.density(state_a)/crossAreas[1]/nParallel;
          vsFM[2] = vs*lengths/sum(lengths);
        elseif modelStructure == ModelStructure.a_v_b then
          m_flows[1] = flowModel.m_flows[1];
          statesFM[1] = state_a;
          statesFM[2] = mediums[iLumped].state;
          statesFM[3] = state_b;
          m_flows[n+1] = flowModel.m_flows[2];
          vsFM[1] = m_flows[1]/Medium.density(state_a)/crossAreas[1]/nParallel;
          vsFM[2] = vs*lengths/sum(lengths);
          vsFM[3] = m_flows[n+1]/Medium.density(state_b)/crossAreas[n]/nParallel;
        else
          assert(false, "Unknown model structure");
        end if;
      else
        if modelStructure == ModelStructure.av_vb then
          //nFM = n-1
          statesFM[1:n] = mediums[1:n].state;
          m_flows[2:n] = flowModel.m_flows[1:n-1];
          vsFM[1:n] = vs;
          port_a.p = mediums[1].p;
          port_b.p = mediums[n].p;
        elseif modelStructure == ModelStructure.av_b then
          //nFM = n
          statesFM[1:n] = mediums[1:n].state;
          statesFM[n+1] = state_b;
          m_flows[2:n+1] = flowModel.m_flows[1:n];
          vsFM[1:n] = vs;
          vsFM[n+1] = m_flows[n+1]/Medium.density(state_b)/crossAreas[n]/nParallel;
          port_a.p = mediums[1].p;
        elseif modelStructure == ModelStructure.a_vb then
          //nFM = n
          statesFM[1] = state_a;
          statesFM[2:n+1] = mediums[1:n].state;
          m_flows[1:n] = flowModel.m_flows[1:n];
          vsFM[1] = m_flows[1]/Medium.density(state_a)/crossAreas[1]/nParallel;
          vsFM[2:n+1] = vs;
          port_b.p = mediums[n].p;
        elseif modelStructure == ModelStructure.a_v_b then
          //nFM = n+1
          statesFM[1] = state_a;
          statesFM[2:n+1] = mediums[1:n].state;
          statesFM[n+2] = state_b;
          m_flows[1:n+1] = flowModel.m_flows[1:n+1];
          vsFM[1] = m_flows[1]/Medium.density(state_a)/crossAreas[1]/nParallel;
          vsFM[2:n+1] = vs;
          vsFM[n+2] = m_flows[n+1]/Medium.density(state_b)/crossAreas[n]/nParallel;
        else
          assert(false, "Unknown model structure");
        end if;
      end if;
    end PartialTwoPortFlow;

    package FlowModels
      "Flow models for pipes, including wall friction, static head and momentum flow"
      extends Modelica.Icons.Package;
          partial model PartialStaggeredFlowModel
        "Base class for momentum balances in flow models"

            //
            // Internal interface
            // (not exposed to GUI; needs to be hard coded when using this model
            //
            replaceable package Medium =
              Modelica.Media.Interfaces.PartialMedium "Medium in the component"

            parameter Integer n=2 "Number of discrete flow volumes"

            // Inputs
            input Medium.ThermodynamicState[n] states
          "Thermodynamic states along design flow";
            input SI.Velocity[n] vs
          "Mean velocities of fluid flow";

            // Geometry parameters and inputs
            parameter Real nParallel
          "Number of identical parallel flow devices"

            input SI.Area[n] crossAreas
          "Cross flow areas at segment boundaries";
            input SI.Length[n] dimensions
          "Characteristic dimensions for fluid flow (diameters for pipe flow)";
            input Modelica.Fluid.Types.Roughness[n] roughnesses
          "Average height of surface asperities";

            // Static head
            input SI.Length[n-1] dheights
          "Height(states[2:n]) - Height(states[1:n-1])";

            parameter SI.Acceleration g=system.g
          "Constant gravity acceleration"

            // Assumptions
            parameter Boolean allowFlowReversal=system.allowFlowReversal
          "= true, if flow reversal is enabled, otherwise restrict flow to design direction (states[1] -> states[n+1])"
            parameter Modelica.Fluid.Types.Dynamics momentumDynamics=system.momentumDynamics
          "Formulation of momentum balance"

            // Initialization
            parameter Medium.MassFlowRate m_flow_start=system.m_flow_start
          "Start value of mass flow rates"
            parameter Medium.AbsolutePressure p_a_start
          "Start value for p[1] at design inflow"
            parameter Medium.AbsolutePressure p_b_start
          "Start value for p[n+1] at design outflow"

            //
            // Implementation of momentum balance
            //
            extends Modelica.Fluid.Interfaces.PartialDistributedFlow(
                       final m = n-1);

            // Advanced parameters
            parameter Boolean useUpstreamScheme = true
          "= false to average upstream and downstream properties across flow segments"

            parameter Boolean use_Ib_flows = momentumDynamics <> Types.Dynamics.SteadyState
          "= true to consider differences in flow of momentum through boundaries"

            // Variables
            Medium.Density[n] rhos = if use_rho_nominal then fill(rho_nominal, n) else Medium.density(states);
            Medium.Density[n-1] rhos_act "Actual density per segment";

            Medium.DynamicViscosity[n] mus = if use_mu_nominal then fill(mu_nominal, n) else Medium.dynamicViscosity(states);
            Medium.DynamicViscosity[n-1] mus_act "Actual viscosity per segment";

            // Variables
            SI.Pressure[n-1] dps_fg(each start = (p_a_start - p_b_start)/(n-1))
          "Pressure drop between states";

            // Reynolds Number
            parameter SI.ReynoldsNumber Re_turbulent = 4000
          "Start of turbulent regime, depending on type of flow device";
            parameter Boolean show_Res = false
          "= true, if Reynolds numbers are included for plotting"
            SI.ReynoldsNumber[n] Res=Modelica.Fluid.Pipes.BaseClasses.CharacteristicNumbers.ReynoldsNumber(
                vs,
                rhos,
                mus,
                dimensions) if show_Res "Reynolds numbers";
            Medium.MassFlowRate[n-1] m_flows_turbulent=
                {nParallel*(crossAreas[i] + crossAreas[i+1])/(dimensions[i] + dimensions[i+1])*mus_act[i]*Re_turbulent for i in 1:n-1} if
                   show_Res "Start of turbulent flow";
      protected
            parameter Boolean use_rho_nominal = false
          "= true, if rho_nominal is used, otherwise computed from medium"
            parameter SI.Density rho_nominal = Medium.density_pTX(Medium.p_default, Medium.T_default, Medium.X_default)
          "Nominal density (e.g., rho_liquidWater = 995, rho_air = 1.2)"

            parameter Boolean use_mu_nominal = false
          "= true, if mu_nominal is used, otherwise computed from medium"
            parameter SI.DynamicViscosity mu_nominal = Medium.dynamicViscosity(
                                                           Medium.setState_pTX(
                                                               Medium.p_default, Medium.T_default, Medium.X_default))
          "Nominal dynamic viscosity (e.g., mu_liquidWater = 1e-3, mu_air = 1.8e-5)"

          equation
            if not allowFlowReversal then
              rhos_act = rhos[1:n-1];
              mus_act = mus[1:n-1];
            elseif not useUpstreamScheme then
              rhos_act = 0.5*(rhos[1:n-1] + rhos[2:n]);
              mus_act = 0.5*(mus[1:n-1] + mus[2:n]);
            else
              for i in 1:n-1 loop
                rhos_act[i] = noEvent(if m_flows[i] > 0 then rhos[i] else rhos[i+1]);
                mus_act[i] = noEvent(if m_flows[i] > 0 then mus[i] else mus[i+1]);
              end for;
            end if;

            if use_Ib_flows then
              Ib_flows = nParallel*{rhos[i]*vs[i]*vs[i]*crossAreas[i] - rhos[i+1]*vs[i+1]*vs[i+1]*crossAreas[i+1] for i in 1:n-1};
              // alternatively use densities rhos_act of actual streams, together with mass flow rates,
              // not conserving momentum if fluid density changes between flow segments:
              //Ib_flows = {((rhos[i]*vs[i])^2*crossAreas[i] - (rhos[i+1]*vs[i+1])^2*crossAreas[i+1])/rhos_act[i] for i in 1:n-1};
            else
              Ib_flows = zeros(n-1);
            end if;

            Fs_p = nParallel*{0.5*(crossAreas[i]+crossAreas[i+1])*(Medium.pressure(states[i+1])-Medium.pressure(states[i])) for i in 1:n-1};

            // Note: the equation is written for dps_fg instead of Fs_fg to help the translator
            dps_fg = {Fs_fg[i]/nParallel*2/(crossAreas[i]+crossAreas[i+1]) for i in 1:n-1};
          end PartialStaggeredFlowModel;

      model NominalLaminarFlow
        "NominalLaminarFlow: Linear laminar flow for given nominal values"
        extends
          Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialStaggeredFlowModel(
           use_mu_nominal=not show_Res);

        // Operational conditions
        parameter SI.AbsolutePressure dp_nominal "Nominal pressure loss";
        parameter SI.MassFlowRate m_flow_nominal
          "Mass flow rate for dp_nominal";

        // Inverse parameterization assuming pipe flow and WallFriction.Laminar
        // Laminar.massFlowRate_dp:
        //   m_flow = dp*pi*diameter^4*d/(128*length*mu);
        SI.Length[n-1] pathLengths_nominal=
          {(dp_nominal/(n-1)-g*dheights[i])*Modelica.Constants.pi*((dimensions[i]+dimensions[i+1])/2)^4*rhos_act[i]/(128*mus_act[i])/
           (m_flow_nominal/nParallel) for i in 1:n-1} if show_Res
          "Lengths resulting from given nominal values for circular tubes";

      equation
        // linear pressure loss
        if  not allowFlowReversal or use_rho_nominal or not useUpstreamScheme then
          dps_fg = {g*dheights[i]*rhos_act[i] for i in 1:n-1} + dp_nominal/(n-1)/m_flow_nominal*m_flows;
        else
          dps_fg = {g*dheights[i]*(if m_flows[i] > 0 then rhos[i] else rhos[i+1]) for i in 1:n-1} + dp_nominal/(n-1)/m_flow_nominal*m_flows;
        end if;
      end NominalLaminarFlow;

          partial model PartialGenericPipeFlow
        "GenericPipeFlow: Pipe flow pressure loss and gravity with replaceable WallFriction package"

            parameter Boolean from_dp = momentumDynamics >= Types.Dynamics.SteadyStateInitial
          "= true, use m_flow = f(dp), otherwise dp = f(m_flow)"

            extends
          Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialStaggeredFlowModel(
           final Re_turbulent=4000);

            replaceable package WallFriction =
              Modelica.Fluid.Pipes.BaseClasses.WallFriction.Detailed
                constrainedby
          Modelica.Fluid.Pipes.BaseClasses.WallFriction.PartialWallFriction
          "Wall friction model"

            input SI.Length[n-1] pathLengths_internal
          "pathLengths used internally; to be defined by extending class";
            input SI.ReynoldsNumber[n-1] Res_turbulent_internal = Re_turbulent*ones(n-1)
          "Re_turbulent used internally; to be defined by extending class";

            // Parameters
            parameter SI.AbsolutePressure dp_nominal
          "Nominal pressure loss (only for nominal models)";
            parameter SI.MassFlowRate m_flow_nominal "Nominal mass flow rate";
            parameter SI.MassFlowRate m_flow_small = if system.use_eps_Re then system.eps_m_flow*m_flow_nominal else system.m_flow_small
          "Within regularization if |m_flows| < m_flow_small (may be wider for large discontinuities in static head)"

      protected
            parameter SI.AbsolutePressure dp_small(start = 1, fixed = false)
          "Within regularization if |dp| < dp_small (may be wider for large discontinuities in static head)"
            final parameter Boolean constantPressureLossCoefficient=
               use_rho_nominal and (use_mu_nominal or not WallFriction.use_mu)
          "= true, if the pressure loss does not depend on fluid states"
            final parameter Boolean continuousFlowReversal=
               (not useUpstreamScheme)
               or constantPressureLossCoefficient
               or not allowFlowReversal
          "= true, if the pressure loss is continuous around zero flow"

            SI.Length[n-1] diameters = 0.5*(dimensions[1:n-1] + dimensions[2:n])
          "Mean diameters between segments";
            SI.AbsolutePressure dp_fric_nominal=
              sum(WallFriction.pressureLoss_m_flow(
                             m_flow_nominal/nParallel,
                             rho_nominal,
                             rho_nominal,
                             mu_nominal,
                             mu_nominal,
                             pathLengths_internal,
                             diameters,
                             (crossAreas[1:n-1]+crossAreas[2:n])/2,
                             (roughnesses[1:n-1]+roughnesses[2:n])/2,
                             m_flow_small/nParallel,
                             Res_turbulent_internal))
          "Pressure loss for nominal conditions";

          initial equation
            // initialize dp_small from flow model
            if system.use_eps_Re then
              dp_small = dp_fric_nominal/m_flow_nominal*m_flow_small;
            else
              dp_small = system.dp_small;
            end if;

          equation
            for i in 1:n-1 loop
              assert(m_flows[i] > -m_flow_small or allowFlowReversal, "Reversing flow occurs even though allowFlowReversal is false");
            end for;

            if continuousFlowReversal then
              // simple regularization
              if from_dp and not WallFriction.dp_is_zero then
                m_flows = homotopy(
                  actual=  WallFriction.massFlowRate_dp(
                             dps_fg - {g*dheights[i]*rhos_act[i] for i in 1:n-1},
                             rhos_act,
                             rhos_act,
                             mus_act,
                             mus_act,
                             pathLengths_internal,
                             diameters,
                             (crossAreas[1:n-1]+crossAreas[2:n])/2,
                             (roughnesses[1:n-1]+roughnesses[2:n])/2,
                             dp_small/(n-1),
                             Res_turbulent_internal)*nParallel,
                  simplified=  m_flow_nominal/dp_nominal*(dps_fg - g*dheights*rho_nominal));
              else
                dps_fg = homotopy(
                  actual=  WallFriction.pressureLoss_m_flow(
                             m_flows/nParallel,
                             rhos_act,
                             rhos_act,
                             mus_act,
                             mus_act,
                             pathLengths_internal,
                             diameters,
                             (crossAreas[1:n-1]+crossAreas[2:n])/2,
                             (roughnesses[1:n-1]+roughnesses[2:n])/2,
                             m_flow_small/nParallel,
                             Res_turbulent_internal) + {g*dheights[i]*rhos_act[i] for i in 1:n-1},
                  simplified=  dp_nominal/m_flow_nominal*m_flows + g*dheights*rho_nominal);
              end if;
            else
              // regularization for discontinuous flow reversal and static head
              if from_dp and not WallFriction.dp_is_zero then
                m_flows = homotopy(
                  actual=  WallFriction.massFlowRate_dp_staticHead(
                             dps_fg,
                             rhos[1:n-1],
                             rhos[2:n],
                             mus[1:n-1],
                             mus[2:n],
                             pathLengths_internal,
                             diameters,
                             g*dheights,
                             (crossAreas[1:n-1]+crossAreas[2:n])/2,
                             (roughnesses[1:n-1]+roughnesses[2:n])/2,
                             dp_small/(n-1),
                             Res_turbulent_internal)*nParallel,
                  simplified=  m_flow_nominal/dp_nominal*(dps_fg - g*dheights*rho_nominal));
              else
                dps_fg = homotopy(
                  actual=  WallFriction.pressureLoss_m_flow_staticHead(
                             m_flows/nParallel,
                             rhos[1:n-1],
                             rhos[2:n],
                             mus[1:n-1],
                             mus[2:n],
                             pathLengths_internal,
                             diameters,
                             g*dheights,
                             (crossAreas[1:n-1]+crossAreas[2:n])/2,
                             (roughnesses[1:n-1]+roughnesses[2:n])/2,
                             m_flow_small/nParallel,
                             Res_turbulent_internal),
                  simplified=  dp_nominal/m_flow_nominal*m_flows + g*dheights*rho_nominal);
              end if;
            end if;
          end PartialGenericPipeFlow;

          model NominalTurbulentPipeFlow
        "NominalTurbulentPipeFlow: Quadratic turbulent flow in circular tubes for given nominal values"
            extends
          Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialGenericPipeFlow(
          redeclare package WallFriction =
              Modelica.Fluid.Pipes.BaseClasses.WallFriction.LaminarAndQuadraticTurbulent,
          use_mu_nominal=not show_Res,
          pathLengths_internal=pathLengths_nominal,
          useUpstreamScheme=false,
          Res_turbulent_internal=Res_turbulent_nominal);

            import Modelica.Constants.pi;

            parameter SI.MassFlowRate m_flow_turbulent(min=0) = if system.use_eps_Re then 0.1*m_flow_nominal else system.m_flow_small
          "Turbulent flow starting from |m_flows| > m_flow_turbulent (may be wider for large discontinuities in static head)"

            // variables for nominal pressure loss
            SI.Length[n-1] pathLengths_nominal
          "pathLengths resulting from nominal pressure loss and geometry";
            SI.ReynoldsNumber[n-1] Res_turbulent_nominal
          "Re_turbulent resulting from nominal turbulent flow and geometry";
            Real[n-1] ks_inv "Coefficient for quadratic flow";
            Real[n-1] zetas "Coefficient for quadratic flow";

            // Reynolds Number
            Medium.AbsolutePressure[n-1] dps_fg_turbulent(each min=0)=
                {(mus_act[i]*diameters[i]*pi/4)^2*Re_turbulent^2/(ks_inv[i]*rhos_act[i]) for i in 1:n-1} if
                   show_Res "Start of turbulent flow in circular tubes";

          initial equation
            for i in 1:n loop
              assert(abs(crossAreas[i] - pi/4*dimensions[i]^2) < 1e-10*crossAreas[i],
                     "NominalTurbulentPipeFlow model requires circular tubes");
            end for;

          equation
            // Inverse parameterization for WallFriction.QuadraticTurbulent
            // Note: the code should be shared with the WallFriction.QuadraticTurbulent model,
            //       but this required a re-design of the WallFriction interfaces ...
            //   zeta = (length_nominal/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
            //   k_inv = (pi*diameter*diameter)^2/(8*zeta);
            //   k = rho*k_inv "Factor in m_flow = sqrt(k*dp)";
            //   m_flow_turbulent = pi/4*diameter*mu*Re_turbulent;
            for i in 1:n-1 loop
              ks_inv[i] = (m_flow_nominal/nParallel)^2/((dp_nominal/(n-1)-g*dheights[i]*rhos_act[i]))/rhos_act[i];
              zetas[i] = (pi*diameters[i]*diameters[i])^2/(8*ks_inv[i]);
              pathLengths_nominal[i] =
                zetas[i]*diameters[i]*(2*Modelica.Math.log10(3.7 /((roughnesses[i]+roughnesses[i+1])/2/diameters[i])))^2;
              Res_turbulent_nominal[i] = m_flow_turbulent/nParallel / (pi/4*diameters[i]*mus_act[i]);
            end for;
          end NominalTurbulentPipeFlow;

          model TurbulentPipeFlow
        "TurbulentPipeFlow: Quadratic turbulent flow in circular tubes (using mu to regularize laminar region)"
            extends
          Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialGenericPipeFlow(
          redeclare package WallFriction =
              Modelica.Fluid.Pipes.BaseClasses.WallFriction.LaminarAndQuadraticTurbulent,
          use_mu_nominal=not show_Res,
          pathLengths_internal=pathLengths,
          dp_nominal(start=if system.use_eps_Re then 1 else 1e3*dp_small, fixed=not system.use_eps_Re),
          m_flow_nominal=if system.use_eps_Re then system.m_flow_nominal else 1e2*m_flow_small,
          Res_turbulent_internal = if use_Re then Re_turbulent*ones(n-1) else zeros(n-1));

            import Modelica.Constants.pi;

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

          initial equation
            for i in 1:n loop
              assert(abs(crossAreas[i] - pi/4*dimensions[i]^2) < 1e-10*crossAreas[i],
                     "NominalTurbulentPipeFlow model requires circular tubes");
            end for;
            // initialize dp_nominal from flow model
            if system.use_eps_Re then
              dp_nominal = dp_fric_nominal + g*sum(dheights)*rho_nominal;
            end if;
          end TurbulentPipeFlow;

          model DetailedPipeFlow
        "DetailedPipeFlow: Detailed characteristic for laminar and turbulent flow"
            extends
          Modelica.Fluid.Pipes.BaseClasses.FlowModels.PartialGenericPipeFlow(
          redeclare package WallFriction =
              Modelica.Fluid.Pipes.BaseClasses.WallFriction.Detailed,
          pathLengths_internal=pathLengths,
          dp_nominal(start=1, fixed=false),
          m_flow_nominal=if system.use_eps_Re then system.m_flow_nominal else 1e2*m_flow_small,
          Res_turbulent_internal = Re_turbulent*ones(n-1));

          initial equation
            // initialize dp_nominal from flow model
            if system.use_eps_Re then
              dp_nominal = dp_fric_nominal + g*sum(dheights)*rho_nominal;
            else
              dp_nominal = 1e3*dp_small;
            end if;
          end DetailedPipeFlow;

    end FlowModels;

  package HeatTransfer "Heat transfer for flow models"
    extends Modelica.Icons.Package;
    partial model PartialFlowHeatTransfer
        "Base class for any pipe heat transfer correlation"
      extends Modelica.Fluid.Interfaces.PartialHeatTransfer;

      // Additional inputs provided to flow heat transfer model
      input SI.Velocity[n] vs "Mean velocities of fluid flow in segments";

      // Geometry parameters and inputs for flow heat transfer
      parameter Real nParallel "Number of identical parallel flow devices"
      input SI.Length[n] lengths "Lengths along flow path";
      input SI.Length[n] dimensions
          "Characteristic dimensions for fluid flow (diameter for pipe flow)";
      input Modelica.Fluid.Types.Roughness[n] roughnesses "Average heights of surface asperities";
    end PartialFlowHeatTransfer;

    model IdealFlowHeatTransfer
        "IdealHeatTransfer: Ideal heat transfer without thermal resistance"
      extends PartialFlowHeatTransfer;
    equation
      Ts = heatPorts.T;
    end IdealFlowHeatTransfer;

    model ConstantFlowHeatTransfer
        "ConstantHeatTransfer: Constant heat transfer coefficient"
      extends PartialFlowHeatTransfer;
      parameter SI.CoefficientOfHeatTransfer alpha0 "Heat transfer coefficient";
    equation
      Q_flows = {alpha0*surfaceAreas[i]*(heatPorts[i].T - Ts[i])*nParallel for i in 1:n};
    end ConstantFlowHeatTransfer;

    partial model PartialPipeFlowHeatTransfer
        "Base class for pipe heat transfer correlation in terms of Nusselt number heat transfer in a circular pipe for laminar and turbulent one-phase flow"
      extends PartialFlowHeatTransfer;
      parameter SI.CoefficientOfHeatTransfer alpha0=100
          "Guess value for heat transfer coefficients";
      SI.CoefficientOfHeatTransfer[n] alphas(each start=alpha0)
          "Heat transfer coefficient";
      Real[n] Res "Reynolds numbers";
      Real[n] Prs "Prandtl numbers";
      Real[n] Nus "Nusselt numbers";
      Medium.Density[n] ds "Densities";
      Medium.DynamicViscosity[n] mus "Dynamic viscosities";
      Medium.ThermalConductivity[n] lambdas "Thermal conductivity";
      SI.Length[n] diameters = dimensions "Hydraulic diameters for pipe flow";
    equation
      ds=Medium.density(states);
      mus=Medium.dynamicViscosity(states);
      lambdas=Medium.thermalConductivity(states);
      Prs = Medium.prandtlNumber(states);
      Res = CharacteristicNumbers.ReynoldsNumber(vs, ds, mus, diameters);
      Nus = CharacteristicNumbers.NusseltNumber(alphas, diameters, lambdas);
      Q_flows={alphas[i]*surfaceAreas[i]*(heatPorts[i].T - Ts[i])*nParallel for i in 1:n};
    end PartialPipeFlowHeatTransfer;

    model LocalPipeFlowHeatTransfer
        "LocalPipeFlowHeatTransfer: Laminar and turbulent forced convection in pipes, local coefficients"
      extends PartialPipeFlowHeatTransfer;
      protected
      Real[n] Nus_turb "Nusselt number for turbulent flow";
      Real[n] Nus_lam "Nusselt number for laminar flow";
      Real Nu_1;
      Real[n] Nus_2;
      Real[n] Xis;
    equation
      Nu_1=3.66;
      for i in 1:n loop
       Nus_turb[i]=smooth(0,(Xis[i]/8)*abs(Res[i])*Prs[i]/(1+12.7*(Xis[i]/8)^0.5*(Prs[i]^(2/3)-1))*(1+1/3*(diameters[i]/lengths[i]/(if vs[i]>=0 then (i-0.5) else (n-i+0.5)))^(2/3)));
       Xis[i]=(1.8*Modelica.Math.log10(max(1e-10,Res[i]))-1.5)^(-2);
       Nus_lam[i]=(Nu_1^3+0.7^3+(Nus_2[i]-0.7)^3)^(1/3);
       Nus_2[i]=smooth(0,1.077*(abs(Res[i])*Prs[i]*diameters[i]/lengths[i]/(if vs[i]>=0 then (i-0.5) else (n-i+0.5)))^(1/3));
       Nus[i]=Modelica.Media.Air.MoistAir.Utilities.spliceFunction(Nus_turb[i], Nus_lam[i], Res[i]-6150, 3850);
      end for;
    end LocalPipeFlowHeatTransfer;
  end HeatTransfer;

    package CharacteristicNumbers "Functions to compute characteristic numbers"
      extends Modelica.Icons.Package;
      function ReynoldsNumber "Return Reynolds number from v, rho, mu, D"
        extends Modelica.Icons.Function;

        input SI.Velocity v "Mean velocity of fluid flow";
        input SI.Density rho "Fluid density";
        input SI.DynamicViscosity mu "Dynamic (absolute) viscosity";
        input SI.Length D
          "Characteristic dimension (hydraulic diameter of pipes)";
        output SI.ReynoldsNumber Re "Reynolds number";
      algorithm
        Re := abs(v)*rho*D/mu;
      end ReynoldsNumber;

      function ReynoldsNumber_m_flow
        "Return Reynolds number from m_flow, mu, D, A"
        extends Modelica.Icons.Function;

        input SI.MassFlowRate m_flow "Mass flow rate";
        input SI.DynamicViscosity mu "Dynamic viscosity";
        input SI.Length D
          "Characteristic dimension (hydraulic diameter of pipes or orifices)";
        input SI.Area A = Modelica.Constants.pi/4*D*D
          "Cross sectional area of fluid flow";
        output SI.ReynoldsNumber Re "Reynolds number";
      algorithm
        Re := abs(m_flow)*D/A/mu;
      end ReynoldsNumber_m_flow;

      function NusseltNumber "Return Nusselt number"
        extends Modelica.Icons.Function;

        input SI.CoefficientOfHeatTransfer alpha "Coefficient of heat transfer";
        input SI.Length D "Characteristic dimension";
        input SI.ThermalConductivity lambda "Thermal conductivity";
        output SI.NusseltNumber Nu "Nusselt number";
      algorithm
        Nu := alpha*D/lambda;
      end NusseltNumber;
    end CharacteristicNumbers;

    package WallFriction
      "Different variants for pressure drops due to pipe wall friction"
      extends Modelica.Icons.Package;
      partial package PartialWallFriction
        "Partial wall friction characteristic (base package of all wall friction characteristics)"
        extends Modelica.Icons.Package;
        import Modelica.Constants.pi;

      // Constants to be set in subpackages
        constant Boolean use_mu = true
          "= true, if mu_a/mu_b are used in function, otherwise value is not used";
        constant Boolean use_roughness = true
          "= true, if roughness is used in function, otherwise value is not used";
        constant Boolean use_dp_small = true
          "= true, if dp_small is used in function, otherwise value is not used";
        constant Boolean use_m_flow_small = true
          "= true, if m_flow_small is used in function, otherwise value is not used";
        constant Boolean dp_is_zero = false
          "= true, if no wall friction is present, i.e., dp = 0 (function massFlowRate_dp() cannot be used)";
        constant Boolean use_Re_turbulent = true
          "= true, if Re_turbulent input is used in function, otherwise value is not used";

      // pressure loss characteristic functions
        replaceable partial function massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"
          extends Modelica.Icons.Function;

          input SI.Pressure dp "Pressure loss (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 (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
            "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input SI.Area crossArea = pi*diameter^2/4 "Inner cross section area";
          input Modelica.Fluid.Types.Roughness roughness = 2.5e-5
            "Absolute roughness of pipe, with a default for a smooth steel pipe (dummy if use_roughness = false)";
          input SI.AbsolutePressure dp_small = 1
            "Regularization of zero flow if |dp| < dp_small (dummy if use_dp_small = false)";
          input SI.ReynoldsNumber Re_turbulent = 4000
            "Turbulent flow if Re >= Re_turbulent (dummy if use_Re_turbulent = false)";

          output SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
        end massFlowRate_dp;

        replaceable partial function massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"
          extends Modelica.Icons.Function;

          input SI.Pressure dp "Pressure loss (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 (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
            "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input Real g_times_height_ab(unit="m2/s2")
            "Gravity times (Height(port_b) - Height(port_a))";
          input SI.Area crossArea = pi*diameter^2/4 "Inner cross section area";
          input Modelica.Fluid.Types.Roughness roughness = 2.5e-5
            "Absolute roughness of pipe, with a default for a smooth steel pipe (dummy if use_roughness = false)";
          input SI.AbsolutePressure dp_small=1
            "Regularization of zero flow if |dp| < dp_small (dummy if use_dp_small = false)";
          input SI.ReynoldsNumber Re_turbulent = 4000
            "Turbulent flow if Re >= Re_turbulent (dummy if use_Re_turbulent = false)";

          output SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
        end massFlowRate_dp_staticHead;

        replaceable partial function pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"
          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 SI.DynamicViscosity mu_a
            "Dynamic viscosity at port_a (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
            "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input SI.Area crossArea = pi*diameter^2/4 "Inner cross section area";
          input Modelica.Fluid.Types.Roughness roughness = 2.5e-5
            "Absolute roughness of pipe, with a default for a smooth steel pipe (dummy if use_roughness = false)";
          input SI.MassFlowRate m_flow_small = 0.01
            "Regularization of zero flow if |m_flow| < m_flow_small (dummy if use_m_flow_small = false)";
          input SI.ReynoldsNumber Re_turbulent = 4000
            "Turbulent flow if Re >= Re_turbulent (dummy if use_Re_turbulent = false)";

          output SI.Pressure dp "Pressure loss (dp = port_a.p - port_b.p)";
        end pressureLoss_m_flow;

        replaceable partial function pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"
                  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 SI.DynamicViscosity mu_a
            "Dynamic viscosity at port_a (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
            "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input Real g_times_height_ab(unit="m2/s2")
            "Gravity times (Height(port_b) - Height(port_a))";
          input SI.Area crossArea = pi*diameter^2/4 "Inner cross section area";
          input Modelica.Fluid.Types.Roughness roughness = 2.5e-5
            "Absolute roughness of pipe, with a default for a smooth steel pipe (dummy if use_roughness = false)";
          input SI.MassFlowRate m_flow_small = 0.01
            "Regularization of zero flow if |m_flow| < m_flow_small (dummy if use_m_flow_small = false)";
          input SI.ReynoldsNumber Re_turbulent = 4000
            "Turbulent flow if Re >= Re_turbulent (dummy if use_Re_turbulent = false)";

          output SI.Pressure dp "Pressure loss (dp = port_a.p - port_b.p)";
        end pressureLoss_m_flow_staticHead;
      end PartialWallFriction;

      package NoFriction "No pipe wall friction, no static head"
        extends Modelica.Icons.Package;

        extends PartialWallFriction(
                  final use_mu = false,
                  final use_roughness = false,
                  final use_dp_small = false,
                  final use_m_flow_small = false,
                  final dp_is_zero = true,
                  final use_Re_turbulent = false);

        redeclare function extends massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"

        algorithm
          assert(false, "Function cannot be used. Use instead
function pressureLoss_m_flow (option: from_dp=false) for WallFriction.NoFriction.");
        end massFlowRate_dp;

        redeclare function extends pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"

        algorithm
          dp := 0;
        end pressureLoss_m_flow;

        redeclare function extends massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"

        algorithm
          assert(false, "Function cannot be used. Use instead
function pressureLoss_m_flow (option: from_dp=false) for WallFriction.NoFriction.");
        end massFlowRate_dp_staticHead;

        redeclare function extends pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"

        /* To include only static head:
protected
  Real dp_grav_a = g_times_height_ab*rho_a
    "Static head if mass flows in design direction (a to b)";
  Real dp_grav_b = g_times_height_ab*rho_b
    "Static head if mass flows against design direction (b to a)";
*/
        algorithm
        //  dp := Utilities.regStep(m_flow, dp_grav_a, dp_grav_a, m_flow_small);
          dp := 0;
          assert(abs(g_times_height_ab) < Modelica.Constants.small,
           "WallFriction.NoFriction does not consider static head and cannot be used with height_ab<>0!");
        end pressureLoss_m_flow_staticHead;
      end NoFriction;

      package Laminar
        "Pipe wall friction for laminar flow in circular tubes (linear correlation)"

        extends PartialWallFriction(
                  final use_mu = true,
                  final use_roughness = false,
                  final use_dp_small = false,
                  final use_m_flow_small = false,
                  final use_Re_turbulent = false);

        redeclare function extends massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"

        algorithm
          // WARNING: The following equation is only correct for circular tubes!
          m_flow :=dp*Modelica.Constants.pi*diameter^4*(rho_a + rho_b)/(128*length*(mu_a + mu_b));
        end massFlowRate_dp;

        redeclare function extends pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"

        algorithm
          // WARNING: The following equation is only correct for circular tubes!
          dp := m_flow*128*length*(mu_a + mu_b)/(Modelica.Constants.pi*diameter^4*(rho_a + rho_b));
        end pressureLoss_m_flow;

        redeclare function extends massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"

          // WARNING: The following equation is only correct for circular tubes!
        protected
          Real k0inv = Modelica.Constants.pi*diameter^4/(128*length)
            "Constant factor";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real dm_flow_ddp_fric_a = k0inv*rho_a/mu_a
            "Slope of mass flow rate over dp if flow in design direction (a to b)";
          Real dm_flow_ddp_fric_b = k0inv*rho_b/mu_b
            "Slope of mass flow rate over dp if flow against design direction (b to a)";

          Real dp_a=max(dp_grav_a,dp_grav_b)+dp_small
            "Upper end of regularization domain of the m_flow(dp) relation";
          Real dp_b=min(dp_grav_a,dp_grav_b)-dp_small
            "Lower end of regularization domain of the m_flow(dp) relation";

          SI.MassFlowRate m_flow_a
            "Value at upper end of regularization domain";
          SI.MassFlowRate m_flow_b
            "Value at lower end of regularization domain";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real dm_flow_ddp_fric_zero;
        algorithm
        /*
  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/d * m_flow
     = k0 * mu/d * m_flow
  k0 = 2*c0/(pi*D_Re^3)
*/

          if dp>=dp_a then
            // Positive flow outside regularization
            m_flow := dm_flow_ddp_fric_a*(dp-dp_grav_a);
          elseif dp<=dp_b then
            // Negative flow outside regularization
            m_flow := dm_flow_ddp_fric_b*(dp-dp_grav_b);
          else
            m_flow_a := dm_flow_ddp_fric_a*(dp_a - dp_grav_a);
            m_flow_b := dm_flow_ddp_fric_b*(dp_b - dp_grav_b);

            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of m_flow is overwritten later)
            (m_flow, dm_flow_ddp_fric_zero) := Utilities.regFun3(dp_zero, dp_b, dp_a, m_flow_b, m_flow_a, dm_flow_ddp_fric_b, dm_flow_ddp_fric_a);
            // Do regularization
            if dp>dp_zero then
              m_flow := Utilities.regFun3(dp, dp_zero, dp_a, m_flow_zero, m_flow_a, dm_flow_ddp_fric_zero, dm_flow_ddp_fric_a);
            else
              m_flow := Utilities.regFun3(dp, dp_b, dp_zero, m_flow_b, m_flow_zero, dm_flow_ddp_fric_b, dm_flow_ddp_fric_zero);
            end if;
          end if;
        /*
  m_flow := if dp<dp_b then dm_flow_ddp_b*(dp-dp_grav_b) else
              (if dp>dp_a then dm_flow_ddp_a*(dp-dp_grav_a) else
                Modelica.Fluid.Utilities.regFun3(dp, dp_b, dp_a, dm_flow_ddp_b*(dp_b - dp_grav_b), dm_flow_ddp_a*(dp_a - dp_grav_a), dm_flow_ddp_b, dm_flow_ddp_a));
*/
        end massFlowRate_dp_staticHead;

        redeclare function extends pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"

          // WARNING: The following equation is only correct for circular tubes!
        protected
          Real k0 = 128*length/(Modelica.Constants.pi*diameter^4)
            "Constant factor";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real ddp_dm_flow_a = k0*mu_a/rho_a
            "Slope of dp over mass flow rate if flow in design direction (a to b)";
          Real ddp_dm_flow_b = k0*mu_b/rho_b
            "Slope of dp over mass flow rate if flow against design direction (b to a)";

          SI.MassFlowRate m_flow_a=if dp_grav_a >= dp_grav_b then m_flow_small else m_flow_small + (dp_grav_b-dp_grav_a)/ddp_dm_flow_a
            "Upper end of regularization domain of the dp(m_flow) relation";
          SI.MassFlowRate m_flow_b=if dp_grav_a >= dp_grav_b then -m_flow_small else -m_flow_small - (dp_grav_b - dp_grav_a)/ddp_dm_flow_b
            "Lower end of regularization domain of the dp(m_flow) relation";

          SI.Pressure dp_a "Value at upper end of regularization domain";
          SI.Pressure dp_b "Value at lower end of regularization domain";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real ddp_dm_flow_zero;
        algorithm
        /*
  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/d * m_flow
     = k0 * mu/d * m_flow
  k0 = 2*c0/(pi*D_Re^3)
*/

          if m_flow>=m_flow_a then
            // Positive flow outside regularization
            dp := (ddp_dm_flow_a*m_flow + dp_grav_a);
          elseif m_flow<=m_flow_b then
            // Negative flow outside regularization
            dp := (ddp_dm_flow_b*m_flow + dp_grav_b);
          else
            // Regularization parameters
            dp_a := ddp_dm_flow_a*m_flow_a + dp_grav_a;
            dp_b := ddp_dm_flow_b*m_flow_b + dp_grav_b;
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of dp is overwritten later)
            (dp, ddp_dm_flow_zero) := Utilities.regFun3(m_flow_zero, m_flow_b, m_flow_a, dp_b, dp_a, ddp_dm_flow_b, ddp_dm_flow_a);
            // Do regularization
            if m_flow>m_flow_zero then
              dp := Utilities.regFun3(m_flow, m_flow_zero, m_flow_a, dp_zero, dp_a, ddp_dm_flow_zero, ddp_dm_flow_a);
            else
              dp := Utilities.regFun3(m_flow, m_flow_b, m_flow_zero, dp_b, dp_zero, ddp_dm_flow_b, ddp_dm_flow_zero);
            end if;
          end if;
        end pressureLoss_m_flow_staticHead;
      end Laminar;

      package QuadraticTurbulent
        "Pipe wall friction for turbulent flow in circular tubes (simple characteristic, mu not used)"
        import Modelica.Constants.pi;

        extends PartialWallFriction(
                  final use_mu = false,
                  final use_roughness = true,
                  final use_dp_small = true,
                  final use_m_flow_small = true,
                  final use_Re_turbulent = false);

        redeclare function extends massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"
          import Modelica.Math;
        protected
          Real zeta;
          Real k_inv;
        algorithm
          /*
   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
  */
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");
          zeta  := (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          // WARNING: The following equation is only correct for circular tubes!
          k_inv := (pi*diameter*diameter)^2/(8*zeta);
          m_flow := Modelica.Fluid.Utilities.regRoot2(dp, dp_small, rho_a*k_inv, rho_b*k_inv);
        end massFlowRate_dp;

        redeclare function extends pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"
          import Modelica.Constants.pi;
          import Modelica.Math;

        protected
          Real zeta;
          Real k;
        algorithm
          /*
   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
  */
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");
          zeta := (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          // WARNING: The following equation is only correct for circular tubes!
          k    := 8*zeta/(pi*diameter*diameter)^2;
          dp   := Modelica.Fluid.Utilities.regSquare2(m_flow, m_flow_small, k/rho_a, k/rho_b);
        end pressureLoss_m_flow;

        redeclare function extends massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"
          import Modelica.Math;
          import Modelica.Constants.pi;
        protected
          Real zeta = (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          // WARNING: The following equation is only correct for circular tubes!
          Real k_inv = (pi*diameter*diameter)^2/(8*zeta);

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real k1 = rho_a*k_inv "Factor in m_flow =  sqrt(k1*(dp-dp_grav_a))";
          Real k2 = rho_b*k_inv "Factor in m_flow = -sqrt(k2*|dp-dp_grav_b|)";

          Real dp_a=max(dp_grav_a,dp_grav_b)+dp_small
            "Upper end of regularization domain of the m_flow(dp) relation";
          Real dp_b=min(dp_grav_a,dp_grav_b)-dp_small
            "Lower end of regularization domain of the m_flow(dp) relation";

          SI.MassFlowRate m_flow_a
            "Value at upper end of regularization domain";
          SI.MassFlowRate m_flow_b
            "Value at lower end of regularization domain";

          SI.MassFlowRate dm_flow_ddp_fric_a
            "Derivative at upper end of regularization domain";
          SI.MassFlowRate dm_flow_ddp_fric_b
            "Derivative at lower end of regularization domain";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real dm_flow_ddp_fric_zero;
        algorithm
          /*
   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
  */
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");

          if dp>=dp_a then
            // Positive flow outside regularization
            m_flow := sqrt(k1*(dp-dp_grav_a));
          elseif dp<=dp_b then
            // Negative flow outside regularization
            m_flow := -sqrt(k2*abs(dp-dp_grav_b));
          else
            m_flow_a := sqrt(k1*(dp_a - dp_grav_a));
            m_flow_b := -sqrt(k2*abs(dp_b - dp_grav_b));

            dm_flow_ddp_fric_a := k1/(2*sqrt(k1*(dp_a - dp_grav_a)));
            dm_flow_ddp_fric_b := k2/(2*sqrt(k2*abs(dp_b - dp_grav_b)));
        /*  dm_flow_ddp_fric_a := if abs(dp_a - dp_grav_a)>0 then k1/(2*sqrt(k1*(dp_a - dp_grav_a))) else  Modelica.Constants.inf);
    dm_flow_ddp_fric_b := if abs(dp_b - dp_grav_b)>0 then k2/(2*sqrt(k2*abs(dp_b - dp_grav_b))) else Modelica.Constants.inf; */

            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of m_flow is overwritten later)
            (m_flow, dm_flow_ddp_fric_zero) := Utilities.regFun3(dp_zero, dp_b, dp_a, m_flow_b, m_flow_a, dm_flow_ddp_fric_b, dm_flow_ddp_fric_a);
            // Do regularization
            if dp>dp_zero then
              m_flow := Utilities.regFun3(dp, dp_zero, dp_a, m_flow_zero, m_flow_a, dm_flow_ddp_fric_zero, dm_flow_ddp_fric_a);
            else
              m_flow := Utilities.regFun3(dp, dp_b, dp_zero, m_flow_b, m_flow_zero, dm_flow_ddp_fric_b, dm_flow_ddp_fric_zero);
            end if;
          end if;
        end massFlowRate_dp_staticHead;

        redeclare function extends pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"
          import Modelica.Math;
          import Modelica.Constants.pi;
        protected
          Real zeta = (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          // WARNING: The following equation is only correct for circular tubes!
          Real k = 8*zeta/(pi*diameter*diameter)^2;

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real k1 = k/rho_a "If m_flow >= 0 then dp = k1*m_flow^2 + dp_grav_a";
          Real k2 = k/rho_b "If m_flow < 0 then dp = -k2*m_flow^2 + dp_grav_b";

          SI.MassFlowRate m_flow_a=if dp_grav_a >= dp_grav_b then m_flow_small else m_flow_small + sqrt((dp_grav_b - dp_grav_a)/k1)
            "Upper end of regularization domain of the dp(m_flow) relation";
          SI.MassFlowRate m_flow_b=if dp_grav_a >= dp_grav_b then -m_flow_small else -m_flow_small - sqrt((dp_grav_b - dp_grav_a)/k2)
            "Lower end of regularization domain of the dp(m_flow) relation";

          SI.Pressure dp_a "Value at upper end of regularization domain";
          SI.Pressure dp_b "Value at lower end of regularization domain";

          Real ddp_dm_flow_a
            "Derivative of pressure drop with mass flow rate at m_flow_a";
          Real ddp_dm_flow_b
            "Derivative of pressure drop with mass flow rate at m_flow_b";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real ddp_dm_flow_zero;

        algorithm
          /*
   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
  */
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");

          if m_flow>=m_flow_a then
            // Positive flow outside regularization
            dp := (k1*m_flow^2 + dp_grav_a);
          elseif m_flow<=m_flow_b then
            // Negative flow outside regularization
            dp := (-k2*m_flow^2 + dp_grav_b);
          else
            // Regularization parameters
            dp_a := k1*m_flow_a^2 + dp_grav_a;
            ddp_dm_flow_a := 2*k1*m_flow_a;
            dp_b := -k2*m_flow_b^2 + dp_grav_b;
            ddp_dm_flow_b := -2*k2*m_flow_b;
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of dp is overwritten later)
            (dp, ddp_dm_flow_zero) := Utilities.regFun3(m_flow_zero, m_flow_b, m_flow_a, dp_b, dp_a, ddp_dm_flow_b, ddp_dm_flow_a);
            // Do regularization
            if m_flow>m_flow_zero then
              dp := Utilities.regFun3(m_flow, m_flow_zero, m_flow_a, dp_zero, dp_a, ddp_dm_flow_zero, ddp_dm_flow_a);
            else
              dp := Utilities.regFun3(m_flow, m_flow_b, m_flow_zero, dp_b, dp_zero, ddp_dm_flow_b, ddp_dm_flow_zero);
            end if;
          end if;
        end pressureLoss_m_flow_staticHead;
      end QuadraticTurbulent;

      package LaminarAndQuadraticTurbulent
        "Pipe wall friction for laminar and turbulent flow in circular tubes (simple characteristic)"

        extends PartialWallFriction(
                  final use_mu = true,
                  final use_roughness = true,
                  final use_dp_small = true,
                  final use_m_flow_small = true,
                  final use_Re_turbulent = true);

        import ln = Modelica.Math.log "Logarithm, base e";
        import Modelica.Math.log10 "Logarithm, base 10";
        import Modelica.Math.exp "Exponential function";
        import Modelica.Constants.pi;

        redeclare function extends massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"
          import Modelica.Math;
        protected
          Real zeta;
          Real k0;
          Real k_inv;
          Real yd0 "Derivative of m_flow=m_flow(dp) at zero";
          SI.AbsolutePressure dp_turbulent;
        algorithm
        /*
Turbulent region:
   // WARNING: The following equations are only correct for circular tubes!
   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:
   // WARNING: The following equation is only correct for circular tubes!
   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.
*/
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");
          // WARNING: The following equations are only correct for circular tubes!
          zeta   := (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          k0     := 128*length/(pi*diameter^4);
          k_inv  := (pi*diameter*diameter)^2/(8*zeta);
          yd0    := (rho_a + rho_b)/(k0*(mu_a + mu_b));
          dp_turbulent := max(((mu_a + mu_b)*diameter*pi/8)^2*Re_turbulent^2/(k_inv*(rho_a+rho_b)/2), dp_small);
          m_flow := Modelica.Fluid.Utilities.regRoot2(dp, dp_turbulent, rho_a*k_inv, rho_b*k_inv,
                                                      use_yd0=true, yd0=yd0);
        end massFlowRate_dp;

        redeclare function extends pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"
          import Modelica.Math;
          import Modelica.Constants.pi;

        protected
          Real zeta;
          Real k0;
          Real k;
          Real yd0 "Derivative of dp = f(m_flow) at zero";
          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)
*/
          assert(roughness > 1e-10,
                 "roughness > 0 required for quadratic turbulent wall friction characteristic");
          // WARNING: The following equations are only correct for circular tubes!
          zeta := (length/diameter)/(2*Math.log10(3.7 /(roughness/diameter)))^2;
          k0   := 128*length/(pi*diameter^4);
          k    := 8*zeta/(pi*diameter*diameter)^2;
          yd0  := k0*(mu_a + mu_b)/(rho_a + rho_b);
          m_flow_turbulent := max((pi/8)*diameter*(mu_a + mu_b)*Re_turbulent, m_flow_small);
          dp :=Modelica.Fluid.Utilities.regSquare2(m_flow, m_flow_turbulent, k/rho_a, k/rho_b,
                                                   use_yd0=true, yd0=yd0);
        end pressureLoss_m_flow;

        redeclare function extends massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"
          import Modelica.Math;

        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re1 = min(745*exp(if Delta <= 0.0065 then 1 else 0.0065/Delta), Re_turbulent)
            "Boundary between laminar regime and transition";
          SI.ReynoldsNumber Re2 = Re_turbulent
            "Boundary between transition and turbulent regime";

          SI.Pressure dp_a
            "Upper end of regularization domain of the m_flow(dp) relation";
          SI.Pressure dp_b
            "Lower end of regularization domain of the m_flow(dp) relation";

          SI.MassFlowRate m_flow_a
            "Value at upper end of regularization domain";
          SI.MassFlowRate m_flow_b
            "Value at lower end of regularization domain";

          SI.MassFlowRate dm_flow_ddp_fric_a
            "Derivative at upper end of regularization domain";
          SI.MassFlowRate dm_flow_ddp_fric_b
            "Derivative at lower end of regularization domain";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real dm_flow_ddp_fric_zero;
        algorithm
          assert(roughness > 1e-10,
            "roughness > 0 required for quadratic turbulent wall friction characteristic");

          dp_a := max(dp_grav_a, dp_grav_b)+dp_small;
          dp_b := min(dp_grav_a, dp_grav_b)-dp_small;

          if dp>=dp_a then
            // Positive flow outside regularization
            m_flow := Internal.m_flow_of_dp_fric(dp - dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
          elseif dp<=dp_b then
            // Negative flow outside regularization
            m_flow := Internal.m_flow_of_dp_fric(dp-dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
          else
            // Regularization parameters
            (m_flow_a, dm_flow_ddp_fric_a) := Internal.m_flow_of_dp_fric(dp_a-dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            (m_flow_b, dm_flow_ddp_fric_b) := Internal.m_flow_of_dp_fric(dp_b-dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of m_flow is overwritten later)
            (m_flow, dm_flow_ddp_fric_zero) := Utilities.regFun3(dp_zero, dp_b, dp_a, m_flow_b, m_flow_a, dm_flow_ddp_fric_b, dm_flow_ddp_fric_a);
            // Do regularization
            if dp>dp_zero then
              m_flow := Utilities.regFun3(dp, dp_zero, dp_a, m_flow_zero, m_flow_a, dm_flow_ddp_fric_zero, dm_flow_ddp_fric_a);
            else
              m_flow := Utilities.regFun3(dp, dp_b, dp_zero, m_flow_b, m_flow_zero, dm_flow_ddp_fric_b, dm_flow_ddp_fric_zero);
            end if;
          end if;
        end massFlowRate_dp_staticHead;

        redeclare function extends pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"
          import Modelica.Math;

        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re1 = min(745*exp(if Delta <= 0.0065 then 1 else 0.0065/Delta), Re_turbulent)
            "Boundary between laminar regime and transition";
          SI.ReynoldsNumber Re2 = Re_turbulent
            "Boundary between transition and turbulent regime";

          SI.MassFlowRate m_flow_a
            "Upper end of regularization domain of the dp(m_flow) relation";
          SI.MassFlowRate m_flow_b
            "Lower end of regularization domain of the dp(m_flow) relation";

          SI.Pressure dp_a "Value at upper end of regularization domain";
          SI.Pressure dp_b "Value at lower end of regularization domain";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real ddp_dm_flow_a
            "Derivative of pressure drop with mass flow rate at m_flow_a";
          Real ddp_dm_flow_b
            "Derivative of pressure drop with mass flow rate at m_flow_b";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real ddp_dm_flow_zero;

        algorithm
          assert(roughness > 1e-10,
            "roughness > 0 required for quadratic turbulent wall friction characteristic");

          m_flow_a := if dp_grav_a<dp_grav_b then
            Internal.m_flow_of_dp_fric(dp_grav_b - dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta)+m_flow_small else
            m_flow_small;
          m_flow_b := if dp_grav_a<dp_grav_b then
            Internal.m_flow_of_dp_fric(dp_grav_a - dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta)-m_flow_small else
            -m_flow_small;

          if m_flow>=m_flow_a then
            // Positive flow outside regularization
            dp := Internal.dp_fric_of_m_flow(m_flow, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta) + dp_grav_a;
          elseif m_flow<=m_flow_b then
            // Negative flow outside regularization
            dp := Internal.dp_fric_of_m_flow(m_flow, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta) + dp_grav_b;
          else
            // Regularization parameters
            (dp_a, ddp_dm_flow_a) := Internal.dp_fric_of_m_flow(m_flow_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            dp_a := dp_a + dp_grav_a "Adding dp_grav to dp_fric to get dp";
            (dp_b, ddp_dm_flow_b) := Internal.dp_fric_of_m_flow(m_flow_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            dp_b := dp_b + dp_grav_b "Adding dp_grav to dp_fric to get dp";
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of dp is overwritten later)
            (dp, ddp_dm_flow_zero) := Utilities.regFun3(m_flow_zero, m_flow_b, m_flow_a, dp_b, dp_a, ddp_dm_flow_b, ddp_dm_flow_a);
            // Do regularization
            if m_flow>m_flow_zero then
              dp := Utilities.regFun3(m_flow, m_flow_zero, m_flow_a, dp_zero, dp_a, ddp_dm_flow_zero, ddp_dm_flow_a);
            else
              dp := Utilities.regFun3(m_flow, m_flow_b, m_flow_zero, dp_b, dp_zero, ddp_dm_flow_b, ddp_dm_flow_zero);
            end if;
          end if;
        end pressureLoss_m_flow_staticHead;

        package Internal
          "Functions to calculate mass flow rate from friction pressure drop and vice versa"
          extends Modelica.Icons.InternalPackage;
          function m_flow_of_dp_fric
            "Calculate mass flow rate as function of pressure drop due to friction"
            extends Modelica.Icons.Function;

            input SI.Pressure dp_fric
              "Pressure loss due to friction (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 (dummy if use_mu = false)";
            input SI.DynamicViscosity mu_b
              "Dynamic viscosity at port_b (dummy if use_mu = false)";
            input SI.Length length "Length of pipe";
            input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
            input SI.Area crossArea "Inner cross section area";
            input SI.ReynoldsNumber Re1
              "Boundary between laminar regime and transition";
            input SI.ReynoldsNumber Re2
              "Boundary between transition and turbulent regime";
            input Real Delta(min=0) "Relative roughness";
            output SI.MassFlowRate m_flow
              "Mass flow rate from port_a to port_b";
            output Real dm_flow_ddp_fric
              "Derivative of mass flow rate with dp_fric";
          protected
            SI.DynamicViscosity mu "Upstream viscosity";
            SI.Density rho "Upstream density";

            Real zeta;
            Real k0;
            Real k_inv;
            Real dm_flow_ddp_laminar
              "Derivative of m_flow=m_flow(dp) in laminar regime";
            SI.AbsolutePressure dp_fric_turbulent
              "The turbulent region is: |dp_fric| >= dp_fric_turbulent, simple quadratic correlation";
            SI.AbsolutePressure dp_fric_laminar
              "The laminar region is: |dp_fric| <= dp_fric_laminar";
          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
   dp_fric_turbulent     =  k/rho *(D_Re*mu*pi/4)^2 * Re_turbulent^2

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)
*/
            // Determine upstream density and upstream viscosity
            if dp_fric >= 0 then
              rho := rho_a;
              mu  := mu_a;
            else
              rho := rho_b;
              mu  := mu_b;
            end if;
            // Quadratic turbulent
            // WARNING: The following equations are only correct for circular tubes!
            zeta := (length/diameter)/(2*log10(3.7/(Delta)))^2;
            k_inv := (pi*diameter*diameter)^2/(8*zeta);
            dp_fric_turbulent := sign(dp_fric)*(mu*diameter*pi/4)^2*Re2^2/(k_inv*rho);

            // Laminar
            k0 := 128*length/(pi*diameter^4);
            dm_flow_ddp_laminar := rho/(k0*mu);
            dp_fric_laminar := sign(dp_fric)*pi*k0*mu^2/rho*diameter/4*Re1;

            if abs(dp_fric) > abs(dp_fric_turbulent) then
              m_flow := sign(dp_fric)*sqrt(rho*k_inv*abs(dp_fric));
              dm_flow_ddp_fric := 0.5*rho*k_inv*(rho*k_inv*abs(dp_fric))^(-0.5);
            elseif abs(dp_fric) < abs(dp_fric_laminar) then
              m_flow := dm_flow_ddp_laminar*dp_fric;
              dm_flow_ddp_fric := dm_flow_ddp_laminar;
            else
              // Preliminary testing seems to indicate that the log-log transform is not required here
              (m_flow,dm_flow_ddp_fric) := Utilities.cubicHermite_withDerivative(
                dp_fric, dp_fric_laminar, dp_fric_turbulent, dm_flow_ddp_laminar*dp_fric_laminar,
                sign(dp_fric_turbulent)*sqrt(rho*k_inv*abs(dp_fric_turbulent)), dm_flow_ddp_laminar,
                if abs(dp_fric_turbulent)>0 then 0.5*rho*k_inv*(rho*k_inv*abs(dp_fric_turbulent))^(-0.5) else Modelica.Constants.inf);
            end if;
          end m_flow_of_dp_fric;

          function dp_fric_of_m_flow
            "Calculate pressure drop due to friction as function of mass flow rate"
            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 SI.DynamicViscosity mu_a
              "Dynamic viscosity at port_a (dummy if use_mu = false)";
            input SI.DynamicViscosity mu_b
              "Dynamic viscosity at port_b (dummy if use_mu = false)";
            input SI.Length length "Length of pipe";
            input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
            input SI.Area crossArea "Inner cross section area";
            input SI.ReynoldsNumber Re1
              "Boundary between laminar regime and transition";
            input SI.ReynoldsNumber Re2
              "Boundary between transition and turbulent regime";
            input Real Delta(min=0) "Relative roughness";
            output SI.Pressure dp_fric
              "Pressure loss due to friction (dp_fric = port_a.p - port_b.p - dp_grav)";
            output Real ddp_fric_dm_flow
              "Derivative of pressure drop with mass flow rate";
          protected
            SI.DynamicViscosity mu "Upstream viscosity";
            SI.Density rho "Upstream density";
            Real zeta;
            Real k0;
            Real k;
            Real ddp_fric_dm_flow_laminar
              "Derivative of dp_fric = f(m_flow) at zero";
            SI.MassFlowRate m_flow_turbulent
              "The turbulent region is: |m_flow| >= m_flow_turbulent";
            SI.MassFlowRate m_flow_laminar
              "The laminar region is: |m_flow| <= m_flow_laminar";
          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

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)
*/
            // Determine upstream density and upstream viscosity
            if m_flow >= 0 then
              rho := rho_a;
              mu  := mu_a;
            else
              rho := rho_b;
              mu  := mu_b;
            end if;

            // Turbulent
            // WARNING: The following equations are only correct for circular tubes!
            zeta := (length/diameter)/(2*log10(3.7/(Delta)))^2;
            k := 8*zeta/(pi*diameter*diameter)^2;
            m_flow_turbulent := sign(m_flow)*(pi/4)*diameter*mu*Re2;

            // Laminar
            k0 := 128*length/(pi*diameter^4);
            ddp_fric_dm_flow_laminar := k0*mu/rho;
            m_flow_laminar := sign(m_flow)*(pi/4)*diameter*mu*Re1;

            if abs(m_flow) > abs(m_flow_turbulent) then
              dp_fric := k/rho*m_flow*abs(m_flow);
              ddp_fric_dm_flow := 2*k/rho*abs(m_flow);
            elseif abs(m_flow) < abs(m_flow_laminar) then
              dp_fric := ddp_fric_dm_flow_laminar*m_flow;
              ddp_fric_dm_flow := ddp_fric_dm_flow_laminar;
            else
              // Preliminary testing seems to indicate that the log-log transform is not required here
              (dp_fric,ddp_fric_dm_flow) := Utilities.cubicHermite_withDerivative(
                m_flow, m_flow_laminar, m_flow_turbulent, ddp_fric_dm_flow_laminar*m_flow_laminar,
                k/rho*m_flow_turbulent*abs(m_flow_turbulent), ddp_fric_dm_flow_laminar, 2*k/rho*abs(m_flow_turbulent));
            end if;
          end dp_fric_of_m_flow;
        end Internal;
      end LaminarAndQuadraticTurbulent;

      package Detailed
        "Pipe wall friction for laminar and turbulent flow (detailed characteristic)"

        extends PartialWallFriction(
                  final use_mu = true,
                  final use_roughness = true,
                  final use_dp_small = true,
                  final use_m_flow_small = true,
                  final use_Re_turbulent = true);

        import ln = Modelica.Math.log "Logarithm, base e";
        import Modelica.Math.log10 "Logarithm, base 10";
        import Modelica.Math.exp "Exponential function";

        redeclare function extends massFlowRate_dp
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction"
          import Modelica.Math;
        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re1 = min((745*Math.exp(if Delta <= 0.0065 then 1 else 0.0065/Delta))^0.97, Re_turbulent)
            "Re leaving laminar curve";
          SI.ReynoldsNumber Re2 = Re_turbulent "Re entering turbulent curve";
          SI.DynamicViscosity mu "Upstream viscosity";
          SI.Density rho "Upstream density";
          SI.ReynoldsNumber Re "Reynolds number";
          Real lambda2 "Modified friction coefficient (= lambda*Re^2)";
          function interpolateInRegion2 = Modelica.Fluid.Dissipation.Utilities.Functions.General.CubicInterpolation_Re
            "Cubic Hermite spline interpolation in transition region";
        algorithm
          // Determine upstream density, upstream viscosity, and lambda2
          rho     := if dp >= 0 then rho_a else rho_b;
          mu      := if dp >= 0 then mu_a else mu_b;
          lambda2 := abs(dp)*2*diameter^3*rho/(length*mu*mu);

          // Determine Re under the assumption of laminar flow
          Re := lambda2/64;

          // Modify Re, if turbulent flow
          if Re > Re1 then
             Re :=-2*sqrt(lambda2)*Math.log10(2.51/sqrt(lambda2) + 0.27*Delta);
             if Re < Re2 then
                Re := interpolateInRegion2(Re, Re1, Re2, Delta, lambda2);
             end if;
          end if;

          // Determine mass flow rate
          m_flow := crossArea/diameter*mu*(if dp >= 0 then Re else -Re);
        end massFlowRate_dp;

        redeclare function extends pressureLoss_m_flow
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction"
          import Modelica.Math;
          import Modelica.Constants.pi;
        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re1 = min(745*Math.exp(if Delta <= 0.0065 then 1 else 0.0065/Delta), Re_turbulent)
            "Re leaving laminar curve";
          SI.ReynoldsNumber Re2 = Re_turbulent "Re entering turbulent curve";
          SI.DynamicViscosity mu "Upstream viscosity";
          SI.Density rho "Upstream density";
          SI.ReynoldsNumber Re "Reynolds number";
          Real lambda2 "Modified friction coefficient (= lambda*Re^2)";
          function interpolateInRegion2 = Modelica.Fluid.Dissipation.Utilities.Functions.General.CubicInterpolation_lambda
            "Cubic Hermite spline interpolation in transition region";
        algorithm
          // Determine upstream density and upstream viscosity
          rho     :=if m_flow >= 0 then rho_a else rho_b;
          mu      :=if m_flow >= 0 then mu_a else mu_b;

          // Determine Re, lambda2 and pressure drop
          Re := diameter*abs(m_flow)/(crossArea*mu);
          lambda2 := if Re <= Re1 then 64*Re else
                    (if Re >= Re2 then 0.25*(Re/Math.log10(Delta/3.7 + 5.74/Re^0.9))^2 else
                     interpolateInRegion2(Re, Re1, Re2, Delta));
          dp :=length*mu*mu/(2*rho*diameter*diameter*diameter)*
               (if m_flow >= 0 then lambda2 else -lambda2);
        end pressureLoss_m_flow;

        redeclare function extends massFlowRate_dp_staticHead
          "Return mass flow rate m_flow as function of pressure loss dp, i.e., m_flow = f(dp), due to wall friction and static head"

        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re "Reynolds number";
          SI.ReynoldsNumber Re1 = min((745*exp(if Delta <= 0.0065 then 1 else 0.0065/Delta))^0.97, Re_turbulent)
            "Boundary between laminar regime and transition";
          SI.ReynoldsNumber Re2 = Re_turbulent
            "Boundary between transition and turbulent regime";
          SI.Pressure dp_a
            "Upper end of regularization domain of the m_flow(dp) relation";
          SI.Pressure dp_b
            "Lower end of regularization domain of the m_flow(dp) relation";
          SI.MassFlowRate m_flow_a
            "Value at upper end of regularization domain";
          SI.MassFlowRate m_flow_b
            "Value at lower end of regularization domain";

          SI.MassFlowRate dm_flow_ddp_fric_a
            "Derivative at upper end of regularization domain";
          SI.MassFlowRate dm_flow_ddp_fric_b
            "Derivative at lower end of regularization domain";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real dm_flow_ddp_fric_zero;

        algorithm
          dp_a := max(dp_grav_a, dp_grav_b)+dp_small;
          dp_b := min(dp_grav_a, dp_grav_b)-dp_small;

          if dp>=dp_a then
            // Positive flow outside regularization
            m_flow := Internal.m_flow_of_dp_fric(dp-dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
          elseif dp<=dp_b then
            // Negative flow outside regularization
            m_flow := Internal.m_flow_of_dp_fric(dp-dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
          else
            // Regularization parameters
            (m_flow_a, dm_flow_ddp_fric_a) := Internal.m_flow_of_dp_fric(dp_a-dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            (m_flow_b, dm_flow_ddp_fric_b) := Internal.m_flow_of_dp_fric(dp_b-dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of m_flow is overwritten later)
            (m_flow, dm_flow_ddp_fric_zero) := Utilities.regFun3(dp_zero, dp_b, dp_a, m_flow_b, m_flow_a, dm_flow_ddp_fric_b, dm_flow_ddp_fric_a);
            // Do regularization
            if dp>dp_zero then
              m_flow := Utilities.regFun3(dp, dp_zero, dp_a, m_flow_zero, m_flow_a, dm_flow_ddp_fric_zero, dm_flow_ddp_fric_a);
            else
              m_flow := Utilities.regFun3(dp, dp_b, dp_zero, m_flow_b, m_flow_zero, dm_flow_ddp_fric_b, dm_flow_ddp_fric_zero);
            end if;
          end if;
        end massFlowRate_dp_staticHead;

        redeclare function extends pressureLoss_m_flow_staticHead
          "Return pressure loss dp as function of mass flow rate m_flow, i.e., dp = f(m_flow), due to wall friction and static head"

        protected
          Real Delta(min=0) = roughness/diameter "Relative roughness";
          SI.ReynoldsNumber Re1 = min(745*exp(if Delta <= 0.0065 then 1 else 0.0065/Delta), Re_turbulent)
            "Boundary between laminar regime and transition";
          SI.ReynoldsNumber Re2 = Re_turbulent
            "Boundary between transition and turbulent regime";

          SI.MassFlowRate m_flow_a
            "Upper end of regularization domain of the dp(m_flow) relation";
          SI.MassFlowRate m_flow_b
            "Lower end of regularization domain of the dp(m_flow) relation";

          SI.Pressure dp_a "Value at upper end of regularization domain";
          SI.Pressure dp_b "Value at lower end of regularization domain";

          SI.Pressure dp_grav_a = g_times_height_ab*rho_a
            "Static head if mass flows in design direction (a to b)";
          SI.Pressure dp_grav_b = g_times_height_ab*rho_b
            "Static head if mass flows against design direction (b to a)";

          Real ddp_dm_flow_a
            "Derivative of pressure drop with mass flow rate at m_flow_a";
          Real ddp_dm_flow_b
            "Derivative of pressure drop with mass flow rate at m_flow_b";

          // Properly define zero mass flow conditions
          SI.MassFlowRate m_flow_zero = 0;
          SI.Pressure dp_zero = (dp_grav_a + dp_grav_b)/2;
          Real ddp_dm_flow_zero;

        algorithm
          m_flow_a := if dp_grav_a<dp_grav_b then
            Internal.m_flow_of_dp_fric(dp_grav_b - dp_grav_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta)+m_flow_small else
            m_flow_small;
          m_flow_b := if dp_grav_a<dp_grav_b then
            Internal.m_flow_of_dp_fric(dp_grav_a - dp_grav_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta)-m_flow_small else
            -m_flow_small;

          if m_flow>=m_flow_a then
            // Positive flow outside regularization
            dp := Internal.dp_fric_of_m_flow(m_flow, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta) + dp_grav_a;
          elseif m_flow<=m_flow_b then
            // Negative flow outside regularization
            dp := Internal.dp_fric_of_m_flow(m_flow, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta) + dp_grav_b;
          else
            // Regularization parameters
            (dp_a, ddp_dm_flow_a) := Internal.dp_fric_of_m_flow(m_flow_a, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            dp_a := dp_a + dp_grav_a "Adding dp_grav to dp_fric to get dp";
            (dp_b, ddp_dm_flow_b) := Internal.dp_fric_of_m_flow(m_flow_b, rho_a, rho_b, mu_a, mu_b, length, diameter, crossArea, Re1, Re2, Delta);
            dp_b := dp_b + dp_grav_b "Adding dp_grav to dp_fric to get dp";
            // Include a properly defined zero mass flow point
            // Obtain a suitable slope from the linear section slope c (value of dp is overwritten later)
            (dp, ddp_dm_flow_zero) := Utilities.regFun3(m_flow_zero, m_flow_b, m_flow_a, dp_b, dp_a, ddp_dm_flow_b, ddp_dm_flow_a);
            // Do regularization
            if m_flow>m_flow_zero then
              dp := Utilities.regFun3(m_flow, m_flow_zero, m_flow_a, dp_zero, dp_a, ddp_dm_flow_zero, ddp_dm_flow_a);
            else
              dp := Utilities.regFun3(m_flow, m_flow_b, m_flow_zero, dp_b, dp_zero, ddp_dm_flow_b, ddp_dm_flow_zero);
            end if;
          end if;
        end pressureLoss_m_flow_staticHead;

      package Internal
          "Functions to calculate mass flow rate from friction pressure drop and vice versa"
        extends Modelica.Icons.InternalPackage;
        function m_flow_of_dp_fric
            "Calculate mass flow rate as function of pressure drop due to friction"
          extends Modelica.Icons.Function;

          input SI.Pressure dp_fric
              "Pressure loss due to friction (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 (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
              "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input SI.Area crossArea "Inner cross section area";
          input SI.ReynoldsNumber Re1
              "Boundary between laminar regime and transition";
          input SI.ReynoldsNumber Re2
              "Boundary between transition and turbulent regime";
          input Real Delta(min=0) "Relative roughness";
          output SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
          output Real dm_flow_ddp_fric
              "Derivative of mass flow rate with dp_fric";

          protected
          function interpolateInRegion2_withDerivative
              "Interpolation in log-log space using a cubic Hermite polynomial, where x=log10(lambda2), y=log10(Re)"
            extends Modelica.Icons.Function;

            input Real lambda2 "Known independent variable";
            input SI.ReynoldsNumber Re1
                "Boundary between laminar regime and transition";
            input SI.ReynoldsNumber Re2
                "Boundary between transition and turbulent regime";
            input Real Delta(min=0) "Relative roughness";
            input SI.Pressure dp_fric
                "Pressure loss due to friction (dp = port_a.p - port_b.p)";
            output SI.ReynoldsNumber Re "Unknown return variable";
            output Real dRe_ddp "Derivative of return value";
            // point lg(lambda2(Re1)) with derivative at lg(Re1)
            protected
            Real x1=log10(64*Re1);
            Real y1=log10(Re1);
            Real y1d=1;

            // Point lg(lambda2(Re2)) with derivative at lg(Re2)
            Real aux2=Delta/3.7 + 5.74/Re2^0.9;
            Real aux3=log10(aux2);
            Real L2=0.25*(Re2/aux3)^2;
            Real aux4=2.51/sqrt(L2) + 0.27*Delta;
            Real aux5=-2*sqrt(L2)*log10(aux4);
            Real x2=log10(L2);
            Real y2=log10(aux5);
            Real y2d=0.5 + (2.51/log(10))/(aux5*aux4);

            // Point of interest in transformed space
            Real x=log10(lambda2);
            Real y;
            Real dy_dx "Derivative in transformed space";
          algorithm
            // Interpolation
            (y, dy_dx) := Utilities.cubicHermite_withDerivative(x, x1, x2, y1, y2, y1d, y2d);

            // Return value
            Re := 10^y;

            // Derivative of return value
            dRe_ddp := Re/abs(dp_fric)*dy_dx;
          end interpolateInRegion2_withDerivative;

          SI.DynamicViscosity mu "Upstream viscosity";
          SI.Density rho "Upstream density";
          Real lambda2 "Modified friction coefficient (= lambda*Re^2)";
          SI.ReynoldsNumber Re "Reynolds number";
          Real dRe_ddp "dRe/ddp";
          Real aux1;
          Real aux2;

        algorithm
          // Determine upstream density and upstream viscosity
          if dp_fric >= 0 then
            rho := rho_a;
            mu  := mu_a;
          else
            rho := rho_b;
            mu  := mu_b;
          end if;

          // Positive mass flow rate
          lambda2 := abs(dp_fric)*2*diameter^3*rho/(length*mu*mu)
              "Known as lambda2=f(dp)";

          aux1:=(2*diameter^3*rho)/(length*mu^2);

          // Determine Re and dRe/ddp under the assumption of laminar flow
          Re := lambda2/64 "Hagen-Poiseuille";
          dRe_ddp := aux1/64 "Hagen-Poiseuille";

          // Modify Re, if turbulent flow
          if Re > Re1 then
            Re :=-2*sqrt(lambda2)*log10(2.51/sqrt(lambda2) + 0.27*Delta)
                "Colebrook-White";
            aux2 := sqrt(aux1*abs(dp_fric));
            dRe_ddp := 1/log(10)*(-2*log(2.51/aux2+0.27*Delta)*aux1/(2*aux2)+2*2.51/(2*abs(dp_fric)*(2.51/aux2+0.27*Delta)));
            if Re < Re2 then
              (Re, dRe_ddp) := interpolateInRegion2_withDerivative(lambda2, Re1, Re2, Delta, dp_fric);
            end if;
          end if;

          // Determine mass flow rate
          m_flow := crossArea/diameter*mu*(if dp_fric >= 0 then Re else -Re);
          // Determine derivative of mass flow rate with dp_fric
          dm_flow_ddp_fric := crossArea/diameter*mu*dRe_ddp;
        end m_flow_of_dp_fric;

        function dp_fric_of_m_flow
            "Calculate pressure drop due to friction as function of mass flow rate"
          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 SI.DynamicViscosity mu_a
              "Dynamic viscosity at port_a (dummy if use_mu = false)";
          input SI.DynamicViscosity mu_b
              "Dynamic viscosity at port_b (dummy if use_mu = false)";
          input SI.Length length "Length of pipe";
          input SI.Diameter diameter "Inner (hydraulic) diameter of pipe";
          input SI.Area crossArea "Inner cross section area";
          input SI.ReynoldsNumber Re1
              "Boundary between laminar regime and transition";
          input SI.ReynoldsNumber Re2
              "Boundary between transition and turbulent regime";
          input Real Delta(min=0) "Relative roughness";
          output SI.Pressure dp_fric
              "Pressure loss due to friction (dp_fric = port_a.p - port_b.p - dp_grav)";
          output Real ddp_fric_dm_flow
              "Derivative of pressure drop with mass flow rate";

          protected
          function interpolateInRegion2
              "Interpolation in log-log space using a cubic Hermite polynomial, where x=log10(Re), y=log10(lambda2)"
            extends Modelica.Icons.Function;

            input SI.ReynoldsNumber Re "Known independent variable";
            input SI.ReynoldsNumber Re1
                "Boundary between laminar regime and transition";
            input SI.ReynoldsNumber Re2
                "Boundary between transition and turbulent regime";
            input Real Delta(min=0) "Relative roughness";
            input SI.MassFlowRate m_flow "Mass flow rate from port_a to port_b";
            output Real lambda2 "Unknown return value";
            output Real dlambda2_dm_flow "Derivative of return value";
            // point lg(lambda2(Re1)) with derivative at lg(Re1)
            protected
            Real x1 = log10(Re1);
            Real y1 = log10(64*Re1);
            Real y1d = 1;

            // Point lg(lambda2(Re2)) with derivative at lg(Re2)
            Real aux2 = Delta/3.7 + 5.74/Re2^0.9;
            Real aux3 = log10(aux2);
            Real L2 = 0.25*(Re2/aux3)^2;
            Real x2 = log10(Re2);
            Real y2 = log10(L2);
            Real y2d = 2+(2*5.74*0.9)/(log(aux2)*Re2^0.9*aux2);

            // Point of interest in transformed space
            Real x=log10(Re);
            Real y;
            Real dy_dx "Derivative in transformed space";
          algorithm
            // Interpolation
            (y, dy_dx) := Utilities.cubicHermite_withDerivative(x, x1, x2, y1, y2, y1d, y2d);

            // Return value
            lambda2 := 10^y;

            // Derivative of return value
            dlambda2_dm_flow := lambda2/abs(m_flow)*dy_dx;
          end interpolateInRegion2;

          SI.DynamicViscosity mu "Upstream viscosity";
          SI.Density rho "Upstream density";
          SI.ReynoldsNumber Re "Reynolds number";
          Real lambda2 "Modified friction coefficient (= lambda*Re^2)";
          Real dlambda2_dm_flow "dlambda2/dm_flow";
          Real aux1;
          Real aux2;

        algorithm
          // Determine upstream density and upstream viscosity
          if m_flow >= 0 then
            rho := rho_a;
            mu  := mu_a;
          else
            rho := rho_b;
            mu  := mu_b;
          end if;

          // Determine Reynolds number
          Re := abs(m_flow)*diameter/(crossArea*mu);

          aux1 := diameter/(crossArea*mu);

          // Use correlation for lambda2 depending on actual conditions
          if Re <= Re1 then
            lambda2 := 64*Re "Hagen-Poiseuille";
            dlambda2_dm_flow := 64*aux1 "Hagen-Poiseuille";
          elseif Re >= Re2 then
            lambda2 := 0.25*(Re/log10(Delta/3.7 + 5.74/Re^0.9))^2 "Swamee-Jain";
            aux2 := Delta/3.7+5.74/((aux1*abs(m_flow))^0.9);
            dlambda2_dm_flow := 0.5*aux1*Re*log(10)^2*(1/(log(aux2)^2)+(5.74*0.9)/(log(aux2)^3*Re^0.9*aux2))
                "Swamee-Jain";
          else
            (lambda2, dlambda2_dm_flow) := interpolateInRegion2(Re, Re1, Re2, Delta, m_flow);
          end if;

          // Compute pressure drop from lambda2
          dp_fric :=length*mu*mu/(2*rho*diameter*diameter*diameter)*
               (if m_flow >= 0 then lambda2 else -lambda2);

          // Compute derivative from dlambda2/dm_flow
          ddp_fric_dm_flow := (length*mu^2)/(2*diameter^3*rho)*dlambda2_dm_flow;
        end dp_fric_of_m_flow;
      end Internal;
      end Detailed;
    end WallFriction;
  end BaseClasses;
end Pipes;