16 Direct reuse without treatment
Problem 16 | Wastewater Treatment and Reuse | Level 1: Guided problem
16.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. Allow direct reuse and freshwater only. Minimize freshwater and disposal costs while meeting both user quality limits.
16.1.1 Before you code
Can source S1 be sent directly to U1 without dilution? Why might some S2 still be useful to U2?
16.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.
16.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.
Remove all treatment variables and minimize \[C=\sum_j f_j+0.10\sum_i d_i.\] The quality limits are concentration limits at fixed total user demand, so they can be written as linear contaminant-load inequalities. Mixing at each user is permitted, but no unknown intermediate pool feeds multiple users.
16.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 1"""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(1))16.4 Optimal Solution
The reference objective is 21.2500 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 | 12.5000 | 37.5000 | 0 | 30.0000 |
| U2 | 5.0000 | 35.0000 | 0 | 60.0000 |
| Quantity | Value |
|---|---|
| Freshwater (m3/h) | 17.5000 |
| Disposal (m3/h) | 37.5000 |
| Technology | none |
16.4.1 Check your solution
Check the overall water balance \(110+\sum_j f_j=90+\sum_i d_i\). Independently calculate each user concentration from incoming contaminant load.
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.
16.5 Brief Discussion
The solution uses 17.500 m3/h of freshwater and discharges 37.500 m3/h, at 21.250 USD/h. Treatment choice: none. Contaminant removed by treatment must leave in the captured-residue stream. Fixed removal and segregated paths are substantive process assumptions, not just numerical conveniences.
16.5.1 Extension to investigate
Raise the U1 concentration limit from 30 to 40 mg/L and predict the freshwater reduction before solving.