Pipe Network Optimiser

Least-cost pipe sizing for water distribution and transmission networks, inside QGIS. Reads and writes EPANET .inp models and has its own steady-state hydraulic solver, so nothing else needs to be installed.

Check the design before it is built. This is a design aid, not a substitute for engineering judgement. A least-cost design sits by construction as close to every constraint as the catalogue allows - the cheapest feasible network is also the one carrying the least spare capacity, so an assumption that turns out to be optimistic has nowhere to absorb it. Before a design leaves your desk: satisfy yourself that the demands, elevations, roughness values and loading scenarios in the model are right, re-simulate the exported .inp independently in EPANET or your usual package, and check the result against the codes and standards that govern your project. Every design must be reviewed and accepted by a qualified engineer who takes responsibility for it. The software is provided without warranty of any kind - see LICENSE.

What it does

You give it a network (an EPANET model), a catalogue of pipes you can actually buy with their rates, and the design rules (minimum pressure, maximum velocity, loading scenarios). It returns the cheapest set of diameters that satisfies every rule in every scenario.

The main output is an EPANET .inp file of the optimised network. As soon as a run finishes, <your model>_optimised.inp is written next to the model you loaded - the original file untouched, the same node and pipe IDs, the same patterns, curves, controls and coordinates, only the diameters (and roughness, if you asked for the catalogue values) replaced by the optimised ones. Open it in EPANET, WaterGEMS or back in QGIS and it runs straight away. Use Save optimised .inp... on the Results tab to write it anywhere else, or to export one of the alternative designs instead of the best one. Switch the automatic write off with the checkbox on the Optimise tab if you do not want it.

Alongside the model you also get a bill of quantities, an HTML report, CSV tables and styled map layers.

Workflow in five tabs

1 – Network

  1. Choose the .inp file and press Load network. The summary tells you the size of the model, its units, and whether it is branched (a tree - the exact solver applies) or looped.
  2. Set the coordinate system the model coordinates are in and press Create QGIS layers. Two layers are written to <model>_design.gpkg next to the .inp file: nodes and pipes.
  3. The pipe layer carries the design attributes. Select pipes on the map and use the buttons:
    AttributeMeaning
    opt_modesize = the optimiser chooses the diameter; fixed = the pipe stays as it is in the model (existing mains, pumps, valves).
    opt_groupPipes with the same group name are forced to the same diameter (a single main split into several links, staged contracts, symmetric branches).
    min_dn / max_dnAllowed DN range for that pipe.
    materialsComma separated materials the pipe may use (e.g. DI,MS) - empty means any.
    The node layer has check_p (1 = pressure is checked here), and pmin_m / pmax_m per-node overrides.
You can edit the attribute tables directly, or use selection + buttons. Anything left empty falls back to the global criteria in tab 3.

2 – Pipe catalogue

Each row is a size you are willing to lay: material, DN, internal diameter, roughness (Hazen-Williams C, Darcy-Weisbach ks, Manning n - the one matching the model's head-loss formula is used) and the unit rate per metre. Presets are provided for DI K9, HDPE PE100 PN10, PVC-U PN10, steel, and the two research benchmark networks.

The preset rates are indicative placeholders. Replace them with your own schedule of rates (supply + laying + specials) before quoting a result. Load / save your rate table as CSV.

Uncheck Use to keep a size in the table but exclude it from this run. "Use the catalogue roughness" replaces the roughness of every sized pipe with the value of the chosen material; switch it off to keep the roughness already in the model.

Material limits by diameter

The small table under the catalogue restricts each material to a DN range, which is how most schemes are actually specified: HDPE up to DN300, ductile iron from DN300 to DN1000, mild steel above DN1000. One row per material, blank = no limit:

MaterialMin DNMax DN
HDPE-PN6300
DI-K73001000
MS1000

Fill from catalogue puts one row in for every material in the table above, keeping any ranges you have already typed. Sizes outside their material's range are removed from the catalogue before the run, so they cannot turn up in the design, the map or the bill of quantities, and the log says how many were dropped. A material with no row is unrestricted. The per-pipe materials attribute on the pipe layer still applies on top of this, so you can also say "this particular main must be DI" and get the intersection of the two rules.

Ranges are compared against DN, the label, not the bore. Watch the sizes that straddle a boundary: HDPE runs 280, 315, 355..., so a limit of 300 stops HDPE at DN280. If you intend DN315 to be available, set the limit to 315.
A material named here must actually exist in the catalogue above. If you write a row for MS but the catalogue holds only DI and HDPE, MS is simply unavailable and the largest pipe you can use is the largest DI - which is usually far too small. The plugin says so ("no size of MS is left inside its DN range") and now warns before the run starts rather than after it. Check for gaps between the bands too: HDPE to DN300 followed by DI from DN350 leaves nothing between them, because HDPE stops at DN280 and DI starts at DN350.
Without this rule the optimiser will simply take the cheapest pipe per bore, and on a normal schedule of rates that means plastic everywhere it physically fits - regardless of pressure class, laying depth or what the specification allows. The limits are what make the answer buildable, and the cost difference between a banded and an unbanded run tells you what the specification is costing.

Catalogue CSV format

Save CSV... writes the table below; Load CSV... reads it back. One row per size, one header row, comma separated. The header names are what matter - column order is free, extra columns are ignored, the names are case-insensitive, and a file saved from Excel (with a BOM) loads fine.

ColumnMeaningIf left empty
use1 = offer this size to the optimiser, 0 = keep the row but skip it. true/false, yes/no also work.used
materialShort material code - DI, HDPE, MS... This is the string the pipe layer's materials attribute matches against.PIPE
dnNominal size, the label only. Used for grouping and for the min_dn / max_dn limits on the pipe layer.copies id_mm
id_mmInternal diameter in mm. This is the number the hydraulics actually use - for plastics it is the bore, not the OD.copies dn
hw_cHazen-Williams C.130
dw_ks_mmDarcy-Weisbach roughness ks in mm.0.1
cm_nChezy-Manning n.0.012
unit_costRate per metre laid, in your currency.0
pnClass or pressure rating, a label only (K9, PN10).blank

Only the roughness column matching the model's head-loss formula is read: hw_c for Hazen-Williams, dw_ks_mm for Darcy-Weisbach, cm_n for Chezy-Manning. The other two can hold anything.

use,material,dn,id_mm,hw_c,dw_ks_mm,cm_n,unit_cost,pn
1,DI,100,100,140,0.1,0.012,85,K9
1,DI,150,150,140,0.1,0.012,120,K9
1,HDPE,110,97.4,145,0.02,0.009,24,PN10
0,PVC,160,144.6,150,0.01,0.009,34,PN10
Two rows with the same internal diameter and different rates are wasteful - the optimiser will only ever pick the cheaper one. Give each size one row per material you will actually consider for that pipe.

3 – Constraints & scenarios

SettingNotes
Minimum residual pressureChecked at every junction (or only at junctions with demand). Per-node overrides win.
Maximum pressure0 = no limit. Sizing can rarely fix a high static head - use PRVs.
Maximum / minimum velocity0 = no limit. Velocities are checked in every scenario.
Max head-loss gradientm per km; a common transmission-main criterion. Pipes shorter than 2 m are ignored here.
Diameters must not increase downstreamTelescoping rule for branched mains.

Friction / head-loss formula

The first setting on this tab chooses the formula used for every hydraulic calculation, exactly like the Friction Method in WaterGEMS' calculation options. Leave it on As in the model to use whatever the .inp specifies, or force one of:

FormulaRoughness column usedTypical values
Hazen-Williamshw_c - the C value140 DI/MS lined, 150 HDPE, 100 old iron
Darcy-Weisbachdw_ks_mm - ks in mm0.1 lined, 0.02 plastic, 1-3 old iron
Chezy-Manningcm_n - Manning n0.011-0.012 lined, 0.009 plastic
Modified Hazen-Williamsmhw_cr - the coefficient CR ~0.95 DI/MS lined, ~1.02 plastic, ~0.88 older iron

Modified Hazen-Williams is the Jain, Mohan & Khanna (1978) form used by the CPHEEO manual and by WaterGEMS: V = 143.534 CR R0.6575 S0.5525, which rearranges to hf = 1.005×10-3 L Q1.81 / (CR1.81 D4.81). Note that CR is a coefficient near 1.0 and is not a Hazen-Williams C. If your catalogue CSV has no mhw_cr column, a CR is derived for each size from its hw_c, which puts C=140 at about CR=0.95 and C=150 at about 1.02.

Changing the formula changes what the roughness number means, so the numbers have to change with it, and a design sized under one formula is not the design you get under another. Two things happen automatically when you switch:

The exported .inp always carries the formula you chose: the HEADLOSS option is rewritten and every pipe roughness is re-expressed in the new units. Without that the file would hold C values under a Darcy-Weisbach header and EPANET would silently compute nonsense.
Exporting a Modified Hazen-Williams design. EPANET has no such option, so the exported .inp says HEADLOSS H-W and each pipe's CR is translated into the Hazen-Williams C that loses the same head at that pipe's own governing design flow. Because the match is made at the operating point rather than at a nominal velocity, EPANET reproduces the design to about a millimetre - but it is still a translation, and the [TITLE] block of every such file carries a warning saying so. The diameters, which are what you are buying, are the Modified Hazen-Williams design and are not altered.

Scenarios are loading cases - every design must satisfy all of the enabled ones: peak hour (demand factor), fire flow at one or more nodes with a lower residual pressure, minimum tank level, a future demand factor, and so on. Empty cells fall back to the global criteria. "Add fire-flow scenario from selected nodes" builds one scenario per selected node.

4 – Optimise

MethodWhen
AutomaticExact MILP if the network is a tree, otherwise the search.
Least cost - searchMemetic GA + spanning-tree MILP seeding + iterated local search. For looped networks.
Least cost - exact MILPBranched networks, control valves included: HiGHS proves the global optimum for the catalogue and scenarios given. Requires SciPy (shipped with QGIS).
Trade-off curve (Pareto)NSGA-II: a curve of designs, cost against the Todini resilience index (spare energy in the network) or against the pressure deficit if the budget is fixed.

Split pipe (exact method only) allows two diameters within one pipe - the classic way to squeeze the last few percent out of a transmission main. The exported .inp then contains the two sub-pipes with a new node between them.

When you press Run optimisation the plugin first sizes every pipe at the largest size that pipe is allowed - which is not always the largest size in the catalogue, because per-pipe DN limits and the material limits by diameter can cap it - and checks the targets against that. If they still cannot be met, no search can help, so it says so and offers to stop rather than spending the whole time limit to return an infeasible design.

The message separates the two faults, because they need opposite fixes:

Nodes with no open path to a source at all are reported separately - that is a model fault (a closed link or a valve status), not a sizing one. The full node lists go to the run log, where they can be copied out.

Set a time limit (the search always uses it fully), optionally several independent runs, and a seed if you need reproducible results. Analyse current design runs the hydraulics of the model as it stands and shows where it fails - a good first step.

Write the optimised .inp when the run finishes (on by default) saves <model>_optimised.inp beside your model the moment a design is found, so the sized EPANET model exists even if you close the dialog immediately. The path is echoed in the log and at the top of the result summary.

5 – Results

The table lists the best design and the alternatives found (or the Pareto front). Selecting a row shows its summary. The buttons:

How the optimisation works

Branched networks. In a tree the flow in every pipe is fixed by the demands, so the head at each node is a linear function of the diameter choice. The least-cost design is then a mixed-integer linear program which HiGHS solves to a proven global optimum - no tuning, no random seed, and a certificate that nothing cheaper exists for that catalogue. With split pipes the same model becomes a pure LP (the Karmeli / Alperovits-Shamir formulation).

Control valves in the exact method

A control valve breaks the chain of pipe head losses that ties a node back to its source, so the pressure behind one is not a simple function of the diameters above it. The exact solver handles this rather than refusing the network:

Valves that are simply open, or throttled to a fixed setting (TCV, GPV, PBV), need none of this: on a tree their flow is fixed, so their loss is just a constant on the path.

Every design still goes through the full non-linear solver afterwards. If a valve does not end up in the state it was designed for, that check catches it and the run says so.

Looped networks. Flows depend on the diameters, so the problem is non-convex and no solver can prove optimality in practice. This plugin uses three ingredients together:

  1. Spanning-tree MILP seeding - random spanning trees of the network are each solved exactly (the chords kept small, then re-solved with the real flows). This produces structurally different, near-optimal starting designs instead of random ones.
  2. A memetic genetic algorithm - integer chromosome, uniform crossover, creep mutation, feasibility-rule selection, restarts.
  3. Iterated local search - greedy downsizing, pairwise and 2-for-1 swaps, and a drop-and-repair ejection chain, with connected "kicks" to escape local optima.

On the two standard research benchmarks it reaches the best solutions published in the literature: 419,000 on the Two-Loop network and 6,081,087 on Hanoi (Hazen-Williams as EPANET computes it), typically in well under two minutes.

Hydraulics

The built-in solver is a Global Gradient Algorithm (Todini & Pilati) implementation that follows EPANET 2.2 conventions: Hazen-Williams, Darcy-Weisbach (Swamee-Jain with the EPANET transitional interpolation) and Chezy-Manning; minor losses; check valves; pumps (single-point, three-point, multi-point and constant-power curves); PRV, PSV, FCV and TCV valves; reservoirs and tanks as fixed heads. It was verified against EPANET (through WNTR) on the EPANET example networks and on the 3,356-node Net6 and 964-node KY4 models - heads agree to millimetres.

Each formula was checked separately against EPANET on single pipes spanning DN80-2000, 0.5-2 m/s and the usual roughness range: Hazen-Williams agrees to 0.001 %, Chezy-Manning to 0.08 % and Darcy-Weisbach to 0.06 % (a constant offset from EPANET's own friction-factor rounding). The Chezy-Manning coefficient is calibrated against EPANET rather than taken from the textbook form, which is about 1 % out.

The design check is steady state: demand patterns, controls and rules are not simulated (they are preserved unchanged in the exported .inp). Model each critical condition as its own scenario.

Practical notes

Limitations