17 Reuse with an existing treatment unit
Problem 17 | Wastewater Treatment and Reuse | Level 2: Model extension
17.1 Problem Statement
Two independent wastewater sources supply S1 = 60 m\(^3\)/h at 40 mg/L and S2 = 50 m\(^3\)/h at 120 mg/L of one contaminant. Users U1 and U2 need 50 and 40 m\(^3\)/h, with maximum inlet concentrations of 30 and 60 mg/L. Freshwater contains no contaminant and costs 1 USD/m\(^3\). Unused source water is discharged at 0.10 USD/m\(^3\). User outlet streams are outside the model boundary; this is a one-pass reuse allocation.
| Treatment | Removal fraction | Capacity (m3/h) | Variable cost (USD/m3) | Hourly ownership charge (USD/h) |
|---|---|---|---|---|
| Moderate | 0.75 | 40 | 0.25 | 3 |
| Advanced | 0.95 | 80 | 0.45 | 12 |
Treatment conserves water volume and transfers removed contaminant to a separate captured-residue stream. Residue handling is included in the assumed treatment cost. Each source remains segregated through treatment, or equivalently uses source-specific modules with a shared total hydraulic capacity. Fixed removal fractions do not depend on loading. These assumptions avoid a common mixed treatment inlet with an unknown concentration.
Your task. Add the moderate treatment option with capacity 40 m\(^3\)/h and removal fraction 0.75. Its ownership cost is sunk, so charge only variable treatment, freshwater, and disposal costs.
17.1.1 Before you code
For a given treated volume, which source yields a larger contaminant reduction? Does that automatically mean all treatment should serve that source?
17.1.2 Deliverables
- Define decision variables, units, objective, and constraints. State which data and assumptions are fixed.
- Predict one feature of the solution and explain why it should occur.
- Build and solve a Pyomo model. Check balances and bounds independently.
- Interpret the result and investigate the follow-up question below. For an open-ended challenge, state and defend your chosen policy separately from the reference solution.
17.2 Model Formulation
Let \(x_{ij}\ge0\) be direct reuse, \(y_{ijk}\ge0\) treated reuse through technology \(k\), \(f_j\ge0\) freshwater, and \(d_i\ge0\) discharge, all in m\(^3\)/h. A binary \(z_k\) selects a treatment option when required. With source supply \(S_i\), user demand \(D_j\), source concentration \(c_i\), user limit \(L_j\), and removal \(r_k\), \[\sum_j\left(x_{ij}+\sum_ky_{ijk}\right)+d_i=S_i,\] \[\sum_i\left(x_{ij}+\sum_ky_{ijk}\right)+f_j=D_j,\] \[\sum_i c_i\left[x_{ij}+\sum_k(1-r_k)y_{ijk}\right]\le L_jD_j,\] \[\sum_{i,j}y_{ijk}\le\overline Q_k z_k.\] Concentration-times-flow products have units mg/L times m\(^3\)/h. Multiplying every term by 1000 gives mg/h, so the common factor cancels in the quality inequality. Concentrations are fixed input coefficients, which keeps this formulation linear.
Fix \(z_{moderate}=1\) and minimize \[C=\sum_jf_j+0.10\sum_id_i+0.25\sum_{i,j}y_{ij,moderate}.\] Removed contaminant is \[R=\sum_{i,j}c_i r_{moderate}y_{ij,moderate}.\] Include this captured load when checking the contaminant balance. A treatment model that reduces concentration while conserving water but has no contaminant outlet is physically incomplete.
17.3 Pyomo Implementation
The code below is the model-building portion of models/water.py. The level argument selects this problem’s assumptions. Run the complete module from the source bundle to reproduce the solution, including its independent checks:
~/.venvs/optim/bin/python -m models.water 2"""Segregated wastewater reuse with fixed-quality source streams."""
import pyomo.environ as pyo
from models.common import value
SUPPLY = {"S1": 60, "S2": 50} # m3/h
NOMINAL = {"S1": 40, "S2": 120} # mg/L
HIGH = {"S1": 60, "S2": 160}
DEMAND = {"U1": 50, "U2": 40} # m3/h
LIMIT = {"U1": 30, "U2": 60} # mg/L
TECH = {"moderate": (0.75, 40, 0.25, 3), "advanced": (0.95, 80, 0.45, 12)}
# removal fraction, hydraulic capacity m3/h, variable USD/m3, fixed USD/h
def build(level):
conc = HIGH if level == 3 else NOMINAL
techs = [] if level == 1 else ["moderate"] if level == 2 else list(TECH)
m = pyo.ConcreteModel(name="Segregated water reuse")
m.I = pyo.Set(initialize=list(SUPPLY))
m.J = pyo.Set(initialize=list(DEMAND))
m.K = pyo.Set(initialize=techs)
m.x = pyo.Var(m.I, m.J, domain=pyo.NonNegativeReals)
m.y = pyo.Var(m.I, m.J, m.K, domain=pyo.NonNegativeReals)
m.f = pyo.Var(m.J, domain=pyo.NonNegativeReals)
m.d = pyo.Var(m.I, domain=pyo.NonNegativeReals)
m.z = pyo.Var(m.K, domain=pyo.Binary)
if level == 2:
m.z["moderate"].fix(1)
if level == 3:
m.choose = pyo.Constraint(expr=sum(m.z[k] for k in m.K) <= 1)
m.source = pyo.Constraint(
m.I,
rule=lambda m, i: (
sum(m.x[i, j] + sum(m.y[i, j, k] for k in m.K) for j in m.J) + m.d[i]
== SUPPLY[i]
),
)
m.sink = pyo.Constraint(
m.J,
rule=lambda m, j: (
sum(m.x[i, j] + sum(m.y[i, j, k] for k in m.K) for i in m.I) + m.f[j]
== DEMAND[j]
),
)
m.quality = pyo.Constraint(
m.J,
rule=lambda m, j: (
sum(
conc[i]
* (m.x[i, j] + sum((1 - TECH[k][0]) * m.y[i, j, k] for k in m.K))
for i in m.I
)
<= LIMIT[j] * DEMAND[j]
),
)
m.cap = pyo.Constraint(
m.K,
rule=lambda m, k: (
sum(m.y[i, j, k] for i in m.I for j in m.J) <= TECH[k][1] * m.z[k]
),
)
m.obj = pyo.Objective(
expr=sum(m.f[j] for j in m.J)
+ 0.1 * sum(m.d[i] for i in m.I)
+ sum(TECH[k][2] * m.y[i, j, k] for i in m.I for j in m.J for k in m.K)
+ (sum(TECH[k][3] * m.z[k] for k in m.K) if level == 3 else 0)
)
m._conc = conc
return mThe common solve function calls appsi_highs, checks optimal termination before loading a solution, and checks constraint residuals, bounds, and integer domains. After building the model, use:
from models.common import solve
m = solve(build(2))17.4 Optimal Solution
The reference objective is 9.5000 USD/h. This value is optimal for the explicitly stated model and data.
| User | Fresh (m3/h) | Direct reuse (m3/h) | Treated reuse (m3/h) | Concentration (mg/L) |
|---|---|---|---|---|
| U1 | 0.0000 | 20.0000 | 30.0000 | 30.0000 |
| U2 | 0.0000 | 40.0000 | 0.0000 | 60.0000 |
| Quantity | Value |
|---|---|
| Freshwater (m3/h) | 0.0000 |
| Disposal (m3/h) | 20.0000 |
| Technology | moderate |
17.4.1 Check your solution
Check source contaminant input equals load sent to users plus load in discharge plus captured contaminant. Convert the final quantities to kg/h using 0.001 kg per mg/L times m3.
The automated residual, bound, and integrality audit passed with a maximum violation of 7.11e-15 in model units. Domain-specific checks passed. Model units differ across equations, so this numerical audit does not replace dimensional analysis.
17.5 Brief Discussion
The solution uses 0.000 m3/h of freshwater and discharges 20.000 m3/h, at 9.500 USD/h. Treatment choice: moderate. Even when freshwater falls to zero, 20 m3/h must leave the system because source supply exceeds user demand. Fixed removal and segregated paths are substantive process assumptions, not just numerical conveniences.
17.5.1 Extension to investigate
Lower treatment removal to 0.50 and determine whether zero freshwater remains feasible at the same capacity.