function quasistatic_sweep(loading_values, P_ic; tol=1e-9, maxiter=50_000)
P = P_ic
out = zeros(length(loading_values))
for (i, L) in enumerate(loading_values)
for _ in 1:maxiter
Pn = P + lake_P_change(P, L, q)
converged = abs(Pn - P) < tol
P = Pn
converged && break
end
out[i] = P
end
return out
end
function simulate_lake_P(P_ic, n_years, L, q)
P = zeros(n_years)
P[1] = P_ic
for t = 2:n_years
P[t] = P[t-1] + lake_P_change(P[t-1], L, q)
end
return P
end
L_up = collect(0:0.05:20)
L_dn = reverse(L_up)
P_up = quasistatic_sweep(L_up, 0.0)
P_dn = quasistatic_sweep(L_dn, P_up[end])
p_hyst = plot(xlims=(0, 20), ylims=(0, 60), legend=:topleft,
xlabel="External loading L [μg/(L⋅yr)]", ylabel="P [μg/L]")
plot!(p_hyst, L_eq, P_range, color=:grey, alpha=0.35, linewidth=2, label=nothing)
plot!(p_hyst, L_up, P_up, color=cb_vermillion, linewidth=5, label="loading up")
plot!(p_hyst, L_dn, P_dn, color=cb_blue, linewidth=4, linestyle=:dash,
label="loading down")
scatter!(p_hyst, [L_eutrophic, L_recover], [P_eutrophic, P_recover], markersize=8,
markercolor=:black, label=nothing)
annotate!(p_hyst, L_eutrophic - 0.5, 44, text("eutrophication", 14, :right))
annotate!(p_hyst, L_recover + 0.5, 21, text("recovery", 14, :left))
# Right: same loading, two initial conditions either side of the unstable point.
example_loading = 8.0
n_years = 60
p_basin = plot(legend=:right, xlabel="Year", ylabel="P [μg/L]", ylims=(0, 60))
plot!(p_basin, simulate_lake_P(31.0, n_years, example_loading, q), color=cb_vermillion,
linewidth=4, label=L"P_0 = 31")
plot!(p_basin, simulate_lake_P(30.0, n_years, example_loading, q), color=:grey,
linewidth=3, linestyle=:dot, label=L"P_0 = 30")
plot!(p_basin, simulate_lake_P(29.0, n_years, example_loading, q), color=cb_blue,
linewidth=4, label=L"P_0 = 29")
plot(p_hyst, p_basin, layout=(1, 2), size=(1200, 490),
left_margin=12mm, right_margin=8mm, bottom_margin=12mm, top_margin=5mm)