Flow Routing and Mass Balance in QUAL2K River Segments
As described in the EPA QUAL2K technical documentation, the model divides the river into discrete reaches (computational elements) and marches downstream from the headwater boundary. At each reach, it performs a conservative mass balance to mix upstream flow with point sources and diffuse sources before applying kinetic reactions.
Downstream Flow Routing Algorithm
The simulation proceeds sequentially from the headwater (reach 0) to the terminus (reach n). At each reach , three steps are performed in order:
Step 1: Mass Balance by Conservative Mixing
The mass entering reach from all upstream sources is summed and divided by the total flow to produce mixed concentrations:
where:
- — upstream flow from reach
- — upstream concentration of constituent
- — point source flow and concentration
- — diffuse source flow and concentration
Point Source Injection
Point sources represent discrete discharges such as wastewater treatment plant effluent, tributary inflow, or industrial discharges. Each point source has a location (km) along the river and is assigned to the reach where it falls:
Diffuse Source Distribution
Diffuse (nonpoint) sources are distributed over a range of river kilometers. The contribution to each reach is proportional to the overlap length between the diffuse source range and the reach:
where and is the total diffuse source length.
Step 2: Kinetic Reactions
After mixing, first-order kinetic reactions are applied over the travel time of the reach. See the BOD Decay and Nitrogen Cycle pages for detailed kinetic equations.
Step 3: Dissolved Oxygen Balance
The final DO concentration is computed by balancing sources (reaeration, photosynthesis) against sinks (BOD oxidation, nitrification, SOD). See the DO Balance page.
Travel Time and Spatial Discretization
The travel time through each reach determines how long kinetic reactions act on the water:
Longer reaches have longer travel times and more kinetic decay. The choice of reach discretization affects the spatial resolution of the model output but does not change the total mass balance (which is conservative).
Python Implementation
for i in range(1, nr + 1):
reach = reaches[i]
q_up = reaches[i - 1].get('q', 0)
# Step 1: Conservative mixing
mass_in = [c * q_up for c in conc[i - 1]]
q_total = q_up
for ps in point_sources:
if _in_reach(ps['xptt'], x_up, x_dn):
q_ps = ps['Qptt']
for k in range(num_vars):
mass_in[k] += q_ps * ps['cpttMean'][k]
q_total += q_ps
c_mix = [m / q_total for m in mass_in]
# Step 2: Kinetics
c_kin = apply_kinetics(c_mix, rates, reach, temp, trav_time)
# Step 3: DO balance
c_kin[2] = calculate_do_balance(c_kin[2], ...)