The logo
The OptimalControl.jl logo is not a drawing. It is the solution of an optimal control problem, an energy-minimal low-thrust orbit transfer, solved once and repeated by symmetry.
A spacecraft spirals from a low circular orbit out to a higher one under continuous weak thrust. Two-body dynamics do not care which way is up, so the same optimal transfer, rotated by
This page rebuilds that figure from scratch. Besides OptimalControl and NLPModelsIpopt, it uses CairoMakie and Colors for the figures: add them to your environment to run it.
The transfer problem
Normalised two-body dynamics (gravitational parameter
and the control
using OptimalControl
using NLPModelsIpopt
r0 = 1.1 # departure orbit radius
rf = 2.2 # target orbit radius
tf = 2π # transfer duration — sets how far the trajectory winds
ocp = @def begin
t ∈ [0, tf], time
x ∈ R⁴, state
u ∈ R², control
x(0) == [r0, 0, 0, 1 / sqrt(r0)] # circular orbit of radius r0
x₁(tf)^2 + x₂(tf)^2 == rf^2 # insertion radius
x₃(tf)^2 + x₄(tf)^2 == 1 / rf # circular speed at radius rf
x₁(tf) * x₃(tf) + x₂(tf) * x₄(tf) == 0 # zero radial velocity
ẋ(t) == [ x₃(t),
x₄(t),
-x₁(t) / (x₁(t)^2 + x₂(t)^2)^(3 / 2) + u₁(t),
-x₂(t) / (x₁(t)^2 + x₂(t)^2)^(3 / 2) + u₂(t) ]
∫(u₁(t)^2 + u₂(t)^2) → min
endSolving it
Low-thrust transfers are hard to solve from a cold start, so we warm-start with a crude analytical spiral: the radius grows linearly from
k = (rf - r0) / tf
function spiral_guess(t)
r = r0 + k * t
θ = (2 / k) * (1 / sqrt(r0) - 1 / sqrt(r))
dθ = r^(-3 / 2)
return [r * cos(θ), r * sin(θ),
k * cos(θ) - r * dθ * sin(θ), k * sin(θ) + r * dθ * cos(θ)]
end
sol = solve(ocp; grid_size = 400, display = false,
init = (state = spiral_guess, control = t -> [0, 0]))
xf = state(sol)(tf)
(insertion_radius = sqrt(xf[1]^2 + xf[2]^2),
insertion_speed = sqrt(xf[3]^2 + xf[4]^2),
radial_velocity = xf[1] * xf[3] + xf[2] * xf[4],
thrust_energy = objective(sol))(insertion_radius = 2.200000000045782, insertion_speed = 0.6741998624766781, radial_velocity = -2.2457036230605354e-12, thrust_energy = 0.035208133027451176)The insertion radius and speed reach their targets,
Looking at the solution
The other pages plot solutions with Plots.jl; this one uses Makie instead. Loading a Makie package makes Makie.plot available for a solution (see Plot with Makie):
using CairoMakie
Makie.plot(sol)The thrust
The trajectory in the plane
The logo lives in the
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)]
fig = Figure(size = (460, 460))
ax = Axis(fig[1, 1]; aspect = DataAspect())
lines!(ax, circle(r0); color = (:gray, 0.5))
lines!(ax, circle(rf); color = (:gray, 0.5))
lines!(ax, arc; color = :purple, linewidth = 3)
scatter!(ax, arc[1]; color = :purple, markersize = 14)
figThree-fold symmetry
Two-body dynamics are rotation-equivariant: if
Colour them with the Julia logo palette, drop a dot at each departure point, add the target 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)
# 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,
)
hidedecorations!(ax)
hidespines!(ax)
limits!(ax, -1.32rf, 1.32rf, -1.32rf, 1.32rf)
poly!(ax, circle(rf); color = :white) # white disk behind everything
for (ψ, c) in arms
P = [Point2f(rot(ψ) * p) for p in arc]
lines!(ax, P; color = c, linewidth = 13)
scatter!(ax, P[1]; color = :white, markersize = 80) # halo
scatter!(ax, P[1]; color = c, markersize = 60) # coloured dot
end
lines!(ax, circle(rf); color = jl.blue, linewidth = 13) # target orbit
scatter!(ax, Point2f(0, 0); color = :white, markersize = 50)
scatter!(ax, Point2f(0, 0); color = jl.blue, markersize = 100)
figThat is the figure the site uses as its logo. The published file is drawn with a few more tuning knobs, from exactly this optimal control problem.
See also
Initial guess: the warm start used here, and the other forms.
Plot with Makie and Plot: plotting a solution.
Example gallery: the other worked problems.