This repository contains tools for water distribution network analysis and optimization, including both optimization capabilities and the Newton-Raphson method for solving water distribution networks using the Hazen-Williams formula.
- Advanced optimization of water distribution networks
- Implementation of the Newton-Raphson method for water network analysis
- Model Predictive Control for water distribution networks with demand variability
- Python 3.10 or higher
- Poetry
- Gurobi Optimizer (for advanced optimization capabilities)
- Download from Gurobi's website
- Free academic license available for academic users
- Clone this repository:
git clone https://github.com/ThyanRevolter/wdn_optimization.git
- cd to repository location
cd wdn_optimization
-
Install Gurobi Optimizer: (Optional if you haven't installed it already)
- Download and install Gurobi from Gurobi's website
- Get a free academic license if you're an academic user
- Add Gurobi to your system PATH
-
Activate the Poetry shell:
eval $(poetry env activate)
- Install dependencies using Poetry (recommended):
poetry install
The project includes advanced optimization capabilities for water distribution networks using both Pyomo and CVXPY solvers. These optimization tools help in:
- Minimizing operational costs
- Optimizing pump operations
- Managing tank levels
- Handling time-varying demands
- Considering electricity tariffs
- Supporting both binary and continuous pump operations
The optimization is implemented using two different optimization frameworks:
-
CVXPY Implementation (
wdn_cvxpy.py
)- Convex optimization capabilities
- Efficient linear programming
- Simplified pump modeling
- CVXPY Documentation
-
Pyomo Implementation (
wdn_pyomo.py
)- Mixed-integer linear programming (MILP) support
- Advanced pump control capabilities
- Pyomo Documentation
-
Dynamic Network Optimization
- Time-step based optimization
- Support for multiple time periods
- Demand pattern integration
- Tank level management
- Reservoir constraints
-
Pump Optimization
- Binary pump control (on/off)
- Continuous pump control
- Multiple pump states
- Power consumption optimization
- Minimum runtime constraints
-
Cost Optimization
- Electricity cost minimization
- Time-of-use tariff support
- Operational cost tracking
- Power consumption monitoring
from wdn_optimization.wdn_cvxpy import DynamicWaterNetworkCVX
from utils import utils as ut
# Initialize the optimization model
params = ut.load_json_file("data/soporon_network_opt_params.json")
wn_opt = DynamicWaterNetworkCVX(params=params)
# Print constraints and objective function for debugging
print("Constraints:")
for constraint_name, constraint in wn_opt.constraints.items():
print(constraint_name)
print(constraint)
print("-"*100)
print("-"*100)
print("Objective function:")
print(wn_opt.electricity_cost_objective)
print("-"*100)
# Solve the optimization problem
wn_opt.solve()
# Print optimization summary
wn_opt.print_optimization_result()
# Get optimization results
packaged_data = wn_opt.package_data(save_to_csv=True)
# Visualize results
ut.plot_results(wn_opt.wn, packaged_data)
The CVXPY implementation provides:
- Support for various solvers (ECOS, GUROBI, MOSEK, CBC, SCIPY)
- Detailed pump operation analysis
from wdn_optimization.wdn_pyomo import DynamicWaterNetwork
# Initialize the optimization model
wn_opt = DynamicWaterNetwork("path/to/optimization_params.json")
# Solve the optimization problem
wn_opt.solve()
# Get and visualize results
wn_opt.plot_results(save_to_file=True)
wn_opt.package_data(save_to_csv=True)
The optimization requires a JSON configuration file with the following parameters:
{
"start_date": "2025-01-01 00:00:00",
"end_date": "2025-01-02 00:00:00",
"time_step": 3600,
"network_path": "data/epanet_networks/simple_pump_tank.inp",
"pump_data_path": null,
"reservoir_data_path": null,
"final_tank_level_deviation": 0,
"binary_pump": false,
"pump_flow_capacity": 700,
"pump_power_capacity": 700,
"verbose": true,
"time_limit": 600,
"save_to_csv": true,
"save_plot_to_file": true
}
The optimization results include:
- Pump operation schedules
- Tank level variations
- Power consumption profiles
- Cost breakdowns
Results can be exported to CSV files and visualized using built-in plotting functions.
The project implements Model Predictive Control for water distribution networks, enabling real-time optimization with demand uncertainty and system disturbances.
- Rolling Horizon Control: Continuously updates optimization based on current system state
- Demand Uncertainty Handling: Incorporates demand variations and forecasting errors
- Tank Level Management: Maintains optimal tank levels across prediction horizons
- Cost Comparison: Compares MPC performance against prescient (perfect foresight) control
- Real-time Adaptation: Updates control decisions based on actual system measurements
from mpc.mpc_wrapper import MPCWrapper
from datetime import datetime
from utils import utils as ut
# Load optimization parameters
params = ut.load_json_file("data/soporon_network_opt_params.json")
# Configure MPC parameters
mpc_params = {
"optimization_params": params,
"simulation_start_date": datetime.strptime("2025-01-01 00:00:00", "%Y-%m-%d %H:%M:%S"),
"simulation_end_date": datetime.strptime("2025-01-02 00:00:00", "%Y-%m-%d %H:%M:%S"),
"simulation_time_step": 3600, # 1 hour
"model_update_interval": 6*3600, # 6 hours
"model_prediction_horizon": 24 # 24 hours
}
# Initialize and run MPC
mpc_wrapper = MPCWrapper(mpc_params)
results = mpc_wrapper.run_mpc()
# Get actual operations and compare with prescient
actual_operations = mpc_wrapper.get_actual_operations(results)
prescient_operations = mpc_wrapper.get_prescient_operations()
# Calculate costs
actual_cost = ut.get_electricity_cost(actual_operations, rate_df)
prescient_cost = ut.get_electricity_cost(prescient_operations, rate_df)
# Run the interactive MPC visualization
poetry run python mpc_visualization.py
The visualization provides:
- Interactive plots of pump flows, tank levels, and demands
- Comparison between MPC and prescient control
- Time series analysis across prediction horizons
- Cost performance metrics
Parameter | Description | Example |
---|---|---|
simulation_time_step |
Time resolution for simulation | 3600 (1 hour) |
model_update_interval |
Frequency of MPC updates | 6*3600 (6 hours) |
model_prediction_horizon |
Length of prediction window | 24 (24 hours) |
This project implements the Hazen-Williams formula for analyzing water distribution networks using the Newton-Raphson method. It provides tools for calculating flow rates, head losses, and pressure distributions in water networks.
from wdn_optimization.simple_nr import WaterNetwork, Units
import numpy as np
# Create a water network object
wn = WaterNetwork("path/to/your/network.inp", units=Units.IMPERIAL)
# Set initial conditions
initial_flow = np.array([4.5, 2, 2, 0.5]) # Initial flow rates for each link
initial_head = np.array([40, 35, 30]) # Initial heads for each junction
# Run the Newton-Raphson method
flow, head = wn.run_newton_raphson(
initial_flow=initial_flow,
initial_head=initial_head,
max_iter=100,
tol=1e-6
)
print(f"Final flow rates: {flow}")
print(f"Final heads: {head}")
You can also run the analysis from the command line:
poetry run python -m wdn_optimization.simple_nr --inp_file_path "path/to/your/network.inp" --units Imperial
project-root/
├── src/
│ └── epanet_tutorial/
│ ├── __init__.py
│ ├── simple_nr.py
│ └── wdn_pyomo.py
├── pyproject.toml
├── poetry.lock
└── README.md
This project uses bumpver for consistent versioning. To bump the version, use:
poetry run bumpver update --patch # or --minor, --major
This will update the version in all relevant files and create a git commit and tag.
The program expects an EPANET INP file format. The INP file should contain:
- [JUNCTIONS] section with node elevations and demands
- [PIPES] section with pipe properties (length, diameter, roughness)
- [RESERVOIRS] section with reservoir heads
- [COORDINATES] section for network layout
Example INP file structure:
[JUNCTIONS]
;ID Elevation Demand
J1 100 0
J2 95 100
J3 90 150
[PIPES]
;ID Node1 Node2 Length Diameter Roughness
P1 R1 J1 1000 12 100
P2 J1 J2 800 10 100
P3 J2 J3 600 8 100
[RESERVOIRS]
;ID Head
R1 150
The program supports both Imperial and Metric units:
- Length: feet (ft)
- Diameter: inches (in)
- Flow: gallons per minute (gpm)
- Head: feet (ft)
- Length: meters (m)
- Diameter: meters (m)
- Flow: cubic meters per second (m³/s)
- Head: meters (m)
The program uses the Hazen-Williams equation for calculating head losses:
h_f = 4.73 * L * Q^1.852 / (C^1.852 * D^4.87)
where:
- h_f = head loss due to friction
- L = length of pipe
- Q = flow rate
- C = Hazen-Williams roughness coefficient
- D = diameter of pipe
The Newton-Raphson method is used to solve the system of nonlinear equations that describe the network's flow and pressure distribution.
The Newton-Raphson method is used to solve the system of nonlinear equations that govern the water distribution network. The algorithm is implemented as follows:
The network is described by two sets of equations:
For each loop in the network:
\sum(h_f) + \sum(\Delta H) - H_{reservoir} = 0
where:
- h_f = head loss in each pipe (from Hazen-Williams equation)
- \Delta H = head difference between nodes
- H_{reservoir} = reservoir head (from RESERVOIRS section)
For each junction:
\sum(Q_{in}) - \sum(Q_{out}) - Q_{demand} = 0
where:
- Q_{in} = incoming flow rates
- Q_{out} = outgoing flow rates
- Q_{demand} = junction demand
-
Initialization
- Set initial flow rates (Q₀) for all pipes
- Set initial heads (H₀) for all junctions
- Define convergence tolerance (tol) and maximum iterations (max_iter)
-
Iteration Process For each iteration k:
a. Calculate the Jacobian matrix J:
J = [∂E_{energy}/∂Q ∂E_{energy}/∂H] [∂E_{continuity}/∂Q ∂E_{continuity}/∂H]
b. Calculate the error vector E:
E = [E_{energy}] [E_{continuity}]
c. Solve the linear system for the update vector \Delta X:
J * \Delta X = -E
d. Update the variables:
Q_{k+1} = Q_k + \Delta Q H_{k+1} = H_k + \Delta H
-
Convergence Check
- If ||\Delta X|| < tol, the solution has converged
- If k > max_iter, stop and report non-convergence
- Otherwise, continue to next iteration
The algorithm is implemented in the run_newton_raphson
method with the following components:
- Head Loss Matrix: Diagonal matrix containing head losses for each pipe
- Flow Difference Matrix: Adjacency matrix representing network topology
- Error Vectors:
- Nodal balance error (energy equations)
- Link flow error (continuity equations)
- Update Vector: Combined flow and head updates
- The algorithm typically converges quadratically near the solution
- Convergence depends on:
- Quality of initial guess
- Network topology
- Pipe properties
- Demand distribution
For a simple network with 3 pipes and 2 junctions:
from wdn_optimization.simple_nr import WaterNetwork, Units
import numpy as np
# Create a water network object
wn = WaterNetwork("path/to/your/network.inp", units=Units.IMPERIAL)
# Initial conditions
initial_flow = np.array([4.5, 2, 2]) # Flow rates for 3 pipes
initial_head = np.array([40, 35]) # Heads for 2 junctions
# Run Newton-Raphson
flow, head = wn.run_newton_raphson(
initial_flow=initial_flow,
initial_head=initial_head,
max_iter=100,
tol=1e-6
)
The algorithm will iteratively solve for the flow rates and heads that satisfy both the energy and continuity equations.