From 461afdf825aff66fee0ac7f537f12afba94585b5 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sat, 28 Feb 2026 22:20:38 +0100 Subject: [PATCH 01/32] add brachistochrone --- ext/Descriptions/brachistochrone.md | 55 ++++++++ ext/JuMPModels/brachistochrone.jl | 128 ++++++++++++++++++ ext/MetaData/brachistochrone.jl | 12 ++ ext/OptimalControlModels/brachistochrone.jl | 93 +++++++++++++ .../brachistochrone_s.jl | 96 +++++++++++++ 5 files changed, 384 insertions(+) create mode 100644 ext/Descriptions/brachistochrone.md create mode 100644 ext/JuMPModels/brachistochrone.jl create mode 100644 ext/MetaData/brachistochrone.jl create mode 100644 ext/OptimalControlModels/brachistochrone.jl create mode 100644 ext/OptimalControlModels_s/brachistochrone_s.jl diff --git a/ext/Descriptions/brachistochrone.md b/ext/Descriptions/brachistochrone.md new file mode 100644 index 00000000..da388cf9 --- /dev/null +++ b/ext/Descriptions/brachistochrone.md @@ -0,0 +1,55 @@ +The **Brachistochrone problem** is a classical benchmark in the history of calculus of variations and optimal control.   +It seeks the shape of a curve (or wire) connecting two points $A$ and $B$ within a vertical plane, such that a bead sliding frictionlessly under the influence of uniform gravity travels from $A$ to $B$ in the shortest possible time.   +Originating from the challenge posed by Johann Bernoulli in 1696 [Bernoulli 1696](https://doi.org/10.1002/andp.19163551307), it marks the birth of modern optimal control theory.   +The problem involves nonlinear dynamics where the state includes position and velocity, and the control is the path's angle, with the final time $t_f$ being a decision variable to be minimised. + +### Mathematical formulation + +The problem can be stated as a free final time optimal control problem: + +```math +\begin{aligned} +\min_{u(\cdot), t_f} \quad & J = t_f = \int_0^{t_f} 1 \,\mathrm{d}t \\[1em] +\text{s.t.} \quad & \dot{x}(t) = v(t) \sin u(t), \\[0.5em] +& \dot{y}(t) = v(t) \cos u(t), \\[0.5em] +& \dot{v}(t) = g \cos u(t), \\[0.5em] +& x(0) = x_0, \; y(0) = y_0, \; v(0) = v_0, \\[0.5em] +& x(t_f) = x_f, \; y(t_f) = y_f, +\end{aligned} +``` + +where $u(t)$ represents the angle of the tangent to the curve with respect to the vertical axis, and $g$ is the gravitational acceleration. + +### Qualitative behaviour + +This problem is a classic example of **minimum time control** with nonlinear dynamics.   +Unlike the shortest path problem (a straight line), the optimal trajectory balances the need to minimize path length with the need to maximize velocity. + +The analytical solution to this problem is a **cycloid**—the curve traced by a point on the rim of a circular wheel as the wheel rolls along a straight line without slipping.   +Specifically: + +- **Energy Conservation**: Since the system is conservative and frictionless, the speed at any height $h$ is given by $v = \sqrt{2gh}$ (assuming start from rest). This implies that maximizing vertical drop early in the trajectory increases velocity for the remainder of the path. +- **Concavity**: The optimal curve is concave up. It starts steeply (vertical tangent if $v_0=0$) to gain speed quickly, then flattens out near the bottom before potentially rising again to reach the target. +- **Singularity**: At the initial point, if $v_0=0$, the control is theoretically singular as the bead has no velocity to direct. Numerical solvers often require a small non-zero initial velocity or a robust initial guess to handle this start. + +The control $u(t)$ is continuous and varies smoothly to trace the cycloidal arc. + +### Characteristics + +- **Nonlinear dynamics** involving trigonometric functions of the control. +- **Free final time** problem (time-optimal). +- Analytical solution is a **Cycloid**. +- Historically significant as the first problem solved using techniques that evolved into the Calculus of Variations. + +### References + +- Bernoulli, J. (1696). *Problema novum ad cujus solutionem Mathematici invitantur*.   +  Acta Eruditorum.   +  The original publication posing the challenge to the mathematicians of Europe, solved by Newton, Leibniz, L'Hôpital, and the Bernoulli brothers. + +- Sussmann, H. J., & Willems, J. C. (1997). *300 years of optimal control: from the brachystochrone to the maximum principle*.   +  IEEE Control Systems Magazine. [doi.org/10.1109/37.588108](https://doi.org/10.1109/37.588108)   +  A comprehensive historical review linking the classical Brachistochrone problem to modern optimal control theory and Pontryagin's Maximum Principle. + +- Dymos Examples: Brachistochrone. [OpenMDAO/Dymos](https://openmdao.github.io/dymos/examples/brachistochrone/brachistochrone.html)   +  The numerical implementation serving as the basis for the current benchmark formulation. \ No newline at end of file diff --git a/ext/JuMPModels/brachistochrone.jl b/ext/JuMPModels/brachistochrone.jl new file mode 100644 index 00000000..71319d34 --- /dev/null +++ b/ext/JuMPModels/brachistochrone.jl @@ -0,0 +1,128 @@ +""" +$(TYPEDSIGNATURES) + +Constructs a JuMP model representing the **Brachistochrone optimal control problem**. +The objective is to minimize the final time `tf` to travel between two points under gravity. +The problem uses a direct transcription (trapezoidal rule) manually implemented in JuMP. + +# Arguments + +- `::JuMPBackend`: Placeholder type to specify the JuMP backend or solver interface. +- `grid_size::Int=50`: (Keyword) Number of discretisation steps for the time grid. + +# Returns + +- `model::JuMP.Model`: A JuMP model containing the decision variables (including final time), dynamics constraints, and boundary conditions. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP +julia> model = OptimalControlProblems.brachistochrone(JuMPBackend(); N=100) +``` + +# References + +- Dymos Brachistochrone: [https://openmdao.github.io/dymos/examples/brachistochrone/brachistochrone.html](https://openmdao.github.io/dymos/examples/brachistochrone/brachistochrone.html) +""" +function OptimalControlProblems.brachistochrone( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:brachistochrone), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:brachistochrone, parameters) + g = params[:g] + t0 = params[:t0] + x0 = params[:x0] + y0 = params[:y0] + v0 = params[:v0] + xf = params[:xf] + yf = params[:yf] + + # model + model = JuMP.Model(args...; kwargs...) + + # N = grid_size + @expression(model, N, grid_size) + + @variables( + model, + begin + 0.1 <= tf <= 20.0, (start = 2.0) + px[0:N] + py[0:N] + v[0:N] + u[0:N] + end + ) + + + model[:time_grid] = () -> range(t0, value(tf), N+1) + model[:state_components] = ["px", "py", "v"] + model[:costate_components] = ["∂px", "∂py", "∂v"] + model[:control_components] = ["u"] + model[:variable_components] = ["tf"] + + for i in 0:N + alpha = i / N + set_start_value(px[i], x0 + alpha * (xf - x0)) + set_start_value(py[i], y0 + alpha * (yf - y0)) + set_start_value(v[i], v0 + alpha * 10.0) # Estimate speed + set_start_value(u[i], 1.57) # ~90 degrees + end + + # boundary constraints + @constraints( + model, + begin + # Start + px[0] == x0 + py[0] == y0 + v[0] == v0 + + # End + px[N] == xf + py[N] == yf + # v[N] is free + end + ) + + # dynamics + @expressions( + model, + begin + # Time step is variable: dt = (tf - t0) / N + dt, (tf - t0) / N + + # Dynamics expressions (Dymos formulation) + # dpx/dt = v * sin(u) + dpx[i = 0:N], v[i] * sin(u[i]) + + # dpy/dt = -v * cos(u) + dpy[i = 0:N], -v[i] * cos(u[i]) + + # dv/dt = g * cos(u) + dv[i = 0:N], g * cos(u[i]) + end + ) + + # Trapezoidal rule integration (Implicit) + @constraints( + model, + begin + ∂px[i = 1:N], px[i] == px[i - 1] + 0.5 * dt * (dpx[i] + dpx[i - 1]) + ∂py[i = 1:N], py[i] == py[i - 1] + 0.5 * dt * (dpy[i] + dpy[i - 1]) + ∂v[i = 1:N], v[i] == v[i - 1] + 0.5 * dt * (dv[i] + dv[i - 1]) + end + ) + + # objective: Minimize final time + @objective(model, Min, tf) + + return model +end \ No newline at end of file diff --git a/ext/MetaData/brachistochrone.jl b/ext/MetaData/brachistochrone.jl new file mode 100644 index 00000000..17d700d0 --- /dev/null +++ b/ext/MetaData/brachistochrone.jl @@ -0,0 +1,12 @@ +brachistochrone_meta = OrderedDict( + :grid_size => 500, + :parameters => ( + g = 9.80665, + t0 = 0.0, + x0 = 0.0, + y0 = 10.0, + v0 = 0.0, + xf = 10.0, + yf = 5.0, + ), +) \ No newline at end of file diff --git a/ext/OptimalControlModels/brachistochrone.jl b/ext/OptimalControlModels/brachistochrone.jl new file mode 100644 index 00000000..860ea7eb --- /dev/null +++ b/ext/OptimalControlModels/brachistochrone.jl @@ -0,0 +1,93 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** representing the Brachistochrone problem using the OptimalControl backend. +The function sets up the state and control variables, boundary conditions, dynamics, and the objective functional. +It then performs direct transcription to generate a discrete optimal control problem (DOCP). + +# Arguments + +- `::OptimalControlBackend`: Placeholder type to specify the OptimalControl backend or solver interface. +- `grid_size::Int=grid_size_data(:brachistochrone)`: (Keyword) Number of discretisation points for the direct transcription grid. +- `parameters::Union{Nothing,NamedTuple}=nothing`: (Keyword) Custom parameters to override defaults. + +# Returns + +- `docp`: The direct optimal control problem object, representing the discretised problem. + +# Example +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.brachistochrone(OptimalControlBackend(); N=100); +``` + +# References + +- Dymos Brachistochrone: https://openmdao.github.io/dymos/examples/brachistochrone/brachistochrone.html +""" +function OptimalControlProblems.brachistochrone( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:brachistochrone), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + params = parameters_data(:brachistochrone, parameters) + g = params[:g] + t0 = params[:t0] + x0 = params[:x0] + y0 = params[:y0] + v0 = params[:v0] + xf = params[:xf] + yf = params[:yf] + + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + + z = (px, py, v) ∈ R³, state + u ∈ R, control + + 0.1 ≤ tf ≤ 20.0 + + px(t0) == x0 + py(t0) == y0 + v(t0) == v0 + + px(tf) == xf + py(tf) == yf + + ∂(px)(t) == v(t) * sin(u(t)) + ∂(py)(t) == -v(t) * cos(u(t)) + ∂(v)(t) == g * cos(u(t)) + + # Objectif + tf → min + end + + # initial guess: linear interpolation to match JuMP + tf_guess = 2.0 + init = ( + state = t -> [ + x0 + (t - t0) / (tf_guess - t0) * (xf - x0), + y0 + (t - t0) / (tf_guess - t0) * (yf - y0), + v0 + (t - t0) / (tf_guess - t0) * 10.0 + ], + control = 1.57, + variable = tf_guess + ) + + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init = init, + grid_size = grid_size, + disc_method=:trapeze, + kwargs... + ) + + return docp +end \ No newline at end of file diff --git a/ext/OptimalControlModels_s/brachistochrone_s.jl b/ext/OptimalControlModels_s/brachistochrone_s.jl new file mode 100644 index 00000000..8d46e45f --- /dev/null +++ b/ext/OptimalControlModels_s/brachistochrone_s.jl @@ -0,0 +1,96 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** representing the Brachistochrone problem using the OptimalControl backend. +The function sets up the state and control variables, boundary conditions, dynamics, and the objective functional. +It then performs direct transcription to generate a discrete optimal control problem (DOCP). + +# Arguments + +- `::OptimalControlBackend`: Placeholder type to specify the OptimalControl backend or solver interface. +- `grid_size::Int=grid_size_data(:brachistochrone)`: (Keyword) Number of discretisation points for the direct transcription grid. +- `parameters::Union{Nothing,NamedTuple}=nothing`: (Keyword) Custom parameters to override defaults. + +# Returns + +- `docp`: The direct optimal control problem object, representing the discretised problem. + +# Example +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.brachistochrone(OptimalControlBackend(); N=100); +``` + +# References + +- Dymos Brachistochrone: https://openmdao.github.io/dymos/examples/brachistochrone/brachistochrone.html +""" +function OptimalControlProblems.brachistochrone_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:brachistochrone), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + params = parameters_data(:brachistochrone, parameters) + g = params[:g] + t0 = params[:t0] + x0 = params[:x0] + y0 = params[:y0] + v0 = params[:v0] + xf = params[:xf] + yf = params[:yf] + + # model + ocp = @def begin + + tf ∈ R, variable + t ∈ [t0, tf], time + + + z = (px, py, v) ∈ R³, state + u ∈ R, control + + + z(t0) == [x0, y0, v0] + + + px(tf) == xf + py(tf) == yf + + 0.1 ≤ tf ≤ 20.0 + + ∂(px)(t) == v(t) * sin(u(t)) + ∂(py)(t) == -v(t) * cos(u(t)) + ∂(v)(t) == g * cos(u(t)) + + tf → min + end + + # initial guess: linear interpolation to match JuMP + tf_guess = 2.0 + init = ( + state = t -> [ + x0 + (t - t0) / (tf_guess - t0) * (xf - x0), + y0 + (t - t0) / (tf_guess - t0) * (yf - y0), + v0 + (t - t0) / (tf_guess - t0) * 10.0 + ], + control = 1.57, + variable = tf_guess + ) + + # discretise the optimal control problem + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end \ No newline at end of file From ad28d77214a3f880e021902eef3ef094ef5e26ed Mon Sep 17 00:00:00 2001 From: Amiel Date: Sat, 28 Feb 2026 23:00:49 +0100 Subject: [PATCH 02/32] fix bug brachistochrone --- ext/JuMPModels/brachistochrone.jl | 4 +++- ext/MetaData/brachistochrone.jl | 4 +++- ext/OptimalControlModels/brachistochrone.jl | 4 +++- ext/OptimalControlModels_s/brachistochrone_s.jl | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ext/JuMPModels/brachistochrone.jl b/ext/JuMPModels/brachistochrone.jl index 71319d34..42ee02dc 100644 --- a/ext/JuMPModels/brachistochrone.jl +++ b/ext/JuMPModels/brachistochrone.jl @@ -43,6 +43,8 @@ function OptimalControlProblems.brachistochrone( v0 = params[:v0] xf = params[:xf] yf = params[:yf] + u_min = params[:u_min] + u_max = params[:u_max] # model model = JuMP.Model(args...; kwargs...) @@ -57,7 +59,7 @@ function OptimalControlProblems.brachistochrone( px[0:N] py[0:N] v[0:N] - u[0:N] + u_min <= u[0:N] <= u_max end ) diff --git a/ext/MetaData/brachistochrone.jl b/ext/MetaData/brachistochrone.jl index 17d700d0..23937070 100644 --- a/ext/MetaData/brachistochrone.jl +++ b/ext/MetaData/brachistochrone.jl @@ -7,6 +7,8 @@ brachistochrone_meta = OrderedDict( y0 = 10.0, v0 = 0.0, xf = 10.0, - yf = 5.0, + yf = 5.0, + u_min = -pi, + u_max = pi, ), ) \ No newline at end of file diff --git a/ext/OptimalControlModels/brachistochrone.jl b/ext/OptimalControlModels/brachistochrone.jl index 860ea7eb..cb2d2eb8 100644 --- a/ext/OptimalControlModels/brachistochrone.jl +++ b/ext/OptimalControlModels/brachistochrone.jl @@ -42,13 +42,15 @@ function OptimalControlProblems.brachistochrone( v0 = params[:v0] xf = params[:xf] yf = params[:yf] + u_min = params[:u_min] + u_max = params[:u_max] ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time z = (px, py, v) ∈ R³, state - u ∈ R, control + u ∈ [u_min, u_max], control 0.1 ≤ tf ≤ 20.0 diff --git a/ext/OptimalControlModels_s/brachistochrone_s.jl b/ext/OptimalControlModels_s/brachistochrone_s.jl index 8d46e45f..f57d2004 100644 --- a/ext/OptimalControlModels_s/brachistochrone_s.jl +++ b/ext/OptimalControlModels_s/brachistochrone_s.jl @@ -42,6 +42,8 @@ function OptimalControlProblems.brachistochrone_s( v0 = params[:v0] xf = params[:xf] yf = params[:yf] + u_min = params[:u_min] + u_max = params[:u_max] # model ocp = @def begin @@ -51,7 +53,7 @@ function OptimalControlProblems.brachistochrone_s( z = (px, py, v) ∈ R³, state - u ∈ R, control + u ∈ [u_min, u_max], control z(t0) == [x0, y0, v0] From dbdc30a41b405ec9284c976a1699832e34239dc1 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sat, 28 Feb 2026 23:09:56 +0100 Subject: [PATCH 03/32] fix bug again --- ext/OptimalControlModels/brachistochrone.jl | 3 ++- ext/OptimalControlModels_s/brachistochrone_s.jl | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ext/OptimalControlModels/brachistochrone.jl b/ext/OptimalControlModels/brachistochrone.jl index cb2d2eb8..c9309a9f 100644 --- a/ext/OptimalControlModels/brachistochrone.jl +++ b/ext/OptimalControlModels/brachistochrone.jl @@ -50,8 +50,9 @@ function OptimalControlProblems.brachistochrone( t ∈ [t0, tf], time z = (px, py, v) ∈ R³, state - u ∈ [u_min, u_max], control + u ∈ R, control + u_min ≤ u(t) ≤ u_max 0.1 ≤ tf ≤ 20.0 px(t0) == x0 diff --git a/ext/OptimalControlModels_s/brachistochrone_s.jl b/ext/OptimalControlModels_s/brachistochrone_s.jl index f57d2004..fdc8452f 100644 --- a/ext/OptimalControlModels_s/brachistochrone_s.jl +++ b/ext/OptimalControlModels_s/brachistochrone_s.jl @@ -47,23 +47,23 @@ function OptimalControlProblems.brachistochrone_s( # model ocp = @def begin - + tf ∈ R, variable t ∈ [t0, tf], time - + z = (px, py, v) ∈ R³, state - u ∈ [u_min, u_max], control + u ∈ R, control + + u_min ≤ u(t) ≤ u_max + 0.1 ≤ tf ≤ 20.0 - z(t0) == [x0, y0, v0] px(tf) == xf py(tf) == yf - 0.1 ≤ tf ≤ 20.0 - ∂(px)(t) == v(t) * sin(u(t)) ∂(py)(t) == -v(t) * cos(u(t)) ∂(v)(t) == g * cos(u(t)) From 38d1fdfc0a4b8ca38d843452ddd5615326624ddd Mon Sep 17 00:00:00 2001 From: Amiel Date: Sat, 28 Feb 2026 23:32:39 +0100 Subject: [PATCH 04/32] fix --- ext/JuMPModels/brachistochrone.jl | 2 +- ext/OptimalControlModels/brachistochrone.jl | 2 +- ext/OptimalControlModels_s/brachistochrone_s.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/JuMPModels/brachistochrone.jl b/ext/JuMPModels/brachistochrone.jl index 42ee02dc..85fa7edc 100644 --- a/ext/JuMPModels/brachistochrone.jl +++ b/ext/JuMPModels/brachistochrone.jl @@ -59,7 +59,7 @@ function OptimalControlProblems.brachistochrone( px[0:N] py[0:N] v[0:N] - u_min <= u[0:N] <= u_max + -1.57 <= u[0:N] <= 1.57 end ) diff --git a/ext/OptimalControlModels/brachistochrone.jl b/ext/OptimalControlModels/brachistochrone.jl index c9309a9f..8bd59b8e 100644 --- a/ext/OptimalControlModels/brachistochrone.jl +++ b/ext/OptimalControlModels/brachistochrone.jl @@ -52,7 +52,7 @@ function OptimalControlProblems.brachistochrone( z = (px, py, v) ∈ R³, state u ∈ R, control - u_min ≤ u(t) ≤ u_max + -1.57 ≤ u(t) ≤ 1.57 0.1 ≤ tf ≤ 20.0 px(t0) == x0 diff --git a/ext/OptimalControlModels_s/brachistochrone_s.jl b/ext/OptimalControlModels_s/brachistochrone_s.jl index fdc8452f..96f2cf31 100644 --- a/ext/OptimalControlModels_s/brachistochrone_s.jl +++ b/ext/OptimalControlModels_s/brachistochrone_s.jl @@ -55,7 +55,7 @@ function OptimalControlProblems.brachistochrone_s( z = (px, py, v) ∈ R³, state u ∈ R, control - u_min ≤ u(t) ≤ u_max + -1.57 ≤ u(t) ≤ 1.57 0.1 ≤ tf ≤ 20.0 z(t0) == [x0, y0, v0] From 02dde16ee07e60475decd887b9ba21cb253c62ea Mon Sep 17 00:00:00 2001 From: Amiel Date: Sun, 1 Mar 2026 19:16:58 +0100 Subject: [PATCH 05/32] add bryson problem --- ext/Descriptions/bryson_denham.md | 56 +++++++++++++++ ext/JuMPModels/bryson_denham.jl | 68 ++++++++++++++++++ ext/MetaData/bryson_denham.jl | 12 ++++ ext/OptimalControlModels/bryson_denham.jl | 70 ++++++++++++++++++ ext/OptimalControlModels_s/bryson_denham_s.jl | 71 +++++++++++++++++++ 5 files changed, 277 insertions(+) create mode 100644 ext/Descriptions/bryson_denham.md create mode 100644 ext/JuMPModels/bryson_denham.jl create mode 100644 ext/MetaData/bryson_denham.jl create mode 100644 ext/OptimalControlModels/bryson_denham.jl create mode 100644 ext/OptimalControlModels_s/bryson_denham_s.jl diff --git a/ext/Descriptions/bryson_denham.md b/ext/Descriptions/bryson_denham.md new file mode 100644 index 00000000..2181b366 --- /dev/null +++ b/ext/Descriptions/bryson_denham.md @@ -0,0 +1,56 @@ +The **Bryson–Denham problem** is a classic optimal control benchmark involving a state inequality constraint. +It models the movement of a unit mass on a frictionless plane, where the goal is to relocate the mass over a fixed time interval with minimum control effort, while ensuring the position never exceeds a certain limit. +Originally introduced in [Bryson et al. 1963](https://doi.org/10.2514/3.2107), it is a standard test case for evaluating the ability of numerical solvers to handle state-constrained trajectories. + +### Mathematical formulation + +The problem can be stated as + +```math +$$ +\begin{aligned} +\min_{x,u} \quad & J(x,u) = \int_0^1 \frac{1}{2} u^2(t) \,\mathrm{d}t \\[1em] +\text{s.t.} \quad & \dot{x}_1(t) = x_2(t), \quad \dot{x}_2(t) = u(t), \\[0.5em] +& x_1(0) = 0, \; x_1(1) = 0, \; x_2(0) = 1, \; x_2(1) = -1, \\[0.5em] +& x_1(t) \le a. +\end{aligned} +$$ +``` + +where $x_1$ represents position, $x_2$ velocity, $u$ acceleration (control), and $a$ is the maximum allowable displacement (e.g., $a=1/9$). + +### Qualitative behaviour + +The problem features a **second-order state constraint** because the control $u$ only appears in the second derivative of the constrained state $x_1$: + +```math +$$ +\ddot{x}_1(t) = u(t). +$$ +``` + +When the trajectory is on a **boundary arc** ($x_1(t) = a$), the derivatives $\dot{x}_1$ and $\ddot{x}_1$ must be zero. This implies that while the constraint is active, the velocity $x_2$ is zero and the control $u$ is zero: + +```math +$$ +u(t) = 0. +$$ +``` + +The solution structure changes based on the value of the constraint parameter $a$: + +* **Unconstrained Case**: If $a$ is sufficiently large ($a \ge 1/4$), the state constraint is never active. +* **Touch Point**: For a critical value of $a$, the trajectory just touches the boundary $x_1 = a$ at a single point $t=0.5$. +* **Boundary Arc**: For smaller values (e.g., $a=1/9$), the trajectory remains on the boundary for a finite time interval. During this interval, the control vanishes. + +### Characteristics + +* Linear–quadratic dynamics with a second-order state inequality constraint. +* Demonstrates a "bang-off-bang" control structure where the control is zero on the constraint boundary. +* Used to test the accuracy of state constraint handling and the detection of switching times. + +### References + +* Bryson, A.E., Denham, W.F., & Dreyfus, S.E. (1963). *Optimal programming problems with inequality constraints I: necessary conditions for extremal solutions*. + AIAA Journal. [doi.org/10.2514/3.2107](https://doi.org/10.2514/3.2107) +* Dymos Documentation: Bryson-Denham Problem. [dymos/examples/bryson_denham](https://openmdao.github.io/dymos/examples/bryson_denham/bryson_denham.html) \ No newline at end of file diff --git a/ext/JuMPModels/bryson_denham.jl b/ext/JuMPModels/bryson_denham.jl new file mode 100644 index 00000000..562b327a --- /dev/null +++ b/ext/JuMPModels/bryson_denham.jl @@ -0,0 +1,68 @@ +""" +$(TYPEDSIGNATURES) + +Constructs a JuMP model representing the **Bryson-Denham optimal control problem**. +The objective is to minimize the control effort while satisfying state constraints and double integrator dynamics. + +# Arguments +- `::JuMPBackend`: Placeholder type to specify the JuMP backend or solver interface. +- `grid_size::Int=500`: (Keyword) Number of discretization steps for the time grid. + +# Returns + +- `model::JuMP.Model`: A JuMP model containing the decision variables, dynamics constraints, boundary conditions, and the quadratic objective function. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP + +julia> model = OptimalControlProblems.bryson_denham(JuMPBackend(); N=100) +``` + +# References + +""" + +function OptimalControlProblems.bryson_denham( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:bryson_denham), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + params = parameters_data(:bryson_denham, parameters) + t0, tf = params[:t0], params[:tf] + x1_t0, x2_t0 = params[:x1_t0], params[:x2_t0] + x1_tf, x2_tf = params[:x1_tf], params[:x2_tf] + x1_max = params[:x1_max] + + model = JuMP.Model(args...; kwargs...) + + model[:time_grid] = () -> range(t0, tf, grid_size+1) + model[:state_components] = ["x1", "x2"] + model[:control_components] = ["u"] + model[:costate_components] = ["p1", "p2"] + model[:variable_components] = [] + + N = grid_size + Δt = (tf- t0) /N + + @variable(model, x1[0:N] <= x1_max, start = 0.0) + @variable(model, x2[0:N], start = 0.0) + @variable(model, u[0:N], start = 0.0) + + @constraints(model, begin + x1[0] == x1_t0 + x2[0] == x2_t0 + x1[N] == x1_tf + x2[N] == x2_tf + p1[i = 1:N], x1[i] == x1[i-1] + 0.5* Δt * (x2[i] + x2[i-1]) + p2[i = 1:N], x2[i] == x2[i-1] + 0.5 * Δt * (u[i] + u[i-1]) + end) + + @objective(model, Min, 0.5 * Δt * sum(0.5 * (u[i]^2 + u[i-1]^2) for i in 1:N)) + + return model +end \ No newline at end of file diff --git a/ext/MetaData/bryson_denham.jl b/ext/MetaData/bryson_denham.jl new file mode 100644 index 00000000..e072ef89 --- /dev/null +++ b/ext/MetaData/bryson_denham.jl @@ -0,0 +1,12 @@ +bryson_denham_meta = OrderedDict( + :grid_size => 500, + :parameters => ( + t0 = 0.0, # Initial time + tf = 1.0, # Final time + x1_t0 = 0.0, # Initial position + x2_t0 = 1.0, # Initial velocity + x1_tf = 0.0, # Final position + x2_tf = -1.0, # Final velocity + x1_max = 1/9 # State constraint: x1(t) <= x1_max + ), +) \ No newline at end of file diff --git a/ext/OptimalControlModels/bryson_denham.jl b/ext/OptimalControlModels/bryson_denham.jl new file mode 100644 index 00000000..95ee822e --- /dev/null +++ b/ext/OptimalControlModels/bryson_denham.jl @@ -0,0 +1,70 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** representing the Bryson-Denham problem using the OptimalControl backend. +The function sets up the state and control variables, boundary conditions, dynamics, path constraints, and the objective functional. +It then performs direct transcription to generate a discrete optimal control problem (DOCP). + +# Arguments + +- `::OptimalControlBackend`: Placeholder type to specify the OptimalControl backend or solver interface. +- `grid_size::Int=500`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object, representing the discretised problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.bryson_denham(OptimalControlBackend(); N=100); +``` + +# References + +Bryson, A. E. and Denham, W. F., "A Steering Program for Optimal Transfer of a Thrust-Limited Rocket Between Neighboring Circular Orbits", 1962. +- Formulation inspired by OptimalControl approach for swing-up control problems. +""" + +function OptimalControlProblems.bryson_denham( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:bryson_denham), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + params = parameters_data(:bryson_denham, parameters) + t0, tf = params[:t0], params[:tf] + x1_t0, x2_t0 = params[:x1_t0], params[:x2_t0] + x1_tf, x2_tf = params[:x1_tf], params[:x2_tf] + x1_max = params[:x1_max] + + ocp = @def begin + t ∈ [t0, tf], time + x ∈ R², state + u ∈ R, control + + x(t0) == [x1_t0, x2_t0] + x(tf) == [x1_tf, x2_tf] + + x₁(t) ≤ x1_max + + ẋ(t) == [x₂(t), u(t)] + + ∫(0.5 * u(t)^2) → min + end + + init = (state=[0.0, 0.0], control=0.0) + + return direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) +end \ No newline at end of file diff --git a/ext/OptimalControlModels_s/bryson_denham_s.jl b/ext/OptimalControlModels_s/bryson_denham_s.jl new file mode 100644 index 00000000..248a6385 --- /dev/null +++ b/ext/OptimalControlModels_s/bryson_denham_s.jl @@ -0,0 +1,71 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** representing the Bryson-Denham problem. +This problem consists of minimizing the integral of the squared acceleration for a particle +required to travel a given distance within a set time, subject to a state constraint on position. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type to specify the OptimalControl backend or solver interface. +- `grid_size::Int=500`: (Keyword) Number of discretization points for the direct transcription grid. + +# Returns + +- `docp`: The discretized optimal control problem (DOCP) object. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.bryson_denham(OptimalControlBackend(); N=100); +``` + +# References + +- Bryson, A. E. and Denham, W. F., "A Steering Program for Optimal Transfer of a Thrust-Limited Rocket Between Neighboring Circular Orbits", 1962. +- Dymos Examples: https://openmdao.github.io/dymos/examples/bryson_denham/bryson_denham.html +""" + +function OptimalControlProblems.bryson_denham_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:bryson_denham), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + params = parameters_data(:bryson_denham, parameters) + t0, tf = params[:t0], params[:tf] + x1_t0, x2_t0 = params[:x1_t0], params[:x2_t0] + x1_tf, x2_tf = params[:x1_tf], params[:x2_tf] + x1_max = params[:x1_max] + + ocp = @def begin + t ∈ [t0, tf], time + x ∈ R², state # x1 (position) et x2(speed) + u ∈ R, control + + x(t0) == [x1_t0, x2_t0] + x(tf) == [x1_tf, x2_tf] + + x₁(t) ≤ x1_max + + ∂(x₁)(t) == x₂(t) + ∂(x₂)(t) == u(t) + + ∫(0.5 * u(t)^2) → min + end + + init = (state=[0.0, 0.0], control=0.0) + + return direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) +end From 20c5624ce9d7eb356a96808f5e01862476435026 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sun, 1 Mar 2026 19:19:16 +0100 Subject: [PATCH 06/32] add balanced field problem --- ext/Descriptions/balanced_field.md | 48 +++++++ ext/JuMPModels/balanced_field.jl | 130 ++++++++++++++++++ ext/MetaData/balanced_field.jl | 29 ++++ ext/OptimalControlModels/balanced_field.jl | 128 +++++++++++++++++ .../balanced_field_s.jl | 106 ++++++++++++++ 5 files changed, 441 insertions(+) create mode 100644 ext/Descriptions/balanced_field.md create mode 100644 ext/JuMPModels/balanced_field.jl create mode 100644 ext/MetaData/balanced_field.jl create mode 100644 ext/OptimalControlModels/balanced_field.jl create mode 100644 ext/OptimalControlModels_s/balanced_field_s.jl diff --git a/ext/Descriptions/balanced_field.md b/ext/Descriptions/balanced_field.md new file mode 100644 index 00000000..d9fae05e --- /dev/null +++ b/ext/Descriptions/balanced_field.md @@ -0,0 +1,48 @@ +The **Aircraft Balanced Field Length Calculation** is a classic aeronautical engineering problem used to determine the minimum runway length required for a safe takeoff or a safe stop in the event of an engine failure. +This implementation focuses on the **takeoff climb** phase with one engine out, starting from the critical engine failure speed $V_1$. + +### Mathematical formulation + +The problem is to minimise the final range $r(t_f)$ to reach the screen height (usually 35 ft). +The state vector is $x(t) = [r(t), v(t), h(t), \gamma(t)]^ op$ and the control is the angle of attack $\alpha(t)$. + +```math +\begin{aligned} +\min_{\alpha, t_f} \quad & r(t_f) \[0.5em] + ext{s.t.} \quad & \dot{r}(t) = v(t) \cos \gamma(t), \[0.5em] +& \dot{v}(t) = \frac{T \cos \alpha(t) - D}{m} - g \sin \gamma(t), \[0.5em] +& \dot{h}(t) = v(t) \sin \gamma(t), \[0.5em] +& \dot{\gamma}(t) = \frac{T \sin \alpha(t) + L}{m v(t)} - \frac{g \cos \gamma(t)}{v(t)}, \[0.5em] +& h(t_f) = 10.668 ext{ m (35 ft)}, \[0.5em] +& \gamma(t_f) = 5^\circ. +\end{aligned} +``` + +The aerodynamic forces $L$ and $D$ depend on the angle of attack $\alpha$, velocity $v$, and altitude $h$ (ground effect). + +### Parameters + +The parameters are based on a mid-size jet aircraft (nominal values from Dymos): + +| Parameter | Symbol | Value | +|-----------|--------|-------| +| Mass | $m$ | 79015.8 kg | +| Surface | $S$ | 124.7 m² | +| Thrust (1 engine) | $T$ | 120102.0 N | +| Screen height | $h_{tf}$ | 10.668 m | + +### Characteristics + +- Multi-state nonlinear dynamics, +- Free final time $t_f$, +- Ground effect inclusion in the drag model ($K$ coefficient), +- Control bounds on the angle of attack $\alpha$, +- Boundary conditions representing $V_1$ conditions and final climb requirements. + +### References + +- **Dymos Documentation**. [*Aircraft Balanced Field Length Calculation*.](https://openmdao.github.io/dymos/examples/balanced_field/balanced_field.html). + Illustrates the full multi-phase BFL calculation using Dymos and OpenMDAO. + +- **Betts, J. T. (2010)**. *Practical Methods for Optimal Control and Estimation Using Nonlinear Programming*. SIAM. + Discusses takeoff and landing trajectory optimization in Chapter 6. diff --git a/ext/JuMPModels/balanced_field.jl b/ext/JuMPModels/balanced_field.jl new file mode 100644 index 00000000..5fbb5dc0 --- /dev/null +++ b/ext/JuMPModels/balanced_field.jl @@ -0,0 +1,130 @@ +""" +$(TYPEDSIGNATURES) + +Constructs and returns a JuMP model for the **Aircraft Balanced Field Length Calculation (Takeoff phase)**. +The model represents the dynamics of the aircraft during takeoff and seeks to minimise the final range. + +# Arguments + +- `::JuMPBackend`: Specifies the backend for building the JuMP model. +- `grid_size::Int=200`: (Keyword) Number of discretisation steps for the time horizon. + +# Returns + +- `model::JuMP.Model`: A JuMP model representing the problem. +""" +function OptimalControlProblems.balanced_field( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:balanced_field), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:balanced_field, parameters) + t0 = params[:t0] + m = params[:m] + g = params[:g] + ρ = params[:ρ] + S = params[:S] + CD0 = params[:CD0] + AR = params[:AR] + e = params[:e] + CL0 = params[:CL0] + CL_max = params[:CL_max] + h_w = params[:h_w] + span = params[:span] + α_max = params[:α_max] + T = params[:T] + + r_t0 = params[:r_t0] + v_t0 = params[:v_t0] + h_t0 = params[:h_t0] + γ_t0 = params[:γ_t0] + h_tf = params[:h_tf] + γ_tf = params[:γ_tf] + + α_min = params[:α_min] + α_max_ctrl = params[:α_max_ctrl] + + # Constants + b = span / 2.0 + K_nom = 1.0 / (pi * AR * e) + + # model + model = JuMP.Model(args...; kwargs...) + + # metadata + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) + model[:state_components] = ["r", "v", "h", "γ"] + model[:costate_components] = ["∂r", "∂v", "∂h", "∂γ"] + model[:control_components] = ["α"] + model[:variable_components] = ["tf"] + + @expression(model, N, grid_size) + + # variables + @variables( + model, + begin + r[0:N], (start = r_t0) + v[0:N] ≥ 1.0, (start = v_t0 + 10.0) + h[0:N] ≥ 0.0, (start = h_tf / 2.0) + γ[0:N], (start = γ_tf) + 0 ≤ α[0:N] ≤ α_max_ctrl, (start = 0.1) + tf ≥ 0.1, (start = 10.0) + end + ) + + # boundary constraints + @constraints( + model, + begin + r[0] == r_t0 + v[0] == v_t0 + h[0] == h_t0 + γ[0] == γ_t0 + + h[N] == h_tf + γ[N] == γ_tf + end + ) + + # dynamics + @expressions( + model, + begin + Δt, (tf - t0) / N + + q[i = 0:N], 0.5 * ρ * v[i]^2 + CL[i = 0:N], CL0 + (α[i] / α_max) * (CL_max - CL0) + L[i = 0:N], q[i] * S * CL[i] + + h_eff[i = 0:N], h[i] + h_w + term_h[i = 0:N], 33.0 * abs(h_eff[i] / b)^1.5 + K[i = 0:N], K_nom * term_h[i] / (1.0 + term_h[i]) + D[i = 0:N], q[i] * S * (CD0 + K[i] * CL[i]^2) + + dr[i = 0:N], v[i] * cos(γ[i]) + dv[i = 0:N], (T * cos(α[i]) - D[i]) / m - g * sin(γ[i]) + dh[i = 0:N], v[i] * sin(γ[i]) + dγ[i = 0:N], (T * sin(α[i]) + L[i]) / (m * v[i]) - (g * cos(γ[i])) / v[i] + end + ) + + @constraints( + model, + begin + ∂r[i = 1:N], r[i] == r[i - 1] + 0.5 * Δt * (dr[i] + dr[i - 1]) + ∂v[i = 1:N], v[i] == v[i - 1] + 0.5 * Δt * (dv[i] + dv[i - 1]) + ∂h[i = 1:N], h[i] == h[i - 1] + 0.5 * Δt * (dh[i] + dh[i - 1]) + ∂γ[i = 1:N], γ[i] == γ[i - 1] + 0.5 * Δt * (dγ[i] + dγ[i - 1]) + end + ) + + # objective + @objective(model, Min, r[N]) + + return model +end diff --git a/ext/MetaData/balanced_field.jl b/ext/MetaData/balanced_field.jl new file mode 100644 index 00000000..f69bd6de --- /dev/null +++ b/ext/MetaData/balanced_field.jl @@ -0,0 +1,29 @@ +balanced_field_meta = OrderedDict( + :grid_size => 200, + :parameters => ( + t0 = 0.0, + m = 79015.8, # mass (kg) + g = 9.80665, # gravity (m/s^2) + ρ = 1.225, # air density (kg/m^3) + S = 124.7, # surface (m^2) + CD0 = 0.03, # zero-lift drag coefficient + AR = 9.45, # aspect ratio + e = 0.801, # Oswald efficiency + CL0 = 0.5, # zero-alpha lift coefficient + CL_max = 2.0, # max lift coefficient + h_w = 1.0, # height of wing above CoG (m) + span = 35.7, # wingspan (m) + α_max = 0.1745, # alpha at CL_max (10 degrees in rad) + T = 120102.0, # thrust (N) - one engine out + r_t0 = 600.0, # initial range (m) at V1 + v_t0 = 70.0, # initial velocity (m/s) at V1 + h_t0 = 0.0, # initial altitude (m) + γ_t0 = 0.0, # initial flight path angle (rad) + h_tf = 10.668, # final altitude (35 ft in m) + γ_tf = 0.0873, # final flight path angle (5 degrees in rad) + v_min = 0.0, + v_max = 100.0, + α_min = -0.1745, # -10 degrees + α_max_ctrl = 0.2618, # 15 degrees + ), +) diff --git a/ext/OptimalControlModels/balanced_field.jl b/ext/OptimalControlModels/balanced_field.jl new file mode 100644 index 00000000..1615645e --- /dev/null +++ b/ext/OptimalControlModels/balanced_field.jl @@ -0,0 +1,128 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Aircraft Balanced Field Length Calculation (Takeoff phase). +The objective is to minimise the final range to reach a specified altitude (35 ft) with one engine out. +The problem formulation is based on the Dymos balanced field example. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=200`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the problem. +- `nlp`: The corresponding nonlinear programming model. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> docp = OptimalControlProblems.balanced_field(OptimalControlBackend()); +``` +""" +function OptimalControlProblems.balanced_field( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:balanced_field), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:balanced_field, parameters) + t0 = params[:t0] + m = params[:m] + g = params[:g] + ρ = params[:ρ] + S = params[:S] + CD0 = params[:CD0] + AR = params[:AR] + e = params[:e] + CL0 = params[:CL0] + CL_max = params[:CL_max] + h_w = params[:h_w] + span = params[:span] + α_max = params[:α_max] + T = params[:T] + + r_t0 = params[:r_t0] + v_t0 = params[:v_t0] + h_t0 = params[:h_t0] + γ_t0 = params[:γ_t0] + h_tf = params[:h_tf] + γ_tf = params[:γ_tf] + + α_min = params[:α_min] + α_max_ctrl = params[:α_max_ctrl] + + # Constants + b = span / 2.0 + K_nom = 1.0 / (pi * AR * e) + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (r, v, h, γ) ∈ R⁴, state + α ∈ R, control + + r(t0) == r_t0 + v(t0) == v_t0 + h(t0) == h_t0 + γ(t0) == γ_t0 + + h(tf) == h_tf + γ(tf) == γ_tf + + 0 ≤ α(t) ≤ α_max_ctrl # alpha + tf ≥ 0.1 + h(t) ≥ 0 + v(t) ≥ 1.0 + + ẋ(t) == dynamics(x(t), α(t), m, g, ρ, S, CD0, CL0, CL_max, α_max, T, b, K_nom, h_w) + + r(tf) → min + end + + function dynamics(x, α, m, g, ρ, S, CD0, CL0, CL_max, α_max, T, b, K_nom, h_w) + r, v, h, γ = x + + q = 0.5 * ρ * v^2 + CL = CL0 + (α / α_max) * (CL_max - CL0) + L = q * S * CL + + h_eff = h + h_w + term_h = 33.0 * abs(h_eff / b)^1.5 + K = K_nom * term_h / (1.0 + term_h) + D = q * S * (CD0 + K * CL^2) + + rdot = v * cos(γ) + vdot = (T * cos(α) - D) / m - g * sin(γ) + hdot = v * sin(γ) + γdot = (T * sin(α) + L) / (m * v) - (g * cos(γ)) / v + + return [rdot, vdot, hdot, γdot] + end + + # initial guess + tf_guess = 10.0 + xinit = [r_t0, v_t0 + 10.0, h_tf / 2.0, γ_tf] + uinit = [0.1] + vinit = [tf_guess] + init = (state=xinit, control=uinit, variable=vinit) + + # discretise + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end diff --git a/ext/OptimalControlModels_s/balanced_field_s.jl b/ext/OptimalControlModels_s/balanced_field_s.jl new file mode 100644 index 00000000..c7ed9b1e --- /dev/null +++ b/ext/OptimalControlModels_s/balanced_field_s.jl @@ -0,0 +1,106 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Aircraft Balanced Field Length Calculation (Takeoff phase) using symbolic syntax. +The objective is to minimise the final range to reach a specified altitude (35 ft) with one engine out. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=200`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the problem. +- `nlp`: The corresponding nonlinear programming model. +""" +function OptimalControlProblems.balanced_field_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:balanced_field), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:balanced_field, parameters) + t0 = params[:t0] + m = params[:m] + g = params[:g] + ρ = params[:ρ] + S = params[:S] + CD0 = params[:CD0] + AR = params[:AR] + e = params[:e] + CL0 = params[:CL0] + CL_max = params[:CL_max] + h_w = params[:h_w] + span = params[:span] + α_max = params[:α_max] + T = params[:T] + + r_t0 = params[:r_t0] + v_t0 = params[:v_t0] + h_t0 = params[:h_t0] + γ_t0 = params[:γ_t0] + h_tf = params[:h_tf] + γ_tf = params[:γ_tf] + + α_min = params[:α_min] + α_max_ctrl = params[:α_max_ctrl] + + # Constants + b = span / 2.0 + K_nom = 1.0 / (pi * AR * e) + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (r, v, h, γ) ∈ R⁴, state + α ∈ R, control + + x(t0) == [r_t0, v_t0, h_t0, γ_t0] + + h(tf) == h_tf + γ(tf) == γ_tf + + 0 ≤ α(t) ≤ α_max_ctrl + tf ≥ 0.1 + h(t) ≥ 0 + v(t) ≥ 1.0 + + # dynamics + q = 0.5 * ρ * v(t)^2 + CL = CL0 + (α(t) / α_max) * (CL_max - CL0) + L = q * S * CL + h_eff = h(t) + h_w + term_h = 33.0 * abs(h_eff / b)^1.5 + K = K_nom * term_h / (1.0 + term_h) + D = q * S * (CD0 + K * CL^2) + + ∂(r)(t) == v(t) * cos(γ(t)) + ∂(v)(t) == (T * cos(α(t)) - D) / m - g * sin(γ(t)) + ∂(h)(t) == v(t) * sin(γ(t)) + ∂(γ)(t) == (T * sin(α(t)) + L) / (m * v(t)) - (g * cos(γ(t))) / v(t) + + r(tf) → min + end + + # initial guess + tf_guess = 10.0 + init = (state=[r_t0, v_t0 + 10.0, h_tf / 2.0, γ_tf], control=[0.1], variable=[tf_guess]) + + # discretise + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end From a022661c148cb5f5a29728dbf062f2e381173119 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sun, 1 Mar 2026 19:21:04 +0100 Subject: [PATCH 07/32] add moutain car problem --- ext/Descriptions/mountain_car.md | 57 ++++++++++ ext/JuMPModels/mountain_car.jl | 114 +++++++++++++++++++ ext/MetaData/mountain_car.jl | 21 ++++ ext/OptimalControlModels/mountain_car.jl | 96 ++++++++++++++++ ext/OptimalControlModels_s/mountain_car_s.jl | 97 ++++++++++++++++ 5 files changed, 385 insertions(+) create mode 100644 ext/Descriptions/mountain_car.md create mode 100644 ext/JuMPModels/mountain_car.jl create mode 100644 ext/MetaData/mountain_car.jl create mode 100644 ext/OptimalControlModels/mountain_car.jl create mode 100644 ext/OptimalControlModels_s/mountain_car_s.jl diff --git a/ext/Descriptions/mountain_car.md b/ext/Descriptions/mountain_car.md new file mode 100644 index 00000000..138bba72 --- /dev/null +++ b/ext/Descriptions/mountain_car.md @@ -0,0 +1,57 @@ +The **Mountain Car problem** is a classic benchmark in control and reinforcement learning. +It involves an underpowered car that must climb a steep hill to reach a target position. +Because the car's engine is too weak to climb the hill directly, it must first drive away from the goal to gain momentum by oscillating in a valley. +The goal is to reach the target position in **minimum time**. + +### Mathematical formulation + +The problem can be stated as + +```math +\begin{aligned} +\min_{x,v,u,t_f} \quad & J = t_f \[0.5em] + ext{s.t.} \quad & \dot{x}(t) = v(t), \[0.5em] +& \dot{v}(t) = a \, u(t) - b \, \cos(c \, x(t)), \[0.5em] +& x(0) = -0.5, \quad v(0) = 0.0, \[0.5em] +& x(t_f) = 0.5, \quad v(t_f) \ge 0.0, \[0.5em] +& -1.2 \le x(t) \le 0.5, \[0.5em] +& -0.07 \le v(t) \le 0.07, \[0.5em] +& -1 \le u(t) \le 1, \[0.5em] +& t_f \ge 0. +\end{aligned} +``` + +### Parameters + +| Parameter | Symbol | Value | +|-----------|--------|-------| +| Power coefficient | $a$ | 0.001 | +| Gravity coefficient | $b$ | 0.0025 | +| Frequency coefficient | $c$ | 3.0 | +| Initial position | $x_0$ | -0.5 | +| Target position | $x_f$ | 0.5 | + +### Qualitative behaviour + +- The car must oscillate back and forth in the valley to build enough kinetic energy to climb the right hill. +- The optimal strategy typically involves a sequence of full-throttle accelerations in alternating directions (**bang-bang control**). +- The minimum time objective makes the problem sensitive to the initial guess for the final time. + +### Characteristics + +- Two-dimensional state space (position and velocity), +- Scalar control (effort/acceleration), +- **Free final time** with minimum time objective, +- State constraints (position and velocity bounds), +- Control constraints (bounds on acceleration). + +### References + +- **Dymos Documentation**. [*The Mountain Car Problem*](https://openmdao.github.io/dymos/examples/mountain_car/mountain_car.html). + Provides a detailed description and implementation of the Mountain Car problem using the Dymos library. + +- **OpenAI Gym**. [*MountainCar-v0*](https://gym.openai.com/envs/MountainCar-v0/). + A standard reinforcement learning environment for the Mountain Car problem. + +- Moore, A. W. (1990). *Efficient Memory-based Learning for Robot Control*. PhD thesis, University of Cambridge. + Introduces the Mountain Car problem as a benchmark for reinforcement learning and control. diff --git a/ext/JuMPModels/mountain_car.jl b/ext/JuMPModels/mountain_car.jl new file mode 100644 index 00000000..3f379e89 --- /dev/null +++ b/ext/JuMPModels/mountain_car.jl @@ -0,0 +1,114 @@ +""" +$(TYPEDSIGNATURES) + +Constructs and returns a JuMP model for the **Mountain Car Problem**. +The model represents the dynamics of an underpowered car climbing a hill. +The objective is to minimize the final time `tf` to reach the target position. + +# Arguments + +- `::JuMPBackend`: Specifies the backend for building the JuMP model. +- `grid_size::Int=100`: (Keyword) Number of discretisation steps for the time horizon. + +# Returns + +- `model::JuMP.Model`: A JuMP model representing the Mountain Car optimal control problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP + +julia> model = OptimalControlProblems.mountain_car(JuMPBackend(); N=100) +``` + +# References + +- Problem formulation: https://openmdao.github.io/dymos/examples/mountain_car/mountain_car.html +""" +function OptimalControlProblems.mountain_car( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:mountain_car), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:mountain_car, parameters) + t0 = params[:t0] + tf_start = params[:tf_start] + tf_min = params[:tf_min] + pos_t0 = params[:pos_t0] + vel_t0 = params[:vel_t0] + pos_tf = params[:pos_tf] + vel_tf_min = params[:vel_tf_min] + pos_min = params[:pos_min] + pos_max = params[:pos_max] + vel_min = params[:vel_min] + vel_max = params[:vel_max] + u_min = params[:u_min] + u_max = params[:u_max] + a = params[:a] + b = params[:b] + c = params[:c] + + # model + model = JuMP.Model(args...; kwargs...) + + # metadata + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) + model[:state_components] = ["pos", "vel"] + model[:costate_components] = ["∂pos", "∂vel"] + model[:control_components] = ["u"] + model[:variable_components] = ["tf"] + + # N = grid_size + @expression(model, N, grid_size) + + # variables + @variables( + model, + begin + pos_min ≤ pos[0:N] ≤ pos_max, (start = pos_t0) + vel_min ≤ vel[0:N] ≤ vel_max, (start = vel_t0) + u_min ≤ u[0:N] ≤ u_max, (start = 0.0) + tf ≥ tf_min, (start = tf_start) + end + ) + + # boundary constraints + @constraints( + model, + begin + pos[0] == pos_t0 + vel[0] == vel_t0 + pos[N] == pos_tf + vel[N] ≥ vel_tf_min + end + ) + + # dynamics + @expressions( + model, + begin + Δt, (tf - t0) / N + dpos[i = 0:N], vel[i] + dvel[i = 0:N], a * u[i] - b * cos(c * pos[i]) + end + ) + + @constraints( + model, + begin + ∂pos[i = 1:N], pos[i] == pos[i - 1] + 0.5 * Δt * (dpos[i] + dpos[i - 1]) + ∂vel[i = 1:N], vel[i] == vel[i - 1] + 0.5 * Δt * (dvel[i] + dvel[i - 1]) + end + ) + + # objective + @objective(model, Min, tf) + + return model +end diff --git a/ext/MetaData/mountain_car.jl b/ext/MetaData/mountain_car.jl new file mode 100644 index 00000000..cc96b7b4 --- /dev/null +++ b/ext/MetaData/mountain_car.jl @@ -0,0 +1,21 @@ +mountain_car_meta = OrderedDict( + :grid_size => 200, + :parameters => ( + t0 = 0.0, + tf_start = 50.0, + tf_min = 0.1, + pos_t0 = -0.5, + vel_t0 = 0.0, + pos_tf = 0.5, + vel_tf_min = 0.0, + pos_min = -1.2, + pos_max = 0.5, + vel_min = -0.07, + vel_max = 0.07, + u_min = -1.0, + u_max = 1.0, + a = 0.001, + b = 0.0025, + c = 3.0, + ), +) diff --git a/ext/OptimalControlModels/mountain_car.jl b/ext/OptimalControlModels/mountain_car.jl new file mode 100644 index 00000000..984675bc --- /dev/null +++ b/ext/OptimalControlModels/mountain_car.jl @@ -0,0 +1,96 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Mountain Car problem. +The goal is to drive an underpowered car up a steep hill. +The objective is to minimize the time required to reach the target position. +The problem formulation can be found [here](https://openmdao.github.io/dymos/examples/mountain_car/mountain_car.html). + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the Mountain Car problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.mountain_car(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.mountain_car( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:mountain_car), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:mountain_car, parameters) + t0 = params[:t0] + tf_start = params[:tf_start] + tf_min = params[:tf_min] + pos_t0 = params[:pos_t0] + vel_t0 = params[:vel_t0] + pos_tf = params[:pos_tf] + vel_tf_min = params[:vel_tf_min] + pos_min = params[:pos_min] + pos_max = params[:pos_max] + vel_min = params[:vel_min] + vel_max = params[:vel_max] + u_min = params[:u_min] + u_max = params[:u_max] + a = params[:a] + b = params[:b] + c = params[:c] + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (pos, vel) ∈ R², state + u ∈ R, control + + # constraints + tf ≥ tf_min + pos_min ≤ pos(t) ≤ pos_max + vel_min ≤ vel(t) ≤ vel_max + u_min ≤ u(t) ≤ u_max + + # initial conditions + pos(t0) == pos_t0 + vel(t0) == vel_t0 + + # final conditions + pos(tf) == pos_tf + vel(tf) ≥ vel_tf_min + + # dynamics + ẋ(t) == [vel(t), a * u(t) - b * cos(c * pos(t))] + + # objective + tf → min + end + + # initial guess + init = (state=[pos_t0, vel_t0], control=0.0, variable=tf_start) + + # discretise the optimal control problem + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end diff --git a/ext/OptimalControlModels_s/mountain_car_s.jl b/ext/OptimalControlModels_s/mountain_car_s.jl new file mode 100644 index 00000000..cd770838 --- /dev/null +++ b/ext/OptimalControlModels_s/mountain_car_s.jl @@ -0,0 +1,97 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Mountain Car problem (symbolic version). +The goal is to drive an underpowered car up a steep hill. +The objective is to minimize the time required to reach the target position. +The problem formulation can be found [here](https://openmdao.github.io/dymos/examples/mountain_car/mountain_car.html). + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the Mountain Car problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.mountain_car_s(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.mountain_car_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:mountain_car), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:mountain_car, parameters) + t0 = params[:t0] + tf_start = params[:tf_start] + tf_min = params[:tf_min] + pos_t0 = params[:pos_t0] + vel_t0 = params[:vel_t0] + pos_tf = params[:pos_tf] + vel_tf_min = params[:vel_tf_min] + pos_min = params[:pos_min] + pos_max = params[:pos_max] + vel_min = params[:vel_min] + vel_max = params[:vel_max] + u_min = params[:u_min] + u_max = params[:u_max] + a = params[:a] + b = params[:b] + c = params[:c] + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (pos, vel) ∈ R², state + u ∈ R, control + + # constraints + tf ≥ tf_min + pos_min ≤ pos(t) ≤ pos_max + vel_min ≤ vel(t) ≤ vel_max + u_min ≤ u(t) ≤ u_max + + # initial conditions + pos(t0) == pos_t0 + vel(t0) == vel_t0 + + # final conditions + pos(tf) == pos_tf + vel(tf) ≥ vel_tf_min + + # dynamics + ∂(pos)(t) == vel(t) + ∂(vel)(t) == a * u(t) - b * cos(c * pos(t)) + + # objective + tf → min + end + + # initial guess + init = (state=[pos_t0, vel_t0], control=0.0, variable=tf_start) + + # discretise the optimal control problem + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end From e98695147c73b59bb225db992c06b53df9cea3e0 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sun, 1 Mar 2026 19:25:34 +0100 Subject: [PATCH 08/32] add glider, robot, space shuttle, steering , insurance problems --- ext/JuMPModels/glider.jl | 6 +-- ext/JuMPModels/robot.jl | 54 ++++++++++++++----- ext/JuMPModels/space_shuttle.jl | 2 +- ext/JuMPModels/steering.jl | 2 +- ext/OptimalControlModels/glider.jl | 14 +++-- ext/OptimalControlModels/robot.jl | 40 +++++++++----- ext/OptimalControlModels/space_shuttle.jl | 6 +-- ext/OptimalControlModels/steering.jl | 2 +- ext/OptimalControlModels_s/glider_s.jl | 12 +++-- ext/OptimalControlModels_s/robot_s.jl | 35 +++++++----- ext/OptimalControlModels_s/space_shuttle_s.jl | 6 +-- ext/OptimalControlModels_s/steering_s.jl | 2 +- 12 files changed, 121 insertions(+), 60 deletions(-) diff --git a/ext/JuMPModels/glider.jl b/ext/JuMPModels/glider.jl index 164600a7..52331893 100644 --- a/ext/JuMPModels/glider.jl +++ b/ext/JuMPModels/glider.jl @@ -76,12 +76,12 @@ function OptimalControlProblems.glider( @variables( model, begin - tf ≥ tf_l, (start = 1) - x[k = 0:N] ≥ x_l, (start = x_t0 + vx_t0 * k / N) + tf ≥ tf_l, (start = 100.0) #### + x[k = 0:N] ≥ x_l, (start = x_t0 + (k / N) * 1248.0) #### y[k = 0:N], (start = y_t0 + (k / N) * (y_tf - y_t0)) vx[k = 0:N] ≥ vx_l, (start = vx_t0) vy[k = 0:N], (start = vy_t0) - cL_min ≤ cL[k = 0:N] ≤ cL_max, (start = cL_max / 2) + cL_min ≤ cL[k = 0:N] ≤ cL_max, (start = 1.0) #### end ) diff --git a/ext/JuMPModels/robot.jl b/ext/JuMPModels/robot.jl index 79bfadc0..bc109fba 100644 --- a/ext/JuMPModels/robot.jl +++ b/ext/JuMPModels/robot.jl @@ -92,22 +92,53 @@ function OptimalControlProblems.robot( @variables( model, begin - ρ_l ≤ ρ[k = 0:N] ≤ ρ_u, (start = ρ_t0) - θ_l ≤ θ[k = 0:N] ≤ θ_u, (start = 2π/3 * (k / N)^2) - ϕ_l ≤ ϕ[k = 0:N] ≤ ϕ_u, (start = ϕ_t0) + ρ_l ≤ ρ[k = 0:N] ≤ ρ_u + θ_l ≤ θ[k = 0:N] ≤ θ_u + ϕ_l ≤ ϕ[k = 0:N] ≤ ϕ_u - dρ[k = 0:N], (start = 0) - dθ[k = 0:N], (start = 4π/3 * (k / N)) - dϕ[k = 0:N], (start = 0) + dρ[k = 0:N] + dθ[k = 0:N] + dϕ[k = 0:N] - uρ_l ≤ uρ[0:N] ≤ uρ_u, (start = 0) - uθ_l ≤ uθ[0:N] ≤ uθ_u, (start = 0) - uϕ_l ≤ uϕ[0:N] ≤ uϕ_u, (start = 0) + uρ_l ≤ uρ[0:N] ≤ uρ_u + uθ_l ≤ uθ[0:N] ≤ uθ_u + uϕ_l ≤ uϕ[0:N] ≤ uϕ_u - tf ≥ tf_l, (start = 1) + tf ≥ tf_l end ) + #INITIAL GUESS + set_start_value(tf, 9.1) + for k in 0:grid_size + # Coefficient d'interpolation entre 0 et 1 + alpha = k / grid_size + + # Interpolation linéaire entre t0 et tf + ρ_val = ρ_t0 + alpha * (ρ_tf - ρ_t0) + θ_val = θ_t0 + alpha * (θ_tf - θ_t0) + ϕ_val = ϕ_t0 + alpha * (ϕ_tf - ϕ_t0) + + # SECURITÉ ANTI-CRASH (Division par zéro) + if abs(ϕ_val) < 1e-6 + ϕ_val = 1e-6 + end + + # Application des valeurs aux variables JuMP + set_start_value(ρ[k], ρ_val) + set_start_value(θ[k], θ_val) + set_start_value(ϕ[k], ϕ_val) + + # Vitesses et contrôles à 0 par défaut + set_start_value(dρ[k], 0.0) + set_start_value(dθ[k], 0.0) + set_start_value(dϕ[k], 0.0) + + set_start_value(uρ[k], 0.0) + set_start_value(uθ[k], 0.0) + set_start_value(uϕ[k], 0.0) + end + # Boundary condition @constraints( model, @@ -137,8 +168,7 @@ function OptimalControlProblems.robot( # Δt, (tf - t0) / N - # - I_θ[i = 0:N], ((L - ρ[i])^3 + ρ[i]^3) * (sin(ϕ[i]))^2 + I_θ[i = 0:N], ((L - ρ[i])^3 + ρ[i]^3) * ((sin(ϕ[i]))^2 + 1e-9) I_ϕ[i = 0:N], (L - ρ[i])^3 + ρ[i]^3 # diff --git a/ext/JuMPModels/space_shuttle.jl b/ext/JuMPModels/space_shuttle.jl index 0479eebf..42d50df7 100644 --- a/ext/JuMPModels/space_shuttle.jl +++ b/ext/JuMPModels/space_shuttle.jl @@ -184,7 +184,7 @@ function OptimalControlProblems.space_shuttle( set_start_value.(model[:ψ], vec(initial_guess[:, 6])) set_start_value.(model[:α], vec(initial_guess[:, 7])) set_start_value.(model[:β], vec(initial_guess[:, 8])) - set_start_value.(model[:tf], (tf_l+tf_u)/2) + set_start_value.(model[:tf], 2000.0) ####### # Functions to restore `h` and `v` to their true scale @expression(model, h[j = 0:N], scaled_h[j] * scaling_h) diff --git a/ext/JuMPModels/steering.jl b/ext/JuMPModels/steering.jl index cd40af69..8554cc42 100644 --- a/ext/JuMPModels/steering.jl +++ b/ext/JuMPModels/steering.jl @@ -51,7 +51,7 @@ function OptimalControlProblems.steering( x₄_tf = params[:x₄_tf] # - tf_start = 1 + tf_start = 0.6 function gen_x0(k, i) if i == 1 || i == 4 return 0.0 diff --git a/ext/OptimalControlModels/glider.jl b/ext/OptimalControlModels/glider.jl index d91059be..cbec4a64 100644 --- a/ext/OptimalControlModels/glider.jl +++ b/ext/OptimalControlModels/glider.jl @@ -110,11 +110,15 @@ function OptimalControlProblems.glider( end # initial guess - tfinit = 1 - xinit = - t -> [x_t0 + vx_t0 * t / tfinit, y_t0 + t / tfinit * (y_tf - y_t0), vx_t0, vy_t0] - uinit = cL_max / 2 - init = (state=xinit, control=uinit, variable=tfinit) + tfinit = 100.0 + xinit = t -> [ + x_t0 + (t / tfinit) * 1248.0, + y_t0 + (t / tfinit) * (y_tf - y_t0), + vx_t0 + (t / tfinit) * (vx_tf - vx_t0), + vy_t0 + (t / tfinit) * (vy_tf - vy_t0) + ] + uinit = 1.0 + init = (state=xinit, control=uinit, variable=tfinit) # discretise the optimal control problem docp = direct_transcription( diff --git a/ext/OptimalControlModels/robot.jl b/ext/OptimalControlModels/robot.jl index 1bfe831a..454ab55b 100644 --- a/ext/OptimalControlModels/robot.jl +++ b/ext/OptimalControlModels/robot.jl @@ -105,30 +105,42 @@ function OptimalControlProblems.robot( dθ(tf) == dθ_tf, (dθ_tf) dϕ(tf) == dϕ_tf, (dϕ_tf) - # aliases - I_θ = ((L - ρ(t))^3 + ρ(t)^3) * sin(ϕ(t))^2 + + I_θ = ((L - ρ(t))^3 + ρ(t)^3) * (sin(ϕ(t))^2 + 1e-9) I_ϕ = (L - ρ(t))^3 + ρ(t)^3 # dynamics - ẋ(t) == [dρ(t), uρ(t) / L, dθ(t), 3 * uθ(t) / I_θ, dϕ(t), 3 * uϕ(t) / I_ϕ] + ẋ(t) == [dρ(t), uρ(t) / L, dθ(t), 3 * uθ(t) / I_θ, dϕ(t), 3 * uϕ(t) / I_ϕ] # objective tf → min end # initial guess - tf = 1 - xinit = - t -> [ - ρ_t0, - 0, - 2π/3 * ((t - t0) / (tf - t0))^2, - 4π/3 * ((t - t0) / (tf - t0)), - ϕ_t0, - 0, + tf_guess = 9.1 + xinit = t -> begin + alpha = clamp((t - t0) / (tf_guess - t0), 0.0, 1.0) + + ρ_val = ρ_t0 + alpha * (ρ_tf - ρ_t0) + θ_val = θ_t0 + alpha * (θ_tf - θ_t0) + ϕ_val = ϕ_t0 + alpha * (ϕ_tf - ϕ_t0) + + if abs(ϕ_val) < 1e-6 + ϕ_val = 1e-6 + end + + return [ + ρ_val, + 0.0, + θ_val, + 0.0, + ϕ_val, + 0.0, ] - uinit = [0, 0, 0] - init = (state=xinit, control=uinit, variable=tf) + end + + uinit = [0.0, 0.0, 0.0] + init = (state=xinit, control=uinit, variable=tf_guess) # discretise the optimal control problem docp = direct_transcription( diff --git a/ext/OptimalControlModels/space_shuttle.jl b/ext/OptimalControlModels/space_shuttle.jl index 2d6e6d25..bdccc605 100644 --- a/ext/OptimalControlModels/space_shuttle.jl +++ b/ext/OptimalControlModels/space_shuttle.jl @@ -56,8 +56,8 @@ function OptimalControlProblems.space_shuttle( # Δt_min = params[:Δt_min] Δt_max = params[:Δt_max] - tf_l = grid_size*Δt_min - tf_u = grid_size*Δt_max + tf_l = 1500.0 # + tf_u = 2500.0 # ## Initial conditions h_t0 = params[:h_t0] @@ -175,7 +175,7 @@ function OptimalControlProblems.space_shuttle( # initial guess: linear interpolation for h, v, gamma (NB. t0 = 0), constant for the rest # variable time step seems to be initialized at 1 in jump # note that ipopt will project the initial guess inside the bounds anyway. - tf_init = (tf_l+tf_u)/2 + tf_init = 2000 x_init = t -> [ h_t0 + (t - t0) / (tf_init - t0) * (h_tf - h_t0), diff --git a/ext/OptimalControlModels/steering.jl b/ext/OptimalControlModels/steering.jl index 36aeabff..a1539e56 100644 --- a/ext/OptimalControlModels/steering.jl +++ b/ext/OptimalControlModels/steering.jl @@ -85,7 +85,7 @@ function OptimalControlProblems.steering( end end xinit = t -> [gen_x0(t, i) for i in 1:4] - init = (state=xinit, control=0, variable=1) + init = (state=xinit, control=0, variable=0.6) # discretise the optimal control problem docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/glider_s.jl b/ext/OptimalControlModels_s/glider_s.jl index 59cd87e7..7e73c295 100644 --- a/ext/OptimalControlModels_s/glider_s.jl +++ b/ext/OptimalControlModels_s/glider_s.jl @@ -104,10 +104,14 @@ function OptimalControlProblems.glider_s( end # initial guess - tfinit = 1 - xinit = - t -> [x_t0 + vx_t0 * t / tfinit, y_t0 + t / tfinit * (y_tf - y_t0), vx_t0, vy_t0] - uinit = cL_max / 2 + tfinit = 100.0 + xinit = t -> [ + x_t0 + (t / tfinit) * 1248.0, + y_t0 + (t / tfinit) * (y_tf - y_t0), + vx_t0 + (t / tfinit) * (vx_tf - vx_t0), + vy_t0 + (t / tfinit) * (vy_tf - vy_t0) + ] + uinit = 1.0 init = (state=xinit, control=uinit, variable=tfinit) # discretise the optimal control problem diff --git a/ext/OptimalControlModels_s/robot_s.jl b/ext/OptimalControlModels_s/robot_s.jl index 2756c18b..b9f245fe 100644 --- a/ext/OptimalControlModels_s/robot_s.jl +++ b/ext/OptimalControlModels_s/robot_s.jl @@ -106,7 +106,7 @@ function OptimalControlProblems.robot_s( dϕ(tf) == dϕ_tf, (dϕ_tf) # aliases - I_θ = ((L - ρ(t))^3 + ρ(t)^3) * sin(ϕ(t))^2 + I_θ = ((L - ρ(t))^3 + ρ(t)^3) * (sin(ϕ(t))^2 + 1e-9) I_ϕ = (L - ρ(t))^3 + ρ(t)^3 # dynamics @@ -122,18 +122,29 @@ function OptimalControlProblems.robot_s( end # initial guess - tf = 1 - xinit = - t -> [ - ρ_t0, - 0, - 2π/3 * ((t - t0) / (tf - t0))^2, - 4π/3 * ((t - t0) / (tf - t0)), - ϕ_t0, - 0, + tf_guess = 9.1 + xinit = t -> begin + alpha = clamp((t - t0) / (tf_guess - t0), 0.0, 1.0) + + ρ_val = ρ_t0 + alpha * (ρ_tf - ρ_t0) + θ_val = θ_t0 + alpha * (θ_tf - θ_t0) + ϕ_val = ϕ_t0 + alpha * (ϕ_tf - ϕ_t0) + + if abs(ϕ_val) < 1e-6 + ϕ_val = 1e-6 + end + + return [ + ρ_val, + 0.0, + θ_val, + 0.0, + ϕ_val, + 0.0, ] - uinit = [0, 0, 0] - init = (state=xinit, control=uinit, variable=tf) + end + uinit = [0.0, 0.0, 0.0] + init = (state=xinit, control=uinit, variable=tf_guess) # discretise the optimal control problem docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/space_shuttle_s.jl b/ext/OptimalControlModels_s/space_shuttle_s.jl index f1469e0a..77b4df0e 100644 --- a/ext/OptimalControlModels_s/space_shuttle_s.jl +++ b/ext/OptimalControlModels_s/space_shuttle_s.jl @@ -56,8 +56,8 @@ function OptimalControlProblems.space_shuttle_s( # Δt_min = params[:Δt_min] Δt_max = params[:Δt_max] - tf_l = grid_size*Δt_min - tf_u = grid_size*Δt_max + tf_l = 1500.0 # + tf_u = 2500.0 # ## Initial conditions h_t0 = params[:h_t0] @@ -164,7 +164,7 @@ function OptimalControlProblems.space_shuttle_s( # initial guess: linear interpolation for h, v, gamma (NB. t0 = 0), constant for the rest # variable time step seems to be initialized at 1 in jump # note that ipopt will project the initial guess inside the bounds anyway. - tf_init = (tf_l+tf_u)/2 + tf_init = 2000 x_init = t -> [ h_t0 + (t - t0) / (tf_init - t0) * (h_tf - h_t0), diff --git a/ext/OptimalControlModels_s/steering_s.jl b/ext/OptimalControlModels_s/steering_s.jl index c78da787..047c2efe 100644 --- a/ext/OptimalControlModels_s/steering_s.jl +++ b/ext/OptimalControlModels_s/steering_s.jl @@ -85,7 +85,7 @@ function OptimalControlProblems.steering_s( end end xinit = t -> [gen_x0(t, i) for i in 1:4] - init = (state=xinit, control=0, variable=1) + init = (state=xinit, control=0, variable=0.6) # discretise the optimal control problem docp = direct_transcription( From 6375ddc9b6d97d5472e240e4db46821827f17ef9 Mon Sep 17 00:00:00 2001 From: Amiel Date: Sun, 1 Mar 2026 19:54:15 +0100 Subject: [PATCH 09/32] remove exclude problem --- src/OptimalControlProblems.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index 9729407d..0ea8a857 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -68,10 +68,6 @@ function make_list_of_problems() :dielectrophoretic_particle, :moonlander, :ducted_fan, - :insurance, - :robot, - :space_shuttle, - :steering, ] list_of_problems = setdiff(list_of_problems, problems_to_exclude) From c7ff0899138c809e8bc129789d936cc5877991e1 Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 22:55:06 +0100 Subject: [PATCH 10/32] fix moutain car 2000 --- ext/MetaData/mountain_car.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/MetaData/mountain_car.jl b/ext/MetaData/mountain_car.jl index cc96b7b4..edfe868c 100644 --- a/ext/MetaData/mountain_car.jl +++ b/ext/MetaData/mountain_car.jl @@ -2,7 +2,7 @@ mountain_car_meta = OrderedDict( :grid_size => 200, :parameters => ( t0 = 0.0, - tf_start = 50.0, + tf_start = 110.0, tf_min = 0.1, pos_t0 = -0.5, vel_t0 = 0.0, From 63e6a35d1686ffb3af9470c4d0c356448387a168 Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Wed, 4 Mar 2026 23:05:02 +0100 Subject: [PATCH 11/32] add cannonball problem --- ext/JuMPModels/cannonball.jl | 113 +++++++++++++++++++++ ext/MetaData/cannonball.jl | 20 ++++ ext/OptimalControlModels/cannonball.jl | 110 ++++++++++++++++++++ ext/OptimalControlModels_s/cannonball_s.jl | 110 ++++++++++++++++++++ 4 files changed, 353 insertions(+) create mode 100644 ext/JuMPModels/cannonball.jl create mode 100644 ext/MetaData/cannonball.jl create mode 100644 ext/OptimalControlModels/cannonball.jl create mode 100644 ext/OptimalControlModels_s/cannonball_s.jl diff --git a/ext/JuMPModels/cannonball.jl b/ext/JuMPModels/cannonball.jl new file mode 100644 index 00000000..e325f7e8 --- /dev/null +++ b/ext/JuMPModels/cannonball.jl @@ -0,0 +1,113 @@ +""" +$(TYPEDSIGNATURES) + +Constructs and returns a JuMP model for the **Multi-Phase Cannonball Problem**. +The goal is to maximise the total range of a cannonball by optimizing its radius, initial velocity, and launch angle, subject to a maximum muzzle energy constraint. + +# Arguments + +- `::JuMPBackend`: Specifies the backend for building the JuMP model. +- `grid_size::Int=100`: (Keyword) Number of discretisation steps for the time horizon. + +# Returns + +- `model::JuMP.Model`: A JuMP model representing the cannonball optimal control problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP + +julia> model = OptimalControlProblems.cannonball(JuMPBackend(); N=100) +``` +""" +function OptimalControlProblems.cannonball( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:cannonball), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:cannonball, parameters) + t0 = params[:t0] + ρ_metal = params[:ρ_metal] + Cd = params[:Cd] + KE_max = params[:KE_max] + g = params[:g] + ρ0 = params[:ρ0] + hr = params[:hr] + r_ball_l = params[:r_ball_l] + r_ball_u = params[:r_ball_u] + v0_l = params[:v0_l] + v0_u = params[:v0_u] + γ0_l = params[:γ0_l] + γ0_u = params[:γ0_u] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + + # model + model = JuMP.Model(args...; kwargs...) + + # metadata + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) + model[:state_components] = ["v", "γ", "h", "r"] + model[:costate_components] = ["∂v", "∂γ", "∂h", "∂r"] + model[:control_components] = String[] + model[:variable_components] = ["v0", "γ0", "r_ball", "tf"] + + N = grid_size + @expression(model, N_expr, N) + + @variables(model, begin + tf_l <= tf <= tf_u, (start = 10.0) + v0_l <= v0 <= v0_u, (start = 100.0) + γ0_l <= γ0 <= γ0_u, (start = π/4) + r_ball_l <= r_ball <= r_ball_u, (start = 0.05) + + v[0:N], (start = 100.0) + γ[0:N], (start = π/4) + h[0:N], (start = 1.0) + r[0:N], (start = 1.0) + end) + + # constraints + @constraints(model, begin + v[0] == v0 + γ[0] == γ0 + h[0] == 0 + r[0] == 0 + h[N] == 0 + end) + + # mass and KE constraint + @expression(model, m, (4/3) * π * ρ_metal * r_ball^3) + @constraint(model, 0.5 * m * v0^2 <= KE_max) + + @expressions(model, begin + Δt, (tf - t0) / N + S, π * r_ball^2 + + # dynamics at each node + ρ_at[i=0:N], ρ0 * exp(-h[i] / hr) + D_at[i=0:N], 0.5 * ρ_at[i] * v[i]^2 * S * Cd + + dv[i=0:N], -D_at[i]/m - g * sin(γ[i]) + dγ[i=0:N], -g * cos(γ[i]) / v[i] + dh[i=0:N], v[i] * sin(γ[i]) + dr[i=0:N], v[i] * cos(γ[i]) + end) + + @constraints(model, begin + ∂v[i=1:N], v[i] == v[i-1] + 0.5 * Δt * (dv[i] + dv[i-1]) + ∂γ[i=1:N], γ[i] == γ[i-1] + 0.5 * Δt * (dγ[i] + dγ[i-1]) + ∂h[i=1:N], h[i] == h[i-1] + 0.5 * Δt * (dh[i] + dh[i-1]) + ∂r[i=1:N], r[i] == r[i-1] + 0.5 * Δt * (dr[i] + dr[i-1]) + end) + + @objective(model, Max, r[N]) + + return model +end diff --git a/ext/MetaData/cannonball.jl b/ext/MetaData/cannonball.jl new file mode 100644 index 00000000..ce4157ff --- /dev/null +++ b/ext/MetaData/cannonball.jl @@ -0,0 +1,20 @@ +cannonball_meta = OrderedDict( + :grid_size => 100, + :parameters => ( + t0 = 0.0, + ρ_metal = 7870.0, + Cd = 0.5, + KE_max = 400000.0, + g = 9.80665, + ρ0 = 1.225, + hr = 8440.0, + r_ball_l = 0.01, + r_ball_u = 0.1, + v0_l = 0.1, + v0_u = 500.0, + γ0_l = 0.08726646259971647, # deg2rad(5.0) + γ0_u = 1.4835298641951802, # deg2rad(85.0) + tf_l = 0.1, + tf_u = 100.0, + ) +) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl new file mode 100644 index 00000000..fddd6e35 --- /dev/null +++ b/ext/OptimalControlModels/cannonball.jl @@ -0,0 +1,110 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Multi-Phase Cannonball problem. +The goal is to maximise the total range of a cannonball by optimizing its radius, initial velocity, and launch angle, subject to a maximum muzzle energy constraint. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the cannonball problem. +- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.cannonball(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.cannonball( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:cannonball), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:cannonball, parameters) + t0 = params[:t0] + ρ_metal = params[:ρ_metal] + Cd = params[:Cd] + KE_max = params[:KE_max] + g = params[:g] + ρ0 = params[:ρ0] + hr = params[:hr] + r_ball_l = params[:r_ball_l] + r_ball_u = params[:r_ball_u] + v0_l = params[:v0_l] + v0_u = params[:v0_u] + γ0_l = params[:γ0_l] + γ0_u = params[:γ0_u] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + + # model + ocp = @def begin + vars ∈ R³, variable + v0 = vars[1] + γ0 = vars[2] + r_ball = vars[3] + tf ∈ R, variable + t ∈ [t0, tf], time + x ∈ R⁴, state + + # variables bounds + v0_l ≤ v0 ≤ v0_u + γ0_l ≤ γ0 ≤ γ0_u + r_ball_l ≤ r_ball ≤ r_ball_u + tf_l ≤ tf ≤ tf_u + + # KE constraint: 0.5 * m * v0^2 ≤ KE_max + # m = (4/3) * π * ρ_metal * r_ball^3 + 0.5 * ((4/3) * π * ρ_metal * r_ball^3) * v0^2 ≤ KE_max + + # initial conditions + x(t0) == [v0, γ0, 0, 0] + + # final conditions + x(tf)[3] == 0 # h(tf) = 0 + + # dynamics + m = (4/3) * π * ρ_metal * r_ball^3 + S = π * r_ball^2 + + # x[1]=v, x[2]=γ, x[3]=h, x[4]=r + ẋ(t) == [ + -(0.5 * ρ0 * exp(-x[3](t) / hr) * x[1](t)^2 * S * Cd) / m - g * sin(x[2](t)), + -g * cos(x[2](t)) / x[1](t), + x[1](t) * sin(x[2](t)), + x[1](t) * cos(x[2](t)) + ] + + x(tf)[4] → max + end + + # initial guess + v0_init = 100.0 + γ0_init = π/4 + r_ball_init = 0.05 + tf_init = 10.0 + init = (state=[v0_init, γ0_init, 1.0, 1.0], variable=[v0_init, γ0_init, r_ball_init, tf_init]) + + # discretise + docp = direct_transcription( + ocp, + description...; + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl new file mode 100644 index 00000000..1b3b60f7 --- /dev/null +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -0,0 +1,110 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Multi-Phase Cannonball problem using scalar state components. +The goal is to maximise the total range of a cannonball by optimizing its radius, initial velocity, and launch angle, subject to a maximum muzzle energy constraint. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the cannonball problem. +- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.cannonball_s(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.cannonball_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:cannonball), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:cannonball, parameters) + t0 = params[:t0] + ρ_metal = params[:ρ_metal] + Cd = params[:Cd] + KE_max = params[:KE_max] + g = params[:g] + ρ0 = params[:ρ0] + hr = params[:hr] + r_ball_l = params[:r_ball_l] + r_ball_u = params[:r_ball_u] + v0_l = params[:v0_l] + v0_u = params[:v0_u] + γ0_l = params[:γ0_l] + γ0_u = params[:γ0_u] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + + # model + ocp = @def begin + v0 ∈ R, variable + γ0 ∈ R, variable + r_ball ∈ R, variable + tf ∈ R, variable + t ∈ [t0, tf], time + x = (v, γ, h, r) ∈ R⁴, state + + # variables bounds + v0_l ≤ v0 ≤ v0_u + γ0_l ≤ γ0 ≤ γ0_u + r_ball_l ≤ r_ball ≤ r_ball_u + tf_l ≤ tf ≤ tf_u + + # KE constraint + m = (4/3) * π * ρ_metal * r_ball^3 + 0.5 * m * v0^2 ≤ KE_max + + # initial conditions + v(t0) == v0 + γ(t0) == γ0 + h(t0) == 0 + r(t0) == 0 + + # final conditions + h(tf) == 0 + + # dynamics + S = π * r_ball^2 + ρ = ρ0 * exp(-h(t) / hr) + D = 0.5 * ρ * v(t)^2 * S * Cd + + ∂(v)(t) == -D/m - g * sin(γ(t)) + ∂(γ)(t) == -g * cos(γ(t)) / v(t) + ∂(h)(t) == v(t) * sin(γ(t)) + ∂(r)(t) == v(t) * cos(γ(t)) + + r(tf) → max + end + + # initial guess + v0_init = 100.0 + γ0_init = π/4 + r_ball_init = 0.05 + tf_init = 10.0 + init = (state=[v0_init, γ0_init, 1.0, 1.0], variable=[v0_init, γ0_init, r_ball_init, tf_init]) + + # discretise + docp = direct_transcription( + ocp, + description...; + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end From d98cd48df2fc9d5a9dcc728c4c2e26e4f7826450 Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 23:11:17 +0100 Subject: [PATCH 12/32] Add water rocket problem --- ext/JuMPModels/water_rocket.jl | 128 +++++++++++++++++++ ext/MetaData/water_rocket.jl | 23 ++++ ext/OptimalControlModels/water_rocket.jl | 116 +++++++++++++++++ ext/OptimalControlModels_s/water_rocket_s.jl | 105 +++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 ext/JuMPModels/water_rocket.jl create mode 100644 ext/MetaData/water_rocket.jl create mode 100644 ext/OptimalControlModels/water_rocket.jl create mode 100644 ext/OptimalControlModels_s/water_rocket_s.jl diff --git a/ext/JuMPModels/water_rocket.jl b/ext/JuMPModels/water_rocket.jl new file mode 100644 index 00000000..f42dab10 --- /dev/null +++ b/ext/JuMPModels/water_rocket.jl @@ -0,0 +1,128 @@ +""" +$(TYPEDSIGNATURES) + +Constructs and returns a JuMP model for the **Water Rocket Problem (Propelled Phase)**. +The model represents the dynamics of a water rocket during the ejection phase. + +# Arguments + +- `::JuMPBackend`: Specifies the backend for building the JuMP model. +- `grid_size::Int=100`: (Keyword) Number of discretisation steps for the time horizon. + +# Returns + +- `model::JuMP.Model`: A JuMP model representing the Water Rocket optimal control problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP + +julia> model = OptimalControlProblems.water_rocket(JuMPBackend(); N=100) +``` +""" +function OptimalControlProblems.water_rocket( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:water_rocket), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:water_rocket, parameters) + g = params[:g] + rho_w = params[:rho_w] + p_a = params[:p_a] + k = params[:k] + V_b = params[:V_b] + A_out = params[:A_out] + S = params[:S] + C_d = params[:C_d] + rho_a = params[:rho_a] + m_empty = params[:m_empty] + t0 = params[:t0] + r_t0 = params[:r_t0] + h_t0 = params[:h_t0] + v_t0 = params[:v_t0] + p_t0 = params[:p_t0] + tf_start = params[:tf_start] + Vw0_start = params[:Vw0_start] + gamma0_start = params[:gamma0_start] + + # model + model = JuMP.Model(args...; kwargs...) + + # metadata + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) + model[:state_components] = ["r", "h", "v", "γ", "p", "Vw"] + model[:costate_components] = ["∂r", "∂h", "∂v", "∂γ", "∂p", "∂Vw"] + model[:control_components] = ["u"] + model[:variable_components] = ["tf", "Vw0", "γ0"] + + # N = grid_size + @expression(model, N, grid_size) + + # variables + @variables( + model, + begin + r[0:N], (start = 0.0) + h[0:N] ≥ 0.0, (start = 0.0) + v[0:N], (start = 1.0) + γ[0:N], (start = 0.785) + p[0:N], (start = 7.0e5) + Vw[0:N], (start = 1.0e-3) + u[0:N], (start = 0.0) + 0.001 ≤ tf ≤ 1.0, (start = tf_start) + 0.1e-3 ≤ Vw0 ≤ 1.9e-3, (start = Vw0_start) + 0.1 ≤ γ0 ≤ 1.5, (start = gamma0_start) + end + ) + + # boundary constraints + @constraints( + model, + begin + r[0] == r_t0 + h[0] == h_t0 + v[0] == v_t0 + γ[0] == γ0 + p[0] == p_t0 + Vw[0] == Vw0 + Vw[N] == 0.0 + end + ) + + # dynamics + @expressions( + model, + begin + Δt, (tf - t0) / N + dr[i = 0:N], v[i] * cos(γ[i]) + dh[i = 0:N], v[i] * sin(γ[i]) + dVw[i = 0:N], -sqrt(2 * (p[i] - p_a) / rho_w) * A_out + dp[i = 0:N], k * p[i] * (-sqrt(2 * (p[i] - p_a) / rho_w) * A_out) / (V_b - Vw[i]) + dv[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v[i]^2 * S * C_d - (m_empty + rho_w * Vw[i]) * g * sin(γ[i])) / (m_empty + rho_w * Vw[i]) + dγ[i = 0:N], - (g * cos(γ[i])) / v[i] + end + ) + + @constraints( + model, + begin + ∂r[i = 1:N], r[i] == r[i - 1] + 0.5 * Δt * (dr[i] + dr[i - 1]) + ∂h[i = 1:N], h[i] == h[i - 1] + 0.5 * Δt * (dh[i] + dh[i - 1]) + ∂v[i = 1:N], v[i] == v[i - 1] + 0.5 * Δt * (dv[i] + dv[i - 1]) + ∂γ[i = 1:N], γ[i] == γ[i - 1] + 0.5 * Δt * (dγ[i] + dγ[i - 1]) + ∂p[i = 1:N], p[i] == p[i - 1] + 0.5 * Δt * (dp[i] + dp[i - 1]) + ∂Vw[i = 1:N], Vw[i] == Vw[i - 1] + 0.5 * Δt * (dVw[i] + dVw[i - 1]) + end + ) + + # objective + @objective(model, Max, h[N]) + + return model +end diff --git a/ext/MetaData/water_rocket.jl b/ext/MetaData/water_rocket.jl new file mode 100644 index 00000000..ca632c53 --- /dev/null +++ b/ext/MetaData/water_rocket.jl @@ -0,0 +1,23 @@ +water_rocket_meta = OrderedDict( + :grid_size => 100, + :parameters => ( + g = 9.80665, + rho_w = 1000.0, + p_a = 1.01325e5, + k = 1.2, + V_b = 2.0e-3, + A_out = pi * (0.013)^2 / 4, + S = pi * (0.106)^2 / 4, + C_d = 0.345, + rho_a = 1.225, + m_empty = 0.15, + t0 = 0.0, + r_t0 = 0.0, + h_t0 = 0.0, + v_t0 = 0.1, + p_t0 = 6.5e5 + 1.01325e5, + tf_start = 0.3, + Vw0_start = 1.0e-3, + gamma0_start = 0.785, # pi/4 approx + ), +) diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl new file mode 100644 index 00000000..8ac8e06e --- /dev/null +++ b/ext/OptimalControlModels/water_rocket.jl @@ -0,0 +1,116 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Water Rocket (Propelled Phase). +The goal is to drive the rocket during the water ejection phase. +The objective is to maximise the altitude at the end of the water ejection. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the Water Rocket problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.water_rocket(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.water_rocket( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:water_rocket), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:water_rocket, parameters) + g = params[:g] + rho_w = params[:rho_w] + p_a = params[:p_a] + k = params[:k] + V_b = params[:V_b] + A_out = params[:A_out] + S = params[:S] + C_d = params[:C_d] + rho_a = params[:rho_a] + m_empty = params[:m_empty] + t0 = params[:t0] + r_t0 = params[:r_t0] + h_t0 = params[:h_t0] + v_t0 = params[:v_t0] + p_t0 = params[:p_t0] + tf_start = params[:tf_start] + Vw0_start = params[:Vw0_start] + gamma0_start = params[:gamma0_start] + + # model + ocp = @def begin + tf ∈ R, variable + Vw0 ∈ R, variable + γ0 ∈ R, variable + t ∈ [t0, tf], time + x = (r, h, v, γ, p, Vw) ∈ R⁶, state + u ∈ R, control # dummy + + # constraints + 0.001 ≤ tf ≤ 1.0 + 0.1e-3 ≤ Vw0 ≤ 1.9e-3 + 0.1 ≤ γ0 ≤ 1.5 + Vw(tf) == 0.0 + h(t) ≥ 0.0 + + # initial conditions + r(t0) == r_t0 + h(t0) == h_t0 + v(t0) == v_t0 + γ(t0) == γ0 + p(t0) == p_t0 + Vw(t0) == Vw0 + + # dynamics + ṙ(t) == v(t) * cos(γ(t)) + ḣ(t) == v(t) * sin(γ(t)) + + # Intermediate calculations (implicitly in dynamics) + # v_out = sqrt(2*(p - p_a)/rho_w) + # Vẇ = -v_out * A_out + # ṗ = k * p * Vẇ / (V_b - Vw) + # T = 2 * A_out * (p - p_a) + # m = m_empty + rho_w * Vw + # D = 0.5 * rho_a * v^2 * S * C_d + # v̇ = (T - D - m * g * sin(γ)) / m + # γ̇ = - (g * cos(γ)) / v + + Vẇ(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out + ṗ(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) + v̇(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)) + γ̇(t) == - (g * cos(γ(t))) / v(t) + + # objective + h(tf) → max + end + + # initial guess + init = (state=[0.0, 0.0, 1.0, 0.785, 7.0e5, 1e-3], control=0.0, variable=[tf_start, Vw0_start, gamma0_start]) + + # discretise the optimal control problem + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl new file mode 100644 index 00000000..0af62672 --- /dev/null +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -0,0 +1,105 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the Water Rocket (Propelled Phase - Symbolic). +The goal is to drive the rocket during the water ejection phase. +The objective is to maximise the altitude at the end of the water ejection. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the Water Rocket problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.water_rocket_s(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.water_rocket_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:water_rocket), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:water_rocket, parameters) + g = params[:g] + rho_w = params[:rho_w] + p_a = params[:p_a] + k = params[:k] + V_b = params[:V_b] + A_out = params[:A_out] + S = params[:S] + C_d = params[:C_d] + rho_a = params[:rho_a] + m_empty = params[:m_empty] + t0 = params[:t0] + r_t0 = params[:r_t0] + h_t0 = params[:h_t0] + v_t0 = params[:v_t0] + p_t0 = params[:p_t0] + tf_start = params[:tf_start] + Vw0_start = params[:Vw0_start] + gamma0_start = params[:gamma0_start] + + # model + ocp = @def begin + tf ∈ R, variable + Vw0 ∈ R, variable + γ0 ∈ R, variable + t ∈ [t0, tf], time + x = (r, h, v, γ, p, Vw) ∈ R⁶, state + u ∈ R, control # dummy + + # constraints + 0.001 ≤ tf ≤ 1.0 + 0.1e-3 ≤ Vw0 ≤ 1.9e-3 + 0.1 ≤ γ0 ≤ 1.5 + Vw(tf) == 0.0 + h(t) ≥ 0.0 + + # initial conditions + r(t0) == r_t0 + h(t0) == h_t0 + v(t0) == v_t0 + γ(t0) == γ0 + p(t0) == p_t0 + Vw(t0) == Vw0 + + # dynamics + ∂(r)(t) == v(t) * cos(γ(t)) + ∂(h)(t) == v(t) * sin(γ(t)) + ∂(Vw)(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out + ∂(p)(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) + ∂(v)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)) + ∂(γ)(t) == - (g * cos(γ(t))) / v(t) + + # objective + h(tf) → max + end + + # initial guess + init = (state=[0.0, 0.0, 1.0, 0.785, 7.0e5, 1e-3], control=0.0, variable=[tf_start, Vw0_start, gamma0_start]) + + # discretise the optimal control problem + docp = direct_transcription( + ocp, + description...; + lagrange_to_mayer=false, + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end From 5f9ddd099226a49b279fb687b7934c6109e906d6 Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 23:22:30 +0100 Subject: [PATCH 13/32] fix bug --- ext/OptimalControlModels/water_rocket.jl | 5 +++-- ext/OptimalControlModels_s/cannonball_s.jl | 2 +- ext/OptimalControlModels_s/water_rocket_s.jl | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index 8ac8e06e..3d9f15df 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,9 +53,10 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin + vars ∈ R², variable + Vw0 = vars[1] + γ0 = vars[2] tf ∈ R, variable - Vw0 ∈ R, variable - γ0 ∈ R, variable t ∈ [t0, tf], time x = (r, h, v, γ, p, Vw) ∈ R⁶, state u ∈ R, control # dummy diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index 1b3b60f7..0852ff87 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -19,7 +19,7 @@ The goal is to maximise the total range of a cannonball by optimizing its radius ```julia-repl julia> using OptimalControlProblems -julia> docp = OptimalControlProblems.cannonball_s(OptimalControlBackend(); N=100); +julia> docp = OptimalControlProblems.cannonball(OptimalControlBackend(); N=100); ``` """ function OptimalControlProblems.cannonball_s( diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index 0af62672..6efefd84 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -53,9 +53,10 @@ function OptimalControlProblems.water_rocket_s( # model ocp = @def begin + vars ∈ R², variable + Vw0 = vars[1] + γ0 = vars[2] tf ∈ R, variable - Vw0 ∈ R, variable - γ0 ∈ R, variable t ∈ [t0, tf], time x = (r, h, v, γ, p, Vw) ∈ R⁶, state u ∈ R, control # dummy From 97276e3294ada1128f8d5e0d3af4a5ce05d51eb5 Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 23:31:42 +0100 Subject: [PATCH 14/32] fix bug 2 --- ext/OptimalControlModels/cannonball.jl | 28 ++++++++---------- ext/OptimalControlModels/water_rocket.jl | 31 +++++++------------- ext/OptimalControlModels_s/cannonball_s.jl | 5 +--- ext/OptimalControlModels_s/water_rocket_s.jl | 5 +--- 4 files changed, 24 insertions(+), 45 deletions(-) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index fddd6e35..3c2071ad 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -50,43 +50,39 @@ function OptimalControlProblems.cannonball( # model ocp = @def begin - vars ∈ R³, variable - v0 = vars[1] - γ0 = vars[2] - r_ball = vars[3] - tf ∈ R, variable + vvar = (v0, γ0, rball, tf) ∈ R⁴, variable t ∈ [t0, tf], time - x ∈ R⁴, state + x = (v, γ, h, r) ∈ R⁴, state # variables bounds v0_l ≤ v0 ≤ v0_u γ0_l ≤ γ0 ≤ γ0_u - r_ball_l ≤ r_ball ≤ r_ball_u + r_ball_l ≤ rball ≤ r_ball_u tf_l ≤ tf ≤ tf_u # KE constraint: 0.5 * m * v0^2 ≤ KE_max # m = (4/3) * π * ρ_metal * r_ball^3 - 0.5 * ((4/3) * π * ρ_metal * r_ball^3) * v0^2 ≤ KE_max + 0.5 * ((4/3) * π * ρ_metal * rball^3) * v0^2 ≤ KE_max # initial conditions x(t0) == [v0, γ0, 0, 0] # final conditions - x(tf)[3] == 0 # h(tf) = 0 + h(tf) == 0 # dynamics - m = (4/3) * π * ρ_metal * r_ball^3 - S = π * r_ball^2 + m = (4/3) * π * ρ_metal * rball^3 + S = π * rball^2 # x[1]=v, x[2]=γ, x[3]=h, x[4]=r ẋ(t) == [ - -(0.5 * ρ0 * exp(-x[3](t) / hr) * x[1](t)^2 * S * Cd) / m - g * sin(x[2](t)), - -g * cos(x[2](t)) / x[1](t), - x[1](t) * sin(x[2](t)), - x[1](t) * cos(x[2](t)) + -(0.5 * ρ0 * exp(-h(t) / hr) * v(t)^2 * S * Cd) / m - g * sin(γ(t)), + -g * cos(γ(t)) / v(t), + v(t) * sin(γ(t)), + v(t) * cos(γ(t)) ] - x(tf)[4] → max + r(tf) → max end # initial guess diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index 3d9f15df..e575c5bd 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,10 +53,7 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - vars ∈ R², variable - Vw0 = vars[1] - γ0 = vars[2] - tf ∈ R, variable + v_opt = (tf, Vw0, γ0) ∈ R³, variable t ∈ [t0, tf], time x = (r, h, v, γ, p, Vw) ∈ R⁶, state u ∈ R, control # dummy @@ -77,23 +74,15 @@ function OptimalControlProblems.water_rocket( Vw(t0) == Vw0 # dynamics - ṙ(t) == v(t) * cos(γ(t)) - ḣ(t) == v(t) * sin(γ(t)) - - # Intermediate calculations (implicitly in dynamics) - # v_out = sqrt(2*(p - p_a)/rho_w) - # Vẇ = -v_out * A_out - # ṗ = k * p * Vẇ / (V_b - Vw) - # T = 2 * A_out * (p - p_a) - # m = m_empty + rho_w * Vw - # D = 0.5 * rho_a * v^2 * S * C_d - # v̇ = (T - D - m * g * sin(γ)) / m - # γ̇ = - (g * cos(γ)) / v - - Vẇ(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out - ṗ(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) - v̇(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)) - γ̇(t) == - (g * cos(γ(t))) / v(t) + # x = (r, h, v, γ, p, Vw) + ẋ(t) == [ + v(t) * cos(γ(t)), + v(t) * sin(γ(t)), + (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)), + - (g * cos(γ(t))) / v(t), + k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)), + -sqrt(2 * (p(t) - p_a) / rho_w) * A_out + ] # objective h(tf) → max diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index 0852ff87..e878f3ed 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -50,10 +50,7 @@ function OptimalControlProblems.cannonball_s( # model ocp = @def begin - v0 ∈ R, variable - γ0 ∈ R, variable - r_ball ∈ R, variable - tf ∈ R, variable + vvar = (v0, γ0, r_ball, tf) ∈ R⁴, variable t ∈ [t0, tf], time x = (v, γ, h, r) ∈ R⁴, state diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index 6efefd84..adb3ab6f 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -53,10 +53,7 @@ function OptimalControlProblems.water_rocket_s( # model ocp = @def begin - vars ∈ R², variable - Vw0 = vars[1] - γ0 = vars[2] - tf ∈ R, variable + v_opt = (tf, Vw0, γ0) ∈ R³, variable t ∈ [t0, tf], time x = (r, h, v, γ, p, Vw) ∈ R⁶, state u ∈ R, control # dummy From 59a9013ea98db5f12c5b9db013cb801b36e65254 Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Wed, 4 Mar 2026 23:39:55 +0100 Subject: [PATCH 15/32] add ssto earth launch problem --- ext/JuMPModels/ssto_earth.jl | 115 +++++++++++++++++++++ ext/MetaData/ssto_earth.jl | 21 ++++ ext/OptimalControlModels/ssto_earth.jl | 111 ++++++++++++++++++++ ext/OptimalControlModels_s/ssto_earth_s.jl | 106 +++++++++++++++++++ 4 files changed, 353 insertions(+) create mode 100644 ext/JuMPModels/ssto_earth.jl create mode 100644 ext/MetaData/ssto_earth.jl create mode 100644 ext/OptimalControlModels/ssto_earth.jl create mode 100644 ext/OptimalControlModels_s/ssto_earth_s.jl diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl new file mode 100644 index 00000000..621f38af --- /dev/null +++ b/ext/JuMPModels/ssto_earth.jl @@ -0,0 +1,115 @@ +""" +$(TYPEDSIGNATURES) + +Constructs and returns a JuMP model for the **SSTO Earth Launch Problem**. +The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. + +# Arguments + +- `::JuMPBackend`: Specifies the backend for building the JuMP model. +- `grid_size::Int=100`: (Keyword) Number of discretisation steps for the time horizon. + +# Returns + +- `model::JuMP.Model`: A JuMP model representing the SSTO Earth optimal control problem. + +# Example + +```julia-repl +julia> using OptimalControlProblems +julia> using JuMP + +julia> model = OptimalControlProblems.ssto_earth(JuMPBackend(); N=100) +``` +""" +function OptimalControlProblems.ssto_earth( + ::JuMPBackend, + args...; + grid_size::Int=grid_size_data(:ssto_earth), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:ssto_earth, parameters) + t0 = params[:t0] + g = params[:g] + rho_ref = params[:rho_ref] + h_scale = params[:h_scale] + Cd = params[:Cd] + S = params[:S] + Thrust = params[:Thrust] + Isp = params[:Isp] + m0 = params[:m0] + y_tf = params[:y_tf] + vx_tf = params[:vx_tf] + vy_tf = params[:vy_tf] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + theta_l = params[:theta_l] + theta_u = params[:theta_u] + + # model + model = JuMP.Model(args...; kwargs...) + + # metadata + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) + model[:state_components] = ["px", "py", "vx", "vy", "m"] + model[:costate_components] = ["∂px", "∂py", "∂vx", "∂vy", "∂m"] + model[:control_components] = ["θ"] + model[:variable_components] = ["tf"] + + N = grid_size + @expression(model, N_expr, N) + + @variables(model, begin + tf_l <= tf <= tf_u, (start = 150.0) + theta_l <= θ[0:N] <= theta_u, (start = 0.5) + + px[0:N], (start = 0.0) + py[0:N], (start = 0.0) + vx[0:N], (start = 0.0) + vy[0:N], (start = 0.0) + m[0:N], (start = m0) + end) + + # constraints + @constraints(model, begin + px[0] == 0.0 + py[0] == 0.0 + vx[0] == 0.0 + vy[0] == 0.0 + m[0] == m0 + + py[N] == y_tf + vx[N] == vx_tf + vy[N] == vy_tf + end) + + @expressions(model, begin + Δt, (tf - t0) / N + + # dynamics at each node + v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2 + 1e-9) # add epsilon to avoid sqrt(0) + rho_at[i=0:N], rho_ref * exp(-py[i] / h_scale) + D_at[i=0:N], 0.5 * rho_at[i] * v_at[i]^2 * Cd * S + + dpx[i=0:N], vx[i] + dpy[i=0:N], vy[i] + dvx[i=0:N], (Thrust * cos(θ[i]) - D_at[i] * vx[i] / v_at[i]) / m[i] + dvy[i=0:N], (Thrust * sin(θ[i]) - D_at[i] * vy[i] / v_at[i]) / m[i] - g + dm[i=0:N], -Thrust / (g * Isp) + end) + + @constraints(model, begin + ∂px[i=1:N], px[i] == px[i-1] + 0.5 * Δt * (dpx[i] + dpx[i-1]) + ∂py[i=1:N], py[i] == py[i-1] + 0.5 * Δt * (dpy[i] + dpy[i-1]) + ∂vx[i=1:N], vx[i] == vx[i-1] + 0.5 * Δt * (dvx[i] + dvx[i-1]) + ∂vy[i=1:N], vy[i] == vy[i-1] + 0.5 * Δt * (dvy[i] + dvy[i-1]) + ∂m[i=1:N], m[i] == m[i-1] + 0.5 * Δt * (dm[i] + dm[i-1]) + end) + + @objective(model, Min, tf) + + return model +end diff --git a/ext/MetaData/ssto_earth.jl b/ext/MetaData/ssto_earth.jl new file mode 100644 index 00000000..0bc0bfef --- /dev/null +++ b/ext/MetaData/ssto_earth.jl @@ -0,0 +1,21 @@ +ssto_earth_meta = OrderedDict( + :grid_size => 100, + :parameters => ( + t0 = 0.0, + g = 9.80665, + rho_ref = 1.225, + h_scale = 8440.0, + Cd = 0.5, + S = 7.069, + Thrust = 2100000.0, + Isp = 265.2, + m0 = 117000.0, + y_tf = 185000.0, + vx_tf = 7796.6961, + vy_tf = 0.0, + tf_l = 10.0, + tf_u = 1000.0, + theta_l = -1.5707963267948966, # -pi/2 + theta_u = 1.5707963267948966, # pi/2 + ) +) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl new file mode 100644 index 00000000..2013c5aa --- /dev/null +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -0,0 +1,111 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the SSTO Earth Launch problem. +The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the SSTO Earth problem. +- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.ssto_earth(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.ssto_earth( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:ssto_earth), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:ssto_earth, parameters) + t0 = params[:t0] + g = params[:g] + rho_ref = params[:rho_ref] + h_scale = params[:h_scale] + Cd = params[:Cd] + S = params[:S] + Thrust = params[:Thrust] + Isp = params[:Isp] + m0 = params[:m0] + y_tf = params[:y_tf] + vx_tf = params[:vx_tf] + vy_tf = params[:vy_tf] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + theta_l = params[:theta_l] + theta_u = params[:theta_u] + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x ∈ R⁵, state + θ ∈ R, control + + # tf bounds + tf_l ≤ tf ≤ tf_u + # control bounds + theta_l ≤ θ(t) ≤ theta_u + + # initial conditions + x(t0) == [0.0, 0.0, 0.0, 0.0, m0] + + # final conditions + x(tf)[2] == y_tf + x(tf)[3] == vx_tf + x(tf)[4] == vy_tf + + # dynamics + # x[1]=x, x[2]=y, x[3]=vx, x[4]=vy, x[5]=m + v = sqrt(x[3](t)^2 + x[4](t)^2) + rho = rho_ref * exp(-x[2](t) / h_scale) + D = 0.5 * rho * v^2 * Cd * S + + # Avoid division by zero for D_cos_gamma and D_sin_gamma + # gamma is the angle of the velocity vector + # cos_gamma = vx / v, sin_gamma = vy / v + # if v is small, we can approximate D_cos_gamma and D_sin_gamma + + ẋ(t) == [ + x[3](t), + x[4](t), + (Thrust * cos(θ(t)) - (v > 1e-6 ? D * x[3](t) / v : 0.0)) / x[5](t), + (Thrust * sin(θ(t)) - (v > 1e-6 ? D * x[4](t) / v : 0.0)) / x[5](t) - g, + -Thrust / (g * Isp) + ] + + tf → min + end + + # initial guess + tf_init = 150.0 + # x(t) = [x, y, vx, vy, m] + x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] + init = (state=x_init, control=[0.5], variable=[tf_init]) + + # discretise + docp = direct_transcription( + ocp, + description...; + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl new file mode 100644 index 00000000..46cd5691 --- /dev/null +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -0,0 +1,106 @@ +""" +$(TYPEDSIGNATURES) + +Constructs an **OptimalControl problem** for the SSTO Earth Launch problem using scalar state components. +The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. + +# Arguments + +- `::OptimalControlBackend`: Placeholder type specifying the OptimalControl backend or solver interface. +- `grid_size::Int=100`: (Keyword) Number of discretisation points for the direct transcription grid. + +# Returns + +- `docp`: The direct optimal control problem object representing the SSTO Earth problem. +- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. + +# Example + +```julia-repl +julia> using OptimalControlProblems + +julia> docp = OptimalControlProblems.ssto_earth_s(OptimalControlBackend(); N=100); +``` +""" +function OptimalControlProblems.ssto_earth_s( + ::OptimalControlBackend, + description::Symbol...; + grid_size::Int=grid_size_data(:ssto_earth), + parameters::Union{Nothing,NamedTuple}=nothing, + kwargs..., +) + + # parameters + params = parameters_data(:ssto_earth, parameters) + t0 = params[:t0] + g = params[:g] + rho_ref = params[:rho_ref] + h_scale = params[:h_scale] + Cd = params[:Cd] + S = params[:S] + Thrust = params[:Thrust] + Isp = params[:Isp] + m0 = params[:m0] + y_tf = params[:y_tf] + vx_tf = params[:vx_tf] + vy_tf = params[:vy_tf] + tf_l = params[:tf_l] + tf_u = params[:tf_u] + theta_l = params[:theta_l] + theta_u = params[:theta_u] + + # model + ocp = @def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (px, py, vx, vy, m) ∈ R⁵, state + θ ∈ R, control + + # tf bounds + tf_l ≤ tf ≤ tf_u + # control bounds + theta_l ≤ θ(t) ≤ theta_u + + # initial conditions + px(t0) == 0 + py(t0) == 0 + vx(t0) == 0 + vy(t0) == 0 + m(t0) == m0 + + # final conditions + py(tf) == y_tf + vx(tf) == vx_tf + vy(tf) == vy_tf + + # dynamics + v = sqrt(vx(t)^2 + vy(t)^2) + rho = rho_ref * exp(-py(t) / h_scale) + D = 0.5 * rho * v^2 * Cd * S + + ∂(px)(t) == vx(t) + ∂(py)(t) == vy(t) + ∂(vx)(t) == (Thrust * cos(θ(t)) - (v > 1e-6 ? D * vx(t) / v : 0.0)) / m(t) + ∂(vy)(t) == (Thrust * sin(θ(t)) - (v > 1e-6 ? D * vy(t) / v : 0.0)) / m(t) - g + ∂(m)(t) == -Thrust / (g * Isp) + + tf → min + end + + # initial guess + tf_init = 150.0 + x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] + init = (state=x_init, control=[0.5], variable=[tf_init]) + + # discretise + docp = direct_transcription( + ocp, + description...; + init=init, + grid_size=grid_size, + disc_method=:trapeze, + kwargs..., + ) + + return docp +end From 89640fa2f0480fb89638efe0692c03f2a70c5116 Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 23:40:16 +0100 Subject: [PATCH 16/32] buf fix 3 --- ext/OptimalControlModels/cannonball.jl | 38 ++++++++++---------- ext/OptimalControlModels/water_rocket.jl | 20 +++++------ ext/OptimalControlModels_s/cannonball_s.jl | 31 ++++++++-------- ext/OptimalControlModels_s/water_rocket_s.jl | 16 ++++----- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index 3c2071ad..ef41d41c 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -33,58 +33,60 @@ function OptimalControlProblems.cannonball( # parameters params = parameters_data(:cannonball, parameters) t0 = params[:t0] - ρ_metal = params[:ρ_metal] + rho_metal = params[:ρ_metal] Cd = params[:Cd] KE_max = params[:KE_max] g = params[:g] - ρ0 = params[:ρ0] + rho0 = params[:ρ0] hr = params[:hr] r_ball_l = params[:r_ball_l] r_ball_u = params[:r_ball_u] v0_l = params[:v0_l] v0_u = params[:v0_u] - γ0_l = params[:γ0_l] - γ0_u = params[:γ0_u] + gamma0_l = params[:γ0_l] + gamma0_u = params[:γ0_u] tf_l = params[:tf_l] tf_u = params[:tf_u] # model ocp = @def begin - vvar = (v0, γ0, rball, tf) ∈ R⁴, variable + v = (v0, gamma0, rball, tf) ∈ R⁴, variable t ∈ [t0, tf], time - x = (v, γ, h, r) ∈ R⁴, state + x = (v_mag, gamma, h, r) ∈ R⁴, state + u ∈ R, control # dummy - # variables bounds + # constraints v0_l ≤ v0 ≤ v0_u - γ0_l ≤ γ0 ≤ γ0_u + gamma0_l ≤ gamma0 ≤ gamma0_u r_ball_l ≤ rball ≤ r_ball_u tf_l ≤ tf ≤ tf_u # KE constraint: 0.5 * m * v0^2 ≤ KE_max - # m = (4/3) * π * ρ_metal * r_ball^3 - 0.5 * ((4/3) * π * ρ_metal * rball^3) * v0^2 ≤ KE_max + # m = (4/3) * π * rho_metal * rball^3 + 0.5 * ((4/3) * π * rho_metal * rball^3) * v0^2 ≤ KE_max # initial conditions - x(t0) == [v0, γ0, 0, 0] + x(t0) == [v0, gamma0, 0, 0] # final conditions h(tf) == 0 # dynamics - m = (4/3) * π * ρ_metal * rball^3 + m = (4/3) * π * rho_metal * rball^3 S = π * rball^2 - - # x[1]=v, x[2]=γ, x[3]=h, x[4]=r + + # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r ẋ(t) == [ - -(0.5 * ρ0 * exp(-h(t) / hr) * v(t)^2 * S * Cd) / m - g * sin(γ(t)), - -g * cos(γ(t)) / v(t), - v(t) * sin(γ(t)), - v(t) * cos(γ(t)) + -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * S * Cd) / m - g * sin(gamma(t)), + -g * cos(gamma(t)) / v_mag(t), + v_mag(t) * sin(gamma(t)), + v_mag(t) * cos(gamma(t)) ] r(tf) → max end + # initial guess v0_init = 100.0 γ0_init = π/4 diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index e575c5bd..1a6eae26 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,33 +53,33 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - v_opt = (tf, Vw0, γ0) ∈ R³, variable + v = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time - x = (r, h, v, γ, p, Vw) ∈ R⁶, state + x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state u ∈ R, control # dummy # constraints 0.001 ≤ tf ≤ 1.0 0.1e-3 ≤ Vw0 ≤ 1.9e-3 - 0.1 ≤ γ0 ≤ 1.5 + 0.1 ≤ gamma0 ≤ 1.5 Vw(tf) == 0.0 h(t) ≥ 0.0 # initial conditions r(t0) == r_t0 h(t0) == h_t0 - v(t0) == v_t0 - γ(t0) == γ0 + v_mag(t0) == v_t0 + gamma(t0) == gamma0 p(t0) == p_t0 Vw(t0) == Vw0 # dynamics - # x = (r, h, v, γ, p, Vw) + # x = (r, h, v_mag, gamma, p, Vw) ẋ(t) == [ - v(t) * cos(γ(t)), - v(t) * sin(γ(t)), - (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)), - - (g * cos(γ(t))) / v(t), + v_mag(t) * cos(gamma(t)), + v_mag(t) * sin(gamma(t)), + (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)), + - (g * cos(gamma(t))) / v_mag(t), k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)), -sqrt(2 * (p(t) - p_a) / rho_w) * A_out ] diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index e878f3ed..3e02140e 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -33,40 +33,41 @@ function OptimalControlProblems.cannonball_s( # parameters params = parameters_data(:cannonball, parameters) t0 = params[:t0] - ρ_metal = params[:ρ_metal] + rho_metal = params[:ρ_metal] Cd = params[:Cd] KE_max = params[:KE_max] g = params[:g] - ρ0 = params[:ρ0] + rho0 = params[:ρ0] hr = params[:hr] r_ball_l = params[:r_ball_l] r_ball_u = params[:r_ball_u] v0_l = params[:v0_l] v0_u = params[:v0_u] - γ0_l = params[:γ0_l] - γ0_u = params[:γ0_u] + gamma0_l = params[:γ0_l] + gamma0_u = params[:γ0_u] tf_l = params[:tf_l] tf_u = params[:tf_u] # model ocp = @def begin - vvar = (v0, γ0, r_ball, tf) ∈ R⁴, variable + vvar = (v0, gamma0, r_ball, tf) ∈ R⁴, variable t ∈ [t0, tf], time - x = (v, γ, h, r) ∈ R⁴, state + x = (v, gamma, h, r) ∈ R⁴, state + u ∈ R, control # dummy # variables bounds v0_l ≤ v0 ≤ v0_u - γ0_l ≤ γ0 ≤ γ0_u + gamma0_l ≤ gamma0 ≤ gamma0_u r_ball_l ≤ r_ball ≤ r_ball_u tf_l ≤ tf ≤ tf_u # KE constraint - m = (4/3) * π * ρ_metal * r_ball^3 + m = (4/3) * π * rho_metal * r_ball^3 0.5 * m * v0^2 ≤ KE_max # initial conditions v(t0) == v0 - γ(t0) == γ0 + gamma(t0) == gamma0 h(t0) == 0 r(t0) == 0 @@ -75,13 +76,13 @@ function OptimalControlProblems.cannonball_s( # dynamics S = π * r_ball^2 - ρ = ρ0 * exp(-h(t) / hr) - D = 0.5 * ρ * v(t)^2 * S * Cd + rho = rho0 * exp(-h(t) / hr) + D = 0.5 * rho * v(t)^2 * S * Cd - ∂(v)(t) == -D/m - g * sin(γ(t)) - ∂(γ)(t) == -g * cos(γ(t)) / v(t) - ∂(h)(t) == v(t) * sin(γ(t)) - ∂(r)(t) == v(t) * cos(γ(t)) + ∂(v)(t) == -D/m - g * sin(gamma(t)) + ∂(gamma)(t) == -g * cos(gamma(t)) / v(t) + ∂(h)(t) == v(t) * sin(gamma(t)) + ∂(r)(t) == v(t) * cos(gamma(t)) r(tf) → max end diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index adb3ab6f..c2b03627 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -53,15 +53,15 @@ function OptimalControlProblems.water_rocket_s( # model ocp = @def begin - v_opt = (tf, Vw0, γ0) ∈ R³, variable + v_opt = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time - x = (r, h, v, γ, p, Vw) ∈ R⁶, state + x = (r, h, v, gamma, p, Vw) ∈ R⁶, state u ∈ R, control # dummy # constraints 0.001 ≤ tf ≤ 1.0 0.1e-3 ≤ Vw0 ≤ 1.9e-3 - 0.1 ≤ γ0 ≤ 1.5 + 0.1 ≤ gamma0 ≤ 1.5 Vw(tf) == 0.0 h(t) ≥ 0.0 @@ -69,17 +69,17 @@ function OptimalControlProblems.water_rocket_s( r(t0) == r_t0 h(t0) == h_t0 v(t0) == v_t0 - γ(t0) == γ0 + gamma(t0) == gamma0 p(t0) == p_t0 Vw(t0) == Vw0 # dynamics - ∂(r)(t) == v(t) * cos(γ(t)) - ∂(h)(t) == v(t) * sin(γ(t)) + ∂(r)(t) == v(t) * cos(gamma(t)) + ∂(h)(t) == v(t) * sin(gamma(t)) ∂(Vw)(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out ∂(p)(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) - ∂(v)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(γ(t))) / (m_empty + rho_w * Vw(t)) - ∂(γ)(t) == - (g * cos(γ(t))) / v(t) + ∂(v)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) + ∂(gamma)(t) == - (g * cos(gamma(t))) / v(t) # objective h(tf) → max From 4fc2cd0f5ab9979c80f40e9f42148101b1d1a47c Mon Sep 17 00:00:00 2001 From: Amiel Date: Wed, 4 Mar 2026 23:51:36 +0100 Subject: [PATCH 17/32] fix --- ext/JuMPModels/cannonball.jl | 132 ++++++++++--------- ext/JuMPModels/ssto_earth.jl | 10 +- ext/JuMPModels/water_rocket.jl | 28 ++-- ext/MetaData/cannonball.jl | 8 +- ext/OptimalControlModels/cannonball.jl | 30 +++-- ext/OptimalControlModels/ssto_earth.jl | 13 +- ext/OptimalControlModels/water_rocket.jl | 2 +- ext/OptimalControlModels_s/cannonball_s.jl | 40 +++--- ext/OptimalControlModels_s/mountain_car_s.jl | 2 +- ext/OptimalControlModels_s/ssto_earth_s.jl | 10 +- ext/OptimalControlModels_s/water_rocket_s.jl | 16 +-- 11 files changed, 153 insertions(+), 138 deletions(-) diff --git a/ext/JuMPModels/cannonball.jl b/ext/JuMPModels/cannonball.jl index e325f7e8..64b7647d 100644 --- a/ext/JuMPModels/cannonball.jl +++ b/ext/JuMPModels/cannonball.jl @@ -2,7 +2,7 @@ $(TYPEDSIGNATURES) Constructs and returns a JuMP model for the **Multi-Phase Cannonball Problem**. -The goal is to maximise the total range of a cannonball by optimizing its radius, initial velocity, and launch angle, subject to a maximum muzzle energy constraint. +The model represents the flight of a cannonball with a maximum muzzle energy constraint. # Arguments @@ -33,18 +33,18 @@ function OptimalControlProblems.cannonball( # parameters params = parameters_data(:cannonball, parameters) t0 = params[:t0] - ρ_metal = params[:ρ_metal] + rho_metal = params[:rho_metal] Cd = params[:Cd] KE_max = params[:KE_max] g = params[:g] - ρ0 = params[:ρ0] + rho0 = params[:rho0] hr = params[:hr] r_ball_l = params[:r_ball_l] r_ball_u = params[:r_ball_u] v0_l = params[:v0_l] v0_u = params[:v0_u] - γ0_l = params[:γ0_l] - γ0_u = params[:γ0_u] + gamma0_l = params[:gamma0_l] + gamma0_u = params[:gamma0_u] tf_l = params[:tf_l] tf_u = params[:tf_u] @@ -52,61 +52,73 @@ function OptimalControlProblems.cannonball( model = JuMP.Model(args...; kwargs...) # metadata - model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) - model[:state_components] = ["v", "γ", "h", "r"] - model[:costate_components] = ["∂v", "∂γ", "∂h", "∂r"] - model[:control_components] = String[] - model[:variable_components] = ["v0", "γ0", "r_ball", "tf"] - - N = grid_size - @expression(model, N_expr, N) - - @variables(model, begin - tf_l <= tf <= tf_u, (start = 10.0) - v0_l <= v0 <= v0_u, (start = 100.0) - γ0_l <= γ0 <= γ0_u, (start = π/4) - r_ball_l <= r_ball <= r_ball_u, (start = 0.05) - - v[0:N], (start = 100.0) - γ[0:N], (start = π/4) - h[0:N], (start = 1.0) - r[0:N], (start = 1.0) - end) - - # constraints - @constraints(model, begin - v[0] == v0 - γ[0] == γ0 - h[0] == 0 - r[0] == 0 - h[N] == 0 - end) - - # mass and KE constraint - @expression(model, m, (4/3) * π * ρ_metal * r_ball^3) - @constraint(model, 0.5 * m * v0^2 <= KE_max) - - @expressions(model, begin - Δt, (tf - t0) / N - S, π * r_ball^2 - - # dynamics at each node - ρ_at[i=0:N], ρ0 * exp(-h[i] / hr) - D_at[i=0:N], 0.5 * ρ_at[i] * v[i]^2 * S * Cd - - dv[i=0:N], -D_at[i]/m - g * sin(γ[i]) - dγ[i=0:N], -g * cos(γ[i]) / v[i] - dh[i=0:N], v[i] * sin(γ[i]) - dr[i=0:N], v[i] * cos(γ[i]) - end) - - @constraints(model, begin - ∂v[i=1:N], v[i] == v[i-1] + 0.5 * Δt * (dv[i] + dv[i-1]) - ∂γ[i=1:N], γ[i] == γ[i-1] + 0.5 * Δt * (dγ[i] + dγ[i-1]) - ∂h[i=1:N], h[i] == h[i-1] + 0.5 * Δt * (dh[i] + dh[i-1]) - ∂r[i=1:N], r[i] == r[i-1] + 0.5 * Δt * (dr[i] + dr[i-1]) - end) - + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) + model[:state_components] = ["v_mag", "gamma", "h", "r"] + model[:costate_components] = ["∂v_mag", "∂gamma", "∂h", "∂r"] + model[:control_components] = ["u"] + model[:variable_components] = ["v0", "gamma0", "rball", "tf"] + + # N = grid_size + @expression(model, N, grid_size) + + # variables + @variables( + model, + begin + v_mag[0:N] ≥ 0.0, (start = 100.0) + gamma[0:N], (start = 0.785) + h[0:N] ≥ 0.0, (start = 1.0) + r[0:N], (start = 1.0) + u[0:N], (start = 0.0) + v0_l ≤ v0 ≤ v0_u, (start = 100.0) + gamma0_l ≤ gamma0 ≤ gamma0_u, (start = 0.785) + r_ball_l ≤ rball ≤ r_ball_u, (start = 0.05) + tf_l ≤ tf ≤ tf_u, (start = 10.0) + end + ) + + # boundary constraints + @constraints( + model, + begin + v_mag[0] == v0 + gamma[0] == gamma0 + h[0] == 0 + r[0] == 0 + h[N] == 0 + end + ) + + # design constraints + @constraint(model, 0.5 * ((4/3) * π * rho_metal * rball^3) * v0^2 ≤ KE_max) + + # dynamics + @expressions( + model, + begin + Δt, (tf - t0) / N + m_eff, (4/3) * π * rho_metal * rball^3 + S_eff, π * rball^2 + rho_val[i = 0:N], rho0 * exp(-h[i] / hr) + D_eff[i = 0:N], 0.5 * rho_val[i] * v_mag[i]^2 * S_eff * Cd + dv_mag[i = 0:N], -D_eff[i] / m_eff - g * sin(gamma[i]) + dgamma[i = 0:N], -g * cos(gamma[i]) / v_mag[i] + dh[i = 0:N], v_mag[i] * sin(gamma[i]) + dr[i = 0:N], v_mag[i] * cos(gamma[i]) + end + ) + + @constraints( + model, + begin + ∂v_mag[i = 1:N], v_mag[i] == v_mag[i - 1] + 0.5 * Δt * (dv_mag[i] + dv_mag[i - 1]) + ∂gamma[i = 1:N], gamma[i] == gamma[i - 1] + 0.5 * Δt * (dgamma[i] + dgamma[i - 1]) + ∂h[i = 1:N], h[i] == h[i - 1] + 0.5 * Δt * (dh[i] + dh[i - 1]) + ∂r[i = 1:N], r[i] == r[i - 1] + 0.5 * Δt * (dr[i] + dr[i - 1]) + end + ) + + # objective @objective(model, Max, r[N]) return model diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index 621f38af..7ccaa414 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -90,14 +90,16 @@ function OptimalControlProblems.ssto_earth( Δt, (tf - t0) / N # dynamics at each node - v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2 + 1e-9) # add epsilon to avoid sqrt(0) + v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2) rho_at[i=0:N], rho_ref * exp(-py[i] / h_scale) - D_at[i=0:N], 0.5 * rho_at[i] * v_at[i]^2 * Cd * S + # D_at = 0.5 * rho_at * v_at^2 * Cd * S + # Drag term: D_at * velocity_component / v_at = 0.5 * rho_at * v_at * velocity_component * Cd * S + D_factor_at[i=0:N], 0.5 * rho_at[i] * v_at[i] * Cd * S dpx[i=0:N], vx[i] dpy[i=0:N], vy[i] - dvx[i=0:N], (Thrust * cos(θ[i]) - D_at[i] * vx[i] / v_at[i]) / m[i] - dvy[i=0:N], (Thrust * sin(θ[i]) - D_at[i] * vy[i] / v_at[i]) / m[i] - g + dvx[i=0:N], (Thrust * cos(θ[i]) - D_factor_at[i] * vx[i]) / m[i] + dvy[i=0:N], (Thrust * sin(θ[i]) - D_factor_at[i] * vy[i]) / m[i] - g dm[i=0:N], -Thrust / (g * Isp) end) diff --git a/ext/JuMPModels/water_rocket.jl b/ext/JuMPModels/water_rocket.jl index f42dab10..309323c2 100644 --- a/ext/JuMPModels/water_rocket.jl +++ b/ext/JuMPModels/water_rocket.jl @@ -56,10 +56,10 @@ function OptimalControlProblems.water_rocket( # metadata model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) - model[:state_components] = ["r", "h", "v", "γ", "p", "Vw"] - model[:costate_components] = ["∂r", "∂h", "∂v", "∂γ", "∂p", "∂Vw"] + model[:state_components] = ["r", "h", "v_mag", "gamma", "p", "Vw"] + model[:costate_components] = ["∂r", "∂h", "∂v_mag", "∂gamma", "∂p", "∂Vw"] model[:control_components] = ["u"] - model[:variable_components] = ["tf", "Vw0", "γ0"] + model[:variable_components] = ["tf", "Vw0", "gamma0"] # N = grid_size @expression(model, N, grid_size) @@ -70,14 +70,14 @@ function OptimalControlProblems.water_rocket( begin r[0:N], (start = 0.0) h[0:N] ≥ 0.0, (start = 0.0) - v[0:N], (start = 1.0) - γ[0:N], (start = 0.785) + v_mag[0:N], (start = 1.0) + gamma[0:N], (start = 0.785) p[0:N], (start = 7.0e5) Vw[0:N], (start = 1.0e-3) u[0:N], (start = 0.0) 0.001 ≤ tf ≤ 1.0, (start = tf_start) 0.1e-3 ≤ Vw0 ≤ 1.9e-3, (start = Vw0_start) - 0.1 ≤ γ0 ≤ 1.5, (start = gamma0_start) + 0.1 ≤ gamma0 ≤ 1.5, (start = gamma0_start) end ) @@ -87,8 +87,8 @@ function OptimalControlProblems.water_rocket( begin r[0] == r_t0 h[0] == h_t0 - v[0] == v_t0 - γ[0] == γ0 + v_mag[0] == v_t0 + gamma[0] == gamma0 p[0] == p_t0 Vw[0] == Vw0 Vw[N] == 0.0 @@ -100,12 +100,12 @@ function OptimalControlProblems.water_rocket( model, begin Δt, (tf - t0) / N - dr[i = 0:N], v[i] * cos(γ[i]) - dh[i = 0:N], v[i] * sin(γ[i]) + dr[i = 0:N], v_mag[i] * cos(gamma[i]) + dh[i = 0:N], v_mag[i] * sin(gamma[i]) dVw[i = 0:N], -sqrt(2 * (p[i] - p_a) / rho_w) * A_out dp[i = 0:N], k * p[i] * (-sqrt(2 * (p[i] - p_a) / rho_w) * A_out) / (V_b - Vw[i]) - dv[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v[i]^2 * S * C_d - (m_empty + rho_w * Vw[i]) * g * sin(γ[i])) / (m_empty + rho_w * Vw[i]) - dγ[i = 0:N], - (g * cos(γ[i])) / v[i] + dv_mag[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v_mag[i]^2 * S * C_d - (m_empty + rho_w * Vw[i]) * g * sin(gamma[i])) / (m_empty + rho_w * Vw[i]) + dgamma[i = 0:N], - (g * cos(gamma[i])) / v_mag[i] end ) @@ -114,8 +114,8 @@ function OptimalControlProblems.water_rocket( begin ∂r[i = 1:N], r[i] == r[i - 1] + 0.5 * Δt * (dr[i] + dr[i - 1]) ∂h[i = 1:N], h[i] == h[i - 1] + 0.5 * Δt * (dh[i] + dh[i - 1]) - ∂v[i = 1:N], v[i] == v[i - 1] + 0.5 * Δt * (dv[i] + dv[i - 1]) - ∂γ[i = 1:N], γ[i] == γ[i - 1] + 0.5 * Δt * (dγ[i] + dγ[i - 1]) + ∂v_mag[i = 1:N], v_mag[i] == v_mag[i - 1] + 0.5 * Δt * (dv_mag[i] + dv_mag[i - 1]) + ∂gamma[i = 1:N], gamma[i] == gamma[i - 1] + 0.5 * Δt * (dgamma[i] + dgamma[i - 1]) ∂p[i = 1:N], p[i] == p[i - 1] + 0.5 * Δt * (dp[i] + dp[i - 1]) ∂Vw[i = 1:N], Vw[i] == Vw[i - 1] + 0.5 * Δt * (dVw[i] + dVw[i - 1]) end diff --git a/ext/MetaData/cannonball.jl b/ext/MetaData/cannonball.jl index ce4157ff..ebfbadd4 100644 --- a/ext/MetaData/cannonball.jl +++ b/ext/MetaData/cannonball.jl @@ -2,18 +2,18 @@ cannonball_meta = OrderedDict( :grid_size => 100, :parameters => ( t0 = 0.0, - ρ_metal = 7870.0, + rho_metal = 7870.0, Cd = 0.5, KE_max = 400000.0, g = 9.80665, - ρ0 = 1.225, + rho0 = 1.225, hr = 8440.0, r_ball_l = 0.01, r_ball_u = 0.1, v0_l = 0.1, v0_u = 500.0, - γ0_l = 0.08726646259971647, # deg2rad(5.0) - γ0_u = 1.4835298641951802, # deg2rad(85.0) + gamma0_l = 0.08726646259971647, # deg2rad(5.0) + gamma0_u = 1.4835298641951802, # deg2rad(85.0) tf_l = 0.1, tf_u = 100.0, ) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index ef41d41c..bdaf35d7 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -33,29 +33,29 @@ function OptimalControlProblems.cannonball( # parameters params = parameters_data(:cannonball, parameters) t0 = params[:t0] - rho_metal = params[:ρ_metal] + rho_metal = params[:rho_metal] Cd = params[:Cd] KE_max = params[:KE_max] g = params[:g] - rho0 = params[:ρ0] + rho0 = params[:rho0] hr = params[:hr] r_ball_l = params[:r_ball_l] r_ball_u = params[:r_ball_u] v0_l = params[:v0_l] v0_u = params[:v0_u] - gamma0_l = params[:γ0_l] - gamma0_u = params[:γ0_u] + gamma0_l = params[:gamma0_l] + gamma0_u = params[:gamma0_u] tf_l = params[:tf_l] tf_u = params[:tf_u] # model ocp = @def begin - v = (v0, gamma0, rball, tf) ∈ R⁴, variable + w = (v0, gamma0, rball, tf) ∈ R⁴, variable t ∈ [t0, tf], time x = (v_mag, gamma, h, r) ∈ R⁴, state u ∈ R, control # dummy - # constraints + # variables bounds v0_l ≤ v0 ≤ v0_u gamma0_l ≤ gamma0 ≤ gamma0_u r_ball_l ≤ rball ≤ r_ball_u @@ -66,18 +66,21 @@ function OptimalControlProblems.cannonball( 0.5 * ((4/3) * π * rho_metal * rball^3) * v0^2 ≤ KE_max # initial conditions - x(t0) == [v0, gamma0, 0, 0] + v_mag(t0) == v0 + gamma(t0) == gamma0 + h(t0) == 0 + r(t0) == 0 # final conditions h(tf) == 0 # dynamics - m = (4/3) * π * rho_metal * rball^3 - S = π * rball^2 - + m_eff = (4/3) * π * rho_metal * rball^3 + S_eff = π * rball^2 + # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r ẋ(t) == [ - -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * S * Cd) / m - g * sin(gamma(t)), + -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * S_eff * Cd) / m_eff - g * sin(gamma(t)), -g * cos(gamma(t)) / v_mag(t), v_mag(t) * sin(gamma(t)), v_mag(t) * cos(gamma(t)) @@ -86,13 +89,12 @@ function OptimalControlProblems.cannonball( r(tf) → max end - # initial guess v0_init = 100.0 - γ0_init = π/4 + gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, γ0_init, 1.0, 1.0], variable=[v0_init, γ0_init, r_ball_init, tf_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[v0_init, gamma0_init, r_ball_init, tf_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 2013c5aa..dd92dc87 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -73,18 +73,15 @@ function OptimalControlProblems.ssto_earth( # x[1]=x, x[2]=y, x[3]=vx, x[4]=vy, x[5]=m v = sqrt(x[3](t)^2 + x[4](t)^2) rho = rho_ref * exp(-x[2](t) / h_scale) - D = 0.5 * rho * v^2 * Cd * S - - # Avoid division by zero for D_cos_gamma and D_sin_gamma - # gamma is the angle of the velocity vector - # cos_gamma = vx / v, sin_gamma = vy / v - # if v is small, we can approximate D_cos_gamma and D_sin_gamma + # D = 0.5 * rho * v^2 * Cd * S + # Drag term: D * velocity_component / v = 0.5 * rho * v * velocity_component * Cd * S + D_factor = 0.5 * rho * v * Cd * S ẋ(t) == [ x[3](t), x[4](t), - (Thrust * cos(θ(t)) - (v > 1e-6 ? D * x[3](t) / v : 0.0)) / x[5](t), - (Thrust * sin(θ(t)) - (v > 1e-6 ? D * x[4](t) / v : 0.0)) / x[5](t) - g, + (Thrust * cos(θ(t)) - D_factor * x[3](t)) / x[5](t), + (Thrust * sin(θ(t)) - D_factor * x[4](t)) / x[5](t) - g, -Thrust / (g * Isp) ] diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index 1a6eae26..e9962082 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,7 +53,7 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - v = (tf, Vw0, gamma0) ∈ R³, variable + w = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state u ∈ R, control # dummy diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index 3e02140e..97dfcd0e 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Constructs an **OptimalControl problem** for the Multi-Phase Cannonball problem using scalar state components. +Constructs an **OptimalControl problem** for the Multi-Phase Cannonball problem using scalar state components (symbolic version). The goal is to maximise the total range of a cannonball by optimizing its radius, initial velocity, and launch angle, subject to a maximum muzzle energy constraint. # Arguments @@ -33,40 +33,40 @@ function OptimalControlProblems.cannonball_s( # parameters params = parameters_data(:cannonball, parameters) t0 = params[:t0] - rho_metal = params[:ρ_metal] + rho_metal = params[:rho_metal] Cd = params[:Cd] KE_max = params[:KE_max] g = params[:g] - rho0 = params[:ρ0] + rho0 = params[:rho0] hr = params[:hr] r_ball_l = params[:r_ball_l] r_ball_u = params[:r_ball_u] v0_l = params[:v0_l] v0_u = params[:v0_u] - gamma0_l = params[:γ0_l] - gamma0_u = params[:γ0_u] + gamma0_l = params[:gamma0_l] + gamma0_u = params[:gamma0_u] tf_l = params[:tf_l] tf_u = params[:tf_u] # model ocp = @def begin - vvar = (v0, gamma0, r_ball, tf) ∈ R⁴, variable + w = (v0, gamma0, rball, tf) ∈ R⁴, variable t ∈ [t0, tf], time - x = (v, gamma, h, r) ∈ R⁴, state + x = (v_mag, gamma, h, r) ∈ R⁴, state u ∈ R, control # dummy # variables bounds v0_l ≤ v0 ≤ v0_u gamma0_l ≤ gamma0 ≤ gamma0_u - r_ball_l ≤ r_ball ≤ r_ball_u + r_ball_l ≤ rball ≤ r_ball_u tf_l ≤ tf ≤ tf_u # KE constraint - m = (4/3) * π * rho_metal * r_ball^3 - 0.5 * m * v0^2 ≤ KE_max + m_eff = (4/3) * π * rho_metal * rball^3 + 0.5 * m_eff * v0^2 ≤ KE_max # initial conditions - v(t0) == v0 + v_mag(t0) == v0 gamma(t0) == gamma0 h(t0) == 0 r(t0) == 0 @@ -75,24 +75,24 @@ function OptimalControlProblems.cannonball_s( h(tf) == 0 # dynamics - S = π * r_ball^2 - rho = rho0 * exp(-h(t) / hr) - D = 0.5 * rho * v(t)^2 * S * Cd + S_eff = π * rball^2 + rho_val = rho0 * exp(-h(t) / hr) + D_eff = 0.5 * rho_val * v_mag(t)^2 * S_eff * Cd - ∂(v)(t) == -D/m - g * sin(gamma(t)) - ∂(gamma)(t) == -g * cos(gamma(t)) / v(t) - ∂(h)(t) == v(t) * sin(gamma(t)) - ∂(r)(t) == v(t) * cos(gamma(t)) + ∂(v_mag)(t) == -D_eff/m_eff - g * sin(gamma(t)) + ∂(gamma)(t) == -g * cos(gamma(t)) / v_mag(t) + ∂(h)(t) == v_mag(t) * sin(gamma(t)) + ∂(r)(t) == v_mag(t) * cos(gamma(t)) r(tf) → max end # initial guess v0_init = 100.0 - γ0_init = π/4 + gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, γ0_init, 1.0, 1.0], variable=[v0_init, γ0_init, r_ball_init, tf_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[v0_init, gamma0_init, r_ball_init, tf_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/mountain_car_s.jl b/ext/OptimalControlModels_s/mountain_car_s.jl index cd770838..26388e86 100644 --- a/ext/OptimalControlModels_s/mountain_car_s.jl +++ b/ext/OptimalControlModels_s/mountain_car_s.jl @@ -20,7 +20,7 @@ The problem formulation can be found [here](https://openmdao.github.io/dymos/exa ```julia-repl julia> using OptimalControlProblems -julia> docp = OptimalControlProblems.mountain_car_s(OptimalControlBackend(); N=100); +julia> docp = OptimalControlProblems.mountain_car(OptimalControlBackend(); N=100); ``` """ function OptimalControlProblems.mountain_car_s( diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 46cd5691..1425280b 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -19,7 +19,7 @@ The goal is to minimise the time required to reach a circular orbit at an altitu ```julia-repl julia> using OptimalControlProblems -julia> docp = OptimalControlProblems.ssto_earth_s(OptimalControlBackend(); N=100); +julia> docp = OptimalControlProblems.ssto_earth(OptimalControlBackend(); N=100); ``` """ function OptimalControlProblems.ssto_earth_s( @@ -76,12 +76,14 @@ function OptimalControlProblems.ssto_earth_s( # dynamics v = sqrt(vx(t)^2 + vy(t)^2) rho = rho_ref * exp(-py(t) / h_scale) - D = 0.5 * rho * v^2 * Cd * S + # D = 0.5 * rho * v^2 * Cd * S + # Drag term: D * velocity_component / v = 0.5 * rho * v * velocity_component * Cd * S + D_factor = 0.5 * rho * v * Cd * S ∂(px)(t) == vx(t) ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(θ(t)) - (v > 1e-6 ? D * vx(t) / v : 0.0)) / m(t) - ∂(vy)(t) == (Thrust * sin(θ(t)) - (v > 1e-6 ? D * vy(t) / v : 0.0)) / m(t) - g + ∂(vx)(t) == (Thrust * cos(θ(t)) - D_factor * vx(t)) / m(t) + ∂(vy)(t) == (Thrust * sin(θ(t)) - D_factor * vy(t)) / m(t) - g ∂(m)(t) == -Thrust / (g * Isp) tf → min diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index c2b03627..ecefd133 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -19,7 +19,7 @@ The objective is to maximise the altitude at the end of the water ejection. ```julia-repl julia> using OptimalControlProblems -julia> docp = OptimalControlProblems.water_rocket_s(OptimalControlBackend(); N=100); +julia> docp = OptimalControlProblems.water_rocket(OptimalControlBackend(); N=100); ``` """ function OptimalControlProblems.water_rocket_s( @@ -53,9 +53,9 @@ function OptimalControlProblems.water_rocket_s( # model ocp = @def begin - v_opt = (tf, Vw0, gamma0) ∈ R³, variable + w = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time - x = (r, h, v, gamma, p, Vw) ∈ R⁶, state + x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state u ∈ R, control # dummy # constraints @@ -68,18 +68,18 @@ function OptimalControlProblems.water_rocket_s( # initial conditions r(t0) == r_t0 h(t0) == h_t0 - v(t0) == v_t0 + v_mag(t0) == v_t0 gamma(t0) == gamma0 p(t0) == p_t0 Vw(t0) == Vw0 # dynamics - ∂(r)(t) == v(t) * cos(gamma(t)) - ∂(h)(t) == v(t) * sin(gamma(t)) + ∂(r)(t) == v_mag(t) * cos(gamma(t)) + ∂(h)(t) == v_mag(t) * sin(gamma(t)) ∂(Vw)(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out ∂(p)(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) - ∂(v)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) - ∂(gamma)(t) == - (g * cos(gamma(t))) / v(t) + ∂(v_mag)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) + ∂(gamma)(t) == - (g * cos(gamma(t))) / v_mag(t) # objective h(tf) → max From cb01381623eb4ecee5834f9f1bda8033f44beb9e Mon Sep 17 00:00:00 2001 From: Amiel Date: Thu, 5 Mar 2026 00:03:49 +0100 Subject: [PATCH 18/32] fix --- ext/JuMPModels/cannonball.jl | 22 ++++----- ext/JuMPModels/ssto_earth.jl | 51 ++++++++++++-------- ext/JuMPModels/water_rocket.jl | 4 +- ext/MetaData/ssto_earth.jl | 6 +-- ext/MetaData/water_rocket.jl | 10 ++-- ext/OptimalControlModels/cannonball.jl | 10 ++-- ext/OptimalControlModels/ssto_earth.jl | 41 ++++++++-------- ext/OptimalControlModels/water_rocket.jl | 12 +++-- ext/OptimalControlModels_s/ssto_earth_s.jl | 29 +++++------ ext/OptimalControlModels_s/water_rocket_s.jl | 4 +- 10 files changed, 99 insertions(+), 90 deletions(-) diff --git a/ext/JuMPModels/cannonball.jl b/ext/JuMPModels/cannonball.jl index 64b7647d..5c6754ac 100644 --- a/ext/JuMPModels/cannonball.jl +++ b/ext/JuMPModels/cannonball.jl @@ -68,7 +68,7 @@ function OptimalControlProblems.cannonball( v_mag[0:N] ≥ 0.0, (start = 100.0) gamma[0:N], (start = 0.785) h[0:N] ≥ 0.0, (start = 1.0) - r[0:N], (start = 1.0) + r[0:N] ≥ 0.0, (start = 1.0) u[0:N], (start = 0.0) v0_l ≤ v0 ≤ v0_u, (start = 100.0) gamma0_l ≤ gamma0 ≤ gamma0_u, (start = 0.785) @@ -83,26 +83,26 @@ function OptimalControlProblems.cannonball( begin v_mag[0] == v0 gamma[0] == gamma0 - h[0] == 0 - r[0] == 0 - h[N] == 0 + h[0] == 0.0 + r[0] == 0.0 + h[N] == 0.0 end ) - # design constraints - @constraint(model, 0.5 * ((4/3) * π * rho_metal * rball^3) * v0^2 ≤ KE_max) + # design constraints (scaled by KE_max) + @constraint(model, (0.5 * ((4/3) * pi * rho_metal * rball^3) * v0^2) / KE_max ≤ 1.0) # dynamics @expressions( model, begin Δt, (tf - t0) / N - m_eff, (4/3) * π * rho_metal * rball^3 - S_eff, π * rball^2 + m_eff, (4/3) * pi * rho_metal * rball^3 + S_eff, pi * rball^2 rho_val[i = 0:N], rho0 * exp(-h[i] / hr) D_eff[i = 0:N], 0.5 * rho_val[i] * v_mag[i]^2 * S_eff * Cd dv_mag[i = 0:N], -D_eff[i] / m_eff - g * sin(gamma[i]) - dgamma[i = 0:N], -g * cos(gamma[i]) / v_mag[i] + dgamma[i = 0:N], -g * cos(gamma[i]) / (v_mag[i] + 1e-6) # prevent div by zero dh[i = 0:N], v_mag[i] * sin(gamma[i]) dr[i = 0:N], v_mag[i] * cos(gamma[i]) end @@ -118,8 +118,8 @@ function OptimalControlProblems.cannonball( end ) - # objective - @objective(model, Max, r[N]) + # objective (scaled by 1000.0) + @objective(model, Max, r[N] / 1000.0) return model end diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index 7ccaa414..8b521eb6 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -53,54 +53,63 @@ function OptimalControlProblems.ssto_earth( model = JuMP.Model(args...; kwargs...) # metadata - model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) model[:state_components] = ["px", "py", "vx", "vy", "m"] model[:costate_components] = ["∂px", "∂py", "∂vx", "∂vy", "∂m"] - model[:control_components] = ["θ"] + model[:control_components] = ["theta"] model[:variable_components] = ["tf"] N = grid_size @expression(model, N_expr, N) + # scaling factors + s_p = 1e5 + s_v = 1e3 + s_m = 1e5 + @variables(model, begin tf_l <= tf <= tf_u, (start = 150.0) - theta_l <= θ[0:N] <= theta_u, (start = 0.5) + theta_l <= theta[0:N] <= theta_u, (start = 0.5) px[0:N], (start = 0.0) - py[0:N], (start = 0.0) - vx[0:N], (start = 0.0) + py[0:N], (start = i/N * y_tf / s_p) + vx[0:N], (start = i/N * vx_tf / s_v) vy[0:N], (start = 0.0) - m[0:N], (start = m0) + m[0:N], (start = m0 / s_m) end) - # constraints + # boundary constraints (scaled) @constraints(model, begin px[0] == 0.0 py[0] == 0.0 vx[0] == 0.0 vy[0] == 0.0 - m[0] == m0 + m[0] == m0 / s_m - py[N] == y_tf - vx[N] == vx_tf - vy[N] == vy_tf + py[N] == y_tf / s_p + vx[N] == vx_tf / s_v + vy[N] == vy_tf / s_v end) @expressions(model, begin Δt, (tf - t0) / N + # Unscale for dynamics + p_val[i=0:N], py[i] * s_p + vx_val[i=0:N], vx[i] * s_v + vy_val[i=0:N], vy[i] * s_v + m_val[i=0:N], m[i] * s_m + # dynamics at each node - v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2) - rho_at[i=0:N], rho_ref * exp(-py[i] / h_scale) - # D_at = 0.5 * rho_at * v_at^2 * Cd * S - # Drag term: D_at * velocity_component / v_at = 0.5 * rho_at * v_at * velocity_component * Cd * S + v_at[i=0:N], sqrt(vx_val[i]^2 + vy_val[i]^2) + rho_at[i=0:N], rho_ref * exp(-p_val[i] / h_scale) D_factor_at[i=0:N], 0.5 * rho_at[i] * v_at[i] * Cd * S - dpx[i=0:N], vx[i] - dpy[i=0:N], vy[i] - dvx[i=0:N], (Thrust * cos(θ[i]) - D_factor_at[i] * vx[i]) / m[i] - dvy[i=0:N], (Thrust * sin(θ[i]) - D_factor_at[i] * vy[i]) / m[i] - g - dm[i=0:N], -Thrust / (g * Isp) + dpx[i=0:N], vx_val[i] / s_p + dpy[i=0:N], vy_val[i] / s_p + dvx[i=0:N], ((Thrust * cos(theta[i]) - D_factor_at[i] * vx_val[i]) / m_val[i]) / s_v + dvy[i=0:N], ((Thrust * sin(theta[i]) - D_factor_at[i] * vy_val[i]) / m_val[i] - g) / s_v + dm[i=0:N], (-Thrust / (g * Isp)) / s_m end) @constraints(model, begin @@ -111,7 +120,7 @@ function OptimalControlProblems.ssto_earth( ∂m[i=1:N], m[i] == m[i-1] + 0.5 * Δt * (dm[i] + dm[i-1]) end) - @objective(model, Min, tf) + @objective(model, Min, tf / 100.0) return model end diff --git a/ext/JuMPModels/water_rocket.jl b/ext/JuMPModels/water_rocket.jl index 309323c2..743968e2 100644 --- a/ext/JuMPModels/water_rocket.jl +++ b/ext/JuMPModels/water_rocket.jl @@ -39,7 +39,7 @@ function OptimalControlProblems.water_rocket( V_b = params[:V_b] A_out = params[:A_out] S = params[:S] - C_d = params[:C_d] + Cd = params[:Cd] rho_a = params[:rho_a] m_empty = params[:m_empty] t0 = params[:t0] @@ -104,7 +104,7 @@ function OptimalControlProblems.water_rocket( dh[i = 0:N], v_mag[i] * sin(gamma[i]) dVw[i = 0:N], -sqrt(2 * (p[i] - p_a) / rho_w) * A_out dp[i = 0:N], k * p[i] * (-sqrt(2 * (p[i] - p_a) / rho_w) * A_out) / (V_b - Vw[i]) - dv_mag[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v_mag[i]^2 * S * C_d - (m_empty + rho_w * Vw[i]) * g * sin(gamma[i])) / (m_empty + rho_w * Vw[i]) + dv_mag[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v_mag[i]^2 * Cd * S - (m_empty + rho_w * Vw[i]) * g * sin(gamma[i])) / (m_empty + rho_w * Vw[i]) dgamma[i = 0:N], - (g * cos(gamma[i])) / v_mag[i] end ) diff --git a/ext/MetaData/ssto_earth.jl b/ext/MetaData/ssto_earth.jl index 0bc0bfef..acbe6868 100644 --- a/ext/MetaData/ssto_earth.jl +++ b/ext/MetaData/ssto_earth.jl @@ -13,9 +13,9 @@ ssto_earth_meta = OrderedDict( y_tf = 185000.0, vx_tf = 7796.6961, vy_tf = 0.0, - tf_l = 10.0, + tf_l = 0.1, tf_u = 1000.0, - theta_l = -1.5707963267948966, # -pi/2 - theta_u = 1.5707963267948966, # pi/2 + theta_l = -1.57, # -pi/2 + theta_u = 1.57, # pi/2 ) ) diff --git a/ext/MetaData/water_rocket.jl b/ext/MetaData/water_rocket.jl index ca632c53..78b03824 100644 --- a/ext/MetaData/water_rocket.jl +++ b/ext/MetaData/water_rocket.jl @@ -3,21 +3,21 @@ water_rocket_meta = OrderedDict( :parameters => ( g = 9.80665, rho_w = 1000.0, - p_a = 1.01325e5, + p_a = 101325.0, k = 1.2, V_b = 2.0e-3, - A_out = pi * (0.013)^2 / 4, + A_out = pi * (0.021)^2 / 4, S = pi * (0.106)^2 / 4, - C_d = 0.345, + Cd = 0.345, rho_a = 1.225, m_empty = 0.15, t0 = 0.0, r_t0 = 0.0, h_t0 = 0.0, v_t0 = 0.1, - p_t0 = 6.5e5 + 1.01325e5, + p_t0 = 650000.0 + 101325.0, # 6.5 bar gauge + atmospheric tf_start = 0.3, Vw0_start = 1.0e-3, - gamma0_start = 0.785, # pi/4 approx + gamma0_start = 0.785, ), ) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index bdaf35d7..bfdc76d9 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -50,7 +50,7 @@ function OptimalControlProblems.cannonball( # model ocp = @def begin - w = (v0, gamma0, rball, tf) ∈ R⁴, variable + v = (v0, gamma0, rball, tf) ∈ R⁴, variable t ∈ [t0, tf], time x = (v_mag, gamma, h, r) ∈ R⁴, state u ∈ R, control # dummy @@ -68,17 +68,17 @@ function OptimalControlProblems.cannonball( # initial conditions v_mag(t0) == v0 gamma(t0) == gamma0 - h(t0) == 0 - r(t0) == 0 + h(t0) == 0.0 + r(t0) == 0.0 # final conditions - h(tf) == 0 + h(tf) == 0.0 # dynamics m_eff = (4/3) * π * rho_metal * rball^3 S_eff = π * rball^2 - # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r + # ẋ(t) ẋ(t) == [ -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * S_eff * Cd) / m_eff - g * sin(gamma(t)), -g * cos(gamma(t)) / v_mag(t), diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index dd92dc87..d710fd81 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -2,7 +2,7 @@ $(TYPEDSIGNATURES) Constructs an **OptimalControl problem** for the SSTO Earth Launch problem. -The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. +The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. # Arguments @@ -12,7 +12,6 @@ The goal is to minimise the time required to reach a circular orbit at an altitu # Returns - `docp`: The direct optimal control problem object representing the SSTO Earth problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -51,37 +50,38 @@ function OptimalControlProblems.ssto_earth( # model ocp = @def begin - tf ∈ R, variable + w = tf ∈ R, variable t ∈ [t0, tf], time - x ∈ R⁵, state - θ ∈ R, control + x = (px, py, vx, vy, m) ∈ R⁵, state + u = theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ θ(t) ≤ theta_u + theta_l ≤ theta(t) ≤ theta_u # initial conditions - x(t0) == [0.0, 0.0, 0.0, 0.0, m0] + px(t0) == 0.0 + py(t0) == 0.0 + vx(t0) == 0.0 + vy(t0) == 0.0 + m(t0) == m0 # final conditions - x(tf)[2] == y_tf - x(tf)[3] == vx_tf - x(tf)[4] == vy_tf + py(tf) == y_tf + vx(tf) == vx_tf + vy(tf) == vy_tf # dynamics - # x[1]=x, x[2]=y, x[3]=vx, x[4]=vy, x[5]=m - v = sqrt(x[3](t)^2 + x[4](t)^2) - rho = rho_ref * exp(-x[2](t) / h_scale) - # D = 0.5 * rho * v^2 * Cd * S - # Drag term: D * velocity_component / v = 0.5 * rho * v * velocity_component * Cd * S - D_factor = 0.5 * rho * v * Cd * S + # v = sqrt(vx^2 + vy^2) + # rho = rho_ref * exp(-py / h_scale) + # D_factor = 0.5 * rho * v * Cd * S ẋ(t) == [ - x[3](t), - x[4](t), - (Thrust * cos(θ(t)) - D_factor * x[3](t)) / x[5](t), - (Thrust * sin(θ(t)) - D_factor * x[4](t)) / x[5](t) - g, + vx(t), + vy(t), + (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vx(t)) / m(t), + (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vy(t)) / m(t) - g, -Thrust / (g * Isp) ] @@ -90,7 +90,6 @@ function OptimalControlProblems.ssto_earth( # initial guess tf_init = 150.0 - # x(t) = [x, y, vx, vy, m] x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] init = (state=x_init, control=[0.5], variable=[tf_init]) diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index e9962082..fc14a72e 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -39,7 +39,7 @@ function OptimalControlProblems.water_rocket( V_b = params[:V_b] A_out = params[:A_out] S = params[:S] - C_d = params[:C_d] + Cd = params[:Cd] rho_a = params[:rho_a] m_empty = params[:m_empty] t0 = params[:t0] @@ -53,7 +53,7 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - w = (tf, Vw0, gamma0) ∈ R³, variable + v = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state u ∈ R, control # dummy @@ -74,11 +74,15 @@ function OptimalControlProblems.water_rocket( Vw(t0) == Vw0 # dynamics - # x = (r, h, v_mag, gamma, p, Vw) + # v_out = sqrt(2 * (p - p_a) / rho_w) + # thrust = rho_w * A_out * v_out^2 = 2 * A_out * (p - p_a) + # m = m_empty + rho_w * Vw + # drag = 0.5 * rho_a * v_mag^2 * Cd * S + ẋ(t) == [ v_mag(t) * cos(gamma(t)), v_mag(t) * sin(gamma(t)), - (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)), + (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * Cd * S - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)), - (g * cos(gamma(t))) / v_mag(t), k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)), -sqrt(2 * (p(t) - p_a) / rho_w) * A_out diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 1425280b..4cb2d381 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Constructs an **OptimalControl problem** for the SSTO Earth Launch problem using scalar state components. +Constructs an **OptimalControl problem** for the SSTO Earth Launch problem using scalar state components (symbolic version). The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. # Arguments @@ -12,7 +12,6 @@ The goal is to minimise the time required to reach a circular orbit at an altitu # Returns - `docp`: The direct optimal control problem object representing the SSTO Earth problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -51,21 +50,21 @@ function OptimalControlProblems.ssto_earth_s( # model ocp = @def begin - tf ∈ R, variable + w = tf ∈ R, variable t ∈ [t0, tf], time x = (px, py, vx, vy, m) ∈ R⁵, state - θ ∈ R, control + u = theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ θ(t) ≤ theta_u + theta_l ≤ theta(t) ≤ theta_u # initial conditions - px(t0) == 0 - py(t0) == 0 - vx(t0) == 0 - vy(t0) == 0 + px(t0) == 0.0 + py(t0) == 0.0 + vx(t0) == 0.0 + vy(t0) == 0.0 m(t0) == m0 # final conditions @@ -74,16 +73,14 @@ function OptimalControlProblems.ssto_earth_s( vy(tf) == vy_tf # dynamics - v = sqrt(vx(t)^2 + vy(t)^2) - rho = rho_ref * exp(-py(t) / h_scale) - # D = 0.5 * rho * v^2 * Cd * S - # Drag term: D * velocity_component / v = 0.5 * rho * v * velocity_component * Cd * S - D_factor = 0.5 * rho * v * Cd * S + v_mag = sqrt(vx(t)^2 + vy(t)^2) + rho_val = rho_ref * exp(-py(t) / h_scale) + D_factor = 0.5 * rho_val * v_mag * Cd * S ∂(px)(t) == vx(t) ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(θ(t)) - D_factor * vx(t)) / m(t) - ∂(vy)(t) == (Thrust * sin(θ(t)) - D_factor * vy(t)) / m(t) - g + ∂(vx)(t) == (Thrust * cos(theta(t)) - D_factor * vx(t)) / m(t) + ∂(vy)(t) == (Thrust * sin(theta(t)) - D_factor * vy(t)) / m(t) - g ∂(m)(t) == -Thrust / (g * Isp) tf → min diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index ecefd133..4c9ae98e 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -39,7 +39,7 @@ function OptimalControlProblems.water_rocket_s( V_b = params[:V_b] A_out = params[:A_out] S = params[:S] - C_d = params[:C_d] + Cd = params[:Cd] rho_a = params[:rho_a] m_empty = params[:m_empty] t0 = params[:t0] @@ -78,7 +78,7 @@ function OptimalControlProblems.water_rocket_s( ∂(h)(t) == v_mag(t) * sin(gamma(t)) ∂(Vw)(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out ∂(p)(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) - ∂(v_mag)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * S * C_d - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) + ∂(v_mag)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * Cd * S - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) ∂(gamma)(t) == - (g * cos(gamma(t))) / v_mag(t) # objective From 590a7905b114b857058a264b5273be16a50b66db Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 00:10:45 +0100 Subject: [PATCH 19/32] fix ssto --- ext/JuMPModels/ssto_earth.jl | 45 ++++++++++---- ext/OptimalControlModels/ssto_earth.jl | 61 +++++++++++-------- ext/OptimalControlModels_s/ssto_earth_s.jl | 68 ++++++++++++++-------- 3 files changed, 116 insertions(+), 58 deletions(-) diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index 621f38af..3886fb63 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -49,12 +49,17 @@ function OptimalControlProblems.ssto_earth( theta_l = params[:theta_l] theta_u = params[:theta_u] + ## Scalings + scaling_p = 1e5 + scaling_v = 1e3 + scaling_m = 1e5 + # model model = JuMP.Model(args...; kwargs...) # metadata model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) - model[:state_components] = ["px", "py", "vx", "vy", "m"] + model[:state_components] = ["spx", "spy", "svx", "svy", "sm"] model[:costate_components] = ["∂px", "∂py", "∂vx", "∂vy", "∂m"] model[:control_components] = ["θ"] model[:variable_components] = ["tf"] @@ -66,13 +71,34 @@ function OptimalControlProblems.ssto_earth( tf_l <= tf <= tf_u, (start = 150.0) theta_l <= θ[0:N] <= theta_u, (start = 0.5) - px[0:N], (start = 0.0) - py[0:N], (start = 0.0) - vx[0:N], (start = 0.0) - vy[0:N], (start = 0.0) - m[0:N], (start = m0) + spx[0:N] + spy[0:N] + svx[0:N] + svy[0:N] + sm[0:N] end) + # Initial guess: linear interpolation between boundary conditions + function linear_interpolate(x_s, x_t, n) + return [x_s + (i - 1) / (n - 1) * (x_t - x_s) for i in 1:n] + end + + px_tf_guess = 5.0e5 # 500 km + m_tf_guess = m0 - (Thrust / (g * Isp)) * 150.0 # rough mass loss estimate + + set_start_value.(spx, linear_interpolate(0.0, px_tf_guess / scaling_p, N + 1)) + set_start_value.(spy, linear_interpolate(0.0, y_tf / scaling_p, N + 1)) + set_start_value.(svx, linear_interpolate(0.0, vx_tf / scaling_v, N + 1)) + set_start_value.(svy, linear_interpolate(0.0, vy_tf / scaling_v, N + 1)) + set_start_value.(sm, linear_interpolate(m0 / scaling_m, m_tf_guess / scaling_m, N + 1)) + + # unscaled expressions + @expression(model, px[i=0:N], spx[i] * scaling_p) + @expression(model, py[i=0:N], spy[i] * scaling_p) + @expression(model, vx[i=0:N], svx[i] * scaling_v) + @expression(model, vy[i=0:N], svy[i] * scaling_v) + @expression(model, m[i=0:N], sm[i] * scaling_m) + # constraints @constraints(model, begin px[0] == 0.0 @@ -90,14 +116,13 @@ function OptimalControlProblems.ssto_earth( Δt, (tf - t0) / N # dynamics at each node - v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2 + 1e-9) # add epsilon to avoid sqrt(0) + v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2 + 1e-9) rho_at[i=0:N], rho_ref * exp(-py[i] / h_scale) - D_at[i=0:N], 0.5 * rho_at[i] * v_at[i]^2 * Cd * S dpx[i=0:N], vx[i] dpy[i=0:N], vy[i] - dvx[i=0:N], (Thrust * cos(θ[i]) - D_at[i] * vx[i] / v_at[i]) / m[i] - dvy[i=0:N], (Thrust * sin(θ[i]) - D_at[i] * vy[i] / v_at[i]) / m[i] - g + dvx[i=0:N], (Thrust * cos(θ[i]) - 0.5 * rho_at[i] * v_at[i] * vx[i] * Cd * S) / m[i] + dvy[i=0:N], (Thrust * sin(θ[i]) - 0.5 * rho_at[i] * v_at[i] * vy[i] * Cd * S) / m[i] - g dm[i=0:N], -Thrust / (g * Isp) end) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 2013c5aa..410c9ce0 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -49,6 +49,11 @@ function OptimalControlProblems.ssto_earth( theta_l = params[:theta_l] theta_u = params[:theta_u] + ## Scalings + scaling_p = 1e5 + scaling_v = 1e3 + scaling_m = 1e5 + # model ocp = @def begin tf ∈ R, variable @@ -61,40 +66,48 @@ function OptimalControlProblems.ssto_earth( # control bounds theta_l ≤ θ(t) ≤ theta_u - # initial conditions - x(t0) == [0.0, 0.0, 0.0, 0.0, m0] + # unscaled helpers + px = x[1](t) * scaling_p + py = x[2](t) * scaling_p + vx = x[3](t) * scaling_v + vy = x[4](t) * scaling_v + m = x[5](t) * scaling_m - # final conditions - x(tf)[2] == y_tf - x(tf)[3] == vx_tf - x(tf)[4] == vy_tf + # initial conditions (scaled) + x(t0) == [0.0, 0.0, 0.0, 0.0, m0 / scaling_m] - # dynamics - # x[1]=x, x[2]=y, x[3]=vx, x[4]=vy, x[5]=m - v = sqrt(x[3](t)^2 + x[4](t)^2) - rho = rho_ref * exp(-x[2](t) / h_scale) - D = 0.5 * rho * v^2 * Cd * S - - # Avoid division by zero for D_cos_gamma and D_sin_gamma - # gamma is the angle of the velocity vector - # cos_gamma = vx / v, sin_gamma = vy / v - # if v is small, we can approximate D_cos_gamma and D_sin_gamma + # final conditions (scaled) + x(tf)[2] == y_tf / scaling_p + x(tf)[3] == vx_tf / scaling_v + x(tf)[4] == vy_tf / scaling_v + + # dynamics (scaled) + v_norm = sqrt(vx^2 + vy^2 + 1e-9) + rho = rho_ref * exp(-py / h_scale) ẋ(t) == [ - x[3](t), - x[4](t), - (Thrust * cos(θ(t)) - (v > 1e-6 ? D * x[3](t) / v : 0.0)) / x[5](t), - (Thrust * sin(θ(t)) - (v > 1e-6 ? D * x[4](t) / v : 0.0)) / x[5](t) - g, - -Thrust / (g * Isp) + vx / scaling_p, + vy / scaling_p, + ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v, + ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v, + (-Thrust / (g * Isp)) / scaling_m ] tf → min end - # initial guess + # initial guess: linear interpolation tf_init = 150.0 - # x(t) = [x, y, vx, vy, m] - x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] + px_tf_guess = 5.0e5 + m_tf_guess = m0 - (Thrust / (g * Isp)) * 150.0 + + x_init = t -> [ + (0.0 + t / tf_init * px_tf_guess) / scaling_p, + (0.0 + t / tf_init * y_tf) / scaling_p, + (0.0 + t / tf_init * vx_tf) / scaling_v, + (0.0 + t / tf_init * vy_tf) / scaling_v, + (m0 + t / tf_init * (m_tf_guess - m0)) / scaling_m + ] init = (state=x_init, control=[0.5], variable=[tf_init]) # discretise diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 46cd5691..7a6dc259 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -49,11 +49,16 @@ function OptimalControlProblems.ssto_earth_s( theta_l = params[:theta_l] theta_u = params[:theta_u] + ## Scalings + scaling_p = 1e5 + scaling_v = 1e3 + scaling_m = 1e5 + # model ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - x = (px, py, vx, vy, m) ∈ R⁵, state + x = (spx, spy, svx, svy, sm) ∈ R⁵, state θ ∈ R, control # tf bounds @@ -61,35 +66,50 @@ function OptimalControlProblems.ssto_earth_s( # control bounds theta_l ≤ θ(t) ≤ theta_u - # initial conditions - px(t0) == 0 - py(t0) == 0 - vx(t0) == 0 - vy(t0) == 0 - m(t0) == m0 - - # final conditions - py(tf) == y_tf - vx(tf) == vx_tf - vy(tf) == vy_tf - - # dynamics - v = sqrt(vx(t)^2 + vy(t)^2) - rho = rho_ref * exp(-py(t) / h_scale) - D = 0.5 * rho * v^2 * Cd * S + # unscaled helpers + px = spx(t) * scaling_p + py = spy(t) * scaling_p + vx = svx(t) * scaling_v + vy = svy(t) * scaling_v + m = sm(t) * scaling_m + + # initial conditions (scaled) + spx(t0) == 0 + spy(t0) == 0 + svx(t0) == 0 + svy(t0) == 0 + sm(t0) == m0 / scaling_m + + # final conditions (scaled) + spy(tf) == y_tf / scaling_p + svx(tf) == vx_tf / scaling_v + svy(tf) == vy_tf / scaling_v + + # dynamics (scaled) + v_norm = sqrt(vx^2 + vy^2 + 1e-9) + rho = rho_ref * exp(-py / h_scale) - ∂(px)(t) == vx(t) - ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(θ(t)) - (v > 1e-6 ? D * vx(t) / v : 0.0)) / m(t) - ∂(vy)(t) == (Thrust * sin(θ(t)) - (v > 1e-6 ? D * vy(t) / v : 0.0)) / m(t) - g - ∂(m)(t) == -Thrust / (g * Isp) + ∂(spx)(t) == vx / scaling_p + ∂(spy)(t) == vy / scaling_p + ∂(svx)(t) == ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v + ∂(svy)(t) == ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v + ∂(sm)(t) == (-Thrust / (g * Isp)) / scaling_m tf → min end - # initial guess + # initial guess: linear interpolation tf_init = 150.0 - x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] + px_tf_guess = 5.0e5 + m_tf_guess = m0 - (Thrust / (g * Isp)) * 150.0 + + x_init = t -> [ + (0.0 + t / tf_init * px_tf_guess) / scaling_p, + (0.0 + t / tf_init * y_tf) / scaling_p, + (0.0 + t / tf_init * vx_tf) / scaling_v, + (0.0 + t / tf_init * vy_tf) / scaling_v, + (m0 + t / tf_init * (m_tf_guess - m0)) / scaling_m + ] init = (state=x_init, control=[0.5], variable=[tf_init]) # discretise From b16df7e38a3ff4923fb965ed4235c3a8d4521775 Mon Sep 17 00:00:00 2001 From: Amiel Date: Thu, 5 Mar 2026 00:17:51 +0100 Subject: [PATCH 20/32] fix 2 --- ext/OptimalControlModels/cannonball.jl | 40 ++++++++++---------- ext/OptimalControlModels/ssto_earth.jl | 35 ++++++++--------- ext/OptimalControlModels/water_rocket.jl | 44 ++++++++++------------ ext/OptimalControlModels_s/cannonball_s.jl | 18 ++++----- ext/OptimalControlModels_s/ssto_earth_s.jl | 3 +- src/OptimalControlProblems.jl | 18 +++++++++ 6 files changed, 85 insertions(+), 73 deletions(-) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index bfdc76d9..a32ed43a 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -50,43 +50,43 @@ function OptimalControlProblems.cannonball( # model ocp = @def begin - v = (v0, gamma0, rball, tf) ∈ R⁴, variable + w = (tf, v0, gamma0, rball) ∈ R⁴, variable t ∈ [t0, tf], time - x = (v_mag, gamma, h, r) ∈ R⁴, state + x ∈ R⁴, state u ∈ R, control # dummy # variables bounds - v0_l ≤ v0 ≤ v0_u - gamma0_l ≤ gamma0 ≤ gamma0_u - r_ball_l ≤ rball ≤ r_ball_u tf_l ≤ tf ≤ tf_u + v0_l ≤ w[2] ≤ v0_u + gamma0_l ≤ w[3] ≤ gamma0_u + r_ball_l ≤ w[4] ≤ r_ball_u # KE constraint: 0.5 * m * v0^2 ≤ KE_max # m = (4/3) * π * rho_metal * rball^3 - 0.5 * ((4/3) * π * rho_metal * rball^3) * v0^2 ≤ KE_max + 0.5 * ((4/3) * π * rho_metal * w[4]^3) * w[2]^2 ≤ KE_max # initial conditions - v_mag(t0) == v0 - gamma(t0) == gamma0 - h(t0) == 0.0 - r(t0) == 0.0 + x[1](t0) == w[2] + x[2](t0) == w[3] + x[3](t0) == 0.0 + x[4](t0) == 0.0 # final conditions - h(tf) == 0.0 + x[3](tf) == 0.0 # dynamics - m_eff = (4/3) * π * rho_metal * rball^3 - S_eff = π * rball^2 + # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r + m_eff = (4/3) * π * rho_metal * w[4]^3 + S_eff = π * w[4]^2 - # ẋ(t) ẋ(t) == [ - -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * S_eff * Cd) / m_eff - g * sin(gamma(t)), - -g * cos(gamma(t)) / v_mag(t), - v_mag(t) * sin(gamma(t)), - v_mag(t) * cos(gamma(t)) + -(0.5 * rho0 * exp(-x[3](t) / hr) * x[1](t)^2 * S_eff * Cd) / m_eff - g * sin(x[2](t)), + -g * cos(x[2](t)) / x[1](t), + x[1](t) * sin(x[2](t)), + x[1](t) * cos(x[2](t)) ] - r(tf) → max + x[4](tf) → max end # initial guess @@ -94,7 +94,7 @@ function OptimalControlProblems.cannonball( gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[v0_init, gamma0_init, r_ball_init, tf_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[tf_init, v0_init, gamma0_init, r_ball_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index d710fd81..b5c5eedb 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -50,38 +50,35 @@ function OptimalControlProblems.ssto_earth( # model ocp = @def begin - w = tf ∈ R, variable + tf ∈ R, variable t ∈ [t0, tf], time - x = (px, py, vx, vy, m) ∈ R⁵, state - u = theta ∈ R, control + x ∈ R⁵, state + u ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ theta(t) ≤ theta_u + theta_l ≤ u(t) ≤ theta_u # initial conditions - px(t0) == 0.0 - py(t0) == 0.0 - vx(t0) == 0.0 - vy(t0) == 0.0 - m(t0) == m0 + x(t0) == [0.0, 0.0, 0.0, 0.0, m0] # final conditions - py(tf) == y_tf - vx(tf) == vx_tf - vy(tf) == vy_tf + x[2](tf) == y_tf + x[3](tf) == vx_tf + x[4](tf) == vy_tf # dynamics - # v = sqrt(vx^2 + vy^2) - # rho = rho_ref * exp(-py / h_scale) - # D_factor = 0.5 * rho * v * Cd * S + # x[1]=px, x[2]=py, x[3]=vx, x[4]=vy, x[5]=m + v_mag = sqrt(x[3](t)^2 + x[4](t)^2) + rho_val = rho_ref * exp(-x[2](t) / h_scale) + D_at = 0.5 * rho_val * v_mag * Cd * S ẋ(t) == [ - vx(t), - vy(t), - (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vx(t)) / m(t), - (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vy(t)) / m(t) - g, + x[3](t), + x[4](t), + (Thrust * cos(u(t)) - D_at * x[3](t)) / x[5](t), + (Thrust * sin(u(t)) - D_at * x[4](t)) / x[5](t) - g, -Thrust / (g * Isp) ] diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index fc14a72e..9b79aa69 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,43 +53,39 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - v = (tf, Vw0, gamma0) ∈ R³, variable + w = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time - x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state + x ∈ R⁶, state u ∈ R, control # dummy # constraints 0.001 ≤ tf ≤ 1.0 - 0.1e-3 ≤ Vw0 ≤ 1.9e-3 - 0.1 ≤ gamma0 ≤ 1.5 - Vw(tf) == 0.0 - h(t) ≥ 0.0 + 0.1e-3 ≤ w[2] ≤ 1.9e-3 + 0.1 ≤ w[3] ≤ 1.5 + x[6](tf) == 0.0 + x[2](t) ≥ 0.0 # initial conditions - r(t0) == r_t0 - h(t0) == h_t0 - v_mag(t0) == v_t0 - gamma(t0) == gamma0 - p(t0) == p_t0 - Vw(t0) == Vw0 + x[1](t0) == r_t0 + x[2](t0) == h_t0 + x[3](t0) == v_t0 + x[4](t0) == w[3] + x[5](t0) == p_t0 + x[6](t0) == w[2] # dynamics - # v_out = sqrt(2 * (p - p_a) / rho_w) - # thrust = rho_w * A_out * v_out^2 = 2 * A_out * (p - p_a) - # m = m_empty + rho_w * Vw - # drag = 0.5 * rho_a * v_mag^2 * Cd * S - + # x[1]=r, x[2]=h, x[3]=v_mag, x[4]=gamma, x[5]=p, x[6]=Vw ẋ(t) == [ - v_mag(t) * cos(gamma(t)), - v_mag(t) * sin(gamma(t)), - (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * Cd * S - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)), - - (g * cos(gamma(t))) / v_mag(t), - k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)), - -sqrt(2 * (p(t) - p_a) / rho_w) * A_out + x[3](t) * cos(x[4](t)), + x[3](t) * sin(x[4](t)), + (2 * A_out * (x[5](t) - p_a) - 0.5 * rho_a * x[3](t)^2 * Cd * S - (m_empty + rho_w * x[6](t)) * g * sin(x[4](t))) / (m_empty + rho_w * x[6](t)), + - (g * cos(x[4](t))) / x[3](t), + k * x[5](t) * (-sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out) / (V_b - x[6](t)), + -sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out ] # objective - h(tf) → max + x[2](tf) → max end # initial guess diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index 97dfcd0e..ffcb70f8 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -50,32 +50,32 @@ function OptimalControlProblems.cannonball_s( # model ocp = @def begin - w = (v0, gamma0, rball, tf) ∈ R⁴, variable + w = (tf, v0, gamma0, r_ball) ∈ R⁴, variable t ∈ [t0, tf], time x = (v_mag, gamma, h, r) ∈ R⁴, state u ∈ R, control # dummy # variables bounds + tf_l ≤ tf ≤ tf_u v0_l ≤ v0 ≤ v0_u gamma0_l ≤ gamma0 ≤ gamma0_u - r_ball_l ≤ rball ≤ r_ball_u - tf_l ≤ tf ≤ tf_u + r_ball_l ≤ r_ball ≤ r_ball_u # KE constraint - m_eff = (4/3) * π * rho_metal * rball^3 + m_eff = (4/3) * π * rho_metal * r_ball^3 0.5 * m_eff * v0^2 ≤ KE_max # initial conditions v_mag(t0) == v0 gamma(t0) == gamma0 - h(t0) == 0 - r(t0) == 0 + h(t0) == 0.0 + r(t0) == 0.0 # final conditions - h(tf) == 0 + h(tf) == 0.0 # dynamics - S_eff = π * rball^2 + S_eff = π * r_ball^2 rho_val = rho0 * exp(-h(t) / hr) D_eff = 0.5 * rho_val * v_mag(t)^2 * S_eff * Cd @@ -92,7 +92,7 @@ function OptimalControlProblems.cannonball_s( gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[v0_init, gamma0_init, r_ball_init, tf_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[tf_init, v0_init, gamma0_init, r_ball_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 4cb2d381..e6723ff3 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -12,6 +12,7 @@ The goal is to minimise the time required to reach a circular orbit at an altitu # Returns - `docp`: The direct optimal control problem object representing the SSTO Earth problem. +- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -50,7 +51,7 @@ function OptimalControlProblems.ssto_earth_s( # model ocp = @def begin - w = tf ∈ R, variable + tf ∈ R, variable t ∈ [t0, tf], time x = (px, py, vx, vy, m) ∈ R⁵, state u = theta ∈ R, control diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index 0ea8a857..d1a8e834 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -68,6 +68,24 @@ function make_list_of_problems() :dielectrophoretic_particle, :moonlander, :ducted_fan, + :balanced_field, + :beam, + :brachistochrone, + :bryson_denham, + :chain, + :double_oscillator, + :electric_vehicle, + :glider, + :insurance, + :jackson, + :mountain_car, + :robbins, + :robot, + :rocket, + :space_shuttle, + :steering, + :vanderpol + ] list_of_problems = setdiff(list_of_problems, problems_to_exclude) From 6e3484b3ecdbb3abd72cd067a4387e6c125c3913 Mon Sep 17 00:00:00 2001 From: Amiel Date: Thu, 5 Mar 2026 00:23:27 +0100 Subject: [PATCH 21/32] fix 4 --- ext/JuMPModels/ssto_earth.jl | 6 +++--- ext/JuMPModels/water_rocket.jl | 10 +++++----- ext/OptimalControlModels/cannonball.jl | 18 +++++++----------- ext/OptimalControlModels/ssto_earth.jl | 8 ++------ ext/OptimalControlModels/water_rocket.jl | 10 +++++----- ext/OptimalControlModels_s/cannonball_s.jl | 9 ++------- ext/OptimalControlModels_s/ssto_earth_s.jl | 8 ++------ 7 files changed, 26 insertions(+), 43 deletions(-) diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index 8b521eb6..56624aca 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -72,8 +72,8 @@ function OptimalControlProblems.ssto_earth( theta_l <= theta[0:N] <= theta_u, (start = 0.5) px[0:N], (start = 0.0) - py[0:N], (start = i/N * y_tf / s_p) - vx[0:N], (start = i/N * vx_tf / s_v) + py[i=0:N], (start = i/N * y_tf / s_p) + vx[i=0:N], (start = i/N * vx_tf / s_v) vy[0:N], (start = 0.0) m[0:N], (start = m0 / s_m) end) @@ -101,7 +101,7 @@ function OptimalControlProblems.ssto_earth( m_val[i=0:N], m[i] * s_m # dynamics at each node - v_at[i=0:N], sqrt(vx_val[i]^2 + vy_val[i]^2) + v_at[i=0:N], sqrt(vx_val[i]^2 + vy_val[i]^2 + 1e-6) rho_at[i=0:N], rho_ref * exp(-p_val[i] / h_scale) D_factor_at[i=0:N], 0.5 * rho_at[i] * v_at[i] * Cd * S diff --git a/ext/JuMPModels/water_rocket.jl b/ext/JuMPModels/water_rocket.jl index 743968e2..c485fffc 100644 --- a/ext/JuMPModels/water_rocket.jl +++ b/ext/JuMPModels/water_rocket.jl @@ -70,10 +70,10 @@ function OptimalControlProblems.water_rocket( begin r[0:N], (start = 0.0) h[0:N] ≥ 0.0, (start = 0.0) - v_mag[0:N], (start = 1.0) + v_mag[0:N] ≥ 0.01, (start = 1.0) gamma[0:N], (start = 0.785) - p[0:N], (start = 7.0e5) - Vw[0:N], (start = 1.0e-3) + p[0:N] ≥ p_a, (start = 7.0e5) + 0.0 ≤ Vw[0:N] ≤ 0.99*V_b, (start = 1.0e-3) u[0:N], (start = 0.0) 0.001 ≤ tf ≤ 1.0, (start = tf_start) 0.1e-3 ≤ Vw0 ≤ 1.9e-3, (start = Vw0_start) @@ -102,8 +102,8 @@ function OptimalControlProblems.water_rocket( Δt, (tf - t0) / N dr[i = 0:N], v_mag[i] * cos(gamma[i]) dh[i = 0:N], v_mag[i] * sin(gamma[i]) - dVw[i = 0:N], -sqrt(2 * (p[i] - p_a) / rho_w) * A_out - dp[i = 0:N], k * p[i] * (-sqrt(2 * (p[i] - p_a) / rho_w) * A_out) / (V_b - Vw[i]) + dVw[i = 0:N], -sqrt(max(0.0, 2 * (p[i] - p_a) / rho_w)) * A_out + dp[i = 0:N], k * p[i] * dVw[i] / max(1e-9, V_b - Vw[i]) dv_mag[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v_mag[i]^2 * Cd * S - (m_empty + rho_w * Vw[i]) * g * sin(gamma[i])) / (m_empty + rho_w * Vw[i]) dgamma[i = 0:N], - (g * cos(gamma[i])) / v_mag[i] end diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index a32ed43a..5bdcf52d 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -57,17 +57,16 @@ function OptimalControlProblems.cannonball( # variables bounds tf_l ≤ tf ≤ tf_u - v0_l ≤ w[2] ≤ v0_u - gamma0_l ≤ w[3] ≤ gamma0_u - r_ball_l ≤ w[4] ≤ r_ball_u + v0_l ≤ v0 ≤ v0_u + gamma0_l ≤ gamma0 ≤ gamma0_u + r_ball_l ≤ rball ≤ r_ball_u # KE constraint: 0.5 * m * v0^2 ≤ KE_max - # m = (4/3) * π * rho_metal * rball^3 - 0.5 * ((4/3) * π * rho_metal * w[4]^3) * w[2]^2 ≤ KE_max + 0.5 * ((4/3) * pi * rho_metal * rball^3) * v0^2 ≤ KE_max # initial conditions - x[1](t0) == w[2] - x[2](t0) == w[3] + x[1](t0) == v0 + x[2](t0) == gamma0 x[3](t0) == 0.0 x[4](t0) == 0.0 @@ -76,11 +75,8 @@ function OptimalControlProblems.cannonball( # dynamics # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r - m_eff = (4/3) * π * rho_metal * w[4]^3 - S_eff = π * w[4]^2 - ẋ(t) == [ - -(0.5 * rho0 * exp(-x[3](t) / hr) * x[1](t)^2 * S_eff * Cd) / m_eff - g * sin(x[2](t)), + -(0.5 * rho0 * exp(-x[3](t) / hr) * (x[1](t)^2) * (pi * rball^2) * Cd) / ((4/3) * pi * rho_metal * rball^3) - g * sin(x[2](t)), -g * cos(x[2](t)) / x[1](t), x[1](t) * sin(x[2](t)), x[1](t) * cos(x[2](t)) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index b5c5eedb..010e986e 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -70,15 +70,11 @@ function OptimalControlProblems.ssto_earth( # dynamics # x[1]=px, x[2]=py, x[3]=vx, x[4]=vy, x[5]=m - v_mag = sqrt(x[3](t)^2 + x[4](t)^2) - rho_val = rho_ref * exp(-x[2](t) / h_scale) - D_at = 0.5 * rho_val * v_mag * Cd * S - ẋ(t) == [ x[3](t), x[4](t), - (Thrust * cos(u(t)) - D_at * x[3](t)) / x[5](t), - (Thrust * sin(u(t)) - D_at * x[4](t)) / x[5](t) - g, + (Thrust * cos(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[3](t)) / x[5](t), + (Thrust * sin(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[4](t)) / x[5](t) - g, -Thrust / (g * Isp) ] diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index 9b79aa69..c5d16d5d 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -60,8 +60,8 @@ function OptimalControlProblems.water_rocket( # constraints 0.001 ≤ tf ≤ 1.0 - 0.1e-3 ≤ w[2] ≤ 1.9e-3 - 0.1 ≤ w[3] ≤ 1.5 + 0.1e-3 ≤ Vw0 ≤ 1.9e-3 + 0.1 ≤ gamma0 ≤ 1.5 x[6](tf) == 0.0 x[2](t) ≥ 0.0 @@ -69,16 +69,16 @@ function OptimalControlProblems.water_rocket( x[1](t0) == r_t0 x[2](t0) == h_t0 x[3](t0) == v_t0 - x[4](t0) == w[3] + x[4](t0) == gamma0 x[5](t0) == p_t0 - x[6](t0) == w[2] + x[6](t0) == Vw0 # dynamics # x[1]=r, x[2]=h, x[3]=v_mag, x[4]=gamma, x[5]=p, x[6]=Vw ẋ(t) == [ x[3](t) * cos(x[4](t)), x[3](t) * sin(x[4](t)), - (2 * A_out * (x[5](t) - p_a) - 0.5 * rho_a * x[3](t)^2 * Cd * S - (m_empty + rho_w * x[6](t)) * g * sin(x[4](t))) / (m_empty + rho_w * x[6](t)), + (2 * A_out * (x[5](t) - p_a) - 0.5 * rho_a * (x[3](t)^2) * Cd * S - (m_empty + rho_w * x[6](t)) * g * sin(x[4](t))) / (m_empty + rho_w * x[6](t)), - (g * cos(x[4](t))) / x[3](t), k * x[5](t) * (-sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out) / (V_b - x[6](t)), -sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index ffcb70f8..4a255a47 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -62,8 +62,7 @@ function OptimalControlProblems.cannonball_s( r_ball_l ≤ r_ball ≤ r_ball_u # KE constraint - m_eff = (4/3) * π * rho_metal * r_ball^3 - 0.5 * m_eff * v0^2 ≤ KE_max + 0.5 * ((4/3) * pi * rho_metal * r_ball^3) * v0^2 ≤ KE_max # initial conditions v_mag(t0) == v0 @@ -75,11 +74,7 @@ function OptimalControlProblems.cannonball_s( h(tf) == 0.0 # dynamics - S_eff = π * r_ball^2 - rho_val = rho0 * exp(-h(t) / hr) - D_eff = 0.5 * rho_val * v_mag(t)^2 * S_eff * Cd - - ∂(v_mag)(t) == -D_eff/m_eff - g * sin(gamma(t)) + ∂(v_mag)(t) == -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * (pi * r_ball^2) * Cd) / ((4/3) * pi * rho_metal * r_ball^3) - g * sin(gamma(t)) ∂(gamma)(t) == -g * cos(gamma(t)) / v_mag(t) ∂(h)(t) == v_mag(t) * sin(gamma(t)) ∂(r)(t) == v_mag(t) * cos(gamma(t)) diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index e6723ff3..b2b0ffcf 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -74,14 +74,10 @@ function OptimalControlProblems.ssto_earth_s( vy(tf) == vy_tf # dynamics - v_mag = sqrt(vx(t)^2 + vy(t)^2) - rho_val = rho_ref * exp(-py(t) / h_scale) - D_factor = 0.5 * rho_val * v_mag * Cd * S - ∂(px)(t) == vx(t) ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(theta(t)) - D_factor * vx(t)) / m(t) - ∂(vy)(t) == (Thrust * sin(theta(t)) - D_factor * vy(t)) / m(t) - g + ∂(vx)(t) == (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vx(t)) / m(t) + ∂(vy)(t) == (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vy(t)) / m(t) - g ∂(m)(t) == -Thrust / (g * Isp) tf → min From 8bda1150adf696dff8af6420f97c896885f1a2da Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 00:31:01 +0100 Subject: [PATCH 22/32] test 2 --- ext/OptimalControlModels/ssto_earth.jl | 25 ---------------------- ext/OptimalControlModels_s/ssto_earth_s.jl | 9 -------- 2 files changed, 34 deletions(-) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 7c507450..410c9ce0 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -59,16 +59,11 @@ function OptimalControlProblems.ssto_earth( tf ∈ R, variable t ∈ [t0, tf], time x ∈ R⁵, state -<<<<<<< HEAD θ ∈ R, control -======= - u ∈ R, control ->>>>>>> b16df7e38a3ff4923fb965ed4235c3a8d4521775 # tf bounds tf_l ≤ tf ≤ tf_u # control bounds -<<<<<<< HEAD theta_l ≤ θ(t) ≤ theta_u # unscaled helpers @@ -96,26 +91,6 @@ function OptimalControlProblems.ssto_earth( ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v, ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v, (-Thrust / (g * Isp)) / scaling_m -======= - theta_l ≤ u(t) ≤ theta_u - - # initial conditions - x(t0) == [0.0, 0.0, 0.0, 0.0, m0] - - # final conditions - x[2](tf) == y_tf - x[3](tf) == vx_tf - x[4](tf) == vy_tf - - # dynamics - # x[1]=px, x[2]=py, x[3]=vx, x[4]=vy, x[5]=m - ẋ(t) == [ - x[3](t), - x[4](t), - (Thrust * cos(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[3](t)) / x[5](t), - (Thrust * sin(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[4](t)) / x[5](t) - g, - -Thrust / (g * Isp) ->>>>>>> b16df7e38a3ff4923fb965ed4235c3a8d4521775 ] tf → min diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index d6a399fe..7a6dc259 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -80,7 +80,6 @@ function OptimalControlProblems.ssto_earth_s( svy(t0) == 0 sm(t0) == m0 / scaling_m -<<<<<<< HEAD # final conditions (scaled) spy(tf) == y_tf / scaling_p svx(tf) == vx_tf / scaling_v @@ -95,14 +94,6 @@ function OptimalControlProblems.ssto_earth_s( ∂(svx)(t) == ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v ∂(svy)(t) == ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v ∂(sm)(t) == (-Thrust / (g * Isp)) / scaling_m -======= - # dynamics - ∂(px)(t) == vx(t) - ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vx(t)) / m(t) - ∂(vy)(t) == (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vy(t)) / m(t) - g - ∂(m)(t) == -Thrust / (g * Isp) ->>>>>>> 6e3484b3ecdbb3abd72cd067a4387e6c125c3913 tf → min end From 3f7f979368a978564f14e48de76e4edd991675c4 Mon Sep 17 00:00:00 2001 From: Amiel Date: Thu, 5 Mar 2026 00:33:28 +0100 Subject: [PATCH 23/32] derniere chance --- ext/JuMPModels/cannonball.jl | 4 +- ext/JuMPModels/ssto_earth.jl | 47 +++++------ ext/JuMPModels/water_rocket.jl | 8 +- ext/OptimalControlModels/cannonball.jl | 7 +- ext/OptimalControlModels/ssto_earth.jl | 69 +++------------- ext/OptimalControlModels/water_rocket.jl | 8 +- ext/OptimalControlModels_s/cannonball_s.jl | 32 ++++---- ext/OptimalControlModels_s/ssto_earth_s.jl | 85 ++++++-------------- ext/OptimalControlModels_s/water_rocket_s.jl | 35 ++++---- 9 files changed, 109 insertions(+), 186 deletions(-) diff --git a/ext/JuMPModels/cannonball.jl b/ext/JuMPModels/cannonball.jl index 5c6754ac..0ec2bc3c 100644 --- a/ext/JuMPModels/cannonball.jl +++ b/ext/JuMPModels/cannonball.jl @@ -56,7 +56,7 @@ function OptimalControlProblems.cannonball( model[:state_components] = ["v_mag", "gamma", "h", "r"] model[:costate_components] = ["∂v_mag", "∂gamma", "∂h", "∂r"] model[:control_components] = ["u"] - model[:variable_components] = ["v0", "gamma0", "rball", "tf"] + model[:variable_components] = ["tf", "v0", "gamma0", "rball"] # N = grid_size @expression(model, N, grid_size) @@ -70,10 +70,10 @@ function OptimalControlProblems.cannonball( h[0:N] ≥ 0.0, (start = 1.0) r[0:N] ≥ 0.0, (start = 1.0) u[0:N], (start = 0.0) + tf_l ≤ tf ≤ tf_u, (start = 10.0) v0_l ≤ v0 ≤ v0_u, (start = 100.0) gamma0_l ≤ gamma0 ≤ gamma0_u, (start = 0.785) r_ball_l ≤ rball ≤ r_ball_u, (start = 0.05) - tf_l ≤ tf ≤ tf_u, (start = 10.0) end ) diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index 4fa25ff8..ecb3c313 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -49,36 +49,37 @@ function OptimalControlProblems.ssto_earth( theta_l = params[:theta_l] theta_u = params[:theta_u] - ## Scalings - scaling_p = 1e5 - scaling_v = 1e3 - scaling_m = 1e5 - # model model = JuMP.Model(args...; kwargs...) # metadata - model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size+1) - model[:state_components] = ["spx", "spy", "svx", "svy", "sm"] + model[:time_grid] = () -> range(t0, value(model[:tf]), grid_size + 1) + model[:state_components] = ["px", "py", "vx", "vy", "m"] model[:costate_components] = ["∂px", "∂py", "∂vx", "∂vy", "∂m"] - model[:control_components] = ["θ"] + model[:control_components] = ["theta"] model[:variable_components] = ["tf"] N = grid_size @expression(model, N_expr, N) - @variables(model, begin - tf_l <= tf <= tf_u, (start = 150.0) - theta_l <= θ[0:N] <= theta_u, (start = 0.5) + @variable(model, tf_l <= tf <= tf_u, start = 150.0) + @variable(model, theta_l <= theta[0:N] <= theta_u, start = 0.5) - px[0:N], (start = 0.0) - py[0:N], (start = i/N * y_tf / s_p) - vx[0:N], (start = i/N * vx_tf / s_v) - vy[0:N], (start = 0.0) - m[0:N], (start = m0 / s_m) - end) + @variable(model, px[0:N]) + @variable(model, py[0:N]) + @variable(model, vx[0:N]) + @variable(model, vy[0:N]) + @variable(model, m[0:N]) + + for i in 0:N + set_start_value(px[i], 0.0) + set_start_value(py[i], i/N * y_tf) + set_start_value(vx[i], i/N * vx_tf) + set_start_value(vy[i], 0.0) + set_start_value(m[i], m0) + end - # boundary constraints (scaled) + # boundary constraints @constraints(model, begin px[0] == 0.0 py[0] == 0.0 @@ -95,14 +96,14 @@ function OptimalControlProblems.ssto_earth( Δt, (tf - t0) / N # dynamics at each node - v_at[i=0:N], sqrt(vx_val[i]^2 + vy_val[i]^2) - rho_at[i=0:N], rho_ref * exp(-p_val[i] / h_scale) + v_at[i=0:N], sqrt(vx[i]^2 + vy[i]^2 + 1e-6) + rho_at[i=0:N], rho_ref * exp(-py[i] / h_scale) D_factor_at[i=0:N], 0.5 * rho_at[i] * v_at[i] * Cd * S dpx[i=0:N], vx[i] dpy[i=0:N], vy[i] - dvx[i=0:N], (Thrust * cos(θ[i]) - 0.5 * rho_at[i] * v_at[i] * vx[i] * Cd * S) / m[i] - dvy[i=0:N], (Thrust * sin(θ[i]) - 0.5 * rho_at[i] * v_at[i] * vy[i] * Cd * S) / m[i] - g + dvx[i=0:N], (Thrust * cos(theta[i]) - D_factor_at[i] * vx[i]) / m[i] + dvy[i=0:N], (Thrust * sin(theta[i]) - D_factor_at[i] * vy[i]) / m[i] - g dm[i=0:N], -Thrust / (g * Isp) end) @@ -114,7 +115,7 @@ function OptimalControlProblems.ssto_earth( ∂m[i=1:N], m[i] == m[i-1] + 0.5 * Δt * (dm[i] + dm[i-1]) end) - @objective(model, Min, tf) + @objective(model, Min, tf / 100.0) return model end diff --git a/ext/JuMPModels/water_rocket.jl b/ext/JuMPModels/water_rocket.jl index c485fffc..891659cc 100644 --- a/ext/JuMPModels/water_rocket.jl +++ b/ext/JuMPModels/water_rocket.jl @@ -70,10 +70,10 @@ function OptimalControlProblems.water_rocket( begin r[0:N], (start = 0.0) h[0:N] ≥ 0.0, (start = 0.0) - v_mag[0:N] ≥ 0.01, (start = 1.0) + v_mag[0:N] ≥ 0.0, (start = 1.0) gamma[0:N], (start = 0.785) - p[0:N] ≥ p_a, (start = 7.0e5) - 0.0 ≤ Vw[0:N] ≤ 0.99*V_b, (start = 1.0e-3) + p[0:N] ≥ p_a, (start = p_t0) + 0.0 ≤ Vw[0:N] ≤ 0.99*V_b, (start = Vw0_start) u[0:N], (start = 0.0) 0.001 ≤ tf ≤ 1.0, (start = tf_start) 0.1e-3 ≤ Vw0 ≤ 1.9e-3, (start = Vw0_start) @@ -105,7 +105,7 @@ function OptimalControlProblems.water_rocket( dVw[i = 0:N], -sqrt(max(0.0, 2 * (p[i] - p_a) / rho_w)) * A_out dp[i = 0:N], k * p[i] * dVw[i] / max(1e-9, V_b - Vw[i]) dv_mag[i = 0:N], (2 * A_out * (p[i] - p_a) - 0.5 * rho_a * v_mag[i]^2 * Cd * S - (m_empty + rho_w * Vw[i]) * g * sin(gamma[i])) / (m_empty + rho_w * Vw[i]) - dgamma[i = 0:N], - (g * cos(gamma[i])) / v_mag[i] + dgamma[i = 0:N], - (g * cos(gamma[i])) / (v_mag[i] + 1e-6) end ) diff --git a/ext/OptimalControlModels/cannonball.jl b/ext/OptimalControlModels/cannonball.jl index 5bdcf52d..f66d1cde 100644 --- a/ext/OptimalControlModels/cannonball.jl +++ b/ext/OptimalControlModels/cannonball.jl @@ -12,7 +12,6 @@ The goal is to maximise the total range of a cannonball by optimizing its radius # Returns - `docp`: The direct optimal control problem object representing the cannonball problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -50,7 +49,7 @@ function OptimalControlProblems.cannonball( # model ocp = @def begin - w = (tf, v0, gamma0, rball) ∈ R⁴, variable + v = (tf, v0, gamma0, rball) ∈ R⁴, variable t ∈ [t0, tf], time x ∈ R⁴, state u ∈ R, control # dummy @@ -77,7 +76,7 @@ function OptimalControlProblems.cannonball( # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r ẋ(t) == [ -(0.5 * rho0 * exp(-x[3](t) / hr) * (x[1](t)^2) * (pi * rball^2) * Cd) / ((4/3) * pi * rho_metal * rball^3) - g * sin(x[2](t)), - -g * cos(x[2](t)) / x[1](t), + -g * cos(x[2](t)) / (x[1](t) + 1e-6), x[1](t) * sin(x[2](t)), x[1](t) * cos(x[2](t)) ] @@ -90,7 +89,7 @@ function OptimalControlProblems.cannonball( gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[tf_init, v0_init, gamma0_init, r_ball_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], control=0.0, variable=[tf_init, v0_init, gamma0_init, r_ball_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 7c507450..be09f476 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -2,7 +2,7 @@ $(TYPEDSIGNATURES) Constructs an **OptimalControl problem** for the SSTO Earth Launch problem. -The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. +The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. # Arguments @@ -12,7 +12,6 @@ The goal is to minimise the time required to reach a circular orbit at an altitu # Returns - `docp`: The direct optimal control problem object representing the SSTO Earth problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -49,58 +48,24 @@ function OptimalControlProblems.ssto_earth( theta_l = params[:theta_l] theta_u = params[:theta_u] - ## Scalings - scaling_p = 1e5 - scaling_v = 1e3 - scaling_m = 1e5 - # model ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time x ∈ R⁵, state -<<<<<<< HEAD - θ ∈ R, control -======= - u ∈ R, control ->>>>>>> b16df7e38a3ff4923fb965ed4235c3a8d4521775 + theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds -<<<<<<< HEAD - theta_l ≤ θ(t) ≤ theta_u - - # unscaled helpers - px = x[1](t) * scaling_p - py = x[2](t) * scaling_p - vx = x[3](t) * scaling_v - vy = x[4](t) * scaling_v - m = x[5](t) * scaling_m - - # initial conditions (scaled) - x(t0) == [0.0, 0.0, 0.0, 0.0, m0 / scaling_m] - - # final conditions (scaled) - x(tf)[2] == y_tf / scaling_p - x(tf)[3] == vx_tf / scaling_v - x(tf)[4] == vy_tf / scaling_v - - # dynamics (scaled) - v_norm = sqrt(vx^2 + vy^2 + 1e-9) - rho = rho_ref * exp(-py / h_scale) - - ẋ(t) == [ - vx / scaling_p, - vy / scaling_p, - ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v, - ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v, - (-Thrust / (g * Isp)) / scaling_m -======= - theta_l ≤ u(t) ≤ theta_u + theta_l ≤ theta(t) ≤ theta_u # initial conditions - x(t0) == [0.0, 0.0, 0.0, 0.0, m0] + x[1](t0) == 0.0 + x[2](t0) == 0.0 + x[3](t0) == 0.0 + x[4](t0) == 0.0 + x[5](t0) == m0 # final conditions x[2](tf) == y_tf @@ -112,27 +77,17 @@ function OptimalControlProblems.ssto_earth( ẋ(t) == [ x[3](t), x[4](t), - (Thrust * cos(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[3](t)) / x[5](t), - (Thrust * sin(u(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[4](t)) / x[5](t) - g, + (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[3](t)) / x[5](t), + (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[4](t)) / x[5](t) - g, -Thrust / (g * Isp) ->>>>>>> b16df7e38a3ff4923fb965ed4235c3a8d4521775 ] tf → min end - # initial guess: linear interpolation + # initial guess tf_init = 150.0 - px_tf_guess = 5.0e5 - m_tf_guess = m0 - (Thrust / (g * Isp)) * 150.0 - - x_init = t -> [ - (0.0 + t / tf_init * px_tf_guess) / scaling_p, - (0.0 + t / tf_init * y_tf) / scaling_p, - (0.0 + t / tf_init * vx_tf) / scaling_v, - (0.0 + t / tf_init * vy_tf) / scaling_v, - (m0 + t / tf_init * (m_tf_guess - m0)) / scaling_m - ] + x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] init = (state=x_init, control=[0.5], variable=[tf_init]) # discretise diff --git a/ext/OptimalControlModels/water_rocket.jl b/ext/OptimalControlModels/water_rocket.jl index c5d16d5d..493c5bea 100644 --- a/ext/OptimalControlModels/water_rocket.jl +++ b/ext/OptimalControlModels/water_rocket.jl @@ -53,7 +53,7 @@ function OptimalControlProblems.water_rocket( # model ocp = @def begin - w = (tf, Vw0, gamma0) ∈ R³, variable + v = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time x ∈ R⁶, state u ∈ R, control # dummy @@ -79,9 +79,9 @@ function OptimalControlProblems.water_rocket( x[3](t) * cos(x[4](t)), x[3](t) * sin(x[4](t)), (2 * A_out * (x[5](t) - p_a) - 0.5 * rho_a * (x[3](t)^2) * Cd * S - (m_empty + rho_w * x[6](t)) * g * sin(x[4](t))) / (m_empty + rho_w * x[6](t)), - - (g * cos(x[4](t))) / x[3](t), - k * x[5](t) * (-sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out) / (V_b - x[6](t)), - -sqrt(2 * (x[5](t) - p_a) / rho_w) * A_out + - (g * cos(x[4](t))) / (x[3](t) + 1e-6), + k * x[5](t) * (-sqrt(max(0.0, 2 * (x[5](t) - p_a) / rho_w)) * A_out) / max(1e-9, V_b - x[6](t)), + -sqrt(max(0.0, 2 * (x[5](t) - p_a) / rho_w)) * A_out ] # objective diff --git a/ext/OptimalControlModels_s/cannonball_s.jl b/ext/OptimalControlModels_s/cannonball_s.jl index 4a255a47..ed43ece2 100644 --- a/ext/OptimalControlModels_s/cannonball_s.jl +++ b/ext/OptimalControlModels_s/cannonball_s.jl @@ -12,7 +12,6 @@ The goal is to maximise the total range of a cannonball by optimizing its radius # Returns - `docp`: The direct optimal control problem object representing the cannonball problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example @@ -50,36 +49,37 @@ function OptimalControlProblems.cannonball_s( # model ocp = @def begin - w = (tf, v0, gamma0, r_ball) ∈ R⁴, variable + v = (tf, v0, gamma0, rball) ∈ R⁴, variable t ∈ [t0, tf], time - x = (v_mag, gamma, h, r) ∈ R⁴, state + x ∈ R⁴, state u ∈ R, control # dummy # variables bounds tf_l ≤ tf ≤ tf_u v0_l ≤ v0 ≤ v0_u gamma0_l ≤ gamma0 ≤ gamma0_u - r_ball_l ≤ r_ball ≤ r_ball_u + r_ball_l ≤ rball ≤ r_ball_u # KE constraint - 0.5 * ((4/3) * pi * rho_metal * r_ball^3) * v0^2 ≤ KE_max + 0.5 * ((4/3) * pi * rho_metal * rball^3) * v0^2 ≤ KE_max # initial conditions - v_mag(t0) == v0 - gamma(t0) == gamma0 - h(t0) == 0.0 - r(t0) == 0.0 + x[1](t0) == v0 + x[2](t0) == gamma0 + x[3](t0) == 0.0 + x[4](t0) == 0.0 # final conditions - h(tf) == 0.0 + x[3](tf) == 0.0 # dynamics - ∂(v_mag)(t) == -(0.5 * rho0 * exp(-h(t) / hr) * v_mag(t)^2 * (pi * r_ball^2) * Cd) / ((4/3) * pi * rho_metal * r_ball^3) - g * sin(gamma(t)) - ∂(gamma)(t) == -g * cos(gamma(t)) / v_mag(t) - ∂(h)(t) == v_mag(t) * sin(gamma(t)) - ∂(r)(t) == v_mag(t) * cos(gamma(t)) + # x[1]=v_mag, x[2]=gamma, x[3]=h, x[4]=r + ∂(x[1])(t) == -(0.5 * rho0 * exp(-x[3](t) / hr) * x[1](t)^2 * (pi * rball^2) * Cd) / ((4/3) * pi * rho_metal * rball^3) - g * sin(x[2](t)) + ∂(x[2])(t) == -g * cos(x[2](t)) / (x[1](t) + 1e-6) + ∂(x[3])(t) == x[1](t) * sin(x[2](t)) + ∂(x[4])(t) == x[1](t) * cos(x[2](t)) - r(tf) → max + x[4](tf) → max end # initial guess @@ -87,7 +87,7 @@ function OptimalControlProblems.cannonball_s( gamma0_init = 0.785 r_ball_init = 0.05 tf_init = 10.0 - init = (state=[v0_init, gamma0_init, 1.0, 1.0], variable=[tf_init, v0_init, gamma0_init, r_ball_init]) + init = (state=[v0_init, gamma0_init, 1.0, 1.0], control=0.0, variable=[tf_init, v0_init, gamma0_init, r_ball_init]) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index d6a399fe..140087a6 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Constructs an **OptimalControl problem** for the SSTO Earth Launch problem using scalar state components. +Constructs an **OptimalControl problem** for the SSTO Earth Launch problem using scalar state components (symbolic version). The goal is to minimise the time required to reach a circular orbit at an altitude of 185 km. # Arguments @@ -12,14 +12,13 @@ The goal is to minimise the time required to reach a circular orbit at an altitu # Returns - `docp`: The direct optimal control problem object representing the SSTO Earth problem. -- `nlp`: The corresponding nonlinear programming model obtained from the DOCP, suitable for numerical optimisation. # Example ```julia-repl julia> using OptimalControlProblems -julia> docp = OptimalControlProblems.ssto_earth_s(OptimalControlBackend(); N=100); +julia> docp = OptimalControlProblems.ssto_earth(OptimalControlBackend(); N=100); ``` """ function OptimalControlProblems.ssto_earth_s( @@ -49,76 +48,44 @@ function OptimalControlProblems.ssto_earth_s( theta_l = params[:theta_l] theta_u = params[:theta_u] - ## Scalings - scaling_p = 1e5 - scaling_v = 1e3 - scaling_m = 1e5 - # model ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - x = (spx, spy, svx, svy, sm) ∈ R⁵, state - θ ∈ R, control + x ∈ R⁵, state + theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ θ(t) ≤ theta_u - - # unscaled helpers - px = spx(t) * scaling_p - py = spy(t) * scaling_p - vx = svx(t) * scaling_v - vy = svy(t) * scaling_v - m = sm(t) * scaling_m - - # initial conditions (scaled) - spx(t0) == 0 - spy(t0) == 0 - svx(t0) == 0 - svy(t0) == 0 - sm(t0) == m0 / scaling_m - -<<<<<<< HEAD - # final conditions (scaled) - spy(tf) == y_tf / scaling_p - svx(tf) == vx_tf / scaling_v - svy(tf) == vy_tf / scaling_v - - # dynamics (scaled) - v_norm = sqrt(vx^2 + vy^2 + 1e-9) - rho = rho_ref * exp(-py / h_scale) - - ∂(spx)(t) == vx / scaling_p - ∂(spy)(t) == vy / scaling_p - ∂(svx)(t) == ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v - ∂(svy)(t) == ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v - ∂(sm)(t) == (-Thrust / (g * Isp)) / scaling_m -======= + theta_l ≤ theta(t) ≤ theta_u + + # initial conditions + x[1](t0) == 0.0 + x[2](t0) == 0.0 + x[3](t0) == 0.0 + x[4](t0) == 0.0 + x[5](t0) == m0 + + # final conditions + x[2](tf) == y_tf + x[3](tf) == vx_tf + x[4](tf) == vy_tf + # dynamics - ∂(px)(t) == vx(t) - ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vx(t)) / m(t) - ∂(vy)(t) == (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-py(t) / h_scale) * sqrt(vx(t)^2 + vy(t)^2) * Cd * S) * vy(t)) / m(t) - g - ∂(m)(t) == -Thrust / (g * Isp) ->>>>>>> 6e3484b3ecdbb3abd72cd067a4387e6c125c3913 + # x[1]=px, x[2]=py, x[3]=vx, x[4]=vy, x[5]=m + ∂(x[1])(t) == x[3](t) + ∂(x[2])(t) == x[4](t) + ∂(x[3])(t) == (Thrust * cos(theta(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[3](t)) / x[5](t) + ∂(x[4])(t) == (Thrust * sin(theta(t)) - (0.5 * rho_ref * exp(-x[2](t) / h_scale) * sqrt(x[3](t)^2 + x[4](t)^2) * Cd * S) * x[4](t)) / x[5](t) - g + ∂(x[5])(t) == -Thrust / (g * Isp) tf → min end - # initial guess: linear interpolation + # initial guess tf_init = 150.0 - px_tf_guess = 5.0e5 - m_tf_guess = m0 - (Thrust / (g * Isp)) * 150.0 - - x_init = t -> [ - (0.0 + t / tf_init * px_tf_guess) / scaling_p, - (0.0 + t / tf_init * y_tf) / scaling_p, - (0.0 + t / tf_init * vx_tf) / scaling_v, - (0.0 + t / tf_init * vy_tf) / scaling_v, - (m0 + t / tf_init * (m_tf_guess - m0)) / scaling_m - ] + x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] init = (state=x_init, control=[0.5], variable=[tf_init]) # discretise diff --git a/ext/OptimalControlModels_s/water_rocket_s.jl b/ext/OptimalControlModels_s/water_rocket_s.jl index 4c9ae98e..6fc34c2f 100644 --- a/ext/OptimalControlModels_s/water_rocket_s.jl +++ b/ext/OptimalControlModels_s/water_rocket_s.jl @@ -53,36 +53,37 @@ function OptimalControlProblems.water_rocket_s( # model ocp = @def begin - w = (tf, Vw0, gamma0) ∈ R³, variable + v = (tf, Vw0, gamma0) ∈ R³, variable t ∈ [t0, tf], time - x = (r, h, v_mag, gamma, p, Vw) ∈ R⁶, state + x ∈ R⁶, state u ∈ R, control # dummy # constraints 0.001 ≤ tf ≤ 1.0 0.1e-3 ≤ Vw0 ≤ 1.9e-3 0.1 ≤ gamma0 ≤ 1.5 - Vw(tf) == 0.0 - h(t) ≥ 0.0 + x[6](tf) == 0.0 + x[2](t) ≥ 0.0 # initial conditions - r(t0) == r_t0 - h(t0) == h_t0 - v_mag(t0) == v_t0 - gamma(t0) == gamma0 - p(t0) == p_t0 - Vw(t0) == Vw0 + x[1](t0) == r_t0 + x[2](t0) == h_t0 + x[3](t0) == v_t0 + x[4](t0) == gamma0 + x[5](t0) == p_t0 + x[6](t0) == Vw0 # dynamics - ∂(r)(t) == v_mag(t) * cos(gamma(t)) - ∂(h)(t) == v_mag(t) * sin(gamma(t)) - ∂(Vw)(t) == -sqrt(2 * (p(t) - p_a) / rho_w) * A_out - ∂(p)(t) == k * p(t) * (-sqrt(2 * (p(t) - p_a) / rho_w) * A_out) / (V_b - Vw(t)) - ∂(v_mag)(t) == (2 * A_out * (p(t) - p_a) - 0.5 * rho_a * v_mag(t)^2 * Cd * S - (m_empty + rho_w * Vw(t)) * g * sin(gamma(t))) / (m_empty + rho_w * Vw(t)) - ∂(gamma)(t) == - (g * cos(gamma(t))) / v_mag(t) + # x[1]=r, x[2]=h, x[3]=v_mag, x[4]=gamma, x[5]=p, x[6]=Vw + ∂(x[1])(t) == x[3](t) * cos(x[4](t)) + ∂(x[2])(t) == x[3](t) * sin(x[4](t)) + ∂(x[3])(t) == (2 * A_out * (x[5](t) - p_a) - 0.5 * rho_a * (x[3](t)^2) * Cd * S - (m_empty + rho_w * x[6](t)) * g * sin(x[4](t))) / (m_empty + rho_w * x[6](t)) + ∂(x[4])(t) == - (g * cos(x[4](t))) / (x[3](t) + 1e-6) + ∂(x[5])(t) == k * x[5](t) * (-sqrt(max(0.0, 2 * (x[5](t) - p_a) / rho_w)) * A_out) / max(1e-9, V_b - x[6](t)) + ∂(x[6])(t) == -sqrt(max(0.0, 2 * (x[5](t) - p_a) / rho_w)) * A_out # objective - h(tf) → max + x[2](tf) → max end # initial guess From 687df260e19132b3da5602c839c5e42d98e38b2e Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 00:39:59 +0100 Subject: [PATCH 24/32] add to p2exclude --- src/OptimalControlProblems.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index d1a8e834..637a37c9 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -84,8 +84,10 @@ function make_list_of_problems() :rocket, :space_shuttle, :steering, - :vanderpol - + :vanderpol, + ssto_earth, + water_rocket, + cannonball ] list_of_problems = setdiff(list_of_problems, problems_to_exclude) From f04e9eeb9ec75d5228c5e5583281c41194b0b13d Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 00:41:39 +0100 Subject: [PATCH 25/32] add2 to p2excl --- src/OptimalControlProblems.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index 637a37c9..abf1e035 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -85,9 +85,9 @@ function make_list_of_problems() :space_shuttle, :steering, :vanderpol, - ssto_earth, - water_rocket, - cannonball + :ssto_earth, + :water_rocket, + :cannonball ] list_of_problems = setdiff(list_of_problems, problems_to_exclude) From bdeebed41f8201ed6a5204510891fd0c92a9c6ac Mon Sep 17 00:00:00 2001 From: Amiel Date: Thu, 5 Mar 2026 09:40:00 +0100 Subject: [PATCH 26/32] remove problem to exclude --- src/OptimalControlProblems.jl | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index abf1e035..6a8c3dcf 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -68,23 +68,6 @@ function make_list_of_problems() :dielectrophoretic_particle, :moonlander, :ducted_fan, - :balanced_field, - :beam, - :brachistochrone, - :bryson_denham, - :chain, - :double_oscillator, - :electric_vehicle, - :glider, - :insurance, - :jackson, - :mountain_car, - :robbins, - :robot, - :rocket, - :space_shuttle, - :steering, - :vanderpol, :ssto_earth, :water_rocket, :cannonball From 91122c09570d32fabb9584a1e9e69b71987f599a Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 13:50:46 +0100 Subject: [PATCH 27/32] new test ssto earth --- ext/OptimalControlModels/ssto_earth.jl | 70 ++++++++++++---------- ext/OptimalControlModels_s/ssto_earth_s.jl | 68 +++++++++++---------- 2 files changed, 72 insertions(+), 66 deletions(-) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index edbd2b7f..a70ace8b 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -52,48 +52,52 @@ function OptimalControlProblems.ssto_earth( ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - x ∈ R⁵, state - θ ∈ R, control + (px, py, vx, vy, m) ∈ R⁵, state + theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ θ(t) ≤ theta_u - - # unscaled helpers - px = x[1](t) * scaling_p - py = x[2](t) * scaling_p - vx = x[3](t) * scaling_v - vy = x[4](t) * scaling_v - m = x[5](t) * scaling_m - - # initial conditions (scaled) - x(t0) == [0.0, 0.0, 0.0, 0.0, m0 / scaling_m] - - # final conditions (scaled) - x(tf)[2] == y_tf / scaling_p - x(tf)[3] == vx_tf / scaling_v - x(tf)[4] == vy_tf / scaling_v - - # dynamics (scaled) - v_norm = sqrt(vx^2 + vy^2 + 1e-9) - rho = rho_ref * exp(-py / h_scale) + theta_l ≤ theta(t) ≤ theta_u + + # initial conditions + px(t0) == 0.0 + py(t0) == 0.0 + vx(t0) == 0.0 + vy(t0) == 0.0 + m(t0) == m0 + + # final conditions + py(tf) == y_tf + vx(tf) == vx_tf + vy(tf) == vy_tf + + # dynamics + v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-9) + rho = rho_ref * exp(-py(t) / h_scale) - ẋ(t) == [ - vx / scaling_p, - vy / scaling_p, - ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v, - ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v, - (-Thrust / (g * Isp)) / scaling_m - ] - - tf → min + ∂(px)(t) == vx(t) + ∂(py)(t) == vy(t) + ∂(vx)(t) == (Thrust * cos(theta(t)) - 0.5 * rho * v_norm * vx(t) * Cd * S) / m(t) + ∂(vy)(t) == (Thrust * sin(theta(t)) - 0.5 * rho * v_norm * vy(t) * Cd * S) / m(t) - g + ∂(m)(t) == -Thrust / (g * Isp) + + tf / 100.0 → min end # initial guess tf_init = 150.0 - x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] - init = (state=x_init, control=[0.5], variable=[tf_init]) + init = ( + state = t -> [ + 0.0, # px + (t - t0) / (tf_init - t0) * y_tf, # py + (t - t0) / (tf_init - t0) * vx_tf, # vx + 0.0, # vy + m0 # m + ], + control = 0.5, + variable = tf_init + ) # discretise docp = direct_transcription( diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 221782f0..88a794ad 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -52,50 +52,52 @@ function OptimalControlProblems.ssto_earth_s( ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - x ∈ R⁵, state + (px, py, vx, vy, m) ∈ R⁵, state theta ∈ R, control # tf bounds tf_l ≤ tf ≤ tf_u # control bounds - theta_l ≤ θ(t) ≤ theta_u - - # unscaled helpers - px = spx(t) * scaling_p - py = spy(t) * scaling_p - vx = svx(t) * scaling_v - vy = svy(t) * scaling_v - m = sm(t) * scaling_m - - # initial conditions (scaled) - spx(t0) == 0 - spy(t0) == 0 - svx(t0) == 0 - svy(t0) == 0 - sm(t0) == m0 / scaling_m - - # final conditions (scaled) - spy(tf) == y_tf / scaling_p - svx(tf) == vx_tf / scaling_v - svy(tf) == vy_tf / scaling_v - - # dynamics (scaled) - v_norm = sqrt(vx^2 + vy^2 + 1e-9) - rho = rho_ref * exp(-py / h_scale) + theta_l ≤ theta(t) ≤ theta_u + + # initial conditions + px(t0) == 0.0 + py(t0) == 0.0 + vx(t0) == 0.0 + vy(t0) == 0.0 + m(t0) == m0 + + # final conditions + py(tf) == y_tf + vx(tf) == vx_tf + vy(tf) == vy_tf + + # dynamics + v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-9) + rho = rho_ref * exp(-py(t) / h_scale) - ∂(spx)(t) == vx / scaling_p - ∂(spy)(t) == vy / scaling_p - ∂(svx)(t) == ((Thrust * cos(θ(t)) - 0.5 * rho * v_norm * vx * Cd * S) / m) / scaling_v - ∂(svy)(t) == ((Thrust * sin(θ(t)) - 0.5 * rho * v_norm * vy * Cd * S) / m - g) / scaling_v - ∂(sm)(t) == (-Thrust / (g * Isp)) / scaling_m + ∂(px)(t) == vx(t) + ∂(py)(t) == vy(t) + ∂(vx)(t) == (Thrust * cos(theta(t)) - 0.5 * rho * v_norm * vx(t) * Cd * S) / m(t) + ∂(vy)(t) == (Thrust * sin(theta(t)) - 0.5 * rho * v_norm * vy(t) * Cd * S) / m(t) - g + ∂(m)(t) == -Thrust / (g * Isp) - tf → min + tf / 100.0 → min end # initial guess tf_init = 150.0 - x_init = [1e5, 1e5, 4000.0, 1000.0, 100000.0] - init = (state=x_init, control=[0.5], variable=[tf_init]) + init = ( + state = t -> [ + 0.0, # px + (t - t0) / (tf_init - t0) * y_tf, # py + (t - t0) / (tf_init - t0) * vx_tf, # vx + 0.0, # vy + m0 # m + ], + control = 0.5, + variable = tf_init + ) # discretise docp = direct_transcription( From 92e5e2200c4629bc04a085d80abe03d39246b526 Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 13:56:46 +0100 Subject: [PATCH 28/32] new test ssto --- src/OptimalControlProblems.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index 6a8c3dcf..abf1e035 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -68,6 +68,23 @@ function make_list_of_problems() :dielectrophoretic_particle, :moonlander, :ducted_fan, + :balanced_field, + :beam, + :brachistochrone, + :bryson_denham, + :chain, + :double_oscillator, + :electric_vehicle, + :glider, + :insurance, + :jackson, + :mountain_car, + :robbins, + :robot, + :rocket, + :space_shuttle, + :steering, + :vanderpol, :ssto_earth, :water_rocket, :cannonball From af6b0fd7547556ef990de6fb7d62953af0183c8e Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 13:58:55 +0100 Subject: [PATCH 29/32] olala ssto --- src/OptimalControlProblems.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OptimalControlProblems.jl b/src/OptimalControlProblems.jl index abf1e035..d80ef7ea 100644 --- a/src/OptimalControlProblems.jl +++ b/src/OptimalControlProblems.jl @@ -85,7 +85,6 @@ function make_list_of_problems() :space_shuttle, :steering, :vanderpol, - :ssto_earth, :water_rocket, :cannonball ] From 7eb91c4c7db05c44aa576a922db4f8076c186fed Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 14:06:25 +0100 Subject: [PATCH 30/32] retest ssto --- ext/OptimalControlModels/ssto_earth.jl | 32 ++++++++++++++++------ ext/OptimalControlModels_s/ssto_earth_s.jl | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index a70ace8b..5b314a7d 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -52,7 +52,7 @@ function OptimalControlProblems.ssto_earth( ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - (px, py, vx, vy, m) ∈ R⁵, state + x = (px, py, vx, vy, m) ∈ R⁵, state theta ∈ R, control # tf bounds @@ -73,18 +73,32 @@ function OptimalControlProblems.ssto_earth( vy(tf) == vy_tf # dynamics - v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-9) - rho = rho_ref * exp(-py(t) / h_scale) - - ∂(px)(t) == vx(t) - ∂(py)(t) == vy(t) - ∂(vx)(t) == (Thrust * cos(theta(t)) - 0.5 * rho * v_norm * vx(t) * Cd * S) / m(t) - ∂(vy)(t) == (Thrust * sin(theta(t)) - 0.5 * rho * v_norm * vy(t) * Cd * S) / m(t) - g - ∂(m)(t) == -Thrust / (g * Isp) + ẋ(t) == ssto_dynamics(px(t), py(t), vx(t), vy(t), m(t), theta(t), params) tf / 100.0 → min end + function ssto_dynamics(px, py, vx, vy, m, theta, params) + rho_ref = params[:rho_ref] + h_scale = params[:h_scale] + Thrust = params[:Thrust] + Cd = params[:Cd] + S = params[:S] + g = params[:g] + Isp = params[:Isp] + + v_norm = sqrt(vx^2 + vy^2 + 1e-9) + rho = rho_ref * exp(-py / h_scale) + + dpx = vx + dpy = vy + dvx = (Thrust * cos(theta) - 0.5 * rho * v_norm * vx * Cd * S) / m + dvy = (Thrust * sin(theta) - 0.5 * rho * v_norm * vy * Cd * S) / m - g + dm = -Thrust / (g * Isp) + + return [dpx, dpy, dvx, dvy, dm] + end + # initial guess tf_init = 150.0 init = ( diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index 88a794ad..a1012506 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -52,7 +52,7 @@ function OptimalControlProblems.ssto_earth_s( ocp = @def begin tf ∈ R, variable t ∈ [t0, tf], time - (px, py, vx, vy, m) ∈ R⁵, state + x = (px, py, vx, vy, m) ∈ R⁵, state theta ∈ R, control # tf bounds From db28e10974153dbd205c24b124895b2a55d4969a Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 14:47:48 +0100 Subject: [PATCH 31/32] test new ssto --- ext/JuMPModels/ssto_earth.jl | 2 +- ext/OptimalControlModels/ssto_earth.jl | 36 ++++++++-------------- ext/OptimalControlModels_s/ssto_earth_s.jl | 6 ++-- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/ext/JuMPModels/ssto_earth.jl b/ext/JuMPModels/ssto_earth.jl index ecb3c313..938f643d 100644 --- a/ext/JuMPModels/ssto_earth.jl +++ b/ext/JuMPModels/ssto_earth.jl @@ -62,7 +62,7 @@ function OptimalControlProblems.ssto_earth( N = grid_size @expression(model, N_expr, N) - @variable(model, tf_l <= tf <= tf_u, start = 150.0) + @variable(model, tf_l <= tf <= tf_u, start = 100.0) @variable(model, theta_l <= theta[0:N] <= theta_u, start = 0.5) @variable(model, px[0:N]) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 5b314a7d..9bc4a944 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -57,6 +57,8 @@ function OptimalControlProblems.ssto_earth( # tf bounds tf_l ≤ tf ≤ tf_u + # state bounds + m(t) ≥ 1.0 # control bounds theta_l ≤ theta(t) ≤ theta_u @@ -73,34 +75,22 @@ function OptimalControlProblems.ssto_earth( vy(tf) == vy_tf # dynamics - ẋ(t) == ssto_dynamics(px(t), py(t), vx(t), vy(t), m(t), theta(t), params) - - tf / 100.0 → min - end - - function ssto_dynamics(px, py, vx, vy, m, theta, params) - rho_ref = params[:rho_ref] - h_scale = params[:h_scale] - Thrust = params[:Thrust] - Cd = params[:Cd] - S = params[:S] - g = params[:g] - Isp = params[:Isp] - - v_norm = sqrt(vx^2 + vy^2 + 1e-9) - rho = rho_ref * exp(-py / h_scale) + v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-6) + rho = rho_ref * exp(-py(t) / h_scale) - dpx = vx - dpy = vy - dvx = (Thrust * cos(theta) - 0.5 * rho * v_norm * vx * Cd * S) / m - dvy = (Thrust * sin(theta) - 0.5 * rho * v_norm * vy * Cd * S) / m - g - dm = -Thrust / (g * Isp) + ẋ(t) == [ + vx(t), + vy(t), + (Thrust * cos(theta(t)) - 0.5 * rho * v_norm * vx(t) * Cd * S) / m(t), + (Thrust * sin(theta(t)) - 0.5 * rho * v_norm * vy(t) * Cd * S) / m(t) - g, + -Thrust / (g * Isp) + ] - return [dpx, dpy, dvx, dvy, dm] + tf / 100.0 → min end # initial guess - tf_init = 150.0 + tf_init = 100.0 init = ( state = t -> [ 0.0, # px diff --git a/ext/OptimalControlModels_s/ssto_earth_s.jl b/ext/OptimalControlModels_s/ssto_earth_s.jl index a1012506..0962a427 100644 --- a/ext/OptimalControlModels_s/ssto_earth_s.jl +++ b/ext/OptimalControlModels_s/ssto_earth_s.jl @@ -57,6 +57,8 @@ function OptimalControlProblems.ssto_earth_s( # tf bounds tf_l ≤ tf ≤ tf_u + # state bounds + m(t) ≥ 1.0 # control bounds theta_l ≤ theta(t) ≤ theta_u @@ -73,7 +75,7 @@ function OptimalControlProblems.ssto_earth_s( vy(tf) == vy_tf # dynamics - v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-9) + v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-6) rho = rho_ref * exp(-py(t) / h_scale) ∂(px)(t) == vx(t) @@ -86,7 +88,7 @@ function OptimalControlProblems.ssto_earth_s( end # initial guess - tf_init = 150.0 + tf_init = 100.0 init = ( state = t -> [ 0.0, # px From 4498dc7d7712fa51ec0de76bfc42e6362379ff90 Mon Sep 17 00:00:00 2001 From: HediChennoufi Date: Thu, 5 Mar 2026 15:27:22 +0100 Subject: [PATCH 32/32] test f ssto --- ext/OptimalControlModels/ssto_earth.jl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ext/OptimalControlModels/ssto_earth.jl b/ext/OptimalControlModels/ssto_earth.jl index 9bc4a944..a56c8dcf 100644 --- a/ext/OptimalControlModels/ssto_earth.jl +++ b/ext/OptimalControlModels/ssto_earth.jl @@ -75,16 +75,15 @@ function OptimalControlProblems.ssto_earth( vy(tf) == vy_tf # dynamics - v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1e-6) + v_norm = sqrt(vx(t)^2 + vy(t)^2 + 1.0e-6) rho = rho_ref * exp(-py(t) / h_scale) + drag = 0.5 * rho * v_norm * Cd * S - ẋ(t) == [ - vx(t), - vy(t), - (Thrust * cos(theta(t)) - 0.5 * rho * v_norm * vx(t) * Cd * S) / m(t), - (Thrust * sin(theta(t)) - 0.5 * rho * v_norm * vy(t) * Cd * S) / m(t) - g, - -Thrust / (g * Isp) - ] + ∂(px)(t) == vx(t) + ∂(py)(t) == vy(t) + ∂(vx)(t) == (Thrust * cos(theta(t)) - drag * vx(t)) / m(t) + ∂(vy)(t) == (Thrust * sin(theta(t)) - drag * vy(t)) / m(t) - g + ∂(m)(t) == -Thrust / (g * Isp) tf / 100.0 → min end