Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/OptimalControl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ include(joinpath(@__DIR__, "solve", "descriptive.jl"))

export methods # non useful since it is already in Base

end
end
4 changes: 1 addition & 3 deletions src/helpers/descriptive_routing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,7 @@ function _build_components_from_routed(
)
# Resolve method with parameter information as early as possible
families = _descriptive_families()
resolved = CTBase.Orchestration.resolve_method(
complete_description, families, registry
)
resolved = CTBase.Orchestration.resolve_method(complete_description, families, registry)

# Build strategies using resolved method
discretizer = CTBase.Orchestration.build_strategy_from_resolved(
Expand Down
8 changes: 6 additions & 2 deletions src/helpers/strategy_builders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ skipping directly to processing the solver component.
- Delegates to solver processing
- Terminal case in the recursion chain
"""
function _build_partial_tuple(::Nothing, solver::Union{CTSolvers.Solvers.AbstractNLPSolver,Nothing})
function _build_partial_tuple(
::Nothing, solver::Union{CTSolvers.Solvers.AbstractNLPSolver,Nothing}
)
return _build_partial_tuple(solver)
end

Expand Down Expand Up @@ -330,5 +332,7 @@ function _build_or_use_strategy(
registry::CTBase.Strategies.StrategyRegistry,
)
# Build path: construct from resolved method
return CTBase.Orchestration.build_strategy_from_resolved(resolved, family_name, families, registry)
return CTBase.Orchestration.build_strategy_from_resolved(
resolved, family_name, families, registry
)
end
3 changes: 1 addition & 2 deletions test/helpers/capabilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ is_cuda_on() = CUDA.functional()
`CUDSS` are all in the session. CPU-runnable: it says nothing about whether a
device exists.
"""
gpu_extension_armed() =
Base.get_extension(CTSolvers, :CTSolversMadNLPGPU) !== nothing
gpu_extension_armed() = Base.get_extension(CTSolvers, :CTSolversMadNLPGPU) !== nothing

"""
on_gpu_runner()
Expand Down
5 changes: 3 additions & 2 deletions test/helpers/reexport.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ end
match. Use it when the owner is awkward to name (e.g. `Base` generics extended
downstream).
"""
same_object(mod::Module, name::Symbol, ref) =
isdefined(mod, name) && getfield(mod, name) === ref
function same_object(mod::Module, name::Symbol, ref)
return isdefined(mod, name) && getfield(mod, name) === ref
end

end # module
2 changes: 1 addition & 1 deletion test/problems/beam.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Return the beam problem as a [`TestProblem`](@ref).
function Beam(form::Symbol=:abstract)
check_form(form)
return cached(:beam, form, ()) do
form === :abstract ? _beam_abstract() : _beam_functional()
return form === :abstract ? _beam_abstract() : _beam_functional()
end
end

Expand Down
5 changes: 2 additions & 3 deletions test/problems/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,8 @@ Throw a readable `ArgumentError` on an unknown form rather than letting it
fall through to a `MethodError` three frames down.
"""
function check_form(form::Symbol)
form in FORMS || throw(
ArgumentError("unknown form $(repr(form)); expected one of $(FORMS)")
)
form in FORMS ||
throw(ArgumentError("unknown form $(repr(form)); expected one of $(FORMS)"))
return form
end

Expand Down
4 changes: 2 additions & 2 deletions test/problems/control_free.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Growth-rate estimation: fit `ẋ = p·x`, `x(0) = 2` to the analytical data
function ExponentialGrowth(form::Symbol=:abstract)
check_form(form)
return cached(:exponential_growth, form, ()) do
form === :abstract ? _exp_growth_abstract() : _exp_growth_functional()
return form === :abstract ? _exp_growth_abstract() : _exp_growth_functional()
end
end

Expand Down Expand Up @@ -101,7 +101,7 @@ minimising `ω²`. The analytical solution is `ω = π/2`, so the objective is
function HarmonicOscillator(form::Symbol=:abstract)
check_form(form)
return cached(:harmonic_oscillator, form, ()) do
form === :abstract ? _harmonic_abstract() : _harmonic_functional()
return form === :abstract ? _harmonic_abstract() : _harmonic_functional()
end
end

Expand Down
19 changes: 12 additions & 7 deletions test/problems/double_integrator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ function _di_energy_cons_shoot_builder(ocp, d)

f_interior = OptimalControl.Flow(ocp, (x, p) -> p[2]; hamiltonian_type)
f_boundary = OptimalControl.Flow(
ocp, (x, p) -> 0.0; constraint=(x, u) -> g(x), multiplier=(x, p) -> μ(p),
ocp,
(x, p) -> 0.0;
constraint=(x, u) -> g(x),
multiplier=(x, p) -> μ(p),
hamiltonian_type,
)

Expand Down Expand Up @@ -122,7 +125,7 @@ Minimise the final time for `ẋ = (x₂, u)`, `u ∈ [-1, 1]`, from `(-1, 0)` t
function DoubleIntegratorTime(form::Symbol=:abstract)
check_form(form)
return cached(:double_integrator_time, form, ()) do
form === :abstract ? _di_time_abstract() : _di_time_functional()
return form === :abstract ? _di_time_abstract() : _di_time_functional()
end
end

Expand Down Expand Up @@ -234,14 +237,12 @@ which makes this the cheapest shooting fixture in the library.
function DoubleIntegratorEnergy(form::Symbol=:abstract)
check_form(form)
return cached(:double_integrator_energy, form, ()) do
form === :abstract ? _di_energy_abstract() : _di_energy_functional()
return form === :abstract ? _di_energy_abstract() : _di_energy_functional()
end
end

const _DI_ENERGY_OBJ = 6.0
const _DI_ENERGY_DATA = (
x0=[-1.0, 0.0], xf=[0.0, 0.0], t0=0.0, tf=1.0, p0=[12.0, 6.0]
)
const _DI_ENERGY_DATA = (x0=[-1.0, 0.0], xf=[0.0, 0.0], t0=0.0, tf=1.0, p0=[12.0, 6.0])

function _di_energy_abstract()
@def ocp begin
Expand Down Expand Up @@ -329,7 +330,11 @@ boundary arc between `t₁ = 0.25` and `t₂ = 0.75`.
function DoubleIntegratorEnergyConstrained(form::Symbol=:abstract)
check_form(form)
return cached(:double_integrator_energy_constrained, form, ()) do
form === :abstract ? _di_energy_cons_abstract() : _di_energy_cons_functional()
return if form === :abstract
_di_energy_cons_abstract()
else
_di_energy_cons_functional()
end
end
end

Expand Down
11 changes: 6 additions & 5 deletions test/problems/goddard.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ plus the constants the shooting function is written against (`vmax`, `mf`,
function Goddard(form::Symbol=:abstract; vmax=0.1, Tmax=3.5)
check_form(form)
return cached(:goddard, form, (vmax, Tmax)) do
form === :abstract ? _goddard_abstract(; vmax, Tmax) :
_goddard_functional(; vmax, Tmax)
return if form === :abstract
_goddard_abstract(; vmax, Tmax)
else
_goddard_functional(; vmax, Tmax)
end
end
end

Expand Down Expand Up @@ -61,9 +64,7 @@ function _goddard_constants(; vmax, Tmax)
)
tf_ref = 0.20204744057100849

return (;
Cd, β, b, r0, v0, m0, mf, vmax, Tmax, x0, F0, F1, p0, switching_times, tf_ref
)
return (; Cd, β, b, r0, v0, m0, mf, vmax, Tmax, x0, F0, F1, p0, switching_times, tf_ref)
end

"""
Expand Down
6 changes: 5 additions & 1 deletion test/problems/quadrotor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ Return the quadrotor tracking problem as a [`TestProblem`](@ref).
function Quadrotor(form::Symbol=:abstract; T=1, g=9.8, r=0.1)
check_form(form)
return cached(:quadrotor, form, (T, g, r)) do
form === :abstract ? _quadrotor_abstract(; T, g, r) : _quadrotor_functional(; T, g, r)
return if form === :abstract
_quadrotor_abstract(; T, g, r)
else
_quadrotor_functional(; T, g, r)
end
end
end

Expand Down
8 changes: 3 additions & 5 deletions test/problems/registry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ rather than a `KeyError`.
"""
function build(name::Symbol, form::Symbol=:abstract; kwargs...)
check_form(form)
haskey(_CONSTRUCTORS, name) || throw(
ArgumentError("unknown problem $(repr(name)); expected one of $(PROBLEMS)")
)
haskey(_CONSTRUCTORS, name) ||
throw(ArgumentError("unknown problem $(repr(name)); expected one of $(PROBLEMS)"))
return _CONSTRUCTORS[name](form; kwargs...)
end

Expand Down Expand Up @@ -85,5 +84,4 @@ end
Names only — cheaper than [`problems_for`](@ref) when the problems themselves
are not needed (building them expands `@def`).
"""
problem_names_for(method::Symbol) =
[pb.name for pb in problems_for(method)]
problem_names_for(method::Symbol) = [pb.name for pb in problems_for(method)]
10 changes: 6 additions & 4 deletions test/problems/transfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ Return the orbital transfer problem as a [`TestProblem`](@ref).
function Transfer(form::Symbol=:abstract; Tmax=60)
check_form(form)
return cached(:transfer, form, (Tmax,)) do
form === :abstract ? _transfer_abstract(; Tmax) : _transfer_functional(; Tmax)
return if form === :abstract
_transfer_abstract(; Tmax)
else
_transfer_functional(; Tmax)
end
end
end

Expand Down Expand Up @@ -168,9 +172,7 @@ function _transfer_functional(; Tmax)
ocp = CTModels.Building.build(pre)

tf_i = 15
init = (
state=t -> x0 + (xf - x0) * t / tf_i, control=[0.1, 0.5, 0.0], variable=tf_i
)
init = (state=t -> x0 + (xf - x0) * t / tf_i, control=[0.1, 0.5, 0.0], variable=tf_i)

return TestProblem(:transfer, :functional, ocp, _TRANSFER_OBJ, init, c)
end
4 changes: 1 addition & 3 deletions test/suite/builders/test_options_forwarding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ function test_options_forwarding()
# shows up in the summary, a missing branch does not.
Test.@testset "backend (CUDA)" begin
gpu_x0_is_cuarray() = begin
modeler = OptimalControl.Exa{OptimalControl.GPU}(
backend=CUDA.CUDABackend()
)
modeler = OptimalControl.Exa{OptimalControl.GPU}(backend=CUDA.CUDABackend())
nlp = OptimalControl.nlp_model(docp, normalized_init, modeler)
# With CUDA backend, x0 should be a CUDA array
nlp.meta.x0 isa CUDA.CuArray
Expand Down
10 changes: 6 additions & 4 deletions test/suite/flows/test_flow_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ function build_labelled()
CTModels.Building.time!(pre; t0=T0, tf=TF)
CTModels.Building.state!(pre, 2)
CTModels.Building.control!(pre, 1)
CTModels.Building.dynamics!(pre, (r, t, x, u, v) -> (r[1] = x[2]; r[2] = u[1]; nothing))
CTModels.Building.dynamics!(pre, (r, t, x, u, v) -> (r[1]=x[2]; r[2]=u[1]; nothing))
CTModels.Building.objective!(pre, :min; lagrange=(t, x, u, v) -> 0.5 * u[1]^2)
CTModels.Building.constraint!(
pre,
:path;
f=(r, t, x, u, v) -> (r[1] = x[2]; nothing),
f=(r, t, x, u, v) -> (r[1]=x[2]; nothing),
lb=[-Inf],
ub=[VMAX],
label=:vmax,
Expand All @@ -71,7 +71,7 @@ function build_nonfixed()
CTModels.Building.time!(pre; t0=T0, indf=1)
CTModels.Building.state!(pre, 2)
CTModels.Building.control!(pre, 1)
CTModels.Building.dynamics!(pre, (r, t, x, u, v) -> (r[1] = x[2]; r[2] = u[1]; nothing))
CTModels.Building.dynamics!(pre, (r, t, x, u, v) -> (r[1]=x[2]; r[2]=u[1]; nothing))
CTModels.Building.objective!(pre, :min; mayer=(x0, xf, v) -> v[1])
CTModels.Building.time_dependence!(pre; autonomous=true)
return CTModels.Building.build(pre)
Expand Down Expand Up @@ -206,7 +206,9 @@ function test_flow_api()
end

Test.@testset "multiplier accepts a Data object" begin
f = Flow(ocp, (x, p) -> 0.0; constraint=:vmax, multiplier=Multiplier((x, p) -> p[1]))
f = Flow(
ocp, (x, p) -> 0.0; constraint=:vmax, multiplier=Multiplier((x, p) -> p[1])
)
Test.@test f(T0, X0, P0, TF) isa Tuple
end

Expand Down
13 changes: 8 additions & 5 deletions test/suite/flows/test_gpu_routing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,13 @@ function test_gpu_routing()
)

for (id, family) in (
(:adnlp, M), (:exa, M),
(:ipopt, N), (:madnlp, N), (:uno, N), (:madncl, N), (:knitro, N),
(:adnlp, M),
(:exa, M),
(:ipopt, N),
(:madnlp, N),
(:uno, N),
(:madncl, N),
(:knitro, N),
)
Test.@testset "$id" begin
params = S.available_parameters(id, family, registry)
Expand Down Expand Up @@ -149,9 +154,7 @@ function test_gpu_routing()
end

Test.@testset "an unknown method is rejected" begin
Test.@test_throws CTBase.Exceptions.CTException Flow(
vf(); method=:quantum
)
Test.@test_throws CTBase.Exceptions.CTException Flow(vf(); method=:quantum)
end
end

Expand Down
13 changes: 8 additions & 5 deletions test/suite/helpers/test_describe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ const S = CTBase.Strategies
# Every strategy id OptimalControl registers, across both registries.
const ALL_STRATEGIES = (
:collocation, # discretizer
:adnlp, :exa, # NLP modelers
:ipopt, :madnlp, :madncl, :uno, :knitro, # NLP solvers
:adnlp,
:exa, # NLP modelers
:ipopt,
:madnlp,
:madncl,
:uno,
:knitro, # NLP solvers
:di, # AD backend
:sciml, # ODE integrator
)
Expand Down Expand Up @@ -82,9 +87,7 @@ function test_describe()
flow_reg = CTFlows.Flows.flow_registry()

Test.@test isempty(intersect(ids(solve_reg), ids(flow_reg)))
Test.@test isempty(
intersect(keys(solve_reg.families), keys(flow_reg.families))
)
Test.@test isempty(intersect(keys(solve_reg.families), keys(flow_reg.families)))

# `:cpu`/`:gpu` exist on both sides; they must be the same types,
# or the merge would silently pick one binding over the other.
Expand Down
31 changes: 21 additions & 10 deletions test/suite/helpers/test_kwarg_extraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,15 @@ function test_kwarg_extraction()

Test.@testset "Extracts all three component types" begin
kw = pairs((; discretizer=DISC, modeler=MOD, solver=SOL, print_level=0))
Test.@test OptimalControl._extract_kwarg(kw, CTSolvers.DOCP.AbstractDiscretizer) ===
DISC
Test.@test OptimalControl._extract_kwarg(kw, CTSolvers.Modelers.AbstractNLPModeler) ===
MOD
Test.@test OptimalControl._extract_kwarg(kw, CTSolvers.Solvers.AbstractNLPSolver) ===
SOL
Test.@test OptimalControl._extract_kwarg(
kw, CTSolvers.DOCP.AbstractDiscretizer
) === DISC
Test.@test OptimalControl._extract_kwarg(
kw, CTSolvers.Modelers.AbstractNLPModeler
) === MOD
Test.@test OptimalControl._extract_kwarg(
kw, CTSolvers.Solvers.AbstractNLPSolver
) === SOL
end

# ====================================================================
Expand Down Expand Up @@ -291,15 +294,19 @@ function test_kwarg_extraction()
Test.@testset "Multiple matching types in kwargs" begin
# Test when multiple instances of the same type are present
kw = pairs((; discretizer=DISC, another_disc=DISC))
result = OptimalControl._extract_kwarg(kw, CTSolvers.DOCP.AbstractDiscretizer)
result = OptimalControl._extract_kwarg(
kw, CTSolvers.DOCP.AbstractDiscretizer
)
Test.@test result === DISC # Should return the first match
end

Test.@testset "Complex nested types" begin
# Test with more complex types
kw = pairs((; discretizer=DISC, some_string="hello", some_number=42))

result1 = OptimalControl._extract_kwarg(kw, CTSolvers.DOCP.AbstractDiscretizer)
result1 = OptimalControl._extract_kwarg(
kw, CTSolvers.DOCP.AbstractDiscretizer
)
result2 = OptimalControl._extract_kwarg(kw, String)
result3 = OptimalControl._extract_kwarg(kw, Int)

Expand Down Expand Up @@ -352,7 +359,9 @@ function test_kwarg_extraction()

# Extract components
disc = OptimalControl._extract_kwarg(kw, CTSolvers.DOCP.AbstractDiscretizer)
mod = OptimalControl._extract_kwarg(kw, CTSolvers.Modelers.AbstractNLPModeler)
mod = OptimalControl._extract_kwarg(
kw, CTSolvers.Modelers.AbstractNLPModeler
)
sol = OptimalControl._extract_kwarg(kw, CTSolvers.Solvers.AbstractNLPSolver)

Test.@test disc === DISC
Expand Down Expand Up @@ -381,7 +390,9 @@ function test_kwarg_extraction()
))

disc = OptimalControl._extract_kwarg(kw, CTSolvers.DOCP.AbstractDiscretizer)
mod = OptimalControl._extract_kwarg(kw, CTSolvers.Modelers.AbstractNLPModeler)
mod = OptimalControl._extract_kwarg(
kw, CTSolvers.Modelers.AbstractNLPModeler
)
sol = OptimalControl._extract_kwarg(kw, CTSolvers.Solvers.AbstractNLPSolver)

Test.@test isnothing(disc)
Expand Down
Loading