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

  1. Define decision variables, units, objective, and constraints. State which data and assumptions are fixed.
  2. Predict one feature of the solution and explain why it should occur.
  3. Build and solve a Pyomo model. Check balances and bounds independently.
  4. 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 m

The 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
Figure 16.1: Wastewater Treatment and Reuse: reference solution for level 1.

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.