Hello all !
I have a nonlinear ODE problem that I set up with ModelingToolkit (great job with this awesome package !), for which I can compute some steady-state (with SteadyStateProblem
) or transient solutions (with ODEroblem
).
Now the solutions I get seem perfectly fine, but I would like to optimize some parameters of my problem to reach some objective, e.g a steady-state value and some oscillation frequency.
Since ModelingTookit supports OptimizationProblems
I figure this could be leveraged for this purpose, but how can I combine or wrap an OptimizationProblem
around a SteadyStateProblem
?
To make the discussion more practical, I made a very simple mass-spring model to illustrate this on a minimal working example.
The main thing I have trouble with is should these parameters of the ODE problem be declared as parameters or as variables, since they become the variables of the optimization problem ? What are the good practices to wrap the optimization around the ODE problem to avoid complicating the optimization process ?
Thanks.
using GLMakie
using ModelingToolkit, SteadyStateDiffEq, OrdinaryDiffEq
using ControlSystemsBase
# Definition of model parameters
@parameters m g l0 k c
# Dict of parameters
ps = Dict([
m => 1,
l0 => -0.5,
g => 9.81,
k => 1,
c => 0.1,
])
# Definition of problem variables
@variables begin
t,
y(t)
yt(t)
ytt(t)
T(t)
end
# Definition of transient problem variables
D = Differential(t)
D2 = D^2
# Definition of transient problem equations
eqs_tot_t = [
T ~ -k*(y-l0),
m * ytt + c * yt ~ -m*g + T,
D2(y) ~ ytt,
D(y) ~ yt,
]
# Definition of the ODE problem including variables, parameters and time span of interest
@named ode = ODESystem(eqs_tot_t, t, [y,yt,ytt,T], [m,c,k,l0,g])
simple_ode = structural_simplify(ode)
# Definition of initial problem conditions for transient problem
guess_t = [
y => 2*ps[l0],
yt => 0,
ytt => 0,
]
# Definition of steady-state problem to solve for steady solution from the ODE problem
prob_ss = SteadyStateProblem(simple_ode, guess_t, ps)
# Actually solve for the steady state solution
sol_ss = solve(prob_ss, DynamicSS(Rodas5()), abstol=1e-8, reltol=1e-8)
# Definition of the ODE problem to solve for a transient solution using the guess
prob = ODEProblem(simple_ode, guess_t, (0, 100), ps)
# Actually solve the transient problem
sol = solve(prob, Rodas5(), abstol=1e-8, reltol=1e-8, saveat=0:0.1:100)
# Define iteration observable
i = Observable(1)
# Build the plot for the transient simulation
begin
# Initialize figure
f = Figure()
# Initialize the axis
ax1 = Axis(f[:, 1], autolimitaspect=1)
# Position reference geometry points as needed
PtO = Observable(Point2f(0, 0))
st = @lift(sol.t[$i])
sy = @lift(sol[simple_ode.y][$i])
syt = @lift(sol[simple_ode.yt][$i])
sytt = @lift(sol[simple_ode.ytt][$i])
sT = @lift(sol[simple_ode.T][$i])
PtM = @lift(Point2(0, $sy))
PtM_0 = Point2(0, sol[simple_ode.y][1])
PtM_ss = Point2(0, sol_ss[simple_ode.y])
widths = Vec2f(0.1,0.1)
rects = @lift([Rect2(Vec2f($PtM - widths/2),widths),])
rects_0 = [Rect2(Vec2f(PtM_0) - widths/2, widths),]
rects_ss = [Rect2(Vec2f(PtM_ss) - widths / 2, widths),]
bar_0 = [PtO[],PtM_0]
bar_ss = [PtO[], PtM_ss]
bar = @lift([$PtO, $PtM])
poly!(rects_0,color=:red)
poly!(rects_ss, color=:green)
poly!(rects,color=:transparent,strokecolor=:blue, strokewidth=5)
linesegments!(bar_0,linestyle = :dot)
linesegments!(bar)
linesegments!(bar_ss, linestyle=:dash)
ymin,ymax = extrema(sol[simple_ode.y])
ylims!(2*ymin,2*ymax)
g2 = GridLayout(f[1, 2])
ax22 = Axis(g2[1, 1], ylabel="Force magnitude [N]", xlabel="Time [s]")
lines!(ax22, sol.t, sol[simple_ode.T])
lines!(ax22, sol.t, fill(ps[m]*ps[g],length(sol.t)) )
vlines!(st, color=:black)
scatter!(st, sT, color=:black)
g3 = GridLayout(f[1, 3])
ax31 = Axis(g3[1, 1], xlabel="Time [s]", ylabel="Position [m]", aspect=1)
lines!(ax31, sol.t, sol[simple_ode.y])
vlines!(ax31, st, color=:black)
scatter!(ax31, st, sy, color=:black)
ax32 = Axis(g3[2, 1], xlabel="Time [s]", ylabel="Velocity yt [m/s]", aspect=1)
lines!(sol.t, sol[simple_ode.yt])
vlines!(st, color=:black)
scatter!(st, syt, color=:black)
ax33 = Axis(g3[3, 1], xlabel="Time [s]", ylabel="Acceleration ytt [m/s^2]", aspect=1)
lines!(sol.t, sol[simple_ode.ytt])
vlines!(st, color=:black)
scatter!(st, sytt, color=:black)
linkxaxes!(ax31, ax32, ax33)
i[] = 1
display(GLMakie.Screen(), f)
end
# Define parameters for animation
framerate = 30
timestamps = range(1, length(sol.t), step=1)
for ii in timestamps
i[] = ii
sleep(0.01)
end
# Operating point for linearization analysis to retrieve dynamic properties
op = merge(Dict(states(simple_ode) .=> sol_ss.u), ps)
# Linearize the system (of course already linear in this case)
(; A, B, C, D), simplified_sys = linearize(ode, [], [y]; t=last(sol.t), op=op)
# Build a steady state model from matrices A, B, C, D
state = ss(A, B, C, D)
# Retrieve dynamic properties
ω, ξ, ps = damp(state)
# Get system frequencies
ff = ω ./ (2 * pi)
##################################################################################################################################################################
#####################################Optmization on the m and k parameters to reach a frequency and steady state position of interest ############################
##################################################################################################################################################################
##################################################################################################################################################################
# Define optimization objectives for y and f
y_obj = -2
f_obj = 5
function loss(p)
# Solve the steady state problem for a set of parameter values
sol_ss = solve(prob_ss, DynamicSS(Rodas5()), abstol=1e-8, reltol=1e-8)
y_test = sol_ss[simple_ode.y]
# Linearize to retrive the frequency around steady state
op = merge(Dict(st .=> sol_ss.u), p)
(; A, B, C, D), _ = linearize(ode, [], [y]; t=last(sol.t), op=op)
state = ss(A, B, C, D)
ω, ξ, ps = damp(state)
f_test = maximum(ω) / (2 * pi)
# Calculate mean square error
mse = (y_test - y_obj)^2 + (f_test - f_obj)^2
return mse
end
function callback(p, l)
println(l, " ", p)
return false
end
# Load additional packages for optimization (maybe a bit much)
using Optimization, OptimizationOptimisers, Zygote, Optim, OptimizationOptimJL
**EDIT**
# Define the system to optimize where k and m are the variables now and l0 and g are considered fixed
@named opt = OptimizationSystem(loss,[k,m],[l0,g])
u0 = [k => 1.0,m=>1.0,g=>9.81,l0 => 1.0]
opt_prob = OptimizationProblem(opt, u0)