Last active
July 11, 2024 13:32
-
-
Save rand-asswad/325b63d8128ba6734b31a608bbc3ce10 to your computer and use it in GitHub Desktop.
Optimal control of Algal-Bacterial consortium in Julia using OptimalControl.jl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Standalone script to calculate optimal control of an algal-bacterial consortium system | |
# using the direct method, using Julia's CTDirect.jl package from https://control-toolbox.org/. | |
import Pkg | |
Pkg.Registry.add(Pkg.RegistrySpec(url = "https://github.com/control-toolbox/ct-registry.git")) | |
Pkg.add.(["OptimalControl", "Plots", "LaTeXStrings"]) | |
using OptimalControl, Plots, LaTeXStrings | |
# System parameters | |
s_in = 0.5 | |
β = 23e-3 | |
γ = 0.44 | |
dmax = 1.5 | |
ϕmax = 6.48; ks = 0.09; | |
ρmax = 27.3e-3; kv = 0.57e-3; | |
μmax = 1.0211; qmin = 2.7628e-3; | |
ϕ(s) = ϕmax * s / (ks + s) | |
ρ(v) = ρmax * v / (kv + v) | |
μ(q) = μmax * (1 - qmin / q) | |
# Initialize model | |
N = 5000 # time steps | |
t0 = 0; tf = 20 | |
x0 = [0.1629, 0.0487, 0.0003, 0.0177, 0.035, 0] | |
@def docp begin | |
t ∈ [t0, tf], time | |
x ∈ R⁶, state | |
u ∈ R², control | |
x(t0) == x0 | |
x₁(t) ≥ 0 | |
x₂(t) ≥ 0 | |
x₃(t) ≥ 0 | |
x₄(t) ≥ qmin | |
x₅(t) ≥ 0 | |
0 ≤ u₁(t) ≤ 1 | |
0 ≤ u₂(t) ≤ dmax | |
ẋ(t) == [ | |
u₂(t)*(s_in - x₁(t)) - ϕ(x₁(t))*x₂(t)/γ, | |
((1 - u₁(t))*ϕ(x₁(t)) - u₂(t))*x₂(t), | |
u₁(t)*β*ϕ(x₁(t))*x₂(t) - ρ(x₃(t))*x₅(t) - u₂(t)*x₃(t), | |
ρ(x₃(t)) - μ(x₄(t))*x₄(t), | |
(μ(x₄(t)) - u₂(t))*x₅(t), | |
u₂(t)*x₅(t), | |
] | |
x₆(tf) → max | |
end | |
# Optimization -------------------------------------------------------------- | |
# Solution data structure | |
struct OCSolution | |
t::Vector{Real} | |
x::Matrix{Real} | |
λ::Matrix{Real} | |
u::Matrix{Real} | |
obj::Real | |
end | |
@timev begin | |
# solve DOCP | |
sol = solve(docp, :direct, :ipopt; grid_size=N, tol=1e-12, mu_strategy="adaptive", print_level=4) | |
# Retrieve OCP solution data | |
t = sol.times | |
x = reduce(vcat, transpose.(sol.state.(t))) | |
λ = reduce(vcat, transpose.(sol.costate.(t))) | |
u = reduce(vcat, transpose.(sol.control.(t))) | |
sol = OCSolution(t, x, λ, u, sol.objective) | |
end | |
# Plots --------------------------------------------------------------------- | |
latexify(tab) = latexstring.(L"$".* tab .* L"$") | |
x = ["s", "e", "v", "q", "c"] | |
u = [raw"\alpha", "d"] | |
ind = [1, 3, 4, 2, 5] | |
l = @layout [° ° °; ° °] | |
function plot_state(sol::OCSolution) | |
plot(sol.t, sol.x[:, ind], label=latexify(reshape(x[ind], (1,5))), layout=l) | |
plot!(fontfamily="sans-serif") | |
xlabel!("Time") | |
end | |
function plot_costate(sol::OCSolution) | |
plot(sol.t, sol.λ[:, ind], label=latexify(reshape("\\lambda_".* x[ind], (1,5))), layout=l) | |
xlabel!("Time") | |
plot!(fontfamily="sans-serif") | |
end | |
function plot_control(sol::OCSolution) | |
p1 = plot(sol.t, sol.u[:,1], label=latexstring(u[1])) | |
p2 = plot(sol.t, sol.u[:,2], label=latexstring(u[2])) | |
plot(p1, p2, layout=(2, 1)) | |
end | |
function plot_objective(sol::OCSolution) | |
plot(sol.t, sol.x[:,end], fillrange=zeros(length(sol.t)), alpha=.5, legend=false) | |
mid = Integer(ceil(length(sol.t) * 0.75)) | |
xpos = sol.t[mid] | |
ypos = sol.x[mid,end] / 2 | |
plot!(annotations=(xpos, ypos, Plots.text(round(sol.obj; digits=3), :hcenter))) | |
end | |
# save plots | |
mkpath("plots/") | |
fname(s) = "plots/ctdirect_" * string(N) * "_" * s * ".pdf" | |
function save_plots(sol) | |
savefig(plot_state(sol), fname("state")) | |
savefig(plot_costate(sol), fname("costate")) | |
savefig(plot_control(sol), fname("control")) | |
savefig(plot_objective(sol), fname("objective")) | |
end | |
save_plots(sol) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment