Skip to content

Network sizing and hydraulics

Once a network topology is drawn and buildings are connected, two sequential tools turn it into a sized, pressure-solved design: Dimension pipes selects a nominal diameter for each segment based on peak flow, and the hydraulic solver (run as part of Simulate network operation) computes velocities, pressure losses, and pump heads.

This page explains the algorithms behind both steps. For the data fields written to each pipe and node, see Networks and heat sources data model. For what the full simulation computes beyond hydraulics, see Heat loss and simulation.

Pipe sizing

From peak power to flow rate

The goal of pipe sizing is to select the smallest nominal diameter (DN) that keeps fluid velocity and pressure drop inside the envelope defined for that DN in the network's pipe specification table — a minimum velocity to prevent sedimentation, and maximum velocity and pressure-gradient limits to control noise, erosion, and pumping cost. The starting point is the peak volumetric flow rate at each pipe segment.

Peak flow is derived from the peak thermal power the segment must carry:

\[Q = \frac{P}{\rho \cdot c_p \cdot \Delta T}\]

where P is the peak power (W), ρ is the fluid density (kg/m³), c_p is the specific heat capacity (J/kg·K), and ΔT = T_\text{supply} − T_\text{return} is the temperature spread. Both ρ and c_p are taken from the network's working fluid at its operating temperatures.

Selecting the nominal diameter

Sizing is driven by the network's pipe specification table — an editable, per-network lookup keyed on DN and material that carries each DN's inner diameter and its velocity and pressure-gradient envelope (see Pipe specifications). For each candidate DN the sizer computes the resulting velocity and pressure gradient and applies the table's limits:

velocity = Q / A_inner          where A_inner = π (d_inner / 2)²
pressure_gradient = Darcy-Weisbach ΔP/L at that velocity

For each segment, walking DNs from largest to smallest:
  reject any DN where velocity > max_velocity_m_s        (hard limit)
  reject any DN where ΔP/L     > max_pressure_gradient_pa_m (hard limit)
  prefer the smallest surviving DN with velocity ≥ min_velocity_m_s
  if none reaches min velocity → use the smallest surviving DN
  if nothing survives the hard limits → use the largest DN (flagged below)

Both the minimum and maximum velocity, and the recommended and maximum pressure gradient, are read per DN row from the spec table — there is no single hard-coded 1–4 m/s rule. The shipped steel defaults use a 0.5 m/s minimum and a 1.5–3.0 m/s maximum that widens with diameter; other materials and edited tables may differ.

Once a DN is selected, the following fields are written to the pipe segment:

flowchart LR P["━━ Pipe segment"] P --> DN["Nominal diameter<br/>(DN)"] P --> ID["inner_diameter_m"] P --> VEL["velocity_m_s"] P --> VOL["fluid_volume_m3"] P --> CAP["maximum_capacity_kw"] classDef pipe fill:#ffab91,stroke:#bf360c,stroke-width:2px,color:#bf360c classDef field fill:#fff3e0,stroke:#ED6000,stroke-width:1.5px,color:#bf360c class P pipe class DN,ID,VEL,VOL,CAP field

maximum_capacity_kw is the thermal power the segment can carry at its DN's maximum velocity — it is a design headroom figure, not a hard limit enforced at runtime.

Sizing validation flags

After a DN is chosen, each pipe is checked against its spec-table row and tagged with two booleans:

Field Meaning
velocity_in_range min_velocity_m_s ≤ v ≤ max_velocity_m_s for the chosen DN.
pressure_gradient_ok ΔP/L ≤ max_pressure_gradient_pa_m for the chosen DN.

A segment whose flow is too low to reach the minimum velocity even at the smallest DN, or whose DN falls outside the spec table, is flagged velocity_in_range = false. These flags surface in the pipe inspector so you can spot segments running outside their envelope — they do not block the run.

Tree vs looped networks

How segment flows are determined before sizing depends on the network topology.

Tree (branched) networks have a unique flow path from each energy center to each building. The peak flow at each segment is the sum of the peak demands of all building substations downstream of it. Sizing runs segment-by-segment from the leaves toward the energy center.

flowchart LR EC["⚙️ Energy center"] EC -->|"Q₁+Q₂+Q₃"| J1["●"] J1 -->|"Q₁+Q₂"| J2["●"] J1 -->|"Q₃"| BS3["🔌 Substation C"] J2 -->|"Q₁"| BS1["🔌 Substation A"] J2 -->|"Q₂"| BS2["🔌 Substation B"] classDef ec fill:#ffccbc,stroke:#bf360c,stroke-width:2px,color:#bf360c classDef bs fill:#ffe0b2,stroke:#e65100,stroke-width:1.5px,color:#bf360c classDef cp fill:#ffffff,stroke:#bf360c,stroke-width:1.5px,color:#bf360c class EC ec class BS1,BS2,BS3 bs class J1,J2 cp

Looped (meshed) networks have multiple paths between energy centers and substations, so flows are not unique. TESSA uses a practical two-pass approach:

  1. Compute the minimum spanning tree (MST) of the loop topology.
  2. Size the MST as a tree network using the procedure above.
  3. For each extra connection that closes a loop, construct a forced MST that includes that connection as a required edge and size it independently.
  4. For each segment, the final DN is the largest DN selected across all MST passes that include that segment.

This ensures every loop chord is sized to carry a feasible peak flow, while avoiding the need for a full hydraulic flow solver at the sizing stage.

Connections

Building connectors and energy-center connectors are sized independently from the other pipes. Their flow rate comes from their own v_dot value (the volumetric flow at the connected substation or energy center), not from the aggregated downstream demand. This reflects that connector pipes run only to a single substation.

Re-running sizing

The overwrite_diameters flag (default: true) controls whether a new sizing run replaces existing diameters. Set it to false to preserve any segments where you have manually entered a DN — for example, if you know a particular backbone segment must be at least DN 150 for a future extension — while still letting TESSA size the remaining segments.

Darcy-Weisbach hydraulics

The hydraulic pressure solve runs as part of Simulate network operation and uses the Darcy-Weisbach equation together with the Colebrook friction-factor correlation.

Reynolds number and friction factor

The Reynolds number for each pipe segment is:

\[\text{Re} = \frac{\rho \cdot v \cdot d}{\mu}\]

where μ is the dynamic viscosity of the working fluid. The friction factor f is then computed:

  • Laminar flow (Re < 2000): the exact solution, f = 64 / Re.
  • Turbulent flow (Re ≥ 2000): TESSA solves the implicit Colebrook equation iteratively:
\[\frac{1}{\sqrt{f}} = -2 \log_{10}\!\left(\frac{\varepsilon}{3.7\,d} + \frac{2.51}{\text{Re}\,\sqrt{f}}\right)\]

where ε is the pipe roughness (m). The roughness comes from the network's pipe specification table — per DN row where set, otherwise the network-wide roughness fallback (the steel default is 0.045 mm). Applying a different material preset, or editing the spec table, changes the roughness used here. See Pipe specifications.

Pressure loss per segment

With f determined, the Darcy-Weisbach pressure loss is:

\[\Delta p = f \cdot \frac{L}{d} \cdot \frac{\rho v^2}{2}\]

A pressure loss surcharge (default 20%) is added on top of the computed Darcy-Weisbach loss to account for local losses at bends, fittings, and valves. The surcharge factor is applied uniformly across all segments and can be adjusted in the network settings.

Head loss in metres of fluid column is stored alongside the Pa figure:

\[h_{f,\text{DW}} = \frac{\Delta p}{\rho \cdot g}\]

Results written per pipe after the hydraulic solve:

Field Description
m_dot_kg_s Mass flow rate (kg/s)
v_dot_m3_s Volumetric flow rate (m³/s)
velocity_m_s Average velocity (m/s)
pressure_loss_pa Total pressure loss including surcharge (Pa)
h_f_dw_m Head loss in metres of fluid column
pressure_loss_pa_m Pressure loss per metre of pipe (Pa/m)

Substation pressure losses

Pressure losses at substations are modelled as fixed drops across the heat exchanger or internal equipment:

  • Building substation (heat exchanger): default 0.7 bar pressure drop
  • Energy center (internal equipment and valves): default 1.0 bar pressure drop

These are converted to head loss via h = ΔP / (ρ · g) and added to the hydraulic path when summing losses from an energy center to a substation.

Static pressure and network pressurisation

After dynamic losses are solved, TESSA computes the absolute pressures at each node. The user designates a reference node — typically the pump inlet at one energy center — and sets a minimum static pressure that must hold everywhere in the network (to prevent the fluid from flashing to steam or drawing in air through joints).

The pressurisation step propagates dynamic pressure from the reference node to every other node along the solved flow paths, then checks that no node drops below the minimum. If any node does, the network's minimum pressure is raised until the constraint is satisfied.

Results per node:

Field Description
pressure_dynamic_supply_pa Absolute pressure on the supply side (Pa)
pressure_dynamic_return_pa Absolute pressure on the return side (Pa)
flowchart LR EC["⚙️ Energy center<br/>(reference node)"] J1["● Junction"] J2["● Junction"] BS1["🔌 Substation A"] BS2["🔌 Substation B"] EC -->|"Δp₁"| J1 J1 -->|"Δp₂"| J2 J1 -->|"Δp₃"| BS1 J2 -->|"Δp₄"| BS2 EC -.p_ref.- REF["📍 p_min constraint"] classDef ec fill:#ffccbc,stroke:#bf360c,stroke-width:2px,color:#bf360c classDef bs fill:#ffe0b2,stroke:#e65100,stroke-width:1.5px,color:#bf360c classDef cp fill:#ffffff,stroke:#bf360c,stroke-width:1.5px,color:#bf360c classDef fluid fill:#bbdefb,stroke:#0d47a1,stroke-width:1.5px,color:#0d47a1 class EC ec class BS1,BS2 bs class J1,J2 cp class REF fluid

Pump sizing

Once all pressure losses are known, TESSA sizes the circulation pump at each energy center.

The required pump head at an energy center is the total head loss along the critical path — the hydraulic path from that energy center to the most distant (highest-resistance) substation:

\[H_\text{pump} = \sum_\text{critical path} h_{f,\text{segment}} + h_{f,\text{substations}}\]

Pump power is then:

\[P_\text{pump} = \frac{\dot{V} \cdot \rho \cdot g \cdot H_\text{pump}}{\eta_\text{pump}}\]

where η_pump is the pump efficiency (a user-adjustable parameter, default 0.7). Pump electricity consumption is recorded hourly in the simulation and added to the network's operating costs and CO₂ figures.

flowchart TD CL["Critical path losses<br/>━━━━━━━━<br/>Σ pipe head losses<br/>+ substation drops"] CL --> H["Pump head H"] H --> PP["Pump power P<br/>(flow × head / efficiency)"] PP --> OPEX["Annual pump electricity<br/>→ operating cost<br/>→ CO₂ emissions"] classDef fluid fill:#bbdefb,stroke:#0d47a1,stroke-width:2px,color:#0d47a1 classDef field fill:#e3f2fd,stroke:#1565c0,stroke-width:1.5px,color:#0d47a1 class CL,H,PP fluid class OPEX field

Putting sizing and hydraulics together

The full workflow for a first-pass design is:

flowchart TD A["🏠 District with load curves<br/>and connected substations"] A --> B["▶ Dimension pipes<br/>(peak flow → DN selection)"] B --> C["▶ Simulate network operation<br/>step 1: heat distribution<br/>step 2: altitude fetch"] C --> D["▶ Hydraulic solve<br/>(Darcy-Weisbach + Colebrook)"] D --> E["▶ Pressurisation<br/>(static pressure check)"] E --> F["▶ Pump sizing<br/>(critical path → H, P)"] F --> G["✅ Pipe results: DN, v, Δp, h_f<br/>Node results: p_supply, p_return<br/>EC results: pump head, pump power"] classDef step fill:#fff3e0,stroke:#ED6000,stroke-width:1.5px,color:#bf360c classDef done fill:#ffab91,stroke:#bf360c,stroke-width:2px,color:#bf360c classDef input fill:#a5d6a7,stroke:#1b5e20,stroke-width:1.5px,color:#1b5e20 class A input class B,C,D,E,F step class G done

Dimension pipes must be run before the simulation. If you run the simulation without having sized the network, head losses and pump figures will be zero or absent.

Where to go next