Analysis of the Hydronic Network
Contents
Analysis of the Hydronic Network#
Let’s Import What We’ll Need#
from hvac import Quantity
from hvac.fluid_flow import load_network, PseudoConduit, SystemCurve
from hvac.fluid_flow.utils import plot_curves
Q_ = Quantity
Load the Hydronic Network back into this Notebook#
In the previous notebook “Design of a Hydronic Network” we saved our PipeNetwork
object on the file path:
file_path = "./networks/hydronic_network.pickle"
Now, let’s load this object again from disk:
pipe_network = load_network(file_path)
Add a Pseudo Conduit with a Fixed Pressure Difference#
A pseudo conduit, as the name suggests, is not a real conduit in a pipe network. We use it to represent a fixed pressure difference between two nodes.
From the design of the hydronic pipe network it follows that we need a pressure difference of about 39 kPa between end node R0 and start node S0 to establish the design volume flow rates in the pipe network. In order to analyze the pipe network, we add a PseudoConduit
object between the end node and the start node of the network with a fixed pressure difference of 39 kPa.
pseudo_conduit = PseudoConduit.create(fixed_pressure_drop=Q_(-39, 'kPa'))
The start node of the network is at a pressure that is 39 kPa higher than the pressure at the end node of the network. If there would be a real conduit between the start and the end node of the network, there would be a flow of water from the start node towards the end node. The direction of this flow would be opposite to the positive, clock-wise sense of the loop L1, as can be deduced from the scheme above. This is the reason why we need to add a minus sign. It can also be noticed that the fixed pressure difference between the end node and the start node is a pressure rise rather than a pressure drop.
After the pseudo conduit has been created, we still need to add it to the network:
pipe_network.add_conduit(
conduit=pseudo_conduit,
conduit_ID='P13',
start_node_ID='R0',
end_node_ID='S0',
loop_ID='L1'
)
More about Loops#
Before we move on, let’s first go back to the pipe configuration csv-file that we discussed in the notebook “Design of a Hydronic Network” and which is displayed again below:
import pandas as pd
table = pd.read_csv('./networks/ex1_closed-pipe-network-design.csv')
ja.display_table(table)
conduit_ID | start_node_ID | end_node_ID | loop_ID | schedule | length | volume_flow_rate | specific_pressure_drop | |
---|---|---|---|---|---|---|---|---|
0 | P1 | S0 | S1 | L1 | copper | 4 | 0.458 | 0.2 |
1 | P2 | S1 | S2 | L2 | copper | 2 | 0.397 | 0.2 |
2 | P3 | S2 | S3 | L3 | copper | 2 | 0.214 | 0.2 |
3 | P4 | S3 | S4 | L4 | copper | 2 | 0.122 | 0.2 |
4 | P5 | S1 | R1 | (L1, L2) | pex | 25 | 0.061 | 0.2 |
5 | P6 | S2 | R2 | (L2, L3) | pex | 25 | 0.183 | 0.2 |
6 | P7 | S3 | R3 | (L3, L4) | pex | 15 | 0.092 | 0.2 |
7 | P8 | S4 | R4 | L4 | pex | 20 | 0.122 | 0.2 |
8 | P9 | R4 | R3 | L4 | copper | 2 | 0.122 | 0.2 |
9 | P10 | R3 | R2 | L3 | copper | 2 | 0.214 | 0.2 |
10 | P11 | R2 | R1 | L2 | copper | 2 | 0.397 | 0.2 |
11 | P12 | R1 | R0 | L1 | copper | 4 | 0.458 | 0.2 |
Inside the column loop_ID, we have specified the loop or loops to which each pipe in the network belongs. For example, pipe 5 belongs to two loops L1 and L2. You must enter the two loop IDs between brackets and separate them with a comma, otherwise the program will give an error.
The sense of the volume flow rates in the pipes is referenced to the sense of the loop the pipes belong to. By convention a clock-wise loop sense is considered to be positive. If the arrow of the volume flow rate in a pipe points in the same direction as the loop arrow, the volume flow rate has a positive sign, otherwise you must give it a negative sign. In the case a pipe belongs to two loops, the sign of the volume flow rate is referenced to the positive sense of the first mentioned loop. In the second loop the flow sign will always be opposite to the flow sign in the first loop, as can be observed in the network scheme above.
Analyze the Pipe Network#
Once alle pipes have been assigned a loop ID, or two loop IDs, and the pseudo conduit has been added between the end node and the start node of the network, we can let the program analyze the network with the Hardy Cross method:
i = pipe_network.analyze(tolerance=Q_(1, 'Pa'), i_max=500)
i
12
The Hardy Cross method is an iterative solution method to find the volume flow rates in a fluid network. The aim is to find the volume flow rate in each pipe of the network, such that the sum of the pressure differences across the pipes in each loop of the network becomes zero (as demanded by the physical law about conservation of energy). As it may take numerous iterations to find a solution for which the loop pressure differences all become exactly zero, a stop criterium is provided by the tolerance
parameter. A tolerance of 1 Pa means that the analyze
method will stop when all loop pressure differences are between 0 and 1 Pa. The parameter i_max
sets the maximum number of iterations. If no solution has been found within i_max
iterations, an OverflowError
excepction will be raised to alert that no solution could be found that fulfills the stop criterium. The volume flow rates we entered in the pipe configuration file are used by the program as an initial guess to start the iterative solving process. Here we use the design flow rates for which we have sized the network.
IMPORTANT!
When we choose a volume flow rate for each pipe in the network, we must make sure that the sum of the flow rates that arrive at a node is equal to the sum of the flow rates that leave at this same node (as demanded by the physical law about conservation of mass).
When the analyze
method is finished and has returned, we can take a look at the results:
pipe_table1 = pipe_network.get_pipe_table()
ja.display_table(pipe_table1)
pipe ID | L [m] | DN [mm] | Di [mm] | V [l/s] | v [m/s] | zeta | Re | Δp-dyn. [kPa] | |
---|---|---|---|---|---|---|---|---|---|
0 | P1 | 4.0 | 35.0 | 32.0 | 0.458009 | 0.569488 | 0.007096 | 32946.314459 | 0.463940 |
1 | P2 | 2.0 | 28.0 | 25.0 | 0.397008 | 0.808777 | -0.011416 | 36554.548492 | 0.580817 |
2 | P3 | 2.0 | 22.0 | 20.0 | 0.214003 | 0.681194 | -0.037437 | 24630.493799 | 0.559739 |
3 | P4 | 2.0 | 18.0 | 16.0 | 0.122002 | 0.606788 | 0.096462 | 17552.098316 | 0.629693 |
4 | P5 | 25.0 | 18.0 | 14.0 | 0.061002 | 0.396274 | 434.804239 | 10029.882171 | 38.030197 |
5 | P6 | 25.0 | 26.0 | 20.0 | 0.183004 | 0.582521 | 186.696474 | 21062.691816 | 36.689134 |
6 | P7 | 15.0 | 20.0 | 16.0 | 0.092001 | 0.457578 | 315.569585 | 13236.018933 | 35.442611 |
7 | P8 | 20.0 | 20.0 | 16.0 | 0.122002 | 0.606788 | 154.280699 | 17552.098316 | 34.185247 |
8 | P9 | 2.0 | 18.0 | 16.0 | 0.122002 | 0.606788 | 0.084951 | 17552.098316 | 0.627599 |
9 | P10 | 2.0 | 22.0 | 20.0 | 0.214003 | 0.681194 | 0.514415 | 24630.493799 | 0.686250 |
10 | P11 | 2.0 | 28.0 | 25.0 | 0.397008 | 0.808777 | 0.543084 | 36554.548492 | 0.760010 |
11 | P12 | 4.0 | 35.0 | 32.0 | 0.458009 | 0.569488 | 0.262696 | 32946.314459 | 0.504894 |
As could have been expected, the volume flow rates are still close to the initial design values, as we have added a fixed pressure rise of 39 kPa that is close to what was calculated when designing the network and because we haven’t changed anything yet to the configuration of the pipe network.
ja.display_table(pipe_network.get_flow_path_table())
path | Δp-elev. [kPa] | Δp-dyn. [kPa] | Δp-tot. [kPa] | Δp-deficit [kPa] | |
---|---|---|---|---|---|
0 | P1|P2|P3|P4|P8|P9|P10|P11|P12 | 0.0 | 38.998188 | 38.998188 | 0.000843 |
1 | P1|P5|P12 | 0.0 | 38.999031 | 38.999031 | 0.000000 |
2 | P1|P2|P6|P11|P12 | 0.0 | 38.998794 | 38.998794 | 0.000237 |
3 | P1|P2|P3|P7|P10|P11|P12 | 0.0 | 38.998260 | 38.998260 | 0.000771 |
Hydraulic Resistance of Pipe Network#
ja.display_list([
f"network hydraulic resistance: <b>{pipe_network.hydraulic_resistance.to('Pa / (m ** 3 / s) ** 2'):~P.5g}</b>"
])
- network hydraulic resistance: 1.8591×10¹¹ Pa·s²/m⁶
system_curve1 = SystemCurve.create(pipe_network.hydraulic_resistance, name='system curve 1')
We will this SystemCurve
object later on when we will display different system curves of the pipe network.
Effect of Closing Control Valves#
What will happen to the volume flow rates in the network when we close the control valves in cross-overs P6 and P7 to 50 % opening? To find this out, we first set the valve opening of these control valves to 50 % of the fully open position:
pipe_network.set_control_valve_opening('P6', percent_open=50)
pipe_network.set_control_valve_opening('P7', percent_open=50)
Then we run the analyze
method again:
i = pipe_network.analyze(tolerance=Q_(1, 'Pa'), i_max=500)
i
155
Now can display the pipe table again, to check the volume flow rates in the network.
pipe_table2 = pipe_network.get_pipe_table()
ja.display_table(pipe_table2)
pipe ID | L [m] | DN [mm] | Di [mm] | V [l/s] | v [m/s] | zeta | Re | Δp-dyn. [kPa] | |
---|---|---|---|---|---|---|---|---|---|
0 | P1 | 4.0 | 35.0 | 32.0 | 0.356759 | 0.443593 | 0.007096 | 25662.971166 | 0.298219 |
1 | P2 | 2.0 | 28.0 | 25.0 | 0.295473 | 0.601933 | -0.011416 | 27205.754582 | 0.344295 |
2 | P3 | 2.0 | 22.0 | 20.0 | 0.175576 | 0.558877 | -0.037437 | 20207.794106 | 0.395152 |
3 | P4 | 2.0 | 18.0 | 16.0 | 0.124235 | 0.617895 | 0.096462 | 17873.388934 | 0.650165 |
4 | P5 | 25.0 | 18.0 | 14.0 | 0.061285 | 0.398116 | 434.804239 | 10076.515197 | 38.379412 |
5 | P6 | 25.0 | 26.0 | 20.0 | 0.119897 | 0.381643 | 486.754974 | 13799.399123 | 37.592559 |
6 | P7 | 15.0 | 20.0 | 16.0 | 0.051341 | 0.255351 | 1108.298588 | 7386.353698 | 36.717880 |
7 | P8 | 20.0 | 20.0 | 16.0 | 0.124235 | 0.617895 | 154.280699 | 17873.388934 | 35.420302 |
8 | P9 | 2.0 | 18.0 | 16.0 | 0.124235 | 0.617895 | 0.084951 | 17873.388934 | 0.647994 |
9 | P10 | 2.0 | 22.0 | 20.0 | 0.175576 | 0.558877 | 0.514415 | 20207.794106 | 0.480309 |
10 | P11 | 2.0 | 28.0 | 25.0 | 0.295473 | 0.601933 | 0.543084 | 27205.754582 | 0.443552 |
11 | P12 | 4.0 | 35.0 | 32.0 | 0.356759 | 0.443593 | 0.262696 | 25662.971166 | 0.323067 |
ja.display_table(pipe_network.get_flow_path_table())
path | Δp-elev. [kPa] | Δp-dyn. [kPa] | Δp-tot. [kPa] | Δp-deficit [kPa] | |
---|---|---|---|---|---|
0 | P1|P2|P3|P4|P8|P9|P10|P11|P12 | 0.0 | 39.003055 | 39.003055 | 0.000000 |
1 | P1|P5|P12 | 0.0 | 39.000698 | 39.000698 | 0.002357 |
2 | P1|P2|P6|P11|P12 | 0.0 | 39.001692 | 39.001692 | 0.001363 |
3 | P1|P2|P3|P7|P10|P11|P12 | 0.0 | 39.002474 | 39.002474 | 0.000581 |
Comparing volume flow rates#
data = {
'pipe_IDs': pipe_table1.iloc[:, 0].copy(),
'V_1': pipe_table1.iloc[:, 4].copy(),
'V_2': pipe_table2.iloc[:, 4].copy()
}
comp_table = pd.DataFrame(data)
comp_table['abs. difference'] = comp_table['V_2'] - comp_table['V_1']
comp_table['% difference'] = comp_table['abs. difference'] / comp_table['V_1'] * 100
ja.display_table(comp_table)
pipe_IDs | V_1 | V_2 | abs. difference | % difference | |
---|---|---|---|---|---|
0 | P1 | 0.458009 | 0.356759 | -0.101251 | -22.106701 |
1 | P2 | 0.397008 | 0.295473 | -0.101534 | -25.574913 |
2 | P3 | 0.214003 | 0.175576 | -0.038427 | -17.956196 |
3 | P4 | 0.122002 | 0.124235 | 0.002233 | 1.830497 |
4 | P5 | 0.061002 | 0.061285 | 0.000284 | 0.464941 |
5 | P6 | 0.183004 | 0.119897 | -0.063108 | -34.484162 |
6 | P7 | 0.092001 | 0.051341 | -0.040660 | -44.195050 |
7 | P8 | 0.122002 | 0.124235 | 0.002233 | 1.830497 |
8 | P9 | 0.122002 | 0.124235 | 0.002233 | 1.830497 |
9 | P10 | 0.214003 | 0.175576 | -0.038427 | -17.956196 |
10 | P11 | 0.397008 | 0.295473 | -0.101534 | -25.574913 |
11 | P12 | 0.458009 | 0.356759 | -0.101251 | -22.106701 |
For the same pressure rise of 39 kPa between the start and the end node of the network, the total flow rate into (and out of) the network has been reduced. In the cross-overs P6 and P7 the volume flow rate has been reduced approximately by 35 % and 44 % respectively. However, in the cross-overs P5 and P8 the volume flow rates have slightly increased, because there is less pressure drop in the supply and return header.
Hydraulic Resistance of Pipe Network#
ja.display_list([
f"network hydraulic resistance: <b>{pipe_network.hydraulic_resistance.to('Pa / (m ** 3 / s) ** 2'):~P.5g}</b>"
])
- network hydraulic resistance: 3.0644×10¹¹ Pa·s²/m⁶
system_curve2 = SystemCurve.create(pipe_network.hydraulic_resistance, name='system curve 2')
Plot the System Curves#
chart = plot_curves(
pump_curves=[],
system_curves=[system_curve1, system_curve2],
working_point=None,
fig_size=(8, 6),
V_step=Q_(0.05, 'L / s'),
V_max=Q_(0.55, 'L / s'),
dP_step=Q_(5, 'kPa'),
dP_max=Q_(50, 'kPa'),
V_unit='L / s',
dP_unit='kPa'
)
chart.show()