Open jbcaillau opened 3 months ago
We did some work on this with Joseph (see the old issue https://github.com/control-toolbox/CTDirect.jl/issues/11 and the branch https://github.com/control-toolbox/CTDirect.jl/tree/old-runge-kutta).
We felt it may be better to move to a more abstract interface with the discretization first, since it does involve quite a bit of modifications.
You are certainly welcome to try on your side, dont hesitate if you have any questions :-)
Ok, we'll try this again with @joseph-gergaud. Hopefully the code is better structured now and should make it easier.
Dear @PierreMartinon, @jbcaillau, and @joseph-gergaud,
Thank you for considering this issue and directing me to your previous work on RK schemes. I have tinkered around with the old-runge-kutta
branch but did not succeed in running it due to divergence with dependencies over the past year. I also could not allocate sufficient time to resolve this issue myself, given that I had to move on to other research topics in order to respect my planned PhD timeline, as you can imagine.
I ended up implementing the same scheme (implicit trapezoidal rule, aka Crank-Nicholson) following your algorithmic guide using JuMP with Ipopt backend. I further implemented a general RK scheme in a similar manner, so I provide here my results.
The control trajectory for the $d(t)$ variable obtained with CTDirect exhibited a chattering effect that I could not resolve by changing the number of time steps (up to 10000, which took a lot of time). While the implementation with JuMP and Ipopt did not exhibit the same phenomenon and resolved the problem for a similar number of steps (I use 5000).
Standalone scripts and plots for both implementations:
I bring this issue to your attention because the difference is significant, as you can see in the attached plots.
I implemented a general RK scheme following your algorithmic guide and the previous RK branch. As expected, there is an improvement to the previous implementation, which becomes more apparent in the plots of the singular arcs (expressions derived from differentiating the switch functions, the expressions depend on the state and costate trajectories as well as the other control).
Here's my standalone script for the JuMP implementation of RK scheme and the control plot with the Gauss-Legendre method of order 4.
Finally, I would be happy to help with a general RK scheme implementation when I have more time at hand. For the time being, I will stop at my current code based on your previous work. Thank you again for all your help!
hey @rand-asswad sounds super nice! 👍🏽currently at the JuliaCon, but will have a look at it asap. cheers from eindhoven to @PierreMartinon
@jbcaillau enjoy JuliaCon and Eindhoven!
@jbcaillau enjoy JuliaCon and Eindhoven!
https://control-toolbox.org/docs/optimalcontrol/stable/juliacon2024.html
I updated the code gists and ran a fresh round of testing with the @timev
macro.
Now this is far from being a benchmark, but here's a brief time and memory comparison.
CTDirect | JuMP (trapezoidal) | JuMP (gauss 2-stages) | |
---|---|---|---|
Total Time | 1375 seconds | 131 seconds | 77 seconds |
Total Memory | 259.8 GiB | 2.952 GiB | 3.314 GiB |
IPOPT time | 299 seconds | 130 seconds | 73 seconds |
IPOPT iterations | 711 | 514 | 126 |
Full logs:
ping @PierreMartinon
Hi, a quick question: did you run the optimization twice to remove the compilation time ? (you can set for instance 2 iterations max).
One suprise is the 100x larger memory usage. Can you limit the iterations to 50 for instance and see if the memory difference is the same ? Maybe we are doing some unnecessary allocations in CTDirect. I should redo a pass on that, there is a nice option when launching julia, that will output .mem files with memory allocations corresponding to each line of code.
Regarding the control oscillations, they can indeed happen especially on singular arcs, and increasing the number of time steps typically does not help. Based on my experience, changing the discretization formula (when possible...) may help, as reducing the number of time steps or setting a smaller tolerance for the NLP solver. In your case it would be interesting to find why Jump seems unaffected (unless it's a random thing).
hi @PierreMartinon @rand-asswad thanks for the feedback. yes, mem allocation difference sounds huge and needs to be understood.
@btime
from BenchmarkTools.jlv0.9
(rewritten build of the constraints)grid_size
All in all, thanks @rand-asswad for the input on this!
First of all, I'm having trouble with v0.9
even with the basic example from the tutorial.
using OptimalControl
@def ocp begin
t ∈ [ 0, 1 ], time
x ∈ R², state
u ∈ R, control
x(0) == [ -1, 0 ]
x(1) == [ 0, 0 ]
ẋ(t) == [ x₂(t), u(t) ]
∫( 0.5u(t)^2 ) → min
end
sol = solve(ocp)
I get the following error:
ERROR: LoadError: MethodError: no method matching init(::CTBase.OptimalControlModel{Autonomous, Fixed}, ::Symbol, ::Symbol; display::Bool, init::Tuple{})
I tried it multiple times on a fresh Julia install and the problem persisted with v0.9
.
I therefore used v0.7.8
, and the tests on CTDirect I have provided are in the following environment:
(@v1.10) pkg> st
Status `~/.local/share/julia/environments/v1.10/Project.toml`
[6e4b80f9] BenchmarkTools v1.5.0
[b6b21f68] Ipopt v1.6.3
[4076af6c] JuMP v1.22.2
[b964fa9f] LaTeXStrings v1.3.1
⌃ [5f98b655] OptimalControl v0.7.8
[91a5bcdd] Plots v1.40.5
Info Packages marked with ⌃ have new versions available and may be upgradable.
Since the memory issue is addressed in v0.9
, the issue I brought might be obsolete. However, I couldn't figure out the issue in the new version (granted, I haven't checked the source code), I thought I'd wait out for the newer release.
This problem aside, a quick round of testing following your recommendations gave similar results (this time I only compared for the same discretization scheme).
I ran the code entirely once to compile, relaxed the tol
attribute for IPOPT, then:
CTDirect | raw JuMP | |
---|---|---|
Time (500 steps) | 18.37 seconds | 4.04 seconds |
Memory (500 steps) | 7.194 GiB | 116.5 MiB |
Benchmark Samples (50 steps) | 8 samples | 28 samples |
Benchmark Time (50 steps) | 719 ms | 183 ms |
Benchmark Memory (50 steps) | 304.46 MiB | 8.58 MiB |
I'll rerun these tests when I solve the issue with v0.9
.
I wish I could be more of help, thank you both!
@PierreMartinon regarding the oscillations in the singular arcs, I tried reducing the number of time-steps as well as the tolerance. Unfortunately, I couldn't succeed in removing them with CTDirect.jl
. I was also surprised to see that the result of the JuMP code didn't have these oscillations with the same numerical scheme. Nevertheless, we are going with the Gauss-Legendre scheme of order 4 in our article because it yields considerably better results as you said.
@rand-asswad sorry for the late reply (been busy at JuliaCon 🥲). We have split things using extensions, you know need to write the following:
using OptimalControl
using NLPModelsIpopt # so as to use Ipopt
using Plots # so as to plot
@def ocp begin
t ∈ [ 0, 1 ], time
x ∈ R², state
u ∈ R, control
x(0) == [ -1, 0 ]
x(1) == [ 0, 0 ]
ẋ(t) == [ x₂(t), u(t) ]
∫( 0.5u(t)^2 ) → min
end
sol = solve(ocp)
plot(sol)
In between, there has been an update of ADNLPModels.jl (and sparsity detection in there): can you please re-run your previous tests with the latest version of OptimalControl.jl, now available from the general registry?
hi @rand-asswad, did you give it a try?
@rand-asswad sorry for the late reply (been busy at JuliaCon 🥲). We have split things using extensions, you know need to write the following [...]
@PierreMartinon @gergaud hi, we may have a use case that requires higher order implicit schemes (Gauss...) It is connected to this discussion.
@rand-asswad @DJEMAwalid do not hesitate to add further comments on the application, e.g. a plot of the periodic control.