Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions docs/src-literate/guided-tour.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ function boundary_energy!(b, x0_, xf_, v)
b[4] = xf_[2] - xf[2]
return nothing
end
constraint!(pre, :boundary; f=boundary_energy!, lb=zeros(4), ub=zeros(4), label=:endpoint)
constraint!(
pre, :boundary;
f=boundary_energy!, lb=zeros(4), ub=zeros(4), label=:endpoint,
)

lagrange_energy(t, x, u, v) = 0.5 * u[1]^2
objective!(pre, :min; lagrange=lagrange_energy)
Expand All @@ -145,7 +148,7 @@ definition(ocp) # the macro records the full DSL expression

#-

has_abstract_definition(ocp_func) # false: functional API stores no abstract definition
has_abstract_definition(ocp_func) # false: no symbolic definition

#md # !!! warning "Two things to keep in mind"
#md # - In the functional API, callbacks are **always vector-valued**: even when the control is scalar, one writes `u[1]` — not `u` — inside `f_energy!` or `lagrange_energy`.
Expand Down Expand Up @@ -274,16 +277,22 @@ using MadNLP
sol_ipopt = solve(goddard; grid_size=250, display=false)
sol_madnlp = solve(goddard, :madnlp; grid_size=250, display=false)

println("Ipopt : r(tf) = ", objective(sol_ipopt), ", ", iterations(sol_ipopt), " iters")
println("MadNLP : r(tf) = ", objective(sol_madnlp), ", ", iterations(sol_madnlp), " iters")
println(
"Ipopt : r(tf) = ", objective(sol_ipopt),
", ", iterations(sol_ipopt), " iters",
)
println(
"MadNLP : r(tf) = ", objective(sol_madnlp),
", ", iterations(sol_madnlp), " iters",
)

# The available methods and their options can be inspected with `methods()` and `describe(:collocation)`; we will not dwell on them here.

# ### Grid continuation by warm-starting
#
# A solution can be passed **directly** as the initial guess of another solve — it is interpolated onto the new grid. This makes discrete continuation trivial and ties back to the initialisation above. On this nonlinear problem it genuinely **pays**: we compare reaching a fine grid of 1000 two ways — a **cold start** (solve `grid_size=1000` directly) versus a **cascade** (solve `grid_size=50` first, then `grid_size=1000` warm-started with that solution).

## solutions computed once, reused for iteration counts and the overlay plot
## computed once, reused for iteration counts and the overlay plot
sol_cold = solve(goddard; grid_size=1000, display=false)

## warm cascade: grid 50 first, then grid 1000 initialised from it
Expand All @@ -306,7 +315,7 @@ plot!(plt, s1000; label="1000")

# How much better is the optimal solution compared to a naive strategy? We simulate **full thrust until fuel depletion, then coast to apogee** — a bang-bang profile with no optimisation, just two ODE integrations with callbacks.

using OrdinaryDiffEqTsit5 # ODE solver (callbacks for the bang-bang simulation)
using OrdinaryDiffEqTsit5 # ODE solver (bang-bang simulation callbacks)

## Phase 1: u = 1, stop when m = mf (fuel depleted)
bang1!(dx, x, p, t) = (dx[:] = F0(x) + F1(x))
Expand Down Expand Up @@ -356,7 +365,10 @@ t_bang = [sol_bang1.t; sol_bang2.t]
r_bang = [sol_bang1[1, :]; sol_bang2[1, :]]

plt_bang = plot(sol_cold; label="optimal", linewidth=2, color=1)
plot!(plt_bang[1], t_bang, r_bang; label="bang-bang", linestyle=:dash, linewidth=2, color=2)
plot!(
plt_bang[1], t_bang, r_bang;
label="bang-bang", linestyle=:dash, linewidth=2, color=2,
)
plot(plt_bang[1]; legend=:bottomright, xlabel="time", ylabel="altitude")

#src ============================================================================
Expand Down Expand Up @@ -456,7 +468,7 @@ p_of_t = costate(direct_sol) # costate as a function of time
p0_guess = p_of_t(t0) # initial costate from the direct method

prob = NonlinearProblem(nle!, p0_guess)
shooting_sol = solve(prob; show_trace=Val(true))
shooting_sol = NonlinearSolve.solve(prob; show_trace=Val(true))
p0_sol = shooting_sol.u

println("costate p0 = ", p0_sol)
Expand Down
12 changes: 8 additions & 4 deletions docs/src/examples/logo.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ orbit to the outer one.
xt = state(sol)
arc = [Point2f(xt(t)[1], xt(t)[2]) for t in range(0, tf; length = 500)]

circle(r) = [Point2f(r * cos(a), r * sin(a)) for a in range(0, 2π; length = 300)]
circle(r) =
[Point2f(r * cos(a), r * sin(a)) for a in range(0, 2π; length = 300)]

fig = Figure(size = (460, 460))
ax = Axis(fig[1, 1]; aspect = DataAspect())
Expand All @@ -141,18 +142,21 @@ orbit in Julia blue and the central body, and the logo is done.
using Colors

rot(ψ) = [cos(ψ) -sin(ψ); sin(ψ) cos(ψ)]
jl = Colors.JULIA_LOGO_COLORS # (red, green, blue, purple)
jl = Colors.JULIA_LOGO_COLORS # (red, green, blue, purple)

# departure points placed as the Julia-logo dots — green on top
arms = [(7π / 6, jl.red), (π / 2, jl.green), (11π / 6, jl.purple)]

fig = Figure(size = (600, 600), backgroundcolor = :transparent)
ax = Axis(fig[1, 1]; aspect = DataAspect(), backgroundcolor = :transparent)
ax = Axis(
fig[1, 1];
aspect = DataAspect(), backgroundcolor = :transparent,
)
hidedecorations!(ax)
hidespines!(ax)
limits!(ax, -1.32rf, 1.32rf, -1.32rf, 1.32rf)

poly!(ax, circle(rf); color = :white) # white disk behind everything
poly!(ax, circle(rf); color = :white) # white disk behind everything

for (ψ, c) in arms
P = [Point2f(rot(ψ) * p) for p in arc]
Expand Down
2 changes: 1 addition & 1 deletion docs/src/modelling/abstract-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ nothing # hide
u ∈ R, control
-1 ≤ v ≤ 1
x₁(0) == -1
x₂(0) - v == 0 # OK: the boundary constraint may involve the variable
x₂(0) - v == 0 # OK: this side may involve the variable
x(1) == [0, 0]
ẋ(t) == [x₂(t), u(t)]
∫(0.5u(t)^2) → min
Expand Down
Loading