# Conditions in the river upstream of the first plant (clean water)
river_flow = 1.0e5 # m³/d
river_DO = 6.8 # mg/L
river_CBOD = 0.0 # mg/L
river_NBOD = 0.0 # mg/L
# One entry per plant, in order going downstream
plant_location = [0.0, 18.0, 36.0] # km
plant_flow = [1.5e4, 1.5e4, 1.5e4] # m³/d
plant_DO = [1.0, 1.0, 1.0] # mg/L
plant_CBOD = [55.0, 50.0, 65.0] # mg/L
plant_NBOD = [33.0, 30.0, 39.0] # mg/L
# Streeter-Phelps: DO after travelling `distance` down a stretch that begins
# with the given DO, CBOD, and NBOD.
function do_downstream(distance, DO_start, CBOD_start, NBOD_start, params)
reaeration = exp(-params.ka * distance / params.U)
cbod_left = exp(-params.kc * distance / params.U)
nbod_left = exp(-params.kn * distance / params.U)
recovery = params.Cs * (1 - reaeration) + DO_start * reaeration
cbod_sag = CBOD_start * (params.kc / (params.ka - params.kc)) * (cbod_left - reaeration)
nbod_sag = NBOD_start * (params.kn / (params.ka - params.kn)) * (nbod_left - reaeration)
return recovery - cbod_sag - nbod_sag
end
# Each stretch below a plant is a "box": mix the effluent in to get the box's
# starting condition, then apply the closed form within the box.
function simulate_river(x_grid, location, flow, effluent_DO, effluent_CBOD, effluent_NBOD, params)
DO = zeros(length(x_grid))
# State of the water entering the current box
current_flow = river_flow
DO_in = river_DO
CBOD_in = river_CBOD
NBOD_in = river_NBOD
# Water upstream of the first plant is clean, so it relaxes toward saturation
for i in 1:length(x_grid)
if x_grid[i] < location[1]
DO[i] = do_downstream(x_grid[i] - x_grid[1], river_DO, river_CBOD, river_NBOD, params)
end
end
# Carry the clean river down to the first plant
distance = location[1] - x_grid[1]
DO_in = do_downstream(distance, DO_in, CBOD_in, NBOD_in, params)
CBOD_in = CBOD_in * exp(-params.kc * distance / params.U)
NBOD_in = NBOD_in * exp(-params.kn * distance / params.U)
for plant in 1:length(location)
# Carry the river from the previous plant down to this one
if plant > 1
distance = location[plant] - location[plant-1]
DO_in = do_downstream(distance, DO_in, CBOD_in, NBOD_in, params)
CBOD_in = CBOD_in * exp(-params.kc * distance / params.U)
NBOD_in = NBOD_in * exp(-params.kn * distance / params.U)
end
# Mix this plant's effluent in. Flow-weighted, so a bigger river dilutes more.
total_flow = current_flow + flow[plant]
DO_in = (current_flow * DO_in + flow[plant] * effluent_DO[plant]) / total_flow
CBOD_in = (current_flow * CBOD_in + flow[plant] * effluent_CBOD[plant]) / total_flow
NBOD_in = (current_flow * NBOD_in + flow[plant] * effluent_NBOD[plant]) / total_flow
current_flow = total_flow
# This box runs from this plant to the next one (or to the end)
if plant < length(location)
box_end = location[plant+1]
else
box_end = maximum(x_grid) + 1.0
end
for i in 1:length(x_grid)
if location[plant] <= x_grid[i] < box_end
distance_into_box = x_grid[i] - location[plant]
DO[i] = do_downstream(distance_into_box, DO_in, CBOD_in, NBOD_in, params)
end
end
end
return DO
end
x_grid = 0:0.02:70
DO_combined = simulate_river(x_grid, plant_location, plant_flow,
plant_DO, plant_CBOD, plant_NBOD, sp_params)
p_multi = plot(xlabel="Distance downstream [km]", ylabel="DO [mg/L]",
legend=:topright, ylims=(1.5, 7))
plot!(p_multi, x_grid, DO_combined, color=:black, linewidth=5, label="DO")
hline!(p_multi, [2.5], color=cb_green, linewidth=3, linestyle=:dot, label="standard: 2.5 mg/L")
for plant in 1:length(plant_location)
vline!(p_multi, [plant_location[plant]], color=cb_vermillion, linewidth=2,
linestyle=:dash, label=nothing)
annotate!(p_multi, plant_location[plant] + 0.7, 6.7,
text("Effluent $plant", 13, cb_vermillion, :left))
end
plot!(p_multi, size=(1200, 500))