예제 #1
0
 def velocity_head(self) -> qty.Pressure:
     """Get the velocity head (*quantities.Pressure*) between end and start node of the flow path."""
     try:
         first = self.get_first_real_section()
         last = self.get_last_real_section()
         vp1 = first.velocity_pressure()
         vp2 = last.velocity_pressure()
     except AttributeError:
         return qty.Pressure(0.0)
     else:
         return qty.Pressure(vp2 - vp1)
 def set_bypass(self, Kvs: float) -> float:
     """
     Get the Kvr setting of the balancing valve in the bypass.
     """
     self._bal_valve_byp = BalancingValve.create_from_str(
         fluid=self.water,
         flow_rate=self.V_des,
         dp_100=qty.Pressure(3.0, 'kPa')
     )
     self._bal_valve_byp.Kvs = Kvs
     dp_sur = self._dP_br_wov() - self._bal_valve_byp.pressure_drop()
     self._bal_valve_byp.set_pressure_excess(qty.Pressure(dp_sur))
     return self._bal_valve_byp.Kvr
예제 #3
0
 def elevation_head(self) -> qty.Pressure:
     """Get the elevation head (*quantities.Pressure*) between end and start node of the flow path."""
     return qty.Pressure(
         sum([
             section.pressure_drop() for section in self
             if section.type == 'pseudo'
         ]))
예제 #4
0
    def pressure_drop(self) -> qty.Pressure:
        """
        Get the pressure drop (*quantities.Pressure*) across the fitting or valve.

        """
        self._calc_pressure_drop()
        return qty.Pressure(self._dp)
예제 #5
0
 def pressure_drop(self) -> qty.Pressure:
     """
     Get the pressure drop between start node and end node of the network.
     A negative value means that the start node is at a higher pressure than the end node (pressure gain).
     """
     dp = self.network_section.pressure_drop()
     return qty.Pressure(-dp)
예제 #6
0
    def added_head(self, V: qty.VolumeFlowRate) -> qty.Pressure:
        """
        Calculate pump head (*quantities.Pressure*) that corresponds with given flow rate (*quantities.VolumeFlowRate*).

        """
        V = V()
        return qty.Pressure(self._a0 + self._a1 * V + self._a2 * V**2)
예제 #7
0
 def elevation_head(self) -> qty.Pressure:
     """
     Get the elevation head (*quantities.Pressure*) between the end node of the last real section and the start node
     of first real section in the path. (Any pseudo sections in the path are ignored.)
     """
     dh = self.height()
     return qty.Pressure(dh, 'm')
예제 #8
0
 def dynamic_head(self) -> qty.Pressure:
     """
     Get the dynamic head (*quantities.Pressure*) between the end node of the last real section and the start node
     of first real section in the path. (Any pseudo sections in the path are ignored.)
     """
     return qty.Pressure(
         sum([section.pressure_drop() for section in self if section.real]))
예제 #9
0
    def create_system_curve(self, V_start: qty.VolumeFlowRate, V_end: qty.VolumeFlowRate, num: int = 50) \
            -> Tuple[List[float], List[float]]:
        """
        Calculate the system curve between an initial and final flow rate.

        **Parameters:**

        - `V_start`: (*quantities.VolumeFlowRate*) = first flow rate to put on the system curve
        - `V_end`: (*quantities.VolumeFlowRate*) = last flow rate to put on the system curve
        - `num`: (*int*) = number of points on the system curve (default = 50)

        **Returns:**
        Tuple with 1st element a list of the flow rates and 2nd element a list of the corresponding
        pressures, both expressed in the desired measuring units set at instantiation of the *SystemCurve*-object.

        """
        V_i = V_start(self._V_unit)
        V_f = V_end(self._V_unit)
        V_arr = np.linspace(V_i, V_f, num, endpoint=True)
        p_arr = self._R * V_arr ** 2 + self._dp_stat + self._dp_elev
        V_qty = [qty.VolumeFlowRate(V, self._V_unit) for V in V_arr]
        p_qty = [qty.Pressure(p, self._p_unit) for p in p_arr]
        V_sys = [V(self._desired_units['flow_rate']) for V in V_qty]
        p_sys = [p(self._desired_units['pressure']) for p in p_qty]
        return V_sys, p_sys
예제 #10
0
    def get_paths(cls) -> pd.DataFrame:
        """
        Returns an overview of the flow paths in the network organised in a Pandas DataFrame.

        Following data about the flow paths is returned:

        - velocity head loss between end node and start node of the path
        - elevation head loss between end node and start node of the path
        - dynamic head loss between end node and start of the path
        - static head required between start node and end node of flow path to establish the design flow rates along the
        path
        - the pressure difference between the critical path and the path under consideration (after balancing the
        network for design flow rates, there should be zero difference)

        """
        keys = [
            'path', f'dp,vel [{cls.units["pressure"]}]',
            f'dp,elev [{cls.units["pressure"]}]',
            f'dp,dyn [{cls.units["pressure"]}]',
            f'dp,stat req. [{cls.units["pressure"]}]',
            f'dp,dif [{cls.units["pressure"]}]'
        ]
        d = {k: [] for k in keys}
        static_head_max = cls.network.critical_path.static_head_required
        for path in cls.network.paths:
            d[keys[0]].append(repr(path))
            d[keys[1]].append(path.velocity_head(cls.units['pressure'], 3))
            d[keys[2]].append(path.elevation_head(cls.units['pressure'], 3))
            d[keys[3]].append(path.dynamic_head(cls.units['pressure'], 3))
            d[keys[4]].append(
                path.static_head_required(cls.units['pressure'], 3))
            dp_dif = qty.Pressure(static_head_max() -
                                  path.static_head_required())
            d[keys[5]].append(dp_dif(cls.units['pressure'], 3))
        return pd.DataFrame(d).sort_values(by=keys[4])
예제 #11
0
 def head_loss(self) -> qty.Pressure:
     """Get the head loss (*quantities.Pressure*) between end and start node of the flow path."""
     sum_ = sum([
         section.sign * section.pressure_drop() for section in self
         if section.type != 'pseudo'
     ])
     return qty.Pressure(sum_)
예제 #12
0
    def add_balancing_valves(
            cls, dp_100_list: List[Tuple[str,
                                         float]]) -> List[Tuple[str, float]]:
        """
        Add one or more balancing valves to the network.

        **Parameters:**

        - `dp_100_list`: (*List[Tuple[str, float]]*)<br>
        List of tuples. The first element (*str*) of the tuple is the id of the section to which the balancing valve
        must be added. The second element (*float*) is the design pressure drop across the fully open balancing valve.
        The measuring unit is taken from the units set (see method `set_units`).

        **Returns:** (*List[Tuple[str, float]]*)<br>
        List of tuples. The first element (*str*) of the tuple is the id of the section to which the balancing valve is
        added. The second element (*float*) is the preliminary Kvs value of the balancing valve.

        """
        Kvs_pre_list: List[Tuple[str, float]] = []
        for section_id, dp_100 in dp_100_list:
            section = cls.network.sections[section_id]
            Kvs_pre = section.add_balancing_valve(
                qty.Pressure(dp_100, cls.units['pressure']))
            Kvs_pre_list.append((section_id, Kvs_pre))
        return Kvs_pre_list
 def calc_bal_valve(
     self, dp_100: qty.Pressure = qty.Pressure(3.0, 'kPa')) -> float:
     """Calculate preliminary Kvs of fully open balancing valve for a given pressure drop."""
     self._bal_valve = BalancingValve.create(fluid=self.water,
                                             flow_rate=self.V_des,
                                             dp_100=dp_100)
     self._dP_bv = dp_100
     return self._bal_valve.Kvs
예제 #14
0
 def pressure_loss(self) -> qty.Pressure:
     """Get the pressure loss (*quantities.Pressure*) across the pipe including its minor losses."""
     dp = 0.0
     if not math.isnan(self._dp_minor):
         dp += self._dp_minor
     if not math.isnan(self._dp_fric):
         dp += self._dp_fric
     return qty.Pressure(dp)
 def set_bal_valve(self, dP_br_avail: qty.Pressure) -> float:
     """
     Determine the balancing valve's Kvr setting if the available feed pressure across the branch is known.
     Call this method after the control valve's commercially available Kvs has been set.
     """
     dP_br_des = self._dP_br_wov() + self._dP_bv() + self._dP_cv()
     dP_sur = qty.Pressure(dP_br_avail() - dP_br_des)
     self._bal_valve.set_pressure_excess(dP_sur)
     self._dP_bv = self._bal_valve.pressure_drop
     return self._bal_valve.Kvr
예제 #16
0
 def velocity_head(self) -> qty.Pressure:
     """
     Get the velocity head (*quantities.Pressure*) between the end node of the last real section and the start node
     of first real section in the path. (Any pseudo sections in the path are ignored.)
     """
     first = self.get_first_real_section()
     last = self.get_last_real_section()
     vp1 = first.pipe.velocity_pressure()
     vp2 = last.pipe.velocity_pressure()
     return qty.Pressure(vp2 - vp1)
예제 #17
0
 def pressure_drop(self) -> qty.Pressure:
     """Get pressure drop (or gain in case of a pump section) (*quantities.Pressure*) across the section."""
     dp = 0.0
     if self.type == 'pipe':
         dp = self.dp_pipe
     elif self.type == 'pump':
         dp = self.dp_pump
     elif self.type == 'pseudo':
         dp = self.dp_pseudo
     return qty.Pressure(dp)
예제 #18
0
def calc_specific_friction_loss(**kwargs):
    """
    Calculate the pressure drop per metre length of pipe that remains available for friction loss and that can to used
    to determine appropriate pipe diameters.

    **kwargs:**

    - `path_length`: (*float*) = total pipe length from point immediately downstream water meter up to the
    draw-off-point [m]
    - `p_supply_min`: (*float*) = minimum available supply pressure [Pa]
    - `p_draw_off_req`: (*float*) = required minimum flow pressure at draw-off point [Pa]
    - `height`: (*float*) = elevation of draw-off point with respect to supply entrance (i.e static head) [m]
    - `dp_appliance`: (*float*) = sum of pressure losses in appliances @ design flow rate [Pa]
    - `dp_check_valve`: (*float*) = sum of pressure losses due to resistance of check valves @ design flow rate [Pa]
    - `dp_fittings_per`: (*float*) = percentage of pressure loss due to fittings [%]

    **Returns:** (*Tuple[qty.Pressure, qty.Pressure]*)<br>

    - frictional pressure drop per metre that remains available for pipe sizing.
    - total available frictional pressure drop along flow path

    """
    dp_fittings_per = kwargs.get('dp_fittings_per', 0.0)
    path_length = kwargs.get('path_length', 0.0)
    p_supply_min = kwargs.get('p_supply_min', 0.0)
    height = kwargs.get('height', 0.0)
    dp_appliance = kwargs.get('dp_appliance', 0.0)
    dp_check_valve = kwargs.get('dp_check_valve', 0.0)
    p_draw_off_req = kwargs.get('p_draw_off_req')

    fluid = Water(10.0)
    g = 9.81
    rho = fluid.density('kg/m^3')
    static_head = rho * g * height
    total_head_req = p_supply_min - p_draw_off_req
    # head that remains available for friction loss
    fric_head_av = total_head_req - static_head - dp_appliance - dp_check_valve
    fric_head_av = (1.0 - dp_fittings_per / 100.0) * fric_head_av  # subtract fitting losses
    # pressure drop per metre pipe length available for friction loss
    dp_fric_spec = fric_head_av / path_length  # Pa / m
    return qty.Pressure(dp_fric_spec), qty.Pressure(fric_head_av)
예제 #19
0
 def pressure_drop(self) -> qty.Pressure:
     """Get the pressure drop (*quantities.Pressure*) across the section."""
     dp = self._pipe.friction_loss()
     dp += sum(
         [fitting.pressure_drop() for fitting in self._fittings.values()])
     if self._balancing_valve is not None:
         dp += self._balancing_valve.pressure_drop()
     if self._pump is not None:
         dp -= self._pump.added_head(self._pipe.flow_rate)()
     if self._control_valve is not None:
         dp += self._control_valve.pressure_drop()
     return qty.Pressure(dp)
예제 #20
0
 def static_head_required(self) -> qty.Pressure:
     """
     Get the static head (*quantities.Pressure*) between the end node of the last real section and the start node
     of first real section in the path. (Any pseudo sections in the path are ignored.)
     This is the pressure difference that must be applied between the end node of the path and its start node in
     order to establish the desired flow rates along the path. However, note that flow paths must also be balanced
     to accomplish the design flow rates in each section of the network.
     """
     dp_vel = self.velocity_head()
     dp_elev = self.elevation_head()
     dp_dyn = self.dynamic_head()
     return qty.Pressure(dp_vel + dp_elev + dp_dyn)
 def calc_ctrl_valve(self, auth_des=0.5) -> float:
     """
     Calculate preliminary Kvs of control valve to attain a certain valve authority at design conditions.
     Before calling this method, the Kvs of the balancing valve must have been specified and the pressure drop
     in the branch without balancing valve and control valve must have been set.
     """
     self._ctrl_valve = ControlValve.create(
         fluid=self.water,
         flow_rate=self.V_des,
         target_authority=auth_des,
         dp_crit_path=qty.Pressure(self._dP_br_wov() + self._dP_bv()))
     return self._ctrl_valve.Kvs
예제 #22
0
    def set_balancing_valves(cls) -> List[Tuple[str, float]]:
        """
        Calculate the Kvr setting of the balancing valves in the network in order to dissipate excess feed pressure.

        **Returns:** (*List[Tuple[str, float]]*)<br>
        List of tuples. The first element (*str*) of the tuple is the id of the section to which the balancing valve was
        added. The second element (*float*) is the calculated Kvr setting of the balancing valve.

        """
        Kvr_list: List[Tuple[str, float]] = []
        bv_dict = cls.network.get_balancing_valves()
        dp_max = cls.network.critical_path.static_head_required()
        for section_id in bv_dict.keys():
            section = cls.network.sections[section_id]
            dp_path = bv_dict[section_id][1].static_head_required()
            section.set_balancing_valve(qty.Pressure(dp_max - dp_path))
            Kvr_list.append((section_id, section.balancing_valve.Kvr))
        return Kvr_list
예제 #23
0
    def static_head(self) -> qty.Pressure:
        """
        Get the static head (*quantities.Pressure*) between end and start node of the flow path.

        Energy equation:
        "static head" + "vel. head" + "elev. head" + "head loss" = 0
        with:
        - "static head" = static pressure at end node - static pressure at start node
        - "vel. head" = velocity pressure at end node - velocity pressure at start node
        - "elev. head" = elevation at end node - elevation at start node (as seen from common reference plane)
        - "head loss" = loss of mechanical energy due to flow friction between start and end node

        Note: if mechanical energy is added by pumps along the flow path, "head loss" could become negative, which means
        that there is "head gain" when going from start to end node, instead of "head loss".
        """
        dp_vel = self.velocity_head()
        dp_elev = self.elevation_head()
        dp_loss = self.head_loss()
        return qty.Pressure(-(dp_vel + dp_elev + dp_loss))
예제 #24
0
 def _check_row(cls, row):
     row_ = [row.iloc[i] for i in (0, 1, 2, 3)]
     for i in (4, 5, 6):
         if pd.isna(row.iloc[i]):
             row_.append(0.0)
         else:
             row_.append(row.iloc[i])
     if (pd.isna(row.iloc[7:10])).any():
         row_.append(None)
     else:
         row_.append((row.iloc[7], row.iloc[8], row.iloc[9]))
     if pd.isna(row.iloc[10]):
         row_.append(None)
     else:
         row_.append(qty.Pressure(row.iloc[10], cls.units['pressure']))
     if pd.isna(row.iloc[11]):
         row_.append(0.0)
     else:
         row_.append(row.iloc[11])
     return row_
예제 #25
0
    def calculate_pressure_loss(self, sum_zeta: float = 0.0) -> qty.Pressure:
        """
        Calculate pressure loss across the pipe if flow rate and nominal diameter are known on creation.

        **Parameters:**

        - `sum_zeta`: (*float*) = sum of resistance coefficients of fittings/valves present in the pipe.

        **Returns:** (*quantities.Pressure*)

        """
        # given: flow rate and cross section (area and hydraulic diameter)
        rho = self._fluid.density()
        mu = self._fluid.kinematic_viscosity()
        di = self._cross_section.diameter()
        v = self._flow_rate / self._cross_section.area()
        re = reynolds_number(v, di, mu)
        rel_pipe_rough = self._rough / di
        f = darcy_friction_factor(re, rel_pipe_rough)
        self._dp_fric = f * self._length / di * rho * v**2.0 / 2.0
        self._dp_minor = sum_zeta * rho * v**2.0 / 2.0
        return qty.Pressure(self._dp_fric)
예제 #26
0
 def minor_losses(self) -> qty.Pressure:
     """Get the sum of pressure losses (*quantities.Pressure*) due to fittings/valves in the pipe."""
     return qty.Pressure(self._dp_minor)
예제 #27
0
 def friction_loss(self) -> qty.Pressure:
     """Get/set the friction loss (*quantities.Pressure*) across the pipe."""
     return qty.Pressure(self._dp_fric)
예제 #28
0
 def velocity_pressure(self) -> qty.Pressure:
     """Get the velocity pressure (*quantities.Pressure*) in the pipe."""
     rho = self._fluid.density()
     v = self._flow_rate / self._cross_section.area()
     return qty.Pressure(rho * v**2.0 / 2.0)
예제 #29
0
 def pressure_drop(self) -> qty.Pressure:
     """Get pressure drop (*quantities.Pressure*) across balancing valve."""
     return qty.Pressure(self._dp)
예제 #30
0
 def velocity_pressure(self) -> qty.Pressure:
     """Get velocity pressure (*quantities.Velocity*) in the section."""
     v = self.velocity()
     rho = self._fluid.density()
     return qty.Pressure(rho * v**2 / 2)