Dissolved Oxygen Balance: Sources, Sinks, and the DO Sag Curve
Dissolved oxygen is the single most important indicator of stream health. The EPA recommends minimum DO criteria for aquatic life protection. The DO balance in QUAL2K accounts for all sources and sinks of oxygen, producing the characteristic DO sag curve downstream of pollution sources.
Full DO Balance Equation
The rate of change of DO at each reach is:
Term-by-Term Explanation
| Term | Formula | Effect | Typical Magnitude |
|---|---|---|---|
| Reaeration | ka · (DOsat − DO) | Source (+) when DO < DOsat | 1–10 mg/L/d |
| Fast CBOD oxidation | kdc · CBODf | Sink (−) consumes O₂ | 0.5–5 mg/L/d |
| Slow CBOD oxidation | kdcs · CBODs | Sink (−) consumes O₂ | 0.01–0.5 mg/L/d |
| Nitrification O₂ demand | ron · kn · NH₄ | Sink (−) consumes O₂ | 0.5–3 mg/L/d |
| Sediment O₂ demand | SOD / H | Sink (−) from benthos | 0.5–5 mg/L/d |
The DO Sag Curve
When a pollution source adds CBOD to a river, the DO drops as bacteria consume oxygen to oxidize the organic matter. Downstream, reaeration from the atmosphere gradually restores DO, producing the classic sag shape:
Nitrogenous Oxygen Demand (NOD)
Nitrification consumes oxygen as bacteria oxidize ammonium to nitrate:
The stoichiometric ratio mg O₂ per mg NH₄-N oxidized. This term can be the dominant oxygen sink in rivers receiving nitrogenous wastewater.
Sediment Oxygen Demand (SOD)
SOD represents oxygen consumed by microbial decomposition of organic matter in the stream bed sediments. It is expressed as a flux (g O₂/m²/d) and converted to a volumetric rate by dividing by stream depth:
Typical SOD values by substrate type
| Substrate | SOD (g O₂/m²/d) | Context |
|---|---|---|
| Sandy / gravel bed | 0.2 – 1.0 | Clean streams with minimal organic deposition |
| Silt / mud bed | 1.0 – 2.0 | Moderate organic enrichment |
| Organic-rich sediment | 2.0 – 5.0 | Downstream of WWTP outfalls |
| Heavily polluted | 5.0 – 10.0 | Industrial discharge zones |
| Municipal sludge deposits | 2.0 – 10.0 | Near combined sewer overflows |
DO Saturation Reference Table
DO saturation (mg/L) at sea level
| Temp (°C) | DOsat | Temp (°C) | DOsat | Temp (°C) | DOsat |
|---|---|---|---|---|---|
| 0 | 14.62 | 10 | 11.29 | 20 | 9.09 |
| 2 | 13.84 | 12 | 10.78 | 22 | 8.74 |
| 4 | 13.13 | 14 | 10.31 | 24 | 8.42 |
| 6 | 12.48 | 16 | 9.87 | 26 | 8.11 |
| 8 | 11.87 | 18 | 9.47 | 28 | 7.83 |
Python Implementation
def calculate_do_balance(do_in, cbod_fast, cbod_slow, nh4, rates, reach, temp, dt):
ka = reach.get('ka', 0)
ka_t = temp_correction(ka, 1.024, temp) # Reaeration
dosat = calculate_dosat(temp, reach.get('elev', 0))
kdc = temp_correction(rates['kdc'], rates.get('kdc_theta', 1.047), temp)
kdcs = temp_correction(rates['kdcs'], rates.get('kdcs_theta', 1.047), temp)
kn = temp_correction(rates.get('kn', 0), rates.get('kn_theta', 1.083), temp)
ron = rates.get('ron', 4.57) # O₂:N stoichiometric ratio
sod = reach.get('SOD', 0)
h = reach.get('H', 1.0)
# Source: reaeration
reaeration = ka_t * (dosat - do_in) * dt
# Sinks
cbod_demand_f = kdc * cbod_fast * dt
cbod_demand_s = kdcs * cbod_slow * dt
nod_demand = ron * kn * nh4 * dt
sod_demand = (sod / h) * dt if h > 0 else 0
do_out = do_in + reaeration - cbod_demand_f - cbod_demand_s - nod_demand - sod_demand
return max(do_out, 0.0) # DO cannot go negative