def calculate_system_curves(flow_paths: List[FlowPath], V_wp: qty.VolumeFlowRate, dp_wp: qty.Pressure, **kwargs) \ -> List[Tuple[List[float], List[float]]]: """ Calculate the system curves of a list of `flow_paths` in a piping network with one common pump. To calculate the hydraulic resistance of the flow paths the working point of the pump must be known, specified by `V_wp` and `dp_wp`. **kwargs:** - `"unit_flow_rate"`: desired unit for flow rate - `"unit_pressure"`: desired unit for pressure - `"V_start"`: first flow rate to put on the system curve - `"V_end"`: last flow rate to put on the system curve """ sys_curves = [] for p in flow_paths: dp_loss = p.head_loss('bar') dp_loss += dp_wp('bar') R = dp_loss / V_wp('m^3/h') ** 2 curve = SystemCurve( R=R, desired_units={ 'flow_rate': kwargs.get('unit_flow_rate', 'm^3/h'), 'pressure': kwargs.get('unit_pressure', 'bar') } ) curve.set_static_head(p.static_head) curve.set_elevation_head(p.elevation_head) V, dp = curve.create_system_curve( V_start=kwargs.get('V_start', qty.VolumeFlowRate(0.0, 'm^3/h')), V_end=kwargs.get('V_end', qty.VolumeFlowRate(5.0, 'm^3/h')) ) sys_curves.append((V, dp)) return sys_curves
def calculate_flow_rate(self, sum_zeta: float = 0.0) -> qty.VolumeFlowRate: """ Calculate flow rate through the pipe if nominal diameter and friction loss are known on creation. **Parameters:** - `sum_zeta`: (*float*) = sum of resistance coefficients of fittings/valves present in the pipe. **Returns:** (*quantities.VolumeFlowRate*) """ # given: friction loss and cross section (area and hydraulic diameter) rho = self._fluid.density() mu = self._fluid.kinematic_viscosity() di = self._cross_section.diameter() rel_pipe_rough = self._rough / di f = 1.325 / math.log10(rel_pipe_rough / 3.7)**2.0 i = 0 v = 0.0 while i < self._max_iterations: var = f * self._length / di + sum_zeta v = math.sqrt(2.0 * self._dp_fric / (rho * var)) re = reynolds_number(v, di, mu) f_new = darcy_friction_factor(re, rel_pipe_rough) if abs(f_new - f) <= 1.0e-5: break else: f = f_new i += 1 if i == self._max_iterations: raise OverflowError( 'too many iterations. no solution found') self._flow_rate = self._cross_section.area() * v return qty.VolumeFlowRate(self._flow_rate)
def check_flow_balance(self, V_ext: Dict[str, Dict[str, Optional[List[qty.VolumeFlowRate]]]] = None) \ -> Dict[str, qty.VolumeFlowRate]: """ Check the flow balances at the nodes of the network. External flow rates that enter or leave at network nodes are added through `V_ext`. It is a dictionary with the following syntax: ```V_ext = {<node_id>: {"in": [<qty.VolumeFlowRate>,...], "out": [<qty.VolumeFlowRate>,...]}}``` The method returns a dictionary `checks` like {<node_id>: <qty.VolumeFlowRate>, ...} The value of given `<node_id>`-key is the difference between the incoming flow rate and the outgoing flow rate. For internal network nodes, this net flow rate should be zero in order to respect the physical law of continuity. If positive, incoming flow rate to the node is greater than flow rate that is leaving the node. On the other hand, if negative, outgoing flow rate is greater than incoming flow rate. """ checks = {} for node in self.nodes.values(): V_in = None V_out = None if V_ext is not None: if node.id in V_ext: V_dict = V_ext.get(node.id) if V_dict['in'] is not None: V_in = V_dict['in'] if V_dict['out'] is not None: V_out = V_dict['out'] dif = node.check_flow_balance(V_ext_in=V_in, V_ext_out=V_out) checks[node.id] = qty.VolumeFlowRate(dif) return checks
def flow_rate(self) -> qty.VolumeFlowRate: """Get the flow rate (*quantities.VolumeFlowRate*) that enters the network.""" start_node = self._nodes[self._start_node_id] V = 0.0 for section in start_node.outgoing: V += section.flow_rate() return qty.VolumeFlowRate(V)
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
def V_des(self) -> qty.VolumeFlowRate: """Get/set required design flow rate.""" rho = self.water.density() c = self.water.specific_heat() Q_des = self._Q_des() dT_des = self._T_sup() - self._T_ret() self._V_des = Q_des / (rho * c * dT_des) return qty.VolumeFlowRate(self._V_des)
def flow_rate(self) -> qty.VolumeFlowRate: """ Get the flow rate (*quantities.VolumeFlowRate*) of the network, i.e. the sum of the leaving flow rates at the start node of the network, which is equal to the total flow rate that enters the network. """ start_node = self.nodes[self.start_node_id] V = 0.0 for section in start_node.outgoing: if section.type != 'pseudo': V += section.flow_rate() return qty.VolumeFlowRate(V)
def _init_series_pumps(cls): cls.series_pump_curve = PumpCurve(desired_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.series_pump_curve.set_coefficients( (11.691103104913244, -1.1730559060355084, -0.9196272691559745), { 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.series_pump_curve_axes = calculate_pump_curve( coeff_values=cls.series_pump_curve.get_coefficients(), coeff_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }, unit_flow_rate='L/s', unit_pressure='bar', V_end=qty.VolumeFlowRate(3.0, 'L/s'))
def _init_parallel_pumps(cls): cls.parallel_pump_curve = PumpCurve(desired_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.parallel_pump_curve.set_coefficients( (5.845551552456622, -0.2932639765088771, -0.11495340864449681), { 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.parallel_pump_curve_axes = calculate_pump_curve( coeff_values=cls.parallel_pump_curve.get_coefficients(), coeff_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }, unit_flow_rate='L/s', unit_pressure='bar', V_end=qty.VolumeFlowRate(6.0, 'L/s'))
def _init_single_pump(cls): cls.single_pump_curve = PumpCurve(desired_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.single_pump_curve.set_coefficients( (5.845551552456625, -0.58652795301776, -0.4598136345779855), { 'flow_rate': 'L/s', 'pressure': 'bar' }) cls.single_pump_curve_axes = calculate_pump_curve( coeff_values=cls.single_pump_curve.get_coefficients(), coeff_units={ 'flow_rate': 'L/s', 'pressure': 'bar' }, unit_flow_rate='L/s', unit_pressure='bar', V_end=qty.VolumeFlowRate(6.0, 'L/s'))
def configure_network_from_df(cls, df: pd.DataFrame, clear=False): """ Configure network from a Pandas DataFrame object. If clear is set to True the internal network objects will be cleared first; this may be needed in case the network needs to be reconfigured. """ if clear is True: cls.network.clear() for _, row in df.iterrows(): row = cls._check_row(row) cls.network.add_section( loop_id=row[0], section_id=row[1], start_node_id=row[2], end_node_id=row[3], nominal_diameter=qty.Length(row[4], cls.units['diameter']), length=qty.Length(row[5], cls.units['length']), zeta=row[6], pump_curve=row[7], dp_fixed=row[8], flow_rate=qty.VolumeFlowRate(row[9], cls.units['flow_rate']) )
def _analyze(cls): # analyze piping network and get the new data for showing in the dashboard try: Analyzer.solve(error=1.0e-2, i_max=MAX_ITERATIONS) except (OverflowError, ValueError) as err: raise err else: df = Analyzer.get_network() # only select the sections with equivalent "floor valves to show in the table" cls.df_floors = df.iloc[[r for r in range(1, 32, 4)], :] cls.total_flow_rate = cls.df_floors['flow_rate [L/s]'].sum() # calculate working point of booster pump cls.V_wp = Analyzer.network.flow_rate cls.dp_wp = cls.current_pump_curve.pump_pressure(cls.V_wp) # calculate flow path curves cls.sys_curves_axes = calculate_system_curves( flow_paths=Analyzer.network.paths, V_wp=Analyzer.network.flow_rate, dp_wp=cls.dp_wp, unit_flow_rate='L/s', unit_pressure='bar', V_end=qty.VolumeFlowRate(6.0, 'L/s'))
def flow_rate(self) -> qty.VolumeFlowRate: """ Get/set the flow rate (*quantities.VolumeFlowRate*) through the fitting or valve. """ return qty.VolumeFlowRate(self._flow_rate)
def flow_rate(self) -> qty.VolumeFlowRate: """Get flow rate (*quantities.VolumeFlowRate*) of the section.""" return qty.VolumeFlowRate(self.V)