diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 01f171f01..b2a8a9475 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -8,7 +8,23 @@ on: pull_request: jobs: - call: + test-cpu-github: uses: control-toolbox/CTActions/.github/workflows/ci.yml@main with: - runs_on: '["ubuntu-latest", "windows-latest"]' + versions: '["1.12"]' + runs_on: '["ubuntu-latest", "macos-latest"]' + runner_type: 'github' + use_ct_registry: true + secrets: + SSH_KEY: ${{ secrets.SSH_KEY }} + + # Job pour le runner self-hosted kkt (GPU/CUDA) + test-gpu-kkt: + uses: control-toolbox/CTActions/.github/workflows/ci.yml@main + with: + versions: '["1"]' + runs_on: '[["kkt"]]' + runner_type: 'self-hosted' + use_ct_registry: true + secrets: + SSH_KEY: ${{ secrets.SSH_KEY }} \ No newline at end of file diff --git a/.github/workflows/Coverage.yml b/.github/workflows/Coverage.yml index dd6506470..b569a5672 100644 --- a/.github/workflows/Coverage.yml +++ b/.github/workflows/Coverage.yml @@ -8,5 +8,8 @@ on: jobs: call: uses: control-toolbox/CTActions/.github/workflows/coverage.yml@main + with: + use_ct_registry: true secrets: codecov-secret: ${{ secrets.CODECOV_TOKEN }} + SSH_KEY: ${{ secrets.SSH_KEY }} diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index e8639ed90..08e925743 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -10,3 +10,7 @@ on: jobs: call: uses: control-toolbox/CTActions/.github/workflows/documentation.yml@main + with: + use_ct_registry: true + secrets: + SSH_KEY: ${{ secrets.SSH_KEY }} diff --git a/.gitignore b/.gitignore index ec2f89bcd..4c9c4dedd 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,8 @@ Manifest.toml */.ipynb_checkpoints # -docs/tmp/ +.tmp/ +.extras/ +#.save/ +.windsurf/ +.reports/ \ No newline at end of file diff --git a/docs/Project.toml b/.save/docs/Project.toml similarity index 95% rename from docs/Project.toml rename to .save/docs/Project.toml index e415fed95..28a5d3ed6 100644 --- a/docs/Project.toml +++ b/.save/docs/Project.toml @@ -28,11 +28,12 @@ Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" [compat] ADNLPModels = "0.8" -CTBase = "0.16" -CTDirect = "0.17" +CTBase = "0.18" +CTDirect = "1" CTFlows = "0.8" -CTModels = "0.6" -CTParser = "0.7" +CTModels = "0.8" +CTParser = "0.8" +CTSolver = "0.2" CommonSolve = "0.2" DataFrames = "1" DifferentiationInterface = "0.7" diff --git a/docs/make.jl b/.save/docs/make.jl similarity index 100% rename from docs/make.jl rename to .save/docs/make.jl diff --git a/docs/src/api-ctbase.md b/.save/docs/src/api-ctbase.md similarity index 100% rename from docs/src/api-ctbase.md rename to .save/docs/src/api-ctbase.md diff --git a/docs/src/api-ctdirect.md b/.save/docs/src/api-ctdirect.md similarity index 100% rename from docs/src/api-ctdirect.md rename to .save/docs/src/api-ctdirect.md diff --git a/docs/src/api-ctflows.md b/.save/docs/src/api-ctflows.md similarity index 100% rename from docs/src/api-ctflows.md rename to .save/docs/src/api-ctflows.md diff --git a/docs/src/api-ctmodels.md b/.save/docs/src/api-ctmodels.md similarity index 100% rename from docs/src/api-ctmodels.md rename to .save/docs/src/api-ctmodels.md diff --git a/docs/src/api-ctparser.md b/.save/docs/src/api-ctparser.md similarity index 100% rename from docs/src/api-ctparser.md rename to .save/docs/src/api-ctparser.md diff --git a/docs/src/api-optimalcontrol-dev.md b/.save/docs/src/api-optimalcontrol-dev.md similarity index 100% rename from docs/src/api-optimalcontrol-dev.md rename to .save/docs/src/api-optimalcontrol-dev.md diff --git a/docs/src/api-optimalcontrol-user.md b/.save/docs/src/api-optimalcontrol-user.md similarity index 100% rename from docs/src/api-optimalcontrol-user.md rename to .save/docs/src/api-optimalcontrol-user.md diff --git a/docs/src/assets/Project.toml b/.save/docs/src/assets/Project.toml similarity index 98% rename from docs/src/assets/Project.toml rename to .save/docs/src/assets/Project.toml index 2d7c27e44..5677beda0 100644 --- a/docs/src/assets/Project.toml +++ b/.save/docs/src/assets/Project.toml @@ -48,7 +48,7 @@ LinearAlgebra = "1" MINPACK = "1" MadNLP = "0.8" MadNLPMumps = "0.5" -NLPModelsIpopt = "0.10" +NLPModelsIpopt = "0.11" NLPModelsKnitro = "0.9" NonlinearSolve = "4" OrdinaryDiffEq = "6" diff --git a/docs/src/assets/affil-lux.jpg b/.save/docs/src/assets/affil-lux.jpg similarity index 100% rename from docs/src/assets/affil-lux.jpg rename to .save/docs/src/assets/affil-lux.jpg diff --git a/docs/src/assets/affil.jpg b/.save/docs/src/assets/affil.jpg similarity index 100% rename from docs/src/assets/affil.jpg rename to .save/docs/src/assets/affil.jpg diff --git a/docs/src/assets/chariot.png b/.save/docs/src/assets/chariot.png similarity index 100% rename from docs/src/assets/chariot.png rename to .save/docs/src/assets/chariot.png diff --git a/docs/src/assets/chariot.svg b/.save/docs/src/assets/chariot.svg similarity index 100% rename from docs/src/assets/chariot.svg rename to .save/docs/src/assets/chariot.svg diff --git a/docs/src/assets/control-toolbox.jpg b/.save/docs/src/assets/control-toolbox.jpg similarity index 100% rename from docs/src/assets/control-toolbox.jpg rename to .save/docs/src/assets/control-toolbox.jpg diff --git a/docs/src/assets/ct-qr-code.svg b/.save/docs/src/assets/ct-qr-code.svg similarity index 100% rename from docs/src/assets/ct-qr-code.svg rename to .save/docs/src/assets/ct-qr-code.svg diff --git a/docs/src/assets/custom.css b/.save/docs/src/assets/custom.css similarity index 100% rename from docs/src/assets/custom.css rename to .save/docs/src/assets/custom.css diff --git a/docs/src/assets/france-2030.png b/.save/docs/src/assets/france-2030.png similarity index 100% rename from docs/src/assets/france-2030.png rename to .save/docs/src/assets/france-2030.png diff --git a/docs/src/assets/goddard-a100.jpg b/.save/docs/src/assets/goddard-a100.jpg similarity index 100% rename from docs/src/assets/goddard-a100.jpg rename to .save/docs/src/assets/goddard-a100.jpg diff --git a/docs/src/assets/goddard-h100.jpg b/.save/docs/src/assets/goddard-h100.jpg similarity index 100% rename from docs/src/assets/goddard-h100.jpg rename to .save/docs/src/assets/goddard-h100.jpg diff --git a/docs/src/assets/jlesc17.jpg b/.save/docs/src/assets/jlesc17.jpg similarity index 100% rename from docs/src/assets/jlesc17.jpg rename to .save/docs/src/assets/jlesc17.jpg diff --git a/docs/src/assets/juliacon-paris-2025.jpg b/.save/docs/src/assets/juliacon-paris-2025.jpg similarity index 100% rename from docs/src/assets/juliacon-paris-2025.jpg rename to .save/docs/src/assets/juliacon-paris-2025.jpg diff --git a/docs/src/assets/juliacon2024.jpg b/.save/docs/src/assets/juliacon2024.jpg similarity index 100% rename from docs/src/assets/juliacon2024.jpg rename to .save/docs/src/assets/juliacon2024.jpg diff --git a/docs/src/assets/juliacon2025.jpg b/.save/docs/src/assets/juliacon2025.jpg similarity index 100% rename from docs/src/assets/juliacon2025.jpg rename to .save/docs/src/assets/juliacon2025.jpg diff --git a/docs/src/assets/quadrotor-a100.jpg b/.save/docs/src/assets/quadrotor-a100.jpg similarity index 100% rename from docs/src/assets/quadrotor-a100.jpg rename to .save/docs/src/assets/quadrotor-a100.jpg diff --git a/docs/src/assets/quadrotor-h100.jpg b/.save/docs/src/assets/quadrotor-h100.jpg similarity index 100% rename from docs/src/assets/quadrotor-h100.jpg rename to .save/docs/src/assets/quadrotor-h100.jpg diff --git a/docs/src/assets/rdnopa-2025.jpg b/.save/docs/src/assets/rdnopa-2025.jpg similarity index 100% rename from docs/src/assets/rdnopa-2025.jpg rename to .save/docs/src/assets/rdnopa-2025.jpg diff --git a/docs/src/assets/rocket-def.jpg b/.save/docs/src/assets/rocket-def.jpg similarity index 100% rename from docs/src/assets/rocket-def.jpg rename to .save/docs/src/assets/rocket-def.jpg diff --git a/docs/src/assets/rocket-def.pdf b/.save/docs/src/assets/rocket-def.pdf similarity index 100% rename from docs/src/assets/rocket-def.pdf rename to .save/docs/src/assets/rocket-def.pdf diff --git a/docs/src/assets/rocket-def.png b/.save/docs/src/assets/rocket-def.png similarity index 100% rename from docs/src/assets/rocket-def.png rename to .save/docs/src/assets/rocket-def.png diff --git a/docs/src/assets/rocket-def.svg b/.save/docs/src/assets/rocket-def.svg similarity index 100% rename from docs/src/assets/rocket-def.svg rename to .save/docs/src/assets/rocket-def.svg diff --git a/docs/src/assets/standup.jpg b/.save/docs/src/assets/standup.jpg similarity index 100% rename from docs/src/assets/standup.jpg rename to .save/docs/src/assets/standup.jpg diff --git a/docs/src/assets/star.jpg b/.save/docs/src/assets/star.jpg similarity index 100% rename from docs/src/assets/star.jpg rename to .save/docs/src/assets/star.jpg diff --git a/docs/src/assets/zhejiang-2025.jpg b/.save/docs/src/assets/zhejiang-2025.jpg similarity index 100% rename from docs/src/assets/zhejiang-2025.jpg rename to .save/docs/src/assets/zhejiang-2025.jpg diff --git a/docs/src/example-double-integrator-energy.md b/.save/docs/src/example-double-integrator-energy.md similarity index 100% rename from docs/src/example-double-integrator-energy.md rename to .save/docs/src/example-double-integrator-energy.md diff --git a/docs/src/example-double-integrator-time.md b/.save/docs/src/example-double-integrator-time.md similarity index 100% rename from docs/src/example-double-integrator-time.md rename to .save/docs/src/example-double-integrator-time.md diff --git a/docs/src/index.md b/.save/docs/src/index.md similarity index 100% rename from docs/src/index.md rename to .save/docs/src/index.md diff --git a/docs/src/jlesc17.md b/.save/docs/src/jlesc17.md similarity index 100% rename from docs/src/jlesc17.md rename to .save/docs/src/jlesc17.md diff --git a/docs/src/juliacon-paris-2025.md b/.save/docs/src/juliacon-paris-2025.md similarity index 100% rename from docs/src/juliacon-paris-2025.md rename to .save/docs/src/juliacon-paris-2025.md diff --git a/docs/src/juliacon2024.md b/.save/docs/src/juliacon2024.md similarity index 100% rename from docs/src/juliacon2024.md rename to .save/docs/src/juliacon2024.md diff --git a/docs/src/manual-abstract.md b/.save/docs/src/manual-abstract.md similarity index 100% rename from docs/src/manual-abstract.md rename to .save/docs/src/manual-abstract.md diff --git a/docs/src/manual-ai-llm.md b/.save/docs/src/manual-ai-llm.md similarity index 100% rename from docs/src/manual-ai-llm.md rename to .save/docs/src/manual-ai-llm.md diff --git a/docs/src/manual-flow-api.md b/.save/docs/src/manual-flow-api.md similarity index 100% rename from docs/src/manual-flow-api.md rename to .save/docs/src/manual-flow-api.md diff --git a/docs/src/manual-flow-ocp.md b/.save/docs/src/manual-flow-ocp.md similarity index 100% rename from docs/src/manual-flow-ocp.md rename to .save/docs/src/manual-flow-ocp.md diff --git a/docs/src/manual-flow-others.md b/.save/docs/src/manual-flow-others.md similarity index 100% rename from docs/src/manual-flow-others.md rename to .save/docs/src/manual-flow-others.md diff --git a/docs/src/manual-initial-guess.md b/.save/docs/src/manual-initial-guess.md similarity index 100% rename from docs/src/manual-initial-guess.md rename to .save/docs/src/manual-initial-guess.md diff --git a/docs/src/manual-model.md b/.save/docs/src/manual-model.md similarity index 98% rename from docs/src/manual-model.md rename to .save/docs/src/manual-model.md index 38cbf477a..a5fed6d0e 100644 --- a/docs/src/manual-model.md +++ b/.save/docs/src/manual-model.md @@ -123,7 +123,7 @@ definition(ocp) !!! note - We refer to [CTModels API](@extref CTModels Types) for more details about this struct and its fields. + We refer to the CTModels documentation for more details about this struct and its fields. ## [Attributes and properties](@id manual-model-attributes) diff --git a/docs/src/manual-plot.md b/.save/docs/src/manual-plot.md similarity index 100% rename from docs/src/manual-plot.md rename to .save/docs/src/manual-plot.md diff --git a/docs/src/manual-solution.md b/.save/docs/src/manual-solution.md similarity index 100% rename from docs/src/manual-solution.md rename to .save/docs/src/manual-solution.md diff --git a/docs/src/manual-solve-gpu.md b/.save/docs/src/manual-solve-gpu.md similarity index 100% rename from docs/src/manual-solve-gpu.md rename to .save/docs/src/manual-solve-gpu.md diff --git a/docs/src/manual-solve.md b/.save/docs/src/manual-solve.md similarity index 96% rename from docs/src/manual-solve.md rename to .save/docs/src/manual-solve.md index 580fb7f07..d0e4e36d9 100644 --- a/docs/src/manual-solve.md +++ b/.save/docs/src/manual-solve.md @@ -84,7 +84,7 @@ solve(ocp, :direct, :adnlp, :ipopt) - `:exa`: the NLP problem is modeled by a [`ExaModels.ExaModel`](@extref). It provides automatic differentiation and [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) abstraction. 3. The third symbol specifies the NLP solver. Possible values are: - `:ipopt`: calls [`NLPModelsIpopt.ipopt`](@extref) to solve the NLP problem. - - `:madnlp`: creates a [MadNLP.MadNLPSolver](@extref) instance from the NLP problem and solve it. [MadNLP.jl](https://madnlp.github.io/MadNLP.jl) is an open-source solver in Julia implementing a filter line-search interior-point algorithm like Ipopt. + - `:madnlp`: creates a [MadNLP.MadNLP](@extref) instance from the NLP problem and solve it. [MadNLP.jl](https://madnlp.github.io/MadNLP.jl) is an open-source solver in Julia implementing a filter line-search interior-point algorithm like Ipopt. - `:knitro`: uses the [Knitro](https://www.artelys.com/solvers/knitro/) solver (license required). !!! warning diff --git a/docs/src/rdnopa-2025.md b/.save/docs/src/rdnopa-2025.md similarity index 100% rename from docs/src/rdnopa-2025.md rename to .save/docs/src/rdnopa-2025.md diff --git a/docs/src/zhejiang-2025.md b/.save/docs/src/zhejiang-2025.md similarity index 100% rename from docs/src/zhejiang-2025.md rename to .save/docs/src/zhejiang-2025.md diff --git a/src/solve.jl b/.save/solve_old.jl similarity index 100% rename from src/solve.jl rename to .save/solve_old.jl diff --git a/.save/src/modelers.jl b/.save/src/modelers.jl new file mode 100644 index 000000000..cca31e888 --- /dev/null +++ b/.save/src/modelers.jl @@ -0,0 +1,84 @@ +# To trigger CTDirectExtADNLP and CTDirectExtExa +using ADNLPModels: ADNLPModels + +import ExaModels: + ExaModels #, +# ExaModel, +# ExaCore, +# variable, +# constraint, +# constraint!, +# objective, +# solution, +# multipliers, +# multipliers_L, +# multipliers_U, +# Constraint + +# # Conflicts of functions defined in several packages +# # ExaModels.variable, CTModels.variable +# # ExaModels.constraint, CTModels.constraint +# # ExaModels.constraint!, CTModels.constraint! +# # ExaModels.objective, CTModels.objective +# """ +# $(TYPEDSIGNATURES) + +# See CTModels.variable. +# """ +# variable(ocp::Model) = CTModels.variable(ocp) + +# """ +# $(TYPEDSIGNATURES) + +# Return the variable or `nothing`. + +# ```@example +# julia> v = variable(sol) +# ``` +# """ +# variable(sol::Solution) = CTModels.variable(sol) + +# """ +# $(TYPEDSIGNATURES) + +# Get a labelled constraint from the model. Returns a tuple of the form +# `(type, f, lb, ub)` where `type` is the type of the constraint, `f` is the function, +# `lb` is the lower bound and `ub` is the upper bound. + +# The function returns an exception if the label is not found in the model. + +# ## Arguments + +# - `model`: The model from which to retrieve the constraint. +# - `label`: The label of the constraint to retrieve. + +# ## Returns + +# - `Tuple`: A tuple containing the type, function, lower bound, and upper bound of the constraint. +# """ +# constraint(ocp::Model, label::Symbol) = CTModels.constraint(ocp, label) + +# """ +# $(TYPEDSIGNATURES) + +# See CTModels.constraint!. +# """ +# function constraint!(ocp::PreModel, type::Symbol; kwargs...) +# CTModels.constraint!(ocp, type; kwargs...) +# end + +# """ +# $(TYPEDSIGNATURES) + +# See CTModels.objective. +# """ +# objective(ocp::Model) = CTModels.objective(ocp) + +# """ +# $(TYPEDSIGNATURES) + +# Return the objective value. +# """ +# objective(sol::Solution) = CTModels.objective(sol) + +# export variable, constraint, objective \ No newline at end of file diff --git a/.save/src/solve.jl b/.save/src/solve.jl new file mode 100644 index 000000000..f5fcc2cdd --- /dev/null +++ b/.save/src/solve.jl @@ -0,0 +1,671 @@ +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +import CommonSolve: CommonSolve, solve + +# Default options +__display() = true +__initial_guess() = nothing + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Main solve function +function _solve( + ocp::CTModels.AbstractModel, + initial_guess, + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTModels.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool=__display(), +)::CTModels.AbstractSolution + + # Validate initial guess against the optimal control problem before discretization. + # Any inconsistency should trigger a CTBase.IncorrectArgument from the validator. + normalized_init = CTModels.build_initial_guess(ocp, initial_guess) + CTModels.validate_initial_guess(ocp, normalized_init) + + discrete_problem = CTDirect.discretize(ocp, discretizer) + return CommonSolve.solve( + discrete_problem, normalized_init, modeler, solver; display=display + ) +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Method registry: available resolution methods for optimal control problems. + +const AVAILABLE_METHODS = ( + (:collocation, :adnlp, :ipopt), + (:collocation, :adnlp, :madnlp), + (:collocation, :adnlp, :knitro), + (:collocation, :exa, :ipopt), + (:collocation, :exa, :madnlp), + (:collocation, :exa, :knitro), +) + +available_methods() = AVAILABLE_METHODS + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Discretizer helpers (symbol type and options). + +function _get_unique_symbol( + method::Tuple{Vararg{Symbol}}, allowed::Tuple{Vararg{Symbol}}, tool_name::AbstractString +) + hits = Symbol[] + for s in method + if s in allowed + push!(hits, s) + end + end + if length(hits) == 1 + return hits[1] + elseif isempty(hits) + msg = "No $(tool_name) symbol from $(allowed) found in method $(method)." + throw(CTBase.IncorrectArgument(msg)) + else + msg = "Multiple $(tool_name) symbols $(hits) found in method $(method); at most one is allowed." + throw(CTBase.IncorrectArgument(msg)) + end +end + +function _get_discretizer_symbol(method::Tuple) + return _get_unique_symbol(method, CTDirect.discretizer_symbols(), "discretizer") +end + +function _build_discretizer_from_method(method::Tuple, discretizer_options::NamedTuple) + disc_sym = _get_discretizer_symbol(method) + return CTDirect.build_discretizer_from_symbol(disc_sym; discretizer_options...) +end + +function _discretizer_options_keys(method::Tuple) + disc_sym = _get_discretizer_symbol(method) + disc_type = CTDirect._discretizer_type_from_symbol(disc_sym) + keys = CTModels.options_keys(disc_type) + keys === missing && return () + return keys +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Modeler helpers (symbol type). + +function _get_modeler_symbol(method::Tuple) + return _get_unique_symbol(method, CTModels.modeler_symbols(), "NLP model") +end + +function _normalize_modeler_options(options) + if options === nothing + return NamedTuple() + elseif options isa NamedTuple + return options + elseif options isa Tuple + return (; options...) + else + msg = "modeler_options must be a NamedTuple or tuple of pairs, got $(typeof(options))." + throw(CTBase.IncorrectArgument(msg)) + end +end + +function _modeler_options_keys(method::Tuple) + model_sym = _get_modeler_symbol(method) + model_type = CTModels._modeler_type_from_symbol(model_sym) + keys = CTModels.options_keys(model_type) + keys === missing && return () + return keys +end + +function _build_modeler_from_method(method::Tuple, modeler_options::NamedTuple) + model_sym = _get_modeler_symbol(method) + return CTModels.build_modeler_from_symbol(model_sym; modeler_options...) +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Solver helpers (symbol type). + +function _get_solver_symbol(method::Tuple) + return _get_unique_symbol(method, CTSolvers.solver_symbols(), "solver") +end + +function _build_solver_from_method(method::Tuple, solver_options::NamedTuple) + solver_sym = _get_solver_symbol(method) + return CTSolvers.build_solver_from_symbol(solver_sym; solver_options...) +end + +function _solver_options_keys(method::Tuple) + solver_sym = _get_solver_symbol(method) + solver_type = CTSolvers._solver_type_from_symbol(solver_sym) + keys = CTModels.options_keys(solver_type) + keys === missing && return () + return keys +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Option routing helpers for description mode. + +const _OCP_TOOLS = (:discretizer, :modeler, :solver, :solve) + +function _extract_option_tool(raw) + if raw isa Tuple{Any,Symbol} + value, tool = raw + if tool in _OCP_TOOLS + return value, tool + end + end + return raw, nothing +end + +function _route_option_for_description( + key::Symbol, raw_value, owners::Vector{Symbol}, source_mode::Symbol +) + value, explicit_tool = _extract_option_tool(raw_value) + + if explicit_tool !== nothing + if !(explicit_tool in owners) + msg = "Keyword option $(key) cannot be routed to $(explicit_tool); valid tools are $(owners)." + throw(CTBase.IncorrectArgument(msg)) + end + return value, explicit_tool + end + + if isempty(owners) + msg = "Keyword option $(key) does not belong to any recognized component for the selected method." + throw(CTBase.IncorrectArgument(msg)) + elseif length(owners) == 1 + return value, owners[1] + else + if source_mode === :description + msg = + "Keyword option $(key) is ambiguous between tools $(owners). " * + "Disambiguate it by writing $(key) = (value, :tool), for example " * + "$(key) = (value, :discretizer) or $(key) = (value, :solver)." + throw(CTBase.IncorrectArgument(msg)) + else + msg = + "Ambiguous keyword option $(key) when routing from explicit mode; " * + "internal calls should use the (value, tool) form." + throw(CTBase.IncorrectArgument(msg)) + end + end +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Display helpers. + +function _display_ocp_method( + io::IO, + method::Tuple, + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTModels.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool, +) + display || return nothing + + version_str = string(Base.pkgversion(@__MODULE__)) + + print(io, "▫ This is CTSolvers version v", version_str, " running with: ") + for (i, m) in enumerate(method) + sep = i == length(method) ? ".\n\n" : ", " + printstyled(io, string(m) * sep; color=:cyan, bold=true) + end + + model_pkg = CTModels.tool_package_name(modeler) + solver_pkg = CTModels.tool_package_name(solver) + + if model_pkg !== missing && solver_pkg !== missing + println( + io, + " ┌─ The NLP is modelled with ", + model_pkg, + " and solved with ", + solver_pkg, + ".", + ) + println(io, " │") + end + + # Discretizer options (including grid size and scheme) + disc_vals = CTModels._options_values(discretizer) + disc_srcs = CTModels._option_sources(discretizer) + + mod_vals = CTModels._options_values(modeler) + mod_srcs = CTModels._option_sources(modeler) + + sol_vals = CTModels._options_values(solver) + sol_srcs = CTModels._option_sources(solver) + + has_disc = !isempty(propertynames(disc_vals)) + has_mod = !isempty(propertynames(mod_vals)) + has_sol = !isempty(propertynames(sol_vals)) + + if has_disc || has_mod || has_sol + println(io, " Options:") + + if has_disc + println(io, " ├─ Discretizer:") + for name in propertynames(disc_vals) + src = haskey(disc_srcs, name) ? disc_srcs[name] : :unknown + println(io, " │ ", name, " = ", disc_vals[name], " (", src, ")") + end + end + + if has_mod + println(io, " ├─ Modeler:") + for name in propertynames(mod_vals) + src = haskey(mod_srcs, name) ? mod_srcs[name] : :unknown + println(io, " │ ", name, " = ", mod_vals[name], " (", src, ")") + end + end + + if has_sol + println(io, " └─ Solver:") + for name in propertynames(sol_vals) + src = haskey(sol_srcs, name) ? sol_srcs[name] : :unknown + println(io, " ", name, " = ", sol_vals[name], " (", src, ")") + end + end + end + + println(io) + + return nothing +end + +function _display_ocp_method( + method::Tuple, + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTModels.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool, +) + return _display_ocp_method( + stdout, method, discretizer, modeler, solver; display=display + ) +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Top-level solve entry: unifies explicit and description modes. + +const _SOLVE_INITIAL_GUESS_ALIASES = (:initial_guess, :init, :i) +const _SOLVE_DISCRETIZER_ALIASES = (:discretizer, :d) +const _SOLVE_MODELER_ALIASES = (:modeler, :modeller, :m) +const _SOLVE_SOLVER_ALIASES = (:solver, :s) +const _SOLVE_DISPLAY_ALIASES = (:display,) +const _SOLVE_MODELER_OPTIONS_ALIASES = (:modeler_options,) + +solve_ocp_option_keys_explicit_mode() = (:initial_guess, :display) + +struct _ParsedTopLevelKwargs + initial_guess + display + discretizer + modeler + solver + modeler_options + other_kwargs::NamedTuple +end + +function _take_solve_kwarg( + kwargs::NamedTuple, names::Tuple{Vararg{Symbol}}, default; only_solve_owner::Bool=false +) + present = Symbol[] + for n in names + if haskey(kwargs, n) + if only_solve_owner + raw = kwargs[n] + _, explicit_tool = _extract_option_tool(raw) + if !(explicit_tool === nothing || explicit_tool === :solve) + continue + end + end + push!(present, n) + end + end + + if isempty(present) + return default, kwargs + elseif length(present) == 1 + name = present[1] + value = kwargs[name] + remaining = (; (k => v for (k, v) in pairs(kwargs) if k != name)...) + return value, remaining + else + msg = + "Conflicting aliases $(present) for argument $(names[1]). " * + "Use only one of $(names)." + throw(CTBase.IncorrectArgument(msg)) + end +end + +function _parse_top_level_kwargs(kwargs::NamedTuple) + initial_guess, kwargs1 = _take_solve_kwarg( + kwargs, _SOLVE_INITIAL_GUESS_ALIASES, __initial_guess() + ) + display, kwargs2 = _take_solve_kwarg(kwargs1, _SOLVE_DISPLAY_ALIASES, __display()) + discretizer, kwargs3 = _take_solve_kwarg(kwargs2, _SOLVE_DISCRETIZER_ALIASES, nothing) + modeler, kwargs4 = _take_solve_kwarg(kwargs3, _SOLVE_MODELER_ALIASES, nothing) + solver, kwargs5 = _take_solve_kwarg(kwargs4, _SOLVE_SOLVER_ALIASES, nothing) + modeler_options, other_kwargs = _take_solve_kwarg( + kwargs5, _SOLVE_MODELER_OPTIONS_ALIASES, nothing + ) + + return _ParsedTopLevelKwargs( + initial_guess, display, discretizer, modeler, solver, modeler_options, other_kwargs + ) +end + +function _parse_top_level_kwargs_description(kwargs::NamedTuple) + # Defaults identical to the explicit-mode parser, but reserved keywords can + # be routed through the central option router in the future if they become + # shared between components. For now, initial_guess, display and + # modeler_options are treated as belonging solely to the top-level solve. + + initial_guess = __initial_guess() + display = __display() + discretizer = nothing + modeler = nothing + solver = nothing + modeler_options = nothing + + # Reserved keywords + initial_guess_raw, kwargs1 = _take_solve_kwarg( + kwargs, _SOLVE_INITIAL_GUESS_ALIASES, __initial_guess(); only_solve_owner=true + ) + value, _ = _route_option_for_description( + :initial_guess, initial_guess_raw, Symbol[:solve], :description + ) + initial_guess = value + + display_raw, kwargs2 = _take_solve_kwarg( + kwargs1, _SOLVE_DISPLAY_ALIASES, __display(); only_solve_owner=true + ) + display_unwrapped, _ = _extract_option_tool(display_raw) + display = display_unwrapped + + modeler_options_raw, kwargs3 = _take_solve_kwarg( + kwargs2, _SOLVE_MODELER_OPTIONS_ALIASES, nothing; only_solve_owner=true + ) + modeler_options_unwrapped, _ = _extract_option_tool(modeler_options_raw) + modeler_options = modeler_options_unwrapped + + # Explicit components, if any + discretizer, kwargs4 = _take_solve_kwarg(kwargs3, _SOLVE_DISCRETIZER_ALIASES, nothing) + modeler, kwargs5 = _take_solve_kwarg(kwargs4, _SOLVE_MODELER_ALIASES, nothing) + solver, kwargs6 = _take_solve_kwarg(kwargs5, _SOLVE_SOLVER_ALIASES, nothing) + + # Everything else goes to other_kwargs and will be routed to discretizer + # or solver by the description-mode splitter. + other_pairs = Pair{Symbol,Any}[] + for (k, v) in pairs(kwargs6) + push!(other_pairs, k => v) + end + + return _ParsedTopLevelKwargs( + initial_guess, + display, + discretizer, + modeler, + solver, + modeler_options, + (; other_pairs...), + ) +end + +function _ensure_no_ambiguous_description_kwargs(method::Tuple, kwargs::NamedTuple) + disc_keys = Set(_discretizer_options_keys(method)) + model_keys = Set(_modeler_options_keys(method)) + solver_keys = Set(_solver_options_keys(method)) + + for (k, raw) in pairs(kwargs) + owners = Symbol[] + + if (k in _SOLVE_INITIAL_GUESS_ALIASES) || + (k in _SOLVE_DISCRETIZER_ALIASES) || + (k in _SOLVE_MODELER_ALIASES) || + (k in _SOLVE_SOLVER_ALIASES) || + (k in _SOLVE_DISPLAY_ALIASES) || + (k in _SOLVE_MODELER_OPTIONS_ALIASES) + push!(owners, :solve) + end + + if k in disc_keys + push!(owners, :discretizer) + end + if k in model_keys + push!(owners, :modeler) + end + if k in solver_keys + push!(owners, :solver) + end + + _route_option_for_description(k, raw, owners, :description) + end + + return nothing +end + +function _has_explicit_components(parsed::_ParsedTopLevelKwargs) + return (parsed.discretizer !== nothing) || + (parsed.modeler !== nothing) || + (parsed.solver !== nothing) +end + +function _ensure_no_unknown_explicit_kwargs(parsed::_ParsedTopLevelKwargs) + allowed = Set(solve_ocp_option_keys_explicit_mode()) + union!(allowed, Set((:discretizer, :modeler, :solver))) + unknown = [k for (k, _) in pairs(parsed.other_kwargs) if !(k in allowed)] + if !isempty(unknown) + msg = "Unknown keyword options in explicit mode: $(unknown)." + throw(CTBase.IncorrectArgument(msg)) + end +end + +function _build_description_from_components(discretizer, modeler, solver) + syms = Symbol[] + if discretizer !== nothing + push!(syms, CTModels.get_symbol(discretizer)) + end + if modeler !== nothing + push!(syms, CTModels.get_symbol(modeler)) + end + if solver !== nothing + push!(syms, CTModels.get_symbol(solver)) + end + return Tuple(syms) +end + +function _solve_from_components_and_description( + ocp::CTModels.AbstractModel, method::Tuple, parsed::_ParsedTopLevelKwargs +) + # method is a COMPLETE description (e.g., (:collocation, :adnlp, :ipopt)) + + # 1. Discretizer + discretizer = if parsed.discretizer === nothing + _build_discretizer_from_method(method, NamedTuple()) + else + parsed.discretizer + end + + # 2. Modeler (no modeler_options in explicit mode) + modeler = if parsed.modeler === nothing + _build_modeler_from_method(method, NamedTuple()) + else + parsed.modeler + end + + # 3. Solver (no solver-specific kwargs in explicit mode) + solver = if parsed.solver === nothing + _build_solver_from_method(method, NamedTuple()) + else + parsed.solver + end + + _display_ocp_method(method, discretizer, modeler, solver; display=parsed.display) + + return _solve( + ocp, parsed.initial_guess, discretizer, modeler, solver; display=parsed.display + ) +end + +function _solve_explicit_mode( + ocp::CTModels.AbstractModel, parsed::_ParsedTopLevelKwargs +) + # 1. No modeler_options in explicit mode + if parsed.modeler_options !== nothing + msg = "modeler_options is not allowed in explicit mode; pass a modeler instance instead." + throw(CTBase.IncorrectArgument(msg)) + end + + # 2. Unknown options check + _ensure_no_unknown_explicit_kwargs(parsed) + + # 3. If all components are provided explicitly, call the low-level API + # directly without going through the description/method registry. This + # allows arbitrary user-defined components (e.g., test doubles) that do + # not participate in the symbol registry. + has_discretizer = parsed.discretizer !== nothing + has_modeler = parsed.modeler !== nothing + has_solver = parsed.solver !== nothing + + if has_discretizer && has_modeler && has_solver + return _solve( + ocp, + parsed.initial_guess, + parsed.discretizer, + parsed.modeler, + parsed.solver; + display=parsed.display, + ) + end + + # 4. Otherwise, build a partial description from the provided components + # and delegate to the description-based pipeline to complete missing + # pieces using the central method registry. + partial_desc = _build_description_from_components( + parsed.discretizer, parsed.modeler, parsed.solver + ) + method = CTBase.complete(partial_desc...; descriptions=available_methods()) + + return _solve_from_components_and_description(ocp, method, parsed) +end + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Description-based solve (including the default solve(ocp) case). + +function _split_kwargs_for_description(method::Tuple, parsed::_ParsedTopLevelKwargs) + # All top-level kwargs except initial_guess, display, modeler_options + # are in parsed.other_kwargs. Among them, some belong to the discretizer, + # some to the modeler, and some to the solver. + disc_keys = Set(_discretizer_options_keys(method)) + model_keys = Set(_modeler_options_keys(method)) + solver_keys = Set(_solver_options_keys(method)) + + disc_pairs = Pair{Symbol,Any}[] + model_pairs = Pair{Symbol,Any}[] + solver_pairs = Pair{Symbol,Any}[] + for (k, raw) in pairs(parsed.other_kwargs) + owners = Symbol[] + if k in disc_keys + push!(owners, :discretizer) + end + if k in model_keys + push!(owners, :modeler) + end + if k in solver_keys + push!(owners, :solver) + end + + value, tool = _route_option_for_description(k, raw, owners, :description) + + if tool === :discretizer + push!(disc_pairs, k => value) + elseif tool === :modeler + push!(model_pairs, k => value) + elseif tool === :solver + push!(solver_pairs, k => value) + else + msg = "Unsupported tool $(tool) for option $(k)." + throw(CTBase.IncorrectArgument(msg)) + end + end + + disc_kwargs = (; disc_pairs...) + model_kwargs = (; model_pairs...) + solver_kwargs = (; solver_pairs...) + + # Normalize user-supplied modeler_options (which may be nothing, a NamedTuple, + # or a tuple of pairs) and merge them with any untagged options that belong + # to the modeler for the selected method. We explicitly build a NamedTuple + # here instead of relying on generic union operators, to avoid type surprises + # and keep the API contract of _build_modeler_from_method, which expects a + # NamedTuple of keyword arguments. + base_modeler_opts = _normalize_modeler_options(parsed.modeler_options) + combined_modeler_opts = (; base_modeler_opts..., model_kwargs...) + + return ( + initial_guess=parsed.initial_guess, + display=parsed.display, + disc_kwargs=disc_kwargs, + modeler_options=combined_modeler_opts, + solver_kwargs=solver_kwargs, + ) +end + +function _solve_from_complete_description( + ocp::CTModels.AbstractModel, + method::Tuple{Vararg{Symbol}}, + parsed::_ParsedTopLevelKwargs, +)::CTModels.AbstractSolution + pieces = _split_kwargs_for_description(method, parsed) + + discretizer = _build_discretizer_from_method(method, pieces.disc_kwargs) + modeler = _build_modeler_from_method(method, pieces.modeler_options) + solver = _build_solver_from_method(method, pieces.solver_kwargs) + + _display_ocp_method(method, discretizer, modeler, solver; display=pieces.display) + + return _solve( + ocp, pieces.initial_guess, discretizer, modeler, solver; display=pieces.display + ) +end + +function _solve_descriptif_mode( + ocp::CTModels.AbstractModel, description::Symbol...; kwargs... +)::CTModels.AbstractSolution + method = CTBase.complete(description...; descriptions=available_methods()) + + _ensure_no_ambiguous_description_kwargs(method, (; kwargs...)) + + parsed = _parse_top_level_kwargs_description((; kwargs...)) + + if _has_explicit_components(parsed) + msg = "Cannot mix explicit components (discretizer/modeler/solver) with a description." + throw(CTBase.IncorrectArgument(msg)) + end + + return _solve_from_complete_description(ocp, method, parsed) +end + +function CommonSolve.solve( + ocp::CTModels.AbstractModel, description::Symbol...; kwargs... +)::CTModels.AbstractSolution + parsed = _parse_top_level_kwargs((; kwargs...)) + + if _has_explicit_components(parsed) && !isempty(description) + msg = "Cannot mix explicit components (discretizer/modeler/solver) with a description." + throw(CTBase.IncorrectArgument(msg)) + end + + if _has_explicit_components(parsed) + # Explicit mode: components provided directly by the user. + return _solve_explicit_mode(ocp, parsed) + else + # Description mode: description may be empty (solve(ocp)) or partial. + return _solve_descriptif_mode(ocp, description...; kwargs...) + end +end diff --git a/test/ctdirect/problems/beam.jl b/.save/test/ctdirect/problems/beam.jl similarity index 100% rename from test/ctdirect/problems/beam.jl rename to .save/test/ctdirect/problems/beam.jl diff --git a/test/ctdirect/problems/beam2.jl b/.save/test/ctdirect/problems/beam2.jl similarity index 100% rename from test/ctdirect/problems/beam2.jl rename to .save/test/ctdirect/problems/beam2.jl diff --git a/test/ctdirect/problems/bolza.jl b/.save/test/ctdirect/problems/bolza.jl similarity index 100% rename from test/ctdirect/problems/bolza.jl rename to .save/test/ctdirect/problems/bolza.jl diff --git a/test/ctdirect/problems/double_integrator.jl b/.save/test/ctdirect/problems/double_integrator.jl similarity index 100% rename from test/ctdirect/problems/double_integrator.jl rename to .save/test/ctdirect/problems/double_integrator.jl diff --git a/test/ctdirect/problems/fuller.jl b/.save/test/ctdirect/problems/fuller.jl similarity index 100% rename from test/ctdirect/problems/fuller.jl rename to .save/test/ctdirect/problems/fuller.jl diff --git a/test/ctdirect/problems/goddard.jl b/.save/test/ctdirect/problems/goddard.jl similarity index 100% rename from test/ctdirect/problems/goddard.jl rename to .save/test/ctdirect/problems/goddard.jl diff --git a/test/ctdirect/problems/jackson.jl b/.save/test/ctdirect/problems/jackson.jl similarity index 100% rename from test/ctdirect/problems/jackson.jl rename to .save/test/ctdirect/problems/jackson.jl diff --git a/test/ctdirect/problems/parametric.jl b/.save/test/ctdirect/problems/parametric.jl similarity index 100% rename from test/ctdirect/problems/parametric.jl rename to .save/test/ctdirect/problems/parametric.jl diff --git a/test/ctdirect/problems/robbins.jl b/.save/test/ctdirect/problems/robbins.jl similarity index 100% rename from test/ctdirect/problems/robbins.jl rename to .save/test/ctdirect/problems/robbins.jl diff --git a/test/ctdirect/problems/simple_integrator.jl b/.save/test/ctdirect/problems/simple_integrator.jl similarity index 100% rename from test/ctdirect/problems/simple_integrator.jl rename to .save/test/ctdirect/problems/simple_integrator.jl diff --git a/test/ctdirect/problems/vanderpol.jl b/.save/test/ctdirect/problems/vanderpol.jl similarity index 100% rename from test/ctdirect/problems/vanderpol.jl rename to .save/test/ctdirect/problems/vanderpol.jl diff --git a/test/ctdirect/suite/test_all_ocp.jl b/.save/test/ctdirect/suite/test_all_ocp.jl similarity index 100% rename from test/ctdirect/suite/test_all_ocp.jl rename to .save/test/ctdirect/suite/test_all_ocp.jl diff --git a/test/ctdirect/suite/test_constraints.jl b/.save/test/ctdirect/suite/test_constraints.jl similarity index 100% rename from test/ctdirect/suite/test_constraints.jl rename to .save/test/ctdirect/suite/test_constraints.jl diff --git a/test/ctdirect/suite/test_continuation.jl b/.save/test/ctdirect/suite/test_continuation.jl similarity index 100% rename from test/ctdirect/suite/test_continuation.jl rename to .save/test/ctdirect/suite/test_continuation.jl diff --git a/test/ctdirect/suite/test_discretization.jl b/.save/test/ctdirect/suite/test_discretization.jl similarity index 100% rename from test/ctdirect/suite/test_discretization.jl rename to .save/test/ctdirect/suite/test_discretization.jl diff --git a/test/ctdirect/suite/test_exa.jl b/.save/test/ctdirect/suite/test_exa.jl similarity index 100% rename from test/ctdirect/suite/test_exa.jl rename to .save/test/ctdirect/suite/test_exa.jl diff --git a/test/ctdirect/suite/test_initial_guess.jl b/.save/test/ctdirect/suite/test_initial_guess.jl similarity index 100% rename from test/ctdirect/suite/test_initial_guess.jl rename to .save/test/ctdirect/suite/test_initial_guess.jl diff --git a/test/ctdirect/suite/test_objective.jl b/.save/test/ctdirect/suite/test_objective.jl similarity index 100% rename from test/ctdirect/suite/test_objective.jl rename to .save/test/ctdirect/suite/test_objective.jl diff --git a/.save/test/extras/check_ownership.jl b/.save/test/extras/check_ownership.jl new file mode 100644 index 000000000..f9ed6550d --- /dev/null +++ b/.save/test/extras/check_ownership.jl @@ -0,0 +1,39 @@ +using OptimalControl +using CTBase +using CTSolvers +using CTDirect +using CTFlows +using CTModels +using CTParser + +# Symbol to check +sym_to_check = :initial_guess + +# List of modules to check +modules = [ + (:CTBase, CTBase), + (:CTSolvers, CTSolvers), + (:CTDirect, CTDirect), + (:CTFlows, CTFlows), + (:CTModels, CTModels), + (:CTParser, CTParser), + (:OptimalControl, OptimalControl) +] + +println("Checking symbol: :$(sym_to_check)") +println("-"^30) + +for (name, mod) in modules + is_defined = isdefined(mod, sym_to_check) + is_exported = sym_to_check in names(mod) + + status = if is_exported + "Exported" + elseif is_defined + "Defined (internal)" + else + "Not found" + end + + println("$(name): $(status)") +end diff --git a/test/extras/cons.jl b/.save/test/extras/cons.jl similarity index 100% rename from test/extras/cons.jl rename to .save/test/extras/cons.jl diff --git a/test/extras/cons_2.jl b/.save/test/extras/cons_2.jl similarity index 100% rename from test/extras/cons_2.jl rename to .save/test/extras/cons_2.jl diff --git a/test/extras/ensemble.jl b/.save/test/extras/ensemble.jl similarity index 100% rename from test/extras/ensemble.jl rename to .save/test/extras/ensemble.jl diff --git a/test/extras/export.jl b/.save/test/extras/export.jl similarity index 100% rename from test/extras/export.jl rename to .save/test/extras/export.jl diff --git a/test/extras/nonautonomous.jl b/.save/test/extras/nonautonomous.jl similarity index 100% rename from test/extras/nonautonomous.jl rename to .save/test/extras/nonautonomous.jl diff --git a/test/extras/ocp.jl b/.save/test/extras/ocp.jl similarity index 100% rename from test/extras/ocp.jl rename to .save/test/extras/ocp.jl diff --git a/test/indirect/Goddard.jl b/.save/test/indirect/Goddard.jl similarity index 100% rename from test/indirect/Goddard.jl rename to .save/test/indirect/Goddard.jl diff --git a/test/indirect/test_goddard_indirect.jl b/.save/test/indirect/test_goddard_indirect.jl similarity index 100% rename from test/indirect/test_goddard_indirect.jl rename to .save/test/indirect/test_goddard_indirect.jl diff --git a/.save/test/problems/beam.jl b/.save/test/problems/beam.jl new file mode 100644 index 000000000..542d75009 --- /dev/null +++ b/.save/test/problems/beam.jl @@ -0,0 +1,28 @@ +# Beam optimal control problem definition used by tests and examples. +# +# Returns a NamedTuple with fields: +# - ocp :: the CTParser-defined optimal control problem +# - obj :: reference optimal objective value (Ipopt / MadNLP, Collocation) +# - name :: a short problem name +# - init :: NamedTuple of components for CTSolvers.initial_guess +function Beam() + ocp = @def begin + t ∈ [0, 1], time + x ∈ R², state + u ∈ R, control + + x(0) == [0, 1] + x(1) == [0, -1] + 0 ≤ x₁(t) ≤ 0.1 + -10 ≤ u(t) ≤ 10 + + ∂(x₁)(t) == x₂(t) + ∂(x₂)(t) == u(t) + + ∫(u(t)^2) → min + end + + init = (state=[0.05, 0.1], control=0.1) + + return (ocp=ocp, obj=8.898598, name="beam", init=init) +end diff --git a/.save/test/problems/goddard.jl b/.save/test/problems/goddard.jl new file mode 100644 index 000000000..310adcdb5 --- /dev/null +++ b/.save/test/problems/goddard.jl @@ -0,0 +1,64 @@ +# Goddard rocket optimal control problem used by CTSolvers tests. + +""" + Goddard(; vmax=0.1, Tmax=3.5) + +Return data for the classical Goddard rocket ascent, formulated as a +*maximization* of the final altitude `r(tf)`. + +The function returns a NamedTuple with fields: + + * `ocp` – CTParser/@def optimal control problem + * `obj` – reference optimal objective value + * `name` – short problem name (`"goddard"`) + * `init` – NamedTuple of components for `CTSolvers.initial_guess`, similar + in spirit to `Beam()`. +""" +function Goddard(; vmax=0.1, Tmax=3.5) + # constants + Cd = 310 + beta = 500 + b = 2 + r0 = 1 + v0 = 0 + m0 = 1 + mf = 0.6 + x0 = [r0, v0, m0] + + @def goddard begin + tf ∈ R, variable + t ∈ [0, tf], time + x ∈ R^3, state + u ∈ R, control + + 0.01 ≤ tf ≤ Inf + + r = x[1] + v = x[2] + m = x[3] + + x(0) == x0 + m(tf) == mf + + r0 ≤ r(t) ≤ r0 + 0.1 + v0 ≤ v(t) ≤ vmax + mf ≤ m(t) ≤ m0 + 0 ≤ u(t) ≤ 1 + + # Component-wise dynamics (Goddard rocket) + D = Cd * v(t)^2 * exp(-beta * (r(t) - r0)) + g = 1 / r(t)^2 + T = Tmax * u(t) + + ∂(r)(t) == v(t) + ∂(v)(t) == (T - D - m(t) * g) / m(t) + ∂(m)(t) == -b * T + + r(tf) → max + end + + # Components for a reasonable initial guess around a feasible trajectory. + init = (state=[1.01, 0.05, 0.8], control=0.5, variable=[0.1]) + + return (ocp=goddard, obj=1.01257, name="goddard", init=init) +end diff --git a/.save/test/runtests.jl b/.save/test/runtests.jl new file mode 100644 index 000000000..0ae5916d5 --- /dev/null +++ b/.save/test/runtests.jl @@ -0,0 +1,52 @@ +using Test +using ADNLPModels +using CommonSolve +using CTBase +using CTDirect +using CTModels +using CTSolvers +using OptimalControl +using NLPModelsIpopt +using MadNLP +using MadNLPMumps +using NLPModels +using LinearAlgebra +using OrdinaryDiffEq +using DifferentiationInterface +using ForwardDiff: ForwardDiff +using NonlinearSolve +using SolverCore +using SplitApplyCombine # for flatten in some tests + +# NB some direct tests use functional definition and are `using CTModels` + +# @testset verbose = true showtiming = true "Optimal control tests" begin + +# # ctdirect tests +# @testset verbose = true showtiming = true "CTDirect tests" begin +# # run all scripts in subfolder suite/ +# include.(filter(contains(r".jl$"), readdir("./ctdirect/suite"; join=true))) +# end + +# # other tests: indirect +# include("./indirect/Goddard.jl") +# for name in (:goddard_indirect,) +# @testset "$(name)" begin +# test_name = Symbol(:test_, name) +# println("Testing: " * string(name)) +# include("./indirect/$(test_name).jl") +# @eval $test_name() +# end +# end +# end + +const VERBOSE = true +const SHOWTIMING = true + +include(joinpath(@__DIR__, "problems", "beam.jl")) +include(joinpath(@__DIR__, "problems", "goddard.jl")) + +@testset verbose = VERBOSE showtiming = SHOWTIMING "Optimal control tests" begin + include(joinpath(@__DIR__, "test_optimalcontrol_solve_api.jl")) + test_optimalcontrol_solve_api() +end \ No newline at end of file diff --git a/.save/test/test_optimalcontrol_solve_api.jl b/.save/test/test_optimalcontrol_solve_api.jl new file mode 100644 index 000000000..346a2fac1 --- /dev/null +++ b/.save/test/test_optimalcontrol_solve_api.jl @@ -0,0 +1,796 @@ +# Optimal control-level tests for solve on OCPs. + +struct OCDummyOCP <: CTModels.AbstractModel end + +struct OCDummyDiscretizedOCP <: CTModels.AbstractOptimizationProblem end + +struct OCDummyInit <: CTModels.AbstractInitialGuess + x0::Vector{Float64} +end + +struct OCDummyStats <: SolverCore.AbstractExecutionStats + tag::Symbol +end + +struct OCDummySolution <: CTModels.AbstractSolution end + +struct OCFakeDiscretizer <: CTDirect.AbstractDiscretizer + calls::Base.RefValue{Int} +end + +function (d::OCFakeDiscretizer)(ocp::CTModels.AbstractModel) + d.calls[] += 1 + return OCDummyDiscretizedOCP() +end + +struct OCFakeModeler <: CTModels.AbstractNLPModeler + model_calls::Base.RefValue{Int} + solution_calls::Base.RefValue{Int} +end + +function (m::OCFakeModeler)( + prob::CTModels.AbstractOptimizationProblem, init::OCDummyInit +)::NLPModels.AbstractNLPModel + m.model_calls[] += 1 + f(z) = sum(z .^ 2) + return ADNLPModels.ADNLPModel(f, init.x0) +end + +function (m::OCFakeModeler)( + prob::CTModels.AbstractOptimizationProblem, + nlp_solution::SolverCore.AbstractExecutionStats, +) + m.solution_calls[] += 1 + return OCDummySolution() +end + +struct OCFakeSolverNLP <: CTSolvers.AbstractNLPSolver + calls::Base.RefValue{Int} +end + +function (s::OCFakeSolverNLP)( + nlp::NLPModels.AbstractNLPModel; display::Bool +)::SolverCore.AbstractExecutionStats + s.calls[] += 1 + return OCDummyStats(:solver_called) +end + +function test_optimalcontrol_solve_api() + Test.@testset "raw defaults" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@test OptimalControl.OptimalControl.__initial_guess() === nothing + end + + Test.@testset "description helpers" verbose = VERBOSE showtiming = SHOWTIMING begin + methods = OptimalControl.available_methods() + Test.@test !isempty(methods) + + first_method = methods[1] + Test.@test first_method[1] === :collocation + Test.@test any( + m -> m[1] === :collocation && (:adnlp in m) && (:ipopt in m), methods + ) + + # Partial descriptions are completed using complete with priority order. + method_from_disc = CTBase.complete(:collocation; descriptions=methods) + Test.@test :collocation in method_from_disc + + method_from_solver = CTBase.complete(:ipopt; descriptions=methods) + Test.@test :ipopt in method_from_solver + + # Discretizer options registry: keys inferred from the Collocation tool + method = (:collocation, :adnlp, :ipopt) + keys_from_method = OptimalControl._discretizer_options_keys(method) + keys_from_type = CTModels.options_keys(OptimalControl.Collocation) + Test.@test keys_from_method == keys_from_type + + # Discretizer symbol helper + for m in methods + Test.@test OptimalControl._get_discretizer_symbol(m) === :collocation + end + + # Error when no discretizer symbol is present in the method + Test.@test_throws OptimalControl.IncorrectArgument OptimalControl._get_discretizer_symbol(( + :adnlp, :ipopt + )) + + # Modeler and solver symbol helpers using registries + for m in methods + msym = OptimalControl._get_modeler_symbol(m) + Test.@test msym in OptimalControl.CTModels.modeler_symbols() + ssym = OptimalControl._get_solver_symbol(m) + Test.@test ssym in CTSolvers.solver_symbols() + end + + # _modeler_options_keys / _solver_options_keys should match options_keys + method_ad_ip = (:collocation, :adnlp, :ipopt) + Test.@test Set(OptimalControl._modeler_options_keys(method_ad_ip)) == + Set(CTModels.options_keys(OptimalControl.ADNLP)) + Test.@test Set(OptimalControl._solver_options_keys(method_ad_ip)) == + Set(CTModels.options_keys(OptimalControl.Ipopt)) + + method_exa_mad = (:collocation, :exa, :madnlp) + Test.@test Set(OptimalControl._modeler_options_keys(method_exa_mad)) == + Set(CTModels.options_keys(OptimalControl.Exa)) + Test.@test Set(OptimalControl._solver_options_keys(method_exa_mad)) == + Set(CTModels.options_keys(OptimalControl.MadNLP)) + + # Multiple symbols of the same family in a method should raise an error + Test.@test_throws OptimalControl.IncorrectArgument OptimalControl._get_modeler_symbol(( + :collocation, :adnlp, :exa, :ipopt + )) + Test.@test_throws OptimalControl.IncorrectArgument OptimalControl._get_solver_symbol(( + :collocation, :adnlp, :ipopt, :madnlp + )) + + # _build_modeler_from_method should construct the appropriate modeler + m_ad = OptimalControl._build_modeler_from_method( + (:collocation, :adnlp, :ipopt), (; backend=:manual) + ) + Test.@test m_ad isa OptimalControl.ADNLP + + m_exa = OptimalControl._build_modeler_from_method( + (:collocation, :exa, :ipopt), NamedTuple() + ) + Test.@test m_exa isa OptimalControl.Exa + + # _build_solver_from_method should construct the appropriate solver + s_ip = OptimalControl._build_solver_from_method( + (:collocation, :adnlp, :ipopt), NamedTuple() + ) + Test.@test s_ip isa OptimalControl.Ipopt + + s_mad = OptimalControl._build_solver_from_method( + (:collocation, :adnlp, :madnlp), NamedTuple() + ) + Test.@test s_mad isa OptimalControl.MadNLP + + # Modeler options normalization helper + Test.@test OptimalControl._normalize_modeler_options(nothing) === NamedTuple() + Test.@test OptimalControl._normalize_modeler_options((backend=:manual,)) == + (backend=:manual,) + Test.@test OptimalControl._normalize_modeler_options((; backend=:manual)) == + (backend=:manual,) + + Test.@testset "description ambiguity pre-check (ownerless key)" verbose = VERBOSE showtiming = SHOWTIMING begin + method = (:collocation, :adnlp, :ipopt) + + # foo does not correspond to any tool nor to solve -> error + Test.@test_throws OptimalControl.IncorrectArgument begin + OptimalControl._ensure_no_ambiguous_description_kwargs(method, (foo=1,)) + end + end + end + + Test.@testset "option routing helpers" verbose = VERBOSE showtiming = SHOWTIMING begin + # _extract_option_tool without explicit tool tag + v, tool = OptimalControl._extract_option_tool(1.0) + Test.@test v == 1.0 + Test.@test tool === nothing + + # _extract_option_tool with explicit tool tag + v2, tool2 = OptimalControl._extract_option_tool((42, :solver)) + Test.@test v2 == 42 + Test.@test tool2 === :solver + + # Non-ambiguous routing: single owner + v3, owner3 = OptimalControl._route_option_for_description( + :tol, 1e-6, Symbol[:solver], :description + ) + Test.@test v3 == 1e-6 + Test.@test owner3 === :solver + + # Unknown ownership: empty owner list + owners_empty = Symbol[] + Test.@test_throws OptimalControl.IncorrectArgument OptimalControl._route_option_for_description( + :foo, 1, owners_empty, :description + ) + + # Ambiguous ownership in description mode + owners_amb = Symbol[:discretizer, :solver] + err = nothing + try + OptimalControl._route_option_for_description(:foo, 1.0, owners_amb, :description) + catch e + err = e + end + Test.@test err isa OptimalControl.IncorrectArgument + + # Disambiguation via (value, tool) + v4, owner4 = OptimalControl._route_option_for_description( + :foo, (2.0, :solver), owners_amb, :description + ) + Test.@test v4 == 2.0 + Test.@test owner4 === :solver + + # Ambiguous when coming from explicit mode should also throw + Test.@test_throws OptimalControl.IncorrectArgument OptimalControl._route_option_for_description( + :foo, 1.0, owners_amb, :explicit + ) + end + + Test.@testset "description kwarg splitting" verbose = VERBOSE showtiming = SHOWTIMING begin + # Ensure that description-mode parsing and splitting of kwargs produces + # well-typed NamedTuples and routes options to the expected tools. + parsed = OptimalControl._parse_top_level_kwargs_description(( + initial_guess=OCDummyInit([1.0, 2.0]), + display=false, + modeler_options=(backend=:manual,), + tol=1e-6, + )) + + pieces = OptimalControl._split_kwargs_for_description( + (:collocation, :adnlp, :ipopt), parsed + ) + + Test.@test pieces.initial_guess isa OCDummyInit + Test.@test pieces.display == false + Test.@test pieces.disc_kwargs == NamedTuple() + Test.@test pieces.modeler_options == (backend=:manual,) + Test.@test haskey(pieces.solver_kwargs, :tol) + Test.@test pieces.solver_kwargs.tol == 1e-6 + + # Solve-level aliases should be accepted in description mode. + parsed_alias = OptimalControl._parse_top_level_kwargs_description(( + init=OCDummyInit([3.0, 4.0]), + display=false, + modeler_options=(backend=:manual,), + tol=2e-6, + )) + + pieces_alias = OptimalControl._split_kwargs_for_description( + (:collocation, :adnlp, :ipopt), parsed_alias + ) + + Test.@test pieces_alias.initial_guess isa OCDummyInit + Test.@test pieces_alias.display == false + Test.@test pieces_alias.disc_kwargs == NamedTuple() + Test.@test pieces_alias.modeler_options == (backend=:manual,) + Test.@test haskey(pieces_alias.solver_kwargs, :tol) + Test.@test pieces_alias.solver_kwargs.tol == 2e-6 + + # Conflicting aliases for initial_guess should raise. + Test.@test_throws OptimalControl.IncorrectArgument begin + OptimalControl._parse_top_level_kwargs_description(( + initial_guess=OCDummyInit([1.0, 2.0]), i=OCDummyInit([3.0, 4.0]) + )) + end + + Test.@testset "description-mode solve/tool disambiguation" verbose = VERBOSE showtiming = SHOWTIMING begin + init = OCDummyInit([1.0, 2.0]) + + # 1) Alias i tagged :solve -> used as initial_guess, not kept in other_kwargs + parsed_solve = OptimalControl._parse_top_level_kwargs_description(( + i=(init, :solve), tol=1e-6 + )) + + Test.@test parsed_solve.initial_guess isa OCDummyInit + Test.@test parsed_solve.initial_guess === init + Test.@test !haskey(parsed_solve.other_kwargs, :i) + Test.@test haskey(parsed_solve.other_kwargs, :tol) + Test.@test parsed_solve.other_kwargs.tol == 1e-6 + + # 2) Alias i tagged :solver -> ignored by solve, left for the tools + parsed_solver = OptimalControl._parse_top_level_kwargs_description(( + i=(init, :solver), tol=2e-6 + )) + + # initial_guess stays at its default, alias i is kept in other_kwargs + Test.@test parsed_solver.initial_guess === OptimalControl.__initial_guess() + Test.@test haskey(parsed_solver.other_kwargs, :i) + Test.@test parsed_solver.other_kwargs.i == (init, :solver) + Test.@test haskey(parsed_solver.other_kwargs, :tol) + Test.@test parsed_solver.other_kwargs.tol == 2e-6 + + # 3) display tagged :solve -> top-level display + parsed_display_solve = OptimalControl._parse_top_level_kwargs_description(( + display=(false, :solve), + )) + Test.@test parsed_display_solve.display == false + Test.@test !haskey(parsed_display_solve.other_kwargs, :display) + + # 4) display tagged :solver -> ignored by solve, left for the tools + parsed_display_solver = OptimalControl._parse_top_level_kwargs_description(( + display=(false, :solver), + )) + Test.@test parsed_display_solver.display == OptimalControl.__display() + Test.@test haskey(parsed_display_solver.other_kwargs, :display) + Test.@test parsed_display_solver.other_kwargs.display == (false, :solver) + end + end + + Test.@testset "explicit-mode solve kwarg aliases" verbose = VERBOSE showtiming = SHOWTIMING begin + prob = OCDummyOCP() + init = OCDummyInit([1.0, 2.0]) + + discretizer_calls = Ref(0) + model_calls = Ref(0) + solution_calls = Ref(0) + solver_calls = Ref(0) + + discretizer = OCFakeDiscretizer(discretizer_calls) + modeler = OCFakeModeler(model_calls, solution_calls) + solver = OCFakeSolverNLP(solver_calls) + + # Using the "init" alias for initial_guess. + sol_init = solve( + prob; + init=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + ) + Test.@test sol_init isa OCDummySolution + + # Using the short "i" alias for initial_guess. + discretizer_calls[] = 0 + model_calls[] = 0 + solution_calls[] = 0 + solver_calls[] = 0 + + sol_i = solve( + prob; + i=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + ) + Test.@test sol_i isa OCDummySolution + Test.@test discretizer_calls[] == 1 + Test.@test model_calls[] == 1 + Test.@test solver_calls[] == 1 + Test.@test solution_calls[] == 1 + + # Short aliases for components d/m/s in explicit mode. + discretizer_calls[] = 0 + model_calls[] = 0 + solution_calls[] = 0 + solver_calls[] = 0 + + sol_dms = solve( + prob; initial_guess=init, d=discretizer, m=modeler, s=solver, display=false + ) + Test.@test sol_dms isa OCDummySolution + Test.@test discretizer_calls[] == 1 + Test.@test model_calls[] == 1 + Test.@test solver_calls[] == 1 + Test.@test solution_calls[] == 1 + + # Conflicting aliases for initial_guess in explicit mode should raise. + Test.@test_throws OptimalControl.IncorrectArgument begin + solve( + prob; + initial_guess=init, + init=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + ) + end + end + + Test.@testset "display helpers" verbose = VERBOSE showtiming = SHOWTIMING begin + method = (:collocation, :adnlp, :ipopt) + discretizer = OptimalControl.Collocation() + modeler = OptimalControl.ADNLP() + solver = OptimalControl.Ipopt() + + buf = sprint() do io + OptimalControl._display_ocp_method( + io, method, discretizer, modeler, solver; display=true + ) + end + Test.@test occursin("ADNLPModels", buf) + Test.@test occursin("NLPModelsIpopt", buf) + end + + # ======================================================================== + # Unit test: solve(ocp, init, discretizer, modeler, solver) + # ======================================================================== + + Test.@testset "solve(ocp, init, discretizer, modeler, solver)" verbose = VERBOSE showtiming = SHOWTIMING begin + prob = OCDummyOCP() + init = OCDummyInit([1.0, 2.0]) + + discretizer_calls = Ref(0) + model_calls = Ref(0) + solution_calls = Ref(0) + solver_calls = Ref(0) + + discretizer = OCFakeDiscretizer(discretizer_calls) + modeler = OCFakeModeler(model_calls, solution_calls) + solver = OCFakeSolverNLP(solver_calls) + + sol = OptimalControl._solve(prob, init, discretizer, modeler, solver; display=false) + + Test.@test sol isa OCDummySolution + Test.@test discretizer_calls[] == 1 + Test.@test model_calls[] == 1 + Test.@test solver_calls[] == 1 + Test.@test solution_calls[] == 1 + end + + Test.@testset "explicit-mode kwarg validation" verbose = VERBOSE showtiming = SHOWTIMING begin + prob = OCDummyOCP() + init = OCDummyInit([1.0, 2.0]) + + discretizer_calls = Ref(0) + model_calls = Ref(0) + solution_calls = Ref(0) + solver_calls = Ref(0) + + discretizer = OCFakeDiscretizer(discretizer_calls) + modeler = OCFakeModeler(model_calls, solution_calls) + solver = OCFakeSolverNLP(solver_calls) + + # modeler_options is forbidden in explicit mode + Test.@test_throws OptimalControl.IncorrectArgument begin + solve( + prob; + initial_guess=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + modeler_options=(backend=:manual,), + ) + end + + # Unknown kwargs are rejected in explicit mode + Test.@test_throws OptimalControl.IncorrectArgument begin + solve( + prob; + initial_guess=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + unknown_kwarg=1, + ) + end + + # Mixing description with explicit components is rejected + Test.@test_throws OptimalControl.IncorrectArgument begin + solve( + prob, + :collocation; + initial_guess=init, + discretizer=discretizer, + display=false, + ) + end + end + + Test.@testset "solve(ocp; kwargs)" verbose = VERBOSE showtiming = SHOWTIMING begin + prob = OCDummyOCP() + init = OCDummyInit([1.0, 2.0]) + + discretizer_calls = Ref(0) + model_calls = Ref(0) + solution_calls = Ref(0) + solver_calls = Ref(0) + + discretizer = OCFakeDiscretizer(discretizer_calls) + modeler = OCFakeModeler(model_calls, solution_calls) + solver = OCFakeSolverNLP(solver_calls) + + sol = solve( + prob; + initial_guess=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + ) + + Test.@test sol isa OCDummySolution + Test.@test discretizer_calls[] == 1 + Test.@test model_calls[] == 1 + Test.@test solver_calls[] == 1 + Test.@test solution_calls[] == 1 + end + + # ======================================================================== + # Integration tests: Beam OCP level with Ipopt and MadNLP + # ======================================================================== + + Test.@testset "Beam OCP level" verbose = VERBOSE showtiming = SHOWTIMING begin + ipopt_options = Dict( + :max_iter => 1000, + :tol => 1e-6, + :print_level => 0, + :mu_strategy => "adaptive", + :linear_solver => "Mumps", + :sb => "yes", + ) + + madnlp_options = Dict(:max_iter => 1000, :tol => 1e-6, :print_level => MadNLP.ERROR) + + beam_data = Beam() + ocp = beam_data.ocp + init = OptimalControl.initial_guess(ocp; beam_data.init...) + discretizer = OptimalControl.Collocation() + + modelers = [OptimalControl.ADNLP(; backend=:manual), OptimalControl.Exa()] + modelers_names = ["ADNLP (manual)", "Exa (CPU)"] + + # ------------------------------------------------------------------ + # OCP level: solve(ocp, init, discretizer, modeler, solver) + # ------------------------------------------------------------------ + + Test.@testset "OCP level (Ipopt)" verbose = VERBOSE showtiming = SHOWTIMING begin + for (modeler, modeler_name) in zip(modelers, modelers_names) + Test.@testset "$(modeler_name)" verbose = VERBOSE showtiming = SHOWTIMING begin + solver = OptimalControl.Ipopt(; ipopt_options...) + sol = OptimalControl._solve( + ocp, init, discretizer, modeler, solver; display=false + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test objective(sol) ≈ beam_data.obj atol = 1e-2 + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + Test.@testset "OCP level (MadNLP)" verbose = VERBOSE showtiming = SHOWTIMING begin + for (modeler, modeler_name) in zip(modelers, modelers_names) + Test.@testset "$(modeler_name)" verbose = VERBOSE showtiming = SHOWTIMING begin + solver = OptimalControl.MadNLP(; madnlp_options...) + sol = OptimalControl._solve( + ocp, init, discretizer, modeler, solver; display=false + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test objective(sol) ≈ beam_data.obj atol = 1e-2 + Test.@test iterations(sol) <= madnlp_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + # ------------------------------------------------------------------ + # OCP level with @init (Ipopt, ADNLP) + # ------------------------------------------------------------------ + + Test.@testset "OCP level with @init (Ipopt, ADNLP)" verbose = VERBOSE showtiming = SHOWTIMING begin + init_macro = OptimalControl.@init ocp begin + x := [0.05, 0.1] + u := 0.1 + end + modeler = OptimalControl.ADNLP(; backend=:manual) + solver = OptimalControl.Ipopt(; ipopt_options...) + sol = OptimalControl._solve( + ocp, init_macro, discretizer, modeler, solver; display=false + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + end + + # ------------------------------------------------------------------ + # OCP level: keyword-based API solve(ocp; ...) + # ------------------------------------------------------------------ + + Test.@testset "OCP level keyword API (Ipopt, ADNLP)" verbose = VERBOSE showtiming = SHOWTIMING begin + modeler = OptimalControl.ADNLP(; backend=:manual) + solver = OptimalControl.Ipopt(; ipopt_options...) + sol = solve( + ocp; + initial_guess=init, + discretizer=discretizer, + modeler=modeler, + solver=solver, + display=false, + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + + # ------------------------------------------------------------------ + # OCP level: description-based API solve(ocp, description; ...) + # ------------------------------------------------------------------ + + Test.@testset "OCP level description API" verbose = VERBOSE showtiming = SHOWTIMING begin + desc_cases = [ + ((:collocation, :adnlp, :ipopt), ipopt_options), + ((:collocation, :adnlp, :madnlp), madnlp_options), + ((:collocation, :exa, :ipopt), ipopt_options), + ((:collocation, :exa, :madnlp), madnlp_options), + ] + + for (method_syms, options) in desc_cases + Test.@testset "description = $(method_syms)" verbose = VERBOSE showtiming = SHOWTIMING begin + sol = solve( + ocp, method_syms...; initial_guess=init, display=false, options... + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + + if :ipopt in method_syms + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + elseif :madnlp in method_syms + Test.@test iterations(sol) <= madnlp_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + # modeler_options is allowed in description mode and forwarded to the + # modeler constructor. + Test.@testset "description API with modeler_options" verbose = VERBOSE showtiming = SHOWTIMING begin + sol = solve( + ocp, + :collocation, + :adnlp, + :ipopt; + initial_guess=init, + modeler_options=(backend=:manual,), + display=false, + ipopt_options..., + ) + Test.@test sol isa Solution + Test.@test successful(sol) + end + + # Tagged options using the (value, tool) convention: discretizer options + # are explicitly routed to the discretizer, and Ipopt options to the solver. + Test.@testset "description API with explicit tool tags" verbose = VERBOSE showtiming = SHOWTIMING begin + sol = solve( + ocp, + :collocation, + :adnlp, + :ipopt; + initial_guess=init, + display=false, + # Discretizer options + grid=(get_option_value(discretizer, :grid), :discretizer), + scheme=(get_option_value(discretizer, :scheme), :discretizer), + # Ipopt solver options + max_iter=(ipopt_options[:max_iter], :solver), + tol=(ipopt_options[:tol], :solver), + print_level=(ipopt_options[:print_level], :solver), + mu_strategy=(ipopt_options[:mu_strategy], :solver), + linear_solver=(ipopt_options[:linear_solver], :solver), + sb=(ipopt_options[:sb], :solver), + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + # ======================================================================== + # Integration tests: Goddard OCP level with Ipopt and MadNLP + # ======================================================================== + + Test.@testset "Goddard OCP level" verbose = VERBOSE showtiming = SHOWTIMING begin + ipopt_options = Dict( + :max_iter => 1000, + :tol => 1e-6, + :print_level => 0, + :mu_strategy => "adaptive", + :linear_solver => "Mumps", + :sb => "yes", + ) + + madnlp_options = Dict(:max_iter => 1000, :tol => 1e-6, :print_level => MadNLP.ERROR) + + gdata = Goddard() + ocp_g = gdata.ocp + init_g = OptimalControl.initial_guess(ocp_g; gdata.init...) + discretizer_g = OptimalControl.Collocation() + + modelers = [OptimalControl.ADNLP(; backend=:manual), OptimalControl.Exa()] + modelers_names = ["ADNLP (manual)", "Exa (CPU)"] + + # ------------------------------------------------------------------ + # OCP level: solve(ocp_g, init_g, discretizer_g, modeler, solver) + # ------------------------------------------------------------------ + + Test.@testset "OCP level (Ipopt)" verbose = VERBOSE showtiming = SHOWTIMING begin + for (modeler, modeler_name) in zip(modelers, modelers_names) + Test.@testset "$(modeler_name)" verbose = VERBOSE showtiming = SHOWTIMING begin + solver = OptimalControl.Ipopt(; ipopt_options...) + sol = OptimalControl._solve( + ocp_g, init_g, discretizer_g, modeler, solver; display=false + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test objective(sol) ≈ gdata.obj atol = 1e-4 + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + Test.@testset "OCP level (MadNLP)" verbose = VERBOSE showtiming = SHOWTIMING begin + for (modeler, modeler_name) in zip(modelers, modelers_names) + Test.@testset "$(modeler_name)" verbose = VERBOSE showtiming = SHOWTIMING begin + solver = OptimalControl.MadNLP(; madnlp_options...) + sol = OptimalControl._solve( + ocp_g, init_g, discretizer_g, modeler, solver; display=false + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test objective(sol) ≈ gdata.obj atol = 1e-4 + Test.@test iterations(sol) <= madnlp_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + + # ------------------------------------------------------------------ + # OCP level keyword API (Ipopt, ADNLP) + # ------------------------------------------------------------------ + + Test.@testset "OCP level keyword API (Ipopt, ADNLP)" verbose = VERBOSE showtiming = SHOWTIMING begin + modeler = OptimalControl.ADNLP(; backend=:manual) + solver = OptimalControl.Ipopt(; ipopt_options...) + sol = solve( + ocp_g; + initial_guess=init_g, + discretizer=discretizer_g, + modeler=modeler, + solver=solver, + display=false, + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + + # ------------------------------------------------------------------ + # OCP level description API (Ipopt and MadNLP) + # ------------------------------------------------------------------ + + Test.@testset "OCP level description API" verbose = VERBOSE showtiming = SHOWTIMING begin + desc_cases = [ + ((:collocation, :adnlp, :ipopt), ipopt_options), + ((:collocation, :adnlp, :madnlp), madnlp_options), + ((:collocation, :exa, :ipopt), ipopt_options), + ((:collocation, :exa, :madnlp), madnlp_options), + ] + + for (method_syms, options) in desc_cases + Test.@testset "description = $(method_syms)" verbose = VERBOSE showtiming = SHOWTIMING begin + sol = solve( + ocp_g, + method_syms...; + initial_guess=init_g, + display=false, + options..., + ) + Test.@test sol isa Solution + Test.@test successful(sol) + Test.@test isfinite(objective(sol)) + + if :ipopt in method_syms + Test.@test iterations(sol) <= ipopt_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + elseif :madnlp in method_syms + Test.@test iterations(sol) <= madnlp_options[:max_iter] + Test.@test constraints_violation(sol) <= 1e-6 + end + end + end + end + end +end \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..bf197fdd7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,271 @@ +# Changelog + +All notable changes to **OptimalControl.jl** are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] — branch `action-options` + +### Added + +- **Action options routing**: `initial_guess` and `display` are now routed through + `CTSolvers.route_all_options`, enabling alias support and a cleaner separation of + concerns between action-level and strategy-level options. +- **Alias `init`** for `initial_guess` in all solve modes: + ```julia + solve(ocp, :collocation; init=x0) + ``` +- **`_extract_action_kwarg`** helper in `src/helpers/kwarg_extraction.jl`: alias-aware + extraction with conflict detection (raises `CTBase.IncorrectArgument` when two aliases + are provided simultaneously). +- **DRY constants** in `src/helpers/descriptive_routing.jl`: + - `_DEFAULT_DISPLAY = true` + - `_DEFAULT_INITIAL_GUESS = nothing` + - `_INITIAL_GUESS_ALIASES_ONLY = (:init,)` — used in `OptionDefinition` + - `_INITIAL_GUESS_ALIASES = (:initial_guess, :init)` — used in `_extract_action_kwarg` +- **Docstring** for the Layer 3 `CommonSolve.solve` method in `src/solve/canonical.jl`. + +### Changed + +- `CommonSolve.solve` top-level signature simplified: `initial_guess` and `display` are + no longer explicit named arguments — they are extracted from `kwargs...` by the routing + layer. +- `solve_descriptive` no longer accepts `initial_guess` and `display` as explicit named + arguments; they are extracted from `kwargs...` via `_build_components_from_routed`. +- `solve_explicit` extracts `initial_guess` (with alias `init`) and `display` from + `kwargs...` using `_extract_action_kwarg`. +- `_build_components_from_routed` now receives `ocp` as first argument to call + `CTModels.build_initial_guess`. + +### Removed + +- Alias `:i` for `initial_guess` (too short, risk of collision with user variables). + +--- + +## [1.1.8-beta] — 2026-01-17 + +### Changed + +- Widened compat for **CTParser** to accept `0.7` and `0.8` (preparation for CTParser + v0.8.x migration, tracked in control-toolbox/CTParser.jl#207). +- Widened compat for **CTBase** to accept `0.16` and `0.17`. + +--- + +## [1.1.7-beta] — 2026-01-17 + +### Changed + +- Added compat for **CTBase v0.17**. +- Merged test dependencies into the main `Project.toml` (previously in a separate + `test/Project.toml`). + +--- + +## [1.1.6] — 2025-10-31 + +### Added + +- **`RecipesBase`** added as a direct dependency, enabling plot recipes for solutions + without requiring `Plots.jl` to be loaded. + +### Fixed + +- Improved error handling for the `Plots.jl` extension: a clear `CTBase.IncorrectArgument` + is now raised when plotting is attempted without `Plots.jl` loaded (#653). +- Fixed maximisation objective sign for ExaModels backend (#663). +- Replaced `Minpack` by `NonlinearSolve` in the shooting extension. + +### Changed + +- Bumped compat for **NLPModelsIpopt** to `0.11`. + +--- + +## [1.1.5] — 2025-10-23 + +### Added + +- AI assistant buttons in the documentation to try examples interactively. +- Spell-check CI workflow (`SpellCheck.yml`). + +--- + +## [1.1.4] — 2025-10-05 + +### Fixed + +- Improved error handling for the `Plots.jl` extension (#653): raises a descriptive + error instead of a cryptic `MethodError` when `Plots` is not loaded. + +### Added + +- JuliaCon Paris 2025 documentation page. +- Responsive CSS columns (math vs code) in documentation. + +--- + +## [1.1.3] — 2025-09-25 + +### Added + +- Documentation for AI-assisted problem description generation (`manual-ai-ded.md`). +- Documentation for GPU solving (`manual-solve-gpu.md` update). +- Usage of `MadNLPMumps` in documentation examples. + +--- + +## [1.1.2] — 2025-09-25 + +### Added + +- **Trapeze scheme** support via CTDirect v0.17 (`scheme=:trapeze`). +- **ExaModels v0.9** compat. +- Indirect method examples in documentation. +- Detailed solver options documentation. + +### Changed + +- Bumped compat for **CTDirect** to `0.17`. +- Bumped compat for **CTParser** to `0.7`. +- Default scheme documented explicitly. + +--- + +## [1.1.1] — 2025-08-06 + +### Changed + +- Bumped compat for **ExaModels** to `0.9`. +- Updated GPU solve documentation. + +--- + +## [1.1.0] — 2025-08-05 + +### Added + +- **`ADNLPModels`** and **`ExaModels`** added as direct dependencies, enabling GPU + solving via ExaModels backend out of the box. +- GPU solving documentation (`manual-solve-gpu.md`). +- Export of `dual` function. +- Flow with state constraints support (CTFlows update). +- Non-autonomous flow tutorial. +- `display` option for `solve`: shows a compact configuration table before solving. +- MadNLP solver added to the registry and available methods. +- Documentation for the `solve` function arguments (tutorial-solve.md). +- Manual pages for OCP model interaction and solution inspection. +- JLESC17 and JuliaCon 2025 conference documentation. + +### Changed + +- Bumped compat for **CTBase** to `0.16`. +- Bumped compat for **CTDirect** to `0.16`. +- Bumped compat for **CTModels** to `0.6`. +- Bumped compat for **CTParser** to `0.6`. +- Bumped compat for **ADNLPModels** to `0.8`. +- Bumped compat for **ExaModels** to `0.8`. + +--- + +## [1.0.3] — 2025-05-08 + +### Changed + +- Bumped compat for **CTModels** to `0.3`. +- Bumped compat for **CTBase** to `0.16`. +- Removed tutorials from the documentation (moved to separate repositories). +- Pretty URLs in documentation. + +--- + +## [1.0.2] — 2025-05-05 + +### Changed + +- Renamed `export`/`import` keyword (internal change following CTBase update). +- Bumped compat for **CTBase**. +- Added `Breakage.yml` CI workflow. + +--- + +## [1.0.1] — 2025-05-04 + +### Added + +- Scalar vs dimension-one variable handling improvement (#478). +- Documentation updates: dependency graph, tutorials, README. + +### Fixed + +- Typo in tutorial (#475, @oameye). + +--- + +## [1.0.0] — 2025-04-18 + +Initial stable release. + +### Dependencies + +| Package | Compat | +|---|---| +| CTBase | 0.15 | +| CTDirect | 0.14 | +| CTFlows | 0.8 | +| CTModels | 0.2 | +| CTParser | 0.2 | +| CommonSolve | 0.2 | +| Julia | ≥ 1.10 | + +--- + +## Breaking Changes Summary + +This section summarises all breaking changes since v1.0.0 for users upgrading across +multiple versions. + +### v1.2.0-beta (current `action-options` branch) + +- **`solve` signature change**: `initial_guess` and `display` are no longer positional + or explicitly named keyword arguments in the top-level `CommonSolve.solve`, + `solve_descriptive`, and `solve_explicit`. They are now extracted from `kwargs...`. + Existing call sites using `solve(ocp; initial_guess=x0, display=false)` continue to + work unchanged — only internal dispatch signatures changed. +- **Alias `:i` removed**: `solve(ocp; i=x0)` now raises `CTBase.IncorrectArgument`. + Use `init=x0` or `initial_guess=x0` instead. + +### v1.1.0 + +- **CTBase v0.16 required** (from v0.15): users of CTBase directly may need to update. +- **CTModels v0.6 required** (from v0.2–v0.3): significant internal API changes in + CTModels; users relying on internal CTModels types should review the CTModels changelog. +- **CTParser v0.6 required** (from v0.2): parser API updated. +- **CTDirect v0.16 required** (from v0.14): discretization API updated. +- **`ADNLPModels` and `ExaModels` are now direct dependencies**: they will be installed + automatically. This should not break existing code but increases installation size. + +### v1.0.2 + +- **`export`/`import` keyword renamed**: if you used `export=...` or `import=...` as + keyword arguments to any OptimalControl function, rename to the new keyword (see + CTBase changelog for details). + +[Unreleased]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.8-beta...HEAD +[1.1.8-beta]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.7-beta...v1.1.8-beta +[1.1.7-beta]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.6...v1.1.7-beta +[1.1.6]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.5...v1.1.6 +[1.1.5]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.4...v1.1.5 +[1.1.4]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.3...v1.1.4 +[1.1.3]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.2...v1.1.3 +[1.1.2]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.0.3...v1.1.0 +[1.0.3]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.0.2...v1.0.3 +[1.0.2]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/control-toolbox/OptimalControl.jl/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/control-toolbox/OptimalControl.jl/releases/tag/v1.0.0 diff --git a/Project.toml b/Project.toml index a48815028..6fdc44553 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "OptimalControl" uuid = "5f98b655-cc9a-415a-b60e-744165666948" +version = "1.2.2-beta" authors = ["Olivier Cots "] -version = "1.1.6" [deps] ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" @@ -10,20 +10,61 @@ CTDirect = "790bbbee-bee9-49ee-8912-a9de031322d5" CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" +CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" [compat] ADNLPModels = "0.8" -CTBase = "0.16" -CTDirect = "0.17" +CTBase = "0.18" +CTDirect = "1" CTFlows = "0.8" -CTModels = "0.6" -CTParser = "0.7" +CTModels = "0.9" +CTParser = "0.8" +CTSolvers = "0.3" +CUDA = "5" CommonSolve = "0.2" +DifferentiationInterface = "0.7" DocStringExtensions = "0.9" ExaModels = "0.9" +ForwardDiff = "0.10, 1.0" +LinearAlgebra = "1" +MadNCL = "0.1" +MadNLP = "0.8" +MadNLPGPU = "0.7" +MadNLPMumps = "0.5" +NLPModels = "0.21.7" +NLPModelsIpopt = "0.11" +NonlinearSolve = "4" +OrdinaryDiffEq = "6" +Printf = "1" RecipesBase = "1" +Reexport = "1" +SolverCore = "0.3.9" +SplitApplyCombine = "1" +Test = "1" julia = "1.10" + +[extras] +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MadNCL = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPGPU = "d72a61cc-809d-412f-99be-fd81f4b8a598" +MadNLPMumps = "3b83494e-c0a4-4895-918b-9157a7a085a1" +NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["CUDA", "DifferentiationInterface", "ForwardDiff", "LinearAlgebra", "MadNCL", "MadNLP", "MadNLPGPU", "MadNLPMumps", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "Printf", "SplitApplyCombine", "Test"] diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml deleted file mode 100644 index 78e84667e..000000000 --- a/docs/src/assets/Manifest.toml +++ /dev/null @@ -1,2875 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.12.1" -manifest_format = "2.0" -project_hash = "ed383b814e8d550b2e19c67b5eb177cab6b8ccff" - -[[deps.ADNLPModels]] -deps = ["ADTypes", "ForwardDiff", "LinearAlgebra", "NLPModels", "Requires", "ReverseDiff", "SparseArrays", "SparseConnectivityTracer", "SparseMatrixColorings"] -git-tree-sha1 = "c51dc1d4eeb37a40f6aecdd08e5cdcddd8332726" -uuid = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" -version = "0.8.13" - -[[deps.ADTypes]] -git-tree-sha1 = "27cecae79e5cc9935255f90c53bb831cc3c870d7" -uuid = "47edcb42-4c32-4615-8424-f2b9edc5f35b" -version = "1.18.0" -weakdeps = ["ChainRulesCore", "ConstructionBase", "EnzymeCore"] - - [deps.ADTypes.extensions] - ADTypesChainRulesCoreExt = "ChainRulesCore" - ADTypesConstructionBaseExt = "ConstructionBase" - ADTypesEnzymeCoreExt = "EnzymeCore" - -[[deps.AMD]] -deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse_jll"] -git-tree-sha1 = "45a1272e3f809d36431e57ab22703c6896b8908f" -uuid = "14f7f29c-3bd6-536c-9a0b-7339e30b5a3e" -version = "0.5.3" - -[[deps.ANSIColoredPrinters]] -git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" -uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" -version = "0.0.1" - -[[deps.ASL_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "6252039f98492252f9e47c312c8ffda0e3b9e78d" -uuid = "ae81ac8f-d209-56e5-92de-9978fef736f9" -version = "0.1.3+0" - -[[deps.AbstractTrees]] -git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" -uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" -version = "0.4.5" - -[[deps.Accessors]] -deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] -git-tree-sha1 = "3b86719127f50670efe356bc11073d84b4ed7a5d" -uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" -version = "0.1.42" - - [deps.Accessors.extensions] - AxisKeysExt = "AxisKeys" - IntervalSetsExt = "IntervalSets" - LinearAlgebraExt = "LinearAlgebra" - StaticArraysExt = "StaticArrays" - StructArraysExt = "StructArrays" - TestExt = "Test" - UnitfulExt = "Unitful" - - [deps.Accessors.weakdeps] - AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5" - IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" - LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" - StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" - Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.Adapt]] -deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "7e35fca2bdfba44d797c53dfe63a51fabf39bfc0" -uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.4.0" -weakdeps = ["SparseArrays", "StaticArrays"] - - [deps.Adapt.extensions] - AdaptSparseArraysExt = "SparseArrays" - AdaptStaticArraysExt = "StaticArrays" - -[[deps.AliasTables]] -deps = ["PtrArrays", "Random"] -git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" -uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" -version = "1.1.3" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.2" - -[[deps.ArrayInterface]] -deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "d81ae5489e13bc03567d4fbbb06c546a5e53c857" -uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.22.0" - - [deps.ArrayInterface.extensions] - ArrayInterfaceBandedMatricesExt = "BandedMatrices" - ArrayInterfaceBlockBandedMatricesExt = "BlockBandedMatrices" - ArrayInterfaceCUDAExt = "CUDA" - ArrayInterfaceCUDSSExt = ["CUDSS", "CUDA"] - ArrayInterfaceChainRulesCoreExt = "ChainRulesCore" - ArrayInterfaceChainRulesExt = "ChainRules" - ArrayInterfaceGPUArraysCoreExt = "GPUArraysCore" - ArrayInterfaceMetalExt = "Metal" - ArrayInterfaceReverseDiffExt = "ReverseDiff" - ArrayInterfaceSparseArraysExt = "SparseArrays" - ArrayInterfaceStaticArraysCoreExt = "StaticArraysCore" - ArrayInterfaceTrackerExt = "Tracker" - - [deps.ArrayInterface.weakdeps] - BandedMatrices = "aae01518-5342-5314-be14-df237901396f" - BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" - ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" - Metal = "dde4c033-4e86-420c-a63e-0dd931031962" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - -[[deps.ArrayLayouts]] -deps = ["FillArrays", "LinearAlgebra", "StaticArrays"] -git-tree-sha1 = "355ab2d61069927d4247cd69ad0e1f140b31e30d" -uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -version = "1.12.0" -weakdeps = ["SparseArrays"] - - [deps.ArrayLayouts.extensions] - ArrayLayoutsSparseArraysExt = "SparseArrays" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -version = "1.11.0" - -[[deps.AxisAlgorithms]] -deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"] -git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712" -uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950" -version = "1.1.0" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" -version = "1.11.0" - -[[deps.BitFlags]] -git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" -uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" -version = "0.1.9" - -[[deps.BitTwiddlingConvenienceFunctions]] -deps = ["Static"] -git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" -uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" -version = "0.1.6" - -[[deps.BracketingNonlinearSolve]] -deps = ["CommonSolve", "ConcreteStructs", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "57f9f59bec88ce80cd3e46efc39f126395789bb0" -uuid = "70df07ce-3d50-431d-a3e7-ca6ddb60ac1e" -version = "1.5.0" -weakdeps = ["ChainRulesCore", "ForwardDiff"] - - [deps.BracketingNonlinearSolve.extensions] - BracketingNonlinearSolveChainRulesCoreExt = ["ChainRulesCore", "ForwardDiff"] - BracketingNonlinearSolveForwardDiffExt = "ForwardDiff" - -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - -[[deps.CPUSummary]] -deps = ["CpuId", "IfElse", "PrecompileTools", "Preferences", "Static"] -git-tree-sha1 = "f3a21d7fc84ba618a779d1ed2fcca2e682865bab" -uuid = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -version = "0.2.7" - -[[deps.CTBase]] -deps = ["DocStringExtensions"] -git-tree-sha1 = "62017f38515aea94b44eaa4892e3552f5ef5dcbf" -uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" -version = "0.16.4" -weakdeps = ["HTTP", "JSON"] - - [deps.CTBase.extensions] - CTBaseDocstrings = ["HTTP", "JSON"] - -[[deps.CTDirect]] -deps = ["CTBase", "CTModels", "DocStringExtensions", "HSL", "MKL", "SparseArrays"] -git-tree-sha1 = "a3e796c81478034446feb316816ccfe1f250f211" -uuid = "790bbbee-bee9-49ee-8912-a9de031322d5" -version = "0.17.3" -weakdeps = ["ADNLPModels", "ExaModels", "MadNLP", "NLPModelsIpopt", "NLPModelsKnitro"] - - [deps.CTDirect.extensions] - CTDirectExtADNLP = ["ADNLPModels"] - CTDirectExtExa = ["ExaModels"] - CTDirectExtIpopt = ["NLPModelsIpopt"] - CTDirectExtKnitro = ["NLPModelsKnitro"] - CTDirectExtMadNLP = ["MadNLP"] - -[[deps.CTFlows]] -deps = ["CTBase", "CTModels", "DocStringExtensions", "ForwardDiff", "LinearAlgebra", "MLStyle", "MacroTools"] -git-tree-sha1 = "fc6d7425e7e54a6cb023b24e05a7c0430c9165bf" -uuid = "1c39547c-7794-42f7-af83-d98194f657c2" -version = "0.8.9" -weakdeps = ["OrdinaryDiffEq"] - - [deps.CTFlows.extensions] - CTFlowsODE = "OrdinaryDiffEq" - -[[deps.CTModels]] -deps = ["CTBase", "DocStringExtensions", "Interpolations", "LinearAlgebra", "MLStyle", "MacroTools", "OrderedCollections", "Parameters", "RecipesBase"] -git-tree-sha1 = "072e5e867715b060729158d4c5fb3b16ff7e82b0" -uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" -version = "0.6.9" -weakdeps = ["JLD2", "JSON3", "Plots"] - - [deps.CTModels.extensions] - CTModelsJLD = "JLD2" - CTModelsJSON = "JSON3" - CTModelsPlots = "Plots" - -[[deps.CTParser]] -deps = ["CTBase", "DocStringExtensions", "MLStyle", "OrderedCollections", "Parameters", "Unicode"] -git-tree-sha1 = "48ec8193487a79277ff278752337c4ffb8fff691" -uuid = "32681960-a1b1-40db-9bff-a1ca817385d1" -version = "0.7.1" - -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "fde3bf89aead2e723284a8ff9cdf5b551ed700e8" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.5+0" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra"] -git-tree-sha1 = "e4c6a16e77171a5f5e25e9646617ab1c276c5607" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.26.0" -weakdeps = ["SparseArrays"] - - [deps.ChainRulesCore.extensions] - ChainRulesCoreSparseArraysExt = "SparseArrays" - -[[deps.ChunkCodecCore]] -git-tree-sha1 = "51f4c10ee01bda57371e977931de39ee0f0cdb3e" -uuid = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1" -version = "1.0.0" - -[[deps.ChunkCodecLibZlib]] -deps = ["ChunkCodecCore", "Zlib_jll"] -git-tree-sha1 = "cee8104904c53d39eb94fd06cbe60cb5acde7177" -uuid = "4c0bbee4-addc-4d73-81a0-b6caacae83c8" -version = "1.0.0" - -[[deps.ChunkCodecLibZstd]] -deps = ["ChunkCodecCore", "Zstd_jll"] -git-tree-sha1 = "34d9873079e4cb3d0c62926a225136824677073f" -uuid = "55437552-ac27-4d47-9aa3-63184e8fd398" -version = "1.0.0" - -[[deps.CloseOpenIntervals]] -deps = ["Static", "StaticArrayInterface"] -git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581" -uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9" -version = "0.1.13" - -[[deps.CodecZlib]] -deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" -uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.8" - -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.31.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.1" -weakdeps = ["StyledStrings"] - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.0" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] -git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.1" - -[[deps.CommonSolve]] -git-tree-sha1 = "0eee5eb66b1cf62cd6ad1b460238e60e4b09400c" -uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" -version = "0.2.4" - -[[deps.CommonSubexpressions]] -deps = ["MacroTools"] -git-tree-sha1 = "cda2cfaebb4be89c9084adaca7dd7333369715c5" -uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" -version = "0.3.1" - -[[deps.CommonWorldInvalidations]] -git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" -uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" -version = "1.0.0" - -[[deps.Compat]] -deps = ["TOML", "UUIDs"] -git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.18.1" -weakdeps = ["Dates", "LinearAlgebra"] - - [deps.Compat.extensions] - CompatLinearAlgebraExt = "LinearAlgebra" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.3.0+1" - -[[deps.CompositionsBase]] -git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad" -uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b" -version = "0.1.2" -weakdeps = ["InverseFunctions"] - - [deps.CompositionsBase.extensions] - CompositionsBaseInverseFunctionsExt = "InverseFunctions" - -[[deps.ConcreteStructs]] -git-tree-sha1 = "f749037478283d372048690eb3b5f92a79432b34" -uuid = "2569d6c7-a4a2-43d3-a901-331e8e4be471" -version = "0.2.3" - -[[deps.ConcurrentUtilities]] -deps = ["Serialization", "Sockets"] -git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd" -uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" -version = "2.5.0" - -[[deps.ConstructionBase]] -git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" -uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" -version = "1.6.0" - - [deps.ConstructionBase.extensions] - ConstructionBaseIntervalSetsExt = "IntervalSets" - ConstructionBaseLinearAlgebraExt = "LinearAlgebra" - ConstructionBaseStaticArraysExt = "StaticArrays" - - [deps.ConstructionBase.weakdeps] - IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" - LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" - -[[deps.Contour]] -git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" -uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" -version = "0.6.3" - -[[deps.CpuId]] -deps = ["Markdown"] -git-tree-sha1 = "fcbb72b032692610bfbdb15018ac16a36cf2e406" -uuid = "adafc99b-e345-5852-983c-f28acb93d879" -version = "0.3.1" - -[[deps.Crayons]] -git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" -uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" -version = "4.1.1" - -[[deps.DataAPI]] -git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" -uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.16.0" - -[[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "d8928e9169ff76c6281f39a659f9bca3a573f24c" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.1" - -[[deps.DataStructures]] -deps = ["OrderedCollections"] -git-tree-sha1 = "6c72198e6a101cccdd4c9731d3985e904ba26037" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.19.1" - -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -version = "1.11.0" - -[[deps.Dbus_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" -uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" -version = "1.16.2+0" - -[[deps.DelimitedFiles]] -deps = ["Mmap"] -git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae" -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" -version = "1.9.1" - -[[deps.DiffEqBase]] -deps = ["ArrayInterface", "ConcreteStructs", "DocStringExtensions", "EnzymeCore", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "Logging", "Markdown", "MuladdMacro", "PrecompileTools", "Printf", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "Setfield", "Static", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface", "TruncatedStacktraces"] -git-tree-sha1 = "087632db966c90079a5534e4147afea9136ca39a" -uuid = "2b5f629d-d688-5b77-993f-72d75c75574e" -version = "6.190.2" - - [deps.DiffEqBase.extensions] - DiffEqBaseCUDAExt = "CUDA" - DiffEqBaseChainRulesCoreExt = "ChainRulesCore" - DiffEqBaseEnzymeExt = ["ChainRulesCore", "Enzyme"] - DiffEqBaseForwardDiffExt = ["ForwardDiff"] - DiffEqBaseGTPSAExt = "GTPSA" - DiffEqBaseGeneralizedGeneratedExt = "GeneralizedGenerated" - DiffEqBaseMPIExt = "MPI" - DiffEqBaseMeasurementsExt = "Measurements" - DiffEqBaseMonteCarloMeasurementsExt = "MonteCarloMeasurements" - DiffEqBaseMooncakeExt = "Mooncake" - DiffEqBaseReverseDiffExt = "ReverseDiff" - DiffEqBaseSparseArraysExt = "SparseArrays" - DiffEqBaseTrackerExt = "Tracker" - DiffEqBaseUnitfulExt = "Unitful" - - [deps.DiffEqBase.weakdeps] - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - GTPSA = "b27dd330-f138-47c5-815b-40db9dd9b6e8" - GeneralizedGenerated = "6b9d7cbe-bcb9-11e9-073f-15a7a543e2eb" - MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" - Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" - MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.DiffResults]] -deps = ["StaticArraysCore"] -git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" -uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" -version = "1.1.0" - -[[deps.DiffRules]] -deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" -uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.15.1" - -[[deps.DifferentiationInterface]] -deps = ["ADTypes", "LinearAlgebra"] -git-tree-sha1 = "529bebbc74b36a4cfea09dd2aecb1288cd713a6d" -uuid = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" -version = "0.7.9" - - [deps.DifferentiationInterface.extensions] - DifferentiationInterfaceChainRulesCoreExt = "ChainRulesCore" - DifferentiationInterfaceDiffractorExt = "Diffractor" - DifferentiationInterfaceEnzymeExt = ["EnzymeCore", "Enzyme"] - DifferentiationInterfaceFastDifferentiationExt = "FastDifferentiation" - DifferentiationInterfaceFiniteDiffExt = "FiniteDiff" - DifferentiationInterfaceFiniteDifferencesExt = "FiniteDifferences" - DifferentiationInterfaceForwardDiffExt = ["ForwardDiff", "DiffResults"] - DifferentiationInterfaceGPUArraysCoreExt = "GPUArraysCore" - DifferentiationInterfaceGTPSAExt = "GTPSA" - DifferentiationInterfaceMooncakeExt = "Mooncake" - DifferentiationInterfacePolyesterForwardDiffExt = ["PolyesterForwardDiff", "ForwardDiff", "DiffResults"] - DifferentiationInterfaceReverseDiffExt = ["ReverseDiff", "DiffResults"] - DifferentiationInterfaceSparseArraysExt = "SparseArrays" - DifferentiationInterfaceSparseConnectivityTracerExt = "SparseConnectivityTracer" - DifferentiationInterfaceSparseMatrixColoringsExt = "SparseMatrixColorings" - DifferentiationInterfaceStaticArraysExt = "StaticArrays" - DifferentiationInterfaceSymbolicsExt = "Symbolics" - DifferentiationInterfaceTrackerExt = "Tracker" - DifferentiationInterfaceZygoteExt = ["Zygote", "ForwardDiff"] - - [deps.DifferentiationInterface.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" - Diffractor = "9f5e2b26-1114-432f-b630-d3fe2085c51c" - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" - FastDifferentiation = "eb9bf01b-bf85-4b60-bf87-ee5de06c00be" - FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41" - FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" - GTPSA = "b27dd330-f138-47c5-815b-40db9dd9b6e8" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - PolyesterForwardDiff = "98d1487c-24ca-40b6-b7ab-df2af84e126b" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" - SparseMatrixColorings = "0a514795-09f3-496d-8182-132a7b665d35" - StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" - Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" -version = "1.11.0" - -[[deps.DocInventories]] -deps = ["CodecZlib", "Downloads", "TOML"] -git-tree-sha1 = "e97cfa8680a39396924dcdca4b7ff1014ed5c499" -uuid = "43dc2714-ed3b-44b5-b226-857eda1aa7de" -version = "1.0.0" - -[[deps.DocStringExtensions]] -git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.5" - -[[deps.Documenter]] -deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] -git-tree-sha1 = "352b9a04e74edd16429aec79f033620cf8e780d4" -uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "1.15.0" - -[[deps.DocumenterInterLinks]] -deps = ["CodecZlib", "DocInventories", "Documenter", "Markdown", "MarkdownAST", "TOML"] -git-tree-sha1 = "d8a8cb2d5b0181fbbd41861016b221b0202c9bc5" -uuid = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" -version = "1.1.0" - -[[deps.DocumenterMermaid]] -deps = ["Documenter", "MarkdownAST"] -git-tree-sha1 = "c0c408289a505a15496d914b19204e272a7e8b0f" -uuid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" -version = "0.2.0" - -[[deps.Downloads]] -deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" - -[[deps.EnumX]] -git-tree-sha1 = "bddad79635af6aec424f53ed8aad5d7555dc6f00" -uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" -version = "1.0.5" - -[[deps.EnzymeCore]] -git-tree-sha1 = "f91e7cb4c17dae77c490b75328f22a226708557c" -uuid = "f151be2c-9106-41f4-ab19-57ee4f262869" -version = "0.8.15" -weakdeps = ["Adapt"] - - [deps.EnzymeCore.extensions] - AdaptExt = "Adapt" - -[[deps.EpollShim_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" -uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" -version = "0.0.20230411+1" - -[[deps.ExaModels]] -deps = ["NLPModels", "Printf", "SolverCore"] -git-tree-sha1 = "228b2c8a02228fb28179c620d8337a8b7a781a15" -uuid = "1037b233-b668-4ce9-9b63-f9f681f55dd2" -version = "0.9.1" - - [deps.ExaModels.extensions] - ExaModelsAMDGPU = "AMDGPU" - ExaModelsCUDA = "CUDA" - ExaModelsIpopt = ["MathOptInterface", "NLPModelsIpopt"] - ExaModelsJuMP = "JuMP" - ExaModelsKernelAbstractions = "KernelAbstractions" - ExaModelsMOI = "MathOptInterface" - ExaModelsMadNLP = ["MadNLP", "MathOptInterface"] - ExaModelsOneAPI = "oneAPI" - ExaModelsOpenCL = "OpenCL" - ExaModelsSpecialFunctions = "SpecialFunctions" - - [deps.ExaModels.weakdeps] - AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" - JuMP = "4076af6c-e467-56ae-b986-b466b2749572" - KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" - MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" - MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" - NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" - OpenCL = "08131aa3-fb12-5dee-8b74-c09406e224a2" - SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" - oneAPI = "8f75cd03-7ff8-4ecb-9b8f-daf728133b1b" - -[[deps.ExceptionUnwrapping]] -deps = ["Test"] -git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" -uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" -version = "0.1.11" - -[[deps.Expat_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "27af30de8b5445644e8ffe3bcb0d72049c089cf1" -uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.7.3+0" - -[[deps.ExponentialUtilities]] -deps = ["Adapt", "ArrayInterface", "GPUArraysCore", "GenericSchur", "LinearAlgebra", "PrecompileTools", "Printf", "SparseArrays", "libblastrampoline_jll"] -git-tree-sha1 = "cae251c76f353e32d32d76fae2fea655eab652af" -uuid = "d4d017d3-3776-5f7e-afef-a10c40355c18" -version = "1.27.0" -weakdeps = ["StaticArrays"] - - [deps.ExponentialUtilities.extensions] - ExponentialUtilitiesStaticArraysExt = "StaticArrays" - -[[deps.ExprTools]] -git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" -uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" -version = "0.1.10" - -[[deps.ExproniconLite]] -git-tree-sha1 = "c13f0b150373771b0fdc1713c97860f8df12e6c2" -uuid = "55351af7-c7e9-48d6-89ff-24e801d99491" -version = "0.10.14" - -[[deps.FFMPEG]] -deps = ["FFMPEG_jll"] -git-tree-sha1 = "95ecf07c2eea562b5adbd0696af6db62c0f52560" -uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" -version = "0.4.5" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "ccc81ba5e42497f4e76553a5545665eed577a663" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "8.0.0+0" - -[[deps.FastBroadcast]] -deps = ["ArrayInterface", "LinearAlgebra", "Polyester", "Static", "StaticArrayInterface", "StrideArraysCore"] -git-tree-sha1 = "ab1b34570bcdf272899062e1a56285a53ecaae08" -uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898" -version = "0.3.5" - -[[deps.FastClosures]] -git-tree-sha1 = "acebe244d53ee1b461970f8910c235b259e772ef" -uuid = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" -version = "0.3.2" - -[[deps.FastGaussQuadrature]] -deps = ["LinearAlgebra", "SpecialFunctions", "StaticArrays"] -git-tree-sha1 = "0044e9f5e49a57e88205e8f30ab73928b05fe5b6" -uuid = "442a2c76-b920-505d-bb47-c5924d526838" -version = "1.1.0" - -[[deps.FastPower]] -git-tree-sha1 = "e47c70bf430175e077d1955d7f04923504acc74c" -uuid = "a4df4552-cc26-4903-aec0-212e50a0e84b" -version = "1.2.0" - - [deps.FastPower.extensions] - FastPowerEnzymeExt = "Enzyme" - FastPowerForwardDiffExt = "ForwardDiff" - FastPowerMeasurementsExt = "Measurements" - FastPowerMonteCarloMeasurementsExt = "MonteCarloMeasurements" - FastPowerMooncakeExt = "Mooncake" - FastPowerReverseDiffExt = "ReverseDiff" - FastPowerTrackerExt = "Tracker" - - [deps.FastPower.weakdeps] - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" - MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - -[[deps.FileIO]] -deps = ["Pkg", "Requires", "UUIDs"] -git-tree-sha1 = "d60eb76f37d7e5a40cc2e7c36974d864b82dc802" -uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -version = "1.17.1" -weakdeps = ["HTTP"] - - [deps.FileIO.extensions] - HTTPExt = "HTTP" - -[[deps.FileWatching]] -uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" -version = "1.11.0" - -[[deps.FillArrays]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "173e4d8f14230a7523ae11b9a3fa9edb3e0efd78" -uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.14.0" - - [deps.FillArrays.extensions] - FillArraysPDMatsExt = "PDMats" - FillArraysSparseArraysExt = "SparseArrays" - FillArraysStatisticsExt = "Statistics" - - [deps.FillArrays.weakdeps] - PDMats = "90014a1f-27ba-587c-ab20-58faa44d9150" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[deps.FiniteDiff]] -deps = ["ArrayInterface", "LinearAlgebra", "Setfield"] -git-tree-sha1 = "9340ca07ca27093ff68418b7558ca37b05f8aeb1" -uuid = "6a86dc24-6348-571c-b903-95158fe2bd41" -version = "2.29.0" - - [deps.FiniteDiff.extensions] - FiniteDiffBandedMatricesExt = "BandedMatrices" - FiniteDiffBlockBandedMatricesExt = "BlockBandedMatrices" - FiniteDiffSparseArraysExt = "SparseArrays" - FiniteDiffStaticArraysExt = "StaticArrays" - - [deps.FiniteDiff.weakdeps] - BandedMatrices = "aae01518-5342-5314-be14-df237901396f" - BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" - -[[deps.FixedPointNumbers]] -deps = ["Statistics"] -git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.5" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "f85dac9a96a01087df6e3a749840015a0ca3817d" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.17.1+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - -[[deps.ForwardDiff]] -deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] -git-tree-sha1 = "ba6ce081425d0afb2bedd00d9884464f764a9225" -uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "1.2.2" -weakdeps = ["StaticArrays"] - - [deps.ForwardDiff.extensions] - ForwardDiffStaticArraysExt = "StaticArrays" - -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.13.4+0" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.17+0" - -[[deps.FunctionWrappers]] -git-tree-sha1 = "d62485945ce5ae9c0c48f124a84998d755bae00e" -uuid = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" -version = "1.1.3" - -[[deps.FunctionWrappersWrappers]] -deps = ["FunctionWrappers"] -git-tree-sha1 = "b104d487b34566608f8b4e1c39fb0b10aa279ff8" -uuid = "77dc65aa-8811-40c2-897b-53d922fa7daf" -version = "0.1.3" - -[[deps.Future]] -deps = ["Random"] -uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" -version = "1.11.0" - -[[deps.GLFW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] -git-tree-sha1 = "fcb0584ff34e25155876418979d4c8971243bb89" -uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" -version = "3.4.0+2" - -[[deps.GPUArraysCore]] -deps = ["Adapt"] -git-tree-sha1 = "83cf05ab16a73219e5f6bd1bdfa9848fa24ac627" -uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.2.0" - -[[deps.GR]] -deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] -git-tree-sha1 = "1828eb7275491981fa5f1752a5e126e8f26f8741" -uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.73.17" - -[[deps.GR_jll]] -deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "27299071cc29e409488ada41ec7643e0ab19091f" -uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.73.17+0" - -[[deps.GenericSchur]] -deps = ["LinearAlgebra", "Printf"] -git-tree-sha1 = "a694e2a57394e409f7a11ee0977362a9fafcb8c7" -uuid = "c145ed77-6b09-5dd9-b285-bf645a82121e" -version = "0.5.6" - -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] -git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.1+0" - -[[deps.Git]] -deps = ["Git_LFS_jll", "Git_jll", "JLLWrappers", "OpenSSH_jll"] -git-tree-sha1 = "824a1890086880696fc908fe12a17bcf61738bd8" -uuid = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" -version = "1.5.0" - -[[deps.Git_LFS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "bb8471f313ed941f299aa53d32a94ab3bee08844" -uuid = "020c3dae-16b3-5ae5-87b3-4cb189e250b2" -version = "3.7.0+0" - -[[deps.Git_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "96fbb1a5c700942301cf65d8546690dd3b2a6a79" -uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" -version = "2.51.2+0" - -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "50c11ffab2a3d50192a228c313f05b5b5dc5acb2" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.86.0+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a6dbda1fd736d60cc477d99f2e7a042acfa46e8" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.15+0" - -[[deps.Grisu]] -git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" -uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" -version = "1.0.2" - -[[deps.HSL]] -deps = ["HSL_jll", "Libdl", "LinearAlgebra", "OpenBLAS32_jll", "Quadmath", "SparseArrays"] -git-tree-sha1 = "4898d678a6f7549c41bd9f187233f979da18bc36" -uuid = "34c5aeac-e683-54a6-a0e9-6e0fdc586c50" -version = "0.5.1" - -[[deps.HSL_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "4b34f6e368aa509b244847e6b0c9b370791bab09" -uuid = "017b0a0e-03f4-516a-9b91-836bbd1904dd" -version = "4.0.4+0" - -[[deps.HTTP]] -deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] -git-tree-sha1 = "5e6fe50ae7f23d171f44e311c2960294aaa0beb5" -uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "1.10.19" - -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "f923f9a774fcf3f5cb761bfa43aeadd689714813" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "8.5.1+0" - -[[deps.HashArrayMappedTries]] -git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" -uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" -version = "0.2.0" - -[[deps.Hwloc_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "XML2_jll", "Xorg_libpciaccess_jll"] -git-tree-sha1 = "3d468106a05408f9f7b6f161d9e7715159af247b" -uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8" -version = "2.12.2+0" - -[[deps.IOCapture]] -deps = ["Logging", "Random"] -git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" -uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.5" - -[[deps.IfElse]] -git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" -uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" -version = "0.1.1" - -[[deps.InlineStrings]] -git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" -uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.5" - - [deps.InlineStrings.extensions] - ArrowTypesExt = "ArrowTypes" - ParsersExt = "Parsers" - - [deps.InlineStrings.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" - -[[deps.IntelOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] -git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303" -uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" -version = "2025.2.0+0" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -version = "1.11.0" - -[[deps.Interpolations]] -deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"] -git-tree-sha1 = "65d505fa4c0d7072990d659ef3fc086eb6da8208" -uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" -version = "0.16.2" - - [deps.Interpolations.extensions] - InterpolationsForwardDiffExt = "ForwardDiff" - InterpolationsUnitfulExt = "Unitful" - - [deps.Interpolations.weakdeps] - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.InverseFunctions]] -git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" -uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.17" -weakdeps = ["Dates", "Test"] - - [deps.InverseFunctions.extensions] - InverseFunctionsDatesExt = "Dates" - InverseFunctionsTestExt = "Test" - -[[deps.InvertedIndices]] -git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" -uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.1" - -[[deps.Ipopt]] -deps = ["Ipopt_jll", "LinearAlgebra", "OpenBLAS32_jll", "PrecompileTools"] -git-tree-sha1 = "84be69cbb8229dd4ac8776f37d4cfd0a16a44482" -uuid = "b6b21f68-93f8-5de0-b562-5493be1d77c9" -version = "1.12.1" - - [deps.Ipopt.extensions] - IpoptMathOptInterfaceExt = "MathOptInterface" - - [deps.Ipopt.weakdeps] - MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" - -[[deps.Ipopt_jll]] -deps = ["ASL_jll", "Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "MUMPS_seq_jll", "SPRAL_jll", "libblastrampoline_jll"] -git-tree-sha1 = "b33cbc78b8d4de87d18fcd705054a82e2999dbac" -uuid = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7" -version = "300.1400.1900+0" - -[[deps.IrrationalConstants]] -git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" -uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.6" - -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JLD2]] -deps = ["ChunkCodecLibZlib", "ChunkCodecLibZstd", "FileIO", "MacroTools", "Mmap", "OrderedCollections", "PrecompileTools", "ScopedValues"] -git-tree-sha1 = "da2e9b4d1abbebdcca0aa68afa0aa272102baad7" -uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" -version = "0.6.2" -weakdeps = ["UnPack"] - - [deps.JLD2.extensions] - UnPackExt = "UnPack" - -[[deps.JLFzf]] -deps = ["REPL", "Random", "fzf_jll"] -git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" -uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" -version = "0.1.11" - -[[deps.JLLWrappers]] -deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" -uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.7.1" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" - -[[deps.JSON3]] -deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] -git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975" -uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" -version = "1.14.3" - - [deps.JSON3.extensions] - JSON3ArrowExt = ["ArrowTypes"] - - [deps.JSON3.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - -[[deps.Jieko]] -deps = ["ExproniconLite"] -git-tree-sha1 = "2f05ed29618da60c06a87e9c033982d4f71d0b6c" -uuid = "ae98c720-c025-4a4a-838c-29b094483192" -version = "0.2.1" - -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "4255f0032eafd6451d707a51d5f0248b8a165e4d" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.1.3+0" - -[[deps.JuliaSyntaxHighlighting]] -deps = ["StyledStrings"] -uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" -version = "1.12.0" - -[[deps.KNITRO]] -deps = ["KNITRO_jll", "Libdl"] -git-tree-sha1 = "37e774dc3e94ef238991addb4a6308b0f685d69c" -uuid = "67920dd8-b58e-52a8-8622-53c4cffbe346" -version = "0.14.10" - - [deps.KNITRO.extensions] - KNITROMathOptInterfaceExt = ["MathOptInterface"] - - [deps.KNITRO.weakdeps] - MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" - -[[deps.KNITRO_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "bdf7d90222a1624de37928c5847425194bf9c8ff" -uuid = "0e6b36f8-8e90-4eb5-b54e-06f667ea875c" -version = "15.0.1" - -[[deps.Krylov]] -deps = ["LinearAlgebra", "Printf", "SparseArrays"] -git-tree-sha1 = "d1fc961038207e43982851e57ee257adc37be5e8" -uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.10.2" - -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LDLFactorizations]] -deps = ["AMD", "LinearAlgebra", "SparseArrays", "Test"] -git-tree-sha1 = "70f582b446a1c3ad82cf87e62b878668beef9d13" -uuid = "40e66cde-538c-5869-a4ad-c39174c6795b" -version = "0.10.1" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "4.0.1+0" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "eb62a3deb62fc6d8822c0c4bef73e4412419c5d8" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "18.1.8+0" - -[[deps.LZO_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a" -uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" -version = "2.10.3+0" - -[[deps.LaTeXStrings]] -git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" -uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.4.0" - -[[deps.Latexify]] -deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "44f93c47f9cd6c7e431f2f2091fcba8f01cd7e8f" -uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.10" - - [deps.Latexify.extensions] - DataFramesExt = "DataFrames" - SparseArraysExt = "SparseArrays" - SymEngineExt = "SymEngine" - TectonicExt = "tectonic_jll" - - [deps.Latexify.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" - tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" - -[[deps.LayoutPointers]] -deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] -git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" -uuid = "10f19ff3-798f-405d-979b-55457f8fc047" -version = "0.1.17" - -[[deps.LazilyInitializedFields]] -git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" -uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" -version = "1.3.0" - -[[deps.LazyArrays]] -deps = ["ArrayLayouts", "FillArrays", "LinearAlgebra", "MacroTools", "SparseArrays"] -git-tree-sha1 = "79ee64f6ba0a5a49930f51c86f60d7526b5e12c8" -uuid = "5078a376-72f3-5289-bfd5-ec5146d43c02" -version = "2.8.0" - - [deps.LazyArrays.extensions] - LazyArraysBandedMatricesExt = "BandedMatrices" - LazyArraysBlockArraysExt = "BlockArrays" - LazyArraysBlockBandedMatricesExt = "BlockBandedMatrices" - LazyArraysStaticArraysExt = "StaticArrays" - - [deps.LazyArrays.weakdeps] - BandedMatrices = "aae01518-5342-5314-be14-df237901396f" - BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" - BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" - StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" - -[[deps.LazyArtifacts]] -deps = ["Artifacts", "Pkg"] -uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" -version = "1.11.0" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.4" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "8.11.1+1" - -[[deps.LibGit2]] -deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" -version = "1.11.0" - -[[deps.LibGit2_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] -uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.9.0+0" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "OpenSSL_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.11.3+1" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -version = "1.11.0" - -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - -[[deps.Libiconv_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" -uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" -version = "1.18.0+0" - -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "3acf07f130a76f87c041cfb2ff7d7284ca67b072" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.41.2+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "f04133fe05eff1667d2054c53d59f9122383fe05" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.7.2+0" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "2a7a12fc0a4e7fb773450d17975322aa77142106" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.41.2+0" - -[[deps.LineSearch]] -deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] -git-tree-sha1 = "97d502765cc5cf3a722120f50da03c2474efce04" -uuid = "87fe0de2-c867-4266-b59a-2f0a94fc965b" -version = "0.1.4" -weakdeps = ["LineSearches"] - - [deps.LineSearch.extensions] - LineSearchLineSearchesExt = "LineSearches" - -[[deps.LineSearches]] -deps = ["LinearAlgebra", "NLSolversBase", "NaNMath", "Parameters", "Printf"] -git-tree-sha1 = "4adee99b7262ad2a1a4bbbc59d993d24e55ea96f" -uuid = "d3d80556-e9d4-5f37-9878-2ab0fcc64255" -version = "7.4.0" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -version = "1.12.0" - -[[deps.LinearOperators]] -deps = ["FastClosures", "LinearAlgebra", "Printf", "Requires", "SparseArrays", "TimerOutputs"] -git-tree-sha1 = "db137007d2c4ed948aa5f2518a2b451851ea8bda" -uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" -version = "2.11.0" - - [deps.LinearOperators.extensions] - LinearOperatorsAMDGPUExt = "AMDGPU" - LinearOperatorsCUDAExt = "CUDA" - LinearOperatorsChainRulesCoreExt = "ChainRulesCore" - LinearOperatorsJLArraysExt = "JLArrays" - LinearOperatorsLDLFactorizationsExt = "LDLFactorizations" - LinearOperatorsMetalExt = "Metal" - - [deps.LinearOperators.weakdeps] - AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" - LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" - Metal = "dde4c033-4e86-420c-a63e-0dd931031962" - -[[deps.LinearSolve]] -deps = ["ArrayInterface", "ChainRulesCore", "ConcreteStructs", "DocStringExtensions", "EnumX", "GPUArraysCore", "InteractiveUtils", "Krylov", "LazyArrays", "Libdl", "LinearAlgebra", "MKL_jll", "Markdown", "OpenBLAS_jll", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "Setfield", "StaticArraysCore", "UnPack"] -git-tree-sha1 = "b5def83652705bdc00035dff671039e707588a00" -uuid = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" -version = "3.46.1" - - [deps.LinearSolve.extensions] - LinearSolveAMDGPUExt = "AMDGPU" - LinearSolveBLISExt = ["blis_jll", "LAPACK_jll"] - LinearSolveBandedMatricesExt = "BandedMatrices" - LinearSolveBlockDiagonalsExt = "BlockDiagonals" - LinearSolveCUDAExt = "CUDA" - LinearSolveCUDSSExt = "CUDSS" - LinearSolveCUSOLVERRFExt = ["CUSOLVERRF", "SparseArrays"] - LinearSolveCliqueTreesExt = ["CliqueTrees", "SparseArrays"] - LinearSolveEnzymeExt = "EnzymeCore" - LinearSolveFastAlmostBandedMatricesExt = "FastAlmostBandedMatrices" - LinearSolveFastLapackInterfaceExt = "FastLapackInterface" - LinearSolveForwardDiffExt = "ForwardDiff" - LinearSolveHYPREExt = "HYPRE" - LinearSolveIterativeSolversExt = "IterativeSolvers" - LinearSolveKernelAbstractionsExt = "KernelAbstractions" - LinearSolveKrylovKitExt = "KrylovKit" - LinearSolveMetalExt = "Metal" - LinearSolveMooncakeExt = "Mooncake" - LinearSolvePardisoExt = ["Pardiso", "SparseArrays"] - LinearSolveRecursiveFactorizationExt = "RecursiveFactorization" - LinearSolveSparseArraysExt = "SparseArrays" - LinearSolveSparspakExt = ["SparseArrays", "Sparspak"] - - [deps.LinearSolve.weakdeps] - AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" - BandedMatrices = "aae01518-5342-5314-be14-df237901396f" - BlockDiagonals = "0a1fb500-61f7-11e9-3c65-f5ef3456f9f0" - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e" - CUSOLVERRF = "a8cc9031-bad2-4722-94f5-40deabb4245c" - CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8" - EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" - FastAlmostBandedMatrices = "9d29842c-ecb8-4973-b1e9-a27b1157504e" - FastLapackInterface = "29a986be-02c6-4525-aec4-84b980013641" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - HYPRE = "b5ffcf37-a2bd-41ab-a3da-4bd9bc8ad771" - IterativeSolvers = "42fd0dbc-a981-5370-80f2-aaf504508153" - KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" - KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" - LAPACK_jll = "51474c39-65e3-53ba-86ba-03b1b862ec14" - Metal = "dde4c033-4e86-420c-a63e-0dd931031962" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - Pardiso = "46dd5b70-b6fb-5a00-ae2d-e8fea33afaf2" - RecursiveFactorization = "f2c3362d-daeb-58d1-803e-2bc74f2840b4" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - Sparspak = "e56a9233-b9d6-4f03-8d0f-1825330902ac" - blis_jll = "6136c539-28a5-5bf0-87cc-b183200dce32" - -[[deps.LogExpFunctions]] -deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" -uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.29" - - [deps.LogExpFunctions.extensions] - LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" - LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" - LogExpFunctionsInverseFunctionsExt = "InverseFunctions" - - [deps.LogExpFunctions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" -version = "1.11.0" - -[[deps.LoggingExtras]] -deps = ["Dates", "Logging"] -git-tree-sha1 = "f00544d95982ea270145636c181ceda21c4e2575" -uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" -version = "1.2.0" - -[[deps.METIS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "2eefa8baa858871ae7770c98c3c2a7e46daba5b4" -uuid = "d00139f3-1899-568f-a2f0-47f597d42d70" -version = "5.1.3+0" - -[[deps.MINPACK]] -deps = ["LinearAlgebra", "Printf", "cminpack_jll"] -git-tree-sha1 = "7833d5fda5c9a7a62b39d37ebd3c7d716fbc0cfc" -uuid = "4854310b-de5a-5eb6-a2a5-c1dee2bd17f9" -version = "1.3.0" - -[[deps.MKL]] -deps = ["Artifacts", "Libdl", "LinearAlgebra", "Logging", "MKL_jll"] -git-tree-sha1 = "fc3df8a0c3447fefe3607e5576de8c69ec6800ea" -uuid = "33e6dc65-8f57-5167-99aa-e5a354878fb2" -version = "0.9.0" - -[[deps.MKL_jll]] -deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"] -git-tree-sha1 = "282cadc186e7b2ae0eeadbd7a4dffed4196ae2aa" -uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" -version = "2025.2.0+0" - -[[deps.MLStyle]] -git-tree-sha1 = "bc38dff0548128765760c79eb7388a4b37fae2c8" -uuid = "d8e11817-5142-5d16-987a-aa16d5891078" -version = "0.4.17" - -[[deps.MUMPS_seq_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "libblastrampoline_jll"] -git-tree-sha1 = "fc0c8442887b48c15aec2b1787a5fc812a99b2fd" -uuid = "d7ed1dd3-d0ae-5e8e-bfb4-87a502085b8d" -version = "500.800.100+0" - -[[deps.MacroTools]] -git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" -uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.16" - -[[deps.MadNLP]] -deps = ["LDLFactorizations", "LinearAlgebra", "Logging", "NLPModels", "Pkg", "Printf", "SolverCore", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "6d4d80f692d35e073d6e5796ebb0a8a07963d781" -uuid = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" -version = "0.8.12" - - [deps.MadNLP.extensions] - MadNLPMOI = "MathOptInterface" - - [deps.MadNLP.weakdeps] - MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" - -[[deps.MadNLPMumps]] -deps = ["LinearAlgebra", "MUMPS_seq_jll", "MadNLP", "OpenBLAS32_jll"] -git-tree-sha1 = "83931ffc69f54e3c2376e751c6bfe5f4eef7d15b" -uuid = "3b83494e-c0a4-4895-918b-9157a7a085a1" -version = "0.5.1" - -[[deps.ManualMemory]] -git-tree-sha1 = "bcaef4fc7a0cfe2cba636d84cda54b5e4e4ca3cd" -uuid = "d125e4d3-2237-4719-b19c-fa641b8a4667" -version = "0.1.8" - -[[deps.Markdown]] -deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" -version = "1.11.0" - -[[deps.MarkdownAST]] -deps = ["AbstractTrees", "Markdown"] -git-tree-sha1 = "465a70f0fc7d443a00dcdc3267a497397b8a3899" -uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" -version = "0.1.2" - -[[deps.MaybeInplace]] -deps = ["ArrayInterface", "LinearAlgebra", "MacroTools"] -git-tree-sha1 = "54e2fdc38130c05b42be423e90da3bade29b74bd" -uuid = "bb5d69b7-63fc-4a16-80bd-7e42200c7bdb" -version = "0.1.4" -weakdeps = ["SparseArrays"] - - [deps.MaybeInplace.extensions] - MaybeInplaceSparseArraysExt = "SparseArrays" - -[[deps.MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] -git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf" -uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.1.9" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "3cce3511ca2c6f87b19c34ffc623417ed2798cbd" -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.10+0" - -[[deps.Measures]] -git-tree-sha1 = "c13304c81eec1ed3af7fc20e75fb6b26092a1102" -uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" -version = "0.3.2" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.2.0" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" -version = "1.11.0" - -[[deps.Moshi]] -deps = ["ExproniconLite", "Jieko"] -git-tree-sha1 = "53f817d3e84537d84545e0ad749e483412dd6b2a" -uuid = "2e0e35c7-a2e4-4343-998d-7ef72827ed2d" -version = "0.3.7" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2025.5.20" - -[[deps.MuladdMacro]] -git-tree-sha1 = "cac9cc5499c25554cba55cd3c30543cff5ca4fab" -uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" -version = "0.2.4" - -[[deps.NLPModels]] -deps = ["FastClosures", "LinearAlgebra", "LinearOperators", "Printf", "SparseArrays"] -git-tree-sha1 = "ac58082a07f0bd559292e869770d462d7ad0a7e2" -uuid = "a4795742-8479-5a88-8948-cc11e1c8c1a6" -version = "0.21.5" - -[[deps.NLPModelsIpopt]] -deps = ["Ipopt", "NLPModels", "SolverCore"] -git-tree-sha1 = "06782efc249b21d7b7c538fec4593d72420a36e3" -uuid = "f4238b75-b362-5c4c-b852-0801c9a21d71" -version = "0.10.4" - -[[deps.NLPModelsKnitro]] -deps = ["KNITRO", "NLPModels", "NLPModelsModifiers", "SolverCore"] -git-tree-sha1 = "d9c618d0b9d8997547b3a4c13ad943b019e05bde" -uuid = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb" -version = "0.9.3" - -[[deps.NLPModelsModifiers]] -deps = ["FastClosures", "LinearAlgebra", "LinearOperators", "NLPModels", "Printf", "SparseArrays"] -git-tree-sha1 = "a80505adbe42104cbbe9674591a5ccd9e9c2dfda" -uuid = "e01155f1-5c6f-4375-a9d8-616dd036575f" -version = "0.7.2" - -[[deps.NLSolversBase]] -deps = ["ADTypes", "DifferentiationInterface", "Distributed", "FiniteDiff", "ForwardDiff"] -git-tree-sha1 = "25a6638571a902ecfb1ae2a18fc1575f86b1d4df" -uuid = "d41bc354-129a-5804-8e4c-c37616107c6c" -version = "7.10.0" - -[[deps.NaNMath]] -deps = ["OpenLibm_jll"] -git-tree-sha1 = "9b8215b1ee9e78a293f99797cd31375471b2bcae" -uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.1.3" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.3.0" - -[[deps.NonlinearSolve]] -deps = ["ADTypes", "ArrayInterface", "BracketingNonlinearSolve", "CommonSolve", "ConcreteStructs", "DifferentiationInterface", "FastClosures", "FiniteDiff", "ForwardDiff", "LineSearch", "LinearAlgebra", "LinearSolve", "NonlinearSolveBase", "NonlinearSolveFirstOrder", "NonlinearSolveQuasiNewton", "NonlinearSolveSpectralMethods", "PrecompileTools", "Preferences", "Reexport", "SciMLBase", "SimpleNonlinearSolve", "StaticArraysCore", "SymbolicIndexingInterface"] -git-tree-sha1 = "1d091cfece012662b06d25c792b3a43a0804c47b" -uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" -version = "4.12.0" - - [deps.NonlinearSolve.extensions] - NonlinearSolveFastLevenbergMarquardtExt = "FastLevenbergMarquardt" - NonlinearSolveFixedPointAccelerationExt = "FixedPointAcceleration" - NonlinearSolveLeastSquaresOptimExt = "LeastSquaresOptim" - NonlinearSolveMINPACKExt = "MINPACK" - NonlinearSolveNLSolversExt = "NLSolvers" - NonlinearSolveNLsolveExt = ["NLsolve", "LineSearches"] - NonlinearSolvePETScExt = ["PETSc", "MPI", "SparseArrays"] - NonlinearSolveSIAMFANLEquationsExt = "SIAMFANLEquations" - NonlinearSolveSpeedMappingExt = "SpeedMapping" - NonlinearSolveSundialsExt = "Sundials" - - [deps.NonlinearSolve.weakdeps] - FastLevenbergMarquardt = "7a0df574-e128-4d35-8cbd-3d84502bf7ce" - FixedPointAcceleration = "817d07cb-a79a-5c30-9a31-890123675176" - LeastSquaresOptim = "0fc2ff8b-aaa3-5acd-a817-1944a5e08891" - LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255" - MINPACK = "4854310b-de5a-5eb6-a2a5-c1dee2bd17f9" - MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" - NLSolvers = "337daf1e-9722-11e9-073e-8b9effe078ba" - NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56" - PETSc = "ace2c81b-2b5f-4b1e-a30d-d662738edfe0" - SIAMFANLEquations = "084e46ad-d928-497d-ad5e-07fa361a48c4" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SpeedMapping = "f1835b91-879b-4a3f-a438-e4baacf14412" - Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" - -[[deps.NonlinearSolveBase]] -deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "Compat", "ConcreteStructs", "DifferentiationInterface", "EnzymeCore", "FastClosures", "LinearAlgebra", "Markdown", "MaybeInplace", "Preferences", "Printf", "RecursiveArrayTools", "SciMLBase", "SciMLJacobianOperators", "SciMLOperators", "SciMLStructures", "Setfield", "StaticArraysCore", "SymbolicIndexingInterface", "TimerOutputs"] -git-tree-sha1 = "9dba8e7ccfaf4c10b3a3b4cc52abf639f8558efd" -uuid = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" -version = "2.0.0" - - [deps.NonlinearSolveBase.extensions] - NonlinearSolveBaseBandedMatricesExt = "BandedMatrices" - NonlinearSolveBaseChainRulesCoreExt = "ChainRulesCore" - NonlinearSolveBaseEnzymeExt = ["ChainRulesCore", "Enzyme"] - NonlinearSolveBaseForwardDiffExt = "ForwardDiff" - NonlinearSolveBaseLineSearchExt = "LineSearch" - NonlinearSolveBaseLinearSolveExt = "LinearSolve" - NonlinearSolveBaseMooncakeExt = "Mooncake" - NonlinearSolveBaseReverseDiffExt = "ReverseDiff" - NonlinearSolveBaseSparseArraysExt = "SparseArrays" - NonlinearSolveBaseSparseMatrixColoringsExt = "SparseMatrixColorings" - NonlinearSolveBaseTrackerExt = "Tracker" - - [deps.NonlinearSolveBase.weakdeps] - BandedMatrices = "aae01518-5342-5314-be14-df237901396f" - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - LineSearch = "87fe0de2-c867-4266-b59a-2f0a94fc965b" - LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SparseMatrixColorings = "0a514795-09f3-496d-8182-132a7b665d35" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - -[[deps.NonlinearSolveFirstOrder]] -deps = ["ADTypes", "ArrayInterface", "CommonSolve", "ConcreteStructs", "FiniteDiff", "ForwardDiff", "LineSearch", "LinearAlgebra", "LinearSolve", "MaybeInplace", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase", "SciMLJacobianOperators", "Setfield", "StaticArraysCore"] -git-tree-sha1 = "01c48c37ba47721ec6489a1668ab3bb1f5b603c0" -uuid = "5959db7a-ea39-4486-b5fe-2dd0bf03d60d" -version = "1.9.0" - -[[deps.NonlinearSolveQuasiNewton]] -deps = ["ArrayInterface", "CommonSolve", "ConcreteStructs", "LinearAlgebra", "LinearSolve", "MaybeInplace", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase", "SciMLOperators", "StaticArraysCore"] -git-tree-sha1 = "a233b7bbb32426170b238e2e2b82b0f9f1c5caba" -uuid = "9a2c21bd-3a47-402d-9113-8faf9a0ee114" -version = "1.10.0" -weakdeps = ["ForwardDiff"] - - [deps.NonlinearSolveQuasiNewton.extensions] - NonlinearSolveQuasiNewtonForwardDiffExt = "ForwardDiff" - -[[deps.NonlinearSolveSpectralMethods]] -deps = ["CommonSolve", "ConcreteStructs", "LineSearch", "MaybeInplace", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "139bf9211930a829703481b3ffd86b1ab309cd07" -uuid = "26075421-4e9a-44e1-8bd1-420ed7ad02b2" -version = "1.5.0" -weakdeps = ["ForwardDiff"] - - [deps.NonlinearSolveSpectralMethods.extensions] - NonlinearSolveSpectralMethodsForwardDiffExt = "ForwardDiff" - -[[deps.OffsetArrays]] -git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151" -uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.17.0" -weakdeps = ["Adapt"] - - [deps.OffsetArrays.extensions] - OffsetArraysAdaptExt = "Adapt" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - -[[deps.OpenBLAS32_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ece4587683695fe4c5f20e990da0ed7e83c351e7" -uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2" -version = "0.3.29+0" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.29+0" - -[[deps.OpenLibm_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.7+0" - -[[deps.OpenSSH_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll"] -git-tree-sha1 = "301412a644646fdc0ad67d0a87487466b491e53d" -uuid = "9bd350c2-7e96-507f-8002-3f2e150b4e1b" -version = "10.2.1+0" - -[[deps.OpenSSL]] -deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] -git-tree-sha1 = "f1a7e086c677df53e064e0fdd2c9d0b0833e3f6e" -uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" -version = "1.5.0" - -[[deps.OpenSSL_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.5.1+0" - -[[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.6+0" - -[[deps.OptimalControl]] -deps = ["ADNLPModels", "CTBase", "CTDirect", "CTFlows", "CTModels", "CTParser", "CommonSolve", "DocStringExtensions", "ExaModels", "RecipesBase"] -path = "/Users/ocots/Research/logiciels/dev/control-toolbox/OptimalControl" -uuid = "5f98b655-cc9a-415a-b60e-744165666948" -version = "1.1.5" - -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c392fc5dd032381919e3b22dd32d6443760ce7ea" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.5.2+0" - -[[deps.OrderedCollections]] -git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.8.1" - -[[deps.OrdinaryDiffEq]] -deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "ExponentialUtilities", "FastBroadcast", "FastClosures", "FillArrays", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "InteractiveUtils", "LineSearches", "LinearAlgebra", "LinearSolve", "Logging", "MacroTools", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqAdamsBashforthMoulton", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqExplicitRK", "OrdinaryDiffEqExponentialRK", "OrdinaryDiffEqExtrapolation", "OrdinaryDiffEqFIRK", "OrdinaryDiffEqFeagin", "OrdinaryDiffEqFunctionMap", "OrdinaryDiffEqHighOrderRK", "OrdinaryDiffEqIMEXMultistep", "OrdinaryDiffEqLinear", "OrdinaryDiffEqLowOrderRK", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqNordsieck", "OrdinaryDiffEqPDIRK", "OrdinaryDiffEqPRK", "OrdinaryDiffEqQPRK", "OrdinaryDiffEqRKN", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqStabilizedIRK", "OrdinaryDiffEqStabilizedRK", "OrdinaryDiffEqSymplecticRK", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "Polyester", "PreallocationTools", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SimpleUnPack", "SparseArrays", "Static", "StaticArrayInterface", "StaticArrays", "TruncatedStacktraces"] -git-tree-sha1 = "89172157d16139165d470602f1e552484b357771" -uuid = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" -version = "6.103.0" - -[[deps.OrdinaryDiffEqAdamsBashforthMoulton]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqLowOrderRK", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "09aae1486c767caa6bce9de892455cbdf5a6fbc8" -uuid = "89bda076-bce5-4f1c-845f-551c83cdda9a" -version = "1.5.0" - -[[deps.OrdinaryDiffEqBDF]] -deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqSDIRK", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays", "TruncatedStacktraces"] -git-tree-sha1 = "ce8db53fd1e4e41c020fd53961e7314f75e4c21c" -uuid = "6ad6398a-0878-4a85-9266-38940aa047c8" -version = "1.10.1" - -[[deps.OrdinaryDiffEqCore]] -deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "FastBroadcast", "FastClosures", "FastPower", "FillArrays", "FunctionWrappersWrappers", "InteractiveUtils", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleUnPack", "Static", "StaticArrayInterface", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"] -git-tree-sha1 = "4b68f9ca0cfa68cb9ee544df96391d47ca0e62a9" -uuid = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" -version = "1.36.0" - - [deps.OrdinaryDiffEqCore.extensions] - OrdinaryDiffEqCoreEnzymeCoreExt = "EnzymeCore" - OrdinaryDiffEqCoreMooncakeExt = "Mooncake" - - [deps.OrdinaryDiffEqCore.weakdeps] - EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - -[[deps.OrdinaryDiffEqDefault]] -deps = ["ADTypes", "DiffEqBase", "EnumX", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "PrecompileTools", "Preferences", "Reexport", "SciMLBase"] -git-tree-sha1 = "7d5ddeee97e1bdcc848f1397cbc3d03bd57f33e7" -uuid = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" -version = "1.8.0" - -[[deps.OrdinaryDiffEqDifferentiation]] -deps = ["ADTypes", "ArrayInterface", "ConcreteStructs", "ConstructionBase", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqCore", "SciMLBase", "SciMLOperators", "SparseMatrixColorings", "StaticArrayInterface", "StaticArrays"] -git-tree-sha1 = "320b5f3e4e61ca0ad863c63c803f69973ba6efce" -uuid = "4302a76b-040a-498a-8c04-15b101fed76b" -version = "1.16.1" -weakdeps = ["SparseArrays"] - - [deps.OrdinaryDiffEqDifferentiation.extensions] - OrdinaryDiffEqDifferentiationSparseArraysExt = "SparseArrays" - -[[deps.OrdinaryDiffEqExplicitRK]] -deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"] -git-tree-sha1 = "4c0633f587395d7aaec0679dc649eb03fcc74e73" -uuid = "9286f039-9fbf-40e8-bf65-aa933bdc4db0" -version = "1.4.0" - -[[deps.OrdinaryDiffEqExponentialRK]] -deps = ["ADTypes", "DiffEqBase", "ExponentialUtilities", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "RecursiveArrayTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "3b81416ff11e55ea0ae7b449efc818256d9d450b" -uuid = "e0540318-69ee-4070-8777-9e2de6de23de" -version = "1.8.0" - -[[deps.OrdinaryDiffEqExtrapolation]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "FastPower", "LinearSolve", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "9e1b11cf448a2c1bca640103c1c848a20aa2f967" -uuid = "becaefa8-8ca2-5cf9-886d-c06f3d2bd2c4" -version = "1.9.0" - -[[deps.OrdinaryDiffEqFIRK]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "FastGaussQuadrature", "FastPower", "LinearAlgebra", "LinearSolve", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators"] -git-tree-sha1 = "b968d66de3de5ffcf18544bc202ca792bad20710" -uuid = "5960d6e9-dd7a-4743-88e7-cf307b64f125" -version = "1.16.0" - -[[deps.OrdinaryDiffEqFeagin]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "815b54211201ec42b8829e0275ab3c9632d16cbe" -uuid = "101fe9f7-ebb6-4678-b671-3a81e7194747" -version = "1.4.0" - -[[deps.OrdinaryDiffEqFunctionMap]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "fe750e4b8c1b1b9e1c1319ff2e052e83ad57b3ac" -uuid = "d3585ca7-f5d3-4ba6-8057-292ed1abd90f" -version = "1.5.0" - -[[deps.OrdinaryDiffEqHighOrderRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "42096f72136078fa02804515f1748ddeb1f0d47d" -uuid = "d28bc4f8-55e1-4f49-af69-84c1a99f0f58" -version = "1.5.0" - -[[deps.OrdinaryDiffEqIMEXMultistep]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Reexport", "SciMLBase"] -git-tree-sha1 = "a5dcd75959dada0005b1707a5ca9359faa1734ba" -uuid = "9f002381-b378-40b7-97a6-27a27c83f129" -version = "1.7.0" - -[[deps.OrdinaryDiffEqLinear]] -deps = ["DiffEqBase", "ExponentialUtilities", "LinearAlgebra", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators"] -git-tree-sha1 = "925fc0136e8128fd19abf126e9358ec1f997390f" -uuid = "521117fe-8c41-49f8-b3b6-30780b3f0fb5" -version = "1.6.0" - -[[deps.OrdinaryDiffEqLowOrderRK]] -deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "3cc4987c8e4725276b55a52e08b56ded4862917e" -uuid = "1344f307-1e59-4825-a18e-ace9aa3fa4c6" -version = "1.6.0" - -[[deps.OrdinaryDiffEqLowStorageRK]] -deps = ["Adapt", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "StaticArrays"] -git-tree-sha1 = "e6bd0a7fb6643a57b06a90415608a81aaf7bd772" -uuid = "b0944070-b475-4768-8dec-fb6eb410534d" -version = "1.7.0" - -[[deps.OrdinaryDiffEqNonlinearSolve]] -deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "FastBroadcast", "FastClosures", "ForwardDiff", "LinearAlgebra", "LinearSolve", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "PreallocationTools", "RecursiveArrayTools", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "StaticArrays"] -git-tree-sha1 = "f59c1c07cfa674c1d3f5dd386c4274d9bc2be221" -uuid = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8" -version = "1.15.0" - -[[deps.OrdinaryDiffEqNordsieck]] -deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqTsit5", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "c90aa7fa0d725472c4098096adf6a08266c2f682" -uuid = "c9986a66-5c92-4813-8696-a7ec84c806c8" -version = "1.4.0" - -[[deps.OrdinaryDiffEqPDIRK]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Polyester", "Reexport", "SciMLBase", "StaticArrays"] -git-tree-sha1 = "9d599d2eafdf74ab26ea6bf3feb28183a2ade143" -uuid = "5dd0a6cf-3d4b-4314-aa06-06d4e299bc89" -version = "1.6.0" - -[[deps.OrdinaryDiffEqPRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "Reexport", "SciMLBase"] -git-tree-sha1 = "8e35132689133255be6d63df4190b5fc97b6cf2b" -uuid = "5b33eab2-c0f1-4480-b2c3-94bc1e80bda1" -version = "1.4.0" - -[[deps.OrdinaryDiffEqQPRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "63fb643a956b27cd0e33a3c6d910c3c118082e0f" -uuid = "04162be5-8125-4266-98ed-640baecc6514" -version = "1.4.0" - -[[deps.OrdinaryDiffEqRKN]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "a31c41f9dbea7c7179c6e544c25c7e144d63868c" -uuid = "af6ede74-add8-4cfd-b1df-9a4dbb109d7a" -version = "1.5.0" - -[[deps.OrdinaryDiffEqRosenbrock]] -deps = ["ADTypes", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "LinearAlgebra", "LinearSolve", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"] -git-tree-sha1 = "f34bc2f58656843596d09a4c4de8c20724ebc2f1" -uuid = "43230ef6-c299-4910-a778-202eb28ce4ce" -version = "1.18.1" - -[[deps.OrdinaryDiffEqSDIRK]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"] -git-tree-sha1 = "20caa72c004414435fb5769fadb711e96ed5bcd4" -uuid = "2d112036-d095-4a1e-ab9a-08536f3ecdbf" -version = "1.7.0" - -[[deps.OrdinaryDiffEqSSPRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "StaticArrays"] -git-tree-sha1 = "3bce87977264916bd92455754ab336faec68bf8a" -uuid = "669c94d9-1f4b-4b64-b377-1aa079aa2388" -version = "1.7.0" - -[[deps.OrdinaryDiffEqStabilizedIRK]] -deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqStabilizedRK", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays"] -git-tree-sha1 = "75abe7462f4b0b2a2463bb512c8a5458bbd39185" -uuid = "e3e12d00-db14-5390-b879-ac3dd2ef6296" -version = "1.6.0" - -[[deps.OrdinaryDiffEqStabilizedRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays"] -git-tree-sha1 = "7e94d3d1b3528b4bcf9e0248198ee0a2fd65a697" -uuid = "358294b1-0aab-51c3-aafe-ad5ab194a2ad" -version = "1.4.0" - -[[deps.OrdinaryDiffEqSymplecticRK]] -deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "e8dd5ab225287947016dc144a5ded1fb83885638" -uuid = "fa646aed-7ef9-47eb-84c4-9443fc8cbfa8" -version = "1.7.0" - -[[deps.OrdinaryDiffEqTsit5]] -deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "TruncatedStacktraces"] -git-tree-sha1 = "778c7d379265f17f40dbe9aaa6f6a2a08bc7fa3e" -uuid = "b1df2697-797e-41e3-8120-5422d3b24e4a" -version = "1.5.0" - -[[deps.OrdinaryDiffEqVerner]] -deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "TruncatedStacktraces"] -git-tree-sha1 = "185578fa7c38119d4318326f9375f1cba0f0ce53" -uuid = "79d7bb75-1356-48c1-b8c0-6832512096c2" -version = "1.6.0" - -[[deps.PCRE2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.44.0+1" - -[[deps.Pango_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1f7f9bbd5f7a2e5a9f7d96e51c9754454ea7f60b" -uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.56.4+0" - -[[deps.Parameters]] -deps = ["OrderedCollections", "UnPack"] -git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" -uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" -version = "0.12.3" - -[[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.3" - -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.44.2+0" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.12.0" -weakdeps = ["REPL"] - - [deps.Pkg.extensions] - REPLExt = "REPL" - -[[deps.PlotThemes]] -deps = ["PlotUtils", "Statistics"] -git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" -uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" -version = "3.3.0" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"] -git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.4.3" - -[[deps.Plots]] -deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "12ce661880f8e309569074a61d3767e5756a199f" -uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.41.1" - - [deps.Plots.extensions] - FileIOExt = "FileIO" - GeometryBasicsExt = "GeometryBasics" - IJuliaExt = "IJulia" - ImageInTerminalExt = "ImageInTerminal" - UnitfulExt = "Unitful" - - [deps.Plots.weakdeps] - FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" - GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.Polyester]] -deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] -git-tree-sha1 = "6f7cd22a802094d239824c57d94c8e2d0f7cfc7d" -uuid = "f517fe37-dbe3-4b94-8317-1923a5111588" -version = "0.7.18" - -[[deps.PolyesterWeave]] -deps = ["BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "Static", "ThreadingUtilities"] -git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" -uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" -version = "0.2.2" - -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.3" - -[[deps.PreallocationTools]] -deps = ["Adapt", "ArrayInterface", "PrecompileTools"] -git-tree-sha1 = "c05b4c6325262152483a1ecb6c69846d2e01727b" -uuid = "d236fae5-4411-538c-8e31-a6e3d9e00b46" -version = "0.4.34" -weakdeps = ["ForwardDiff", "ReverseDiff", "SparseConnectivityTracer"] - - [deps.PreallocationTools.extensions] - PreallocationToolsForwardDiffExt = "ForwardDiff" - PreallocationToolsReverseDiffExt = "ReverseDiff" - PreallocationToolsSparseConnectivityTracerExt = "SparseConnectivityTracer" - -[[deps.PrecompileTools]] -deps = ["Preferences"] -git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" -uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.3.3" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.5.0" - -[[deps.PrettyTables]] -deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "6b8e2f0bae3f678811678065c09571c1619da219" -uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.1.0" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" -version = "1.11.0" - -[[deps.PtrArrays]] -git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d" -uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.3.0" - -[[deps.Qt6Base_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "34f7e5d2861083ec7596af8b8c092531facf2192" -uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.8.2+2" - -[[deps.Qt6Declarative_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll"] -git-tree-sha1 = "da7adf145cce0d44e892626e647f9dcbe9cb3e10" -uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" -version = "6.8.2+1" - -[[deps.Qt6ShaderTools_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "9eca9fc3fe515d619ce004c83c31ffd3f85c7ccf" -uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" -version = "6.8.2+1" - -[[deps.Qt6Wayland_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] -git-tree-sha1 = "8f528b0851b5b7025032818eb5abbeb8a736f853" -uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" -version = "6.8.2+2" - -[[deps.Quadmath]] -deps = ["Compat", "Printf", "Random", "Requires"] -git-tree-sha1 = "6bc924717c495f24de85867aa94da4de0e6cd1a1" -uuid = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd" -version = "0.5.13" - -[[deps.REPL]] -deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" -version = "1.11.0" - -[[deps.Random]] -deps = ["SHA"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -version = "1.11.0" - -[[deps.Ratios]] -deps = ["Requires"] -git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b" -uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439" -version = "0.4.5" -weakdeps = ["FixedPointNumbers"] - - [deps.Ratios.extensions] - RatiosFixedPointNumbersExt = "FixedPointNumbers" - -[[deps.RecipesBase]] -deps = ["PrecompileTools"] -git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" -uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -version = "1.3.4" - -[[deps.RecipesPipeline]] -deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] -git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" -uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.6.12" - -[[deps.RecursiveArrayTools]] -deps = ["Adapt", "ArrayInterface", "DocStringExtensions", "GPUArraysCore", "LinearAlgebra", "RecipesBase", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface"] -git-tree-sha1 = "51bdb23afaaa551f923a0e990f7c44a4451a26f1" -uuid = "731186ca-8d62-57ce-b412-fbd966d074cd" -version = "3.39.0" - - [deps.RecursiveArrayTools.extensions] - RecursiveArrayToolsFastBroadcastExt = "FastBroadcast" - RecursiveArrayToolsForwardDiffExt = "ForwardDiff" - RecursiveArrayToolsKernelAbstractionsExt = "KernelAbstractions" - RecursiveArrayToolsMeasurementsExt = "Measurements" - RecursiveArrayToolsMonteCarloMeasurementsExt = "MonteCarloMeasurements" - RecursiveArrayToolsReverseDiffExt = ["ReverseDiff", "Zygote"] - RecursiveArrayToolsSparseArraysExt = ["SparseArrays"] - RecursiveArrayToolsStructArraysExt = "StructArrays" - RecursiveArrayToolsTablesExt = ["Tables"] - RecursiveArrayToolsTrackerExt = "Tracker" - RecursiveArrayToolsZygoteExt = "Zygote" - - [deps.RecursiveArrayTools.weakdeps] - FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" - Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" - MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" - Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" - -[[deps.Reexport]] -git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "1.2.2" - -[[deps.RegistryInstances]] -deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] -git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" -uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" -version = "0.1.0" - -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - -[[deps.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.1" - -[[deps.ReverseDiff]] -deps = ["ChainRulesCore", "DiffResults", "DiffRules", "ForwardDiff", "FunctionWrappers", "LinearAlgebra", "LogExpFunctions", "MacroTools", "NaNMath", "Random", "SpecialFunctions", "StaticArrays", "Statistics"] -git-tree-sha1 = "3ab8eee3620451b09f0272c271875b4bc02146d9" -uuid = "37e2e3b7-166d-5795-8a7a-e32c996b4267" -version = "1.16.1" - -[[deps.RuntimeGeneratedFunctions]] -deps = ["ExprTools", "SHA", "Serialization"] -git-tree-sha1 = "86a8a8b783481e1ea6b9c91dd949cb32191f8ab4" -uuid = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" -version = "0.5.15" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.SIMDTypes]] -git-tree-sha1 = "330289636fb8107c5f32088d2741e9fd7a061a5c" -uuid = "94e857df-77ce-4151-89e5-788b33177be4" -version = "0.1.0" - -[[deps.SPRAL_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Hwloc_jll", "JLLWrappers", "Libdl", "METIS_jll", "libblastrampoline_jll"] -git-tree-sha1 = "4f9833187a65ead66ed1907b44d5f20606282e3f" -uuid = "319450e9-13b8-58e8-aa9f-8fd1420848ab" -version = "2025.5.20+0" - -[[deps.SciMLBase]] -deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "CommonSolve", "ConstructionBase", "Distributed", "DocStringExtensions", "EnumX", "FunctionWrappersWrappers", "IteratorInterfaceExtensions", "LinearAlgebra", "Logging", "Markdown", "Moshi", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "RecipesBase", "RecursiveArrayTools", "Reexport", "RuntimeGeneratedFunctions", "SciMLLogging", "SciMLOperators", "SciMLPublic", "SciMLStructures", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface"] -git-tree-sha1 = "7614a1b881317b6800a8c66eb1180c6ea5b986f3" -uuid = "0bca4576-84f4-4d90-8ffe-ffa030f20462" -version = "2.124.0" - - [deps.SciMLBase.extensions] - SciMLBaseChainRulesCoreExt = "ChainRulesCore" - SciMLBaseDifferentiationInterfaceExt = "DifferentiationInterface" - SciMLBaseDistributionsExt = "Distributions" - SciMLBaseEnzymeExt = "Enzyme" - SciMLBaseForwardDiffExt = "ForwardDiff" - SciMLBaseMLStyleExt = "MLStyle" - SciMLBaseMakieExt = "Makie" - SciMLBaseMeasurementsExt = "Measurements" - SciMLBaseMonteCarloMeasurementsExt = "MonteCarloMeasurements" - SciMLBaseMooncakeExt = "Mooncake" - SciMLBasePartialFunctionsExt = "PartialFunctions" - SciMLBasePyCallExt = "PyCall" - SciMLBasePythonCallExt = "PythonCall" - SciMLBaseRCallExt = "RCall" - SciMLBaseReverseDiffExt = "ReverseDiff" - SciMLBaseTrackerExt = "Tracker" - SciMLBaseZygoteExt = ["Zygote", "ChainRulesCore"] - - [deps.SciMLBase.weakdeps] - ChainRules = "082447d4-558c-5d27-93f4-14fc19e9eca2" - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" - Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" - Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" - Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" - MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca" - Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" - PartialFunctions = "570af359-4316-4cb7-8c74-252c00c2016b" - PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" - PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" - RCall = "6f49c342-dc21-5d91-9882-a32aef131414" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" - -[[deps.SciMLJacobianOperators]] -deps = ["ADTypes", "ArrayInterface", "ConcreteStructs", "ConstructionBase", "DifferentiationInterface", "FastClosures", "LinearAlgebra", "SciMLBase", "SciMLOperators"] -git-tree-sha1 = "a273b291c90909ba6fe08402dd68e09aae423008" -uuid = "19f34311-ddf3-4b8b-af20-060888a46c0e" -version = "0.1.11" - -[[deps.SciMLLogging]] -deps = ["Logging", "LoggingExtras", "Preferences"] -git-tree-sha1 = "5a026f5549ad167cda34c67b62f8d3dc55754da3" -uuid = "a6db7da4-7206-11f0-1eab-35f2a5dbe1d1" -version = "1.3.1" - -[[deps.SciMLOperators]] -deps = ["Accessors", "ArrayInterface", "DocStringExtensions", "LinearAlgebra", "MacroTools"] -git-tree-sha1 = "c1053ba68ede9e4005fc925dd4e8723fcd96eef8" -uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" -version = "1.9.0" -weakdeps = ["SparseArrays", "StaticArraysCore"] - - [deps.SciMLOperators.extensions] - SciMLOperatorsSparseArraysExt = "SparseArrays" - SciMLOperatorsStaticArraysCoreExt = "StaticArraysCore" - -[[deps.SciMLPublic]] -git-tree-sha1 = "ed647f161e8b3f2973f24979ec074e8d084f1bee" -uuid = "431bcebd-1456-4ced-9d72-93c2757fff0b" -version = "1.0.0" - -[[deps.SciMLStructures]] -deps = ["ArrayInterface"] -git-tree-sha1 = "566c4ed301ccb2a44cbd5a27da5f885e0ed1d5df" -uuid = "53ae85a6-f571-4167-b2af-e1d143709226" -version = "1.7.0" - -[[deps.ScopedValues]] -deps = ["HashArrayMappedTries", "Logging"] -git-tree-sha1 = "c3b2323466378a2ba15bea4b2f73b081e022f473" -uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" -version = "1.5.0" - -[[deps.Scratch]] -deps = ["Dates"] -git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" -uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.3.0" - -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "712fb0231ee6f9120e005ccd56297abbc053e7e0" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.8" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -version = "1.11.0" - -[[deps.Setfield]] -deps = ["ConstructionBase", "Future", "MacroTools", "StaticArraysCore"] -git-tree-sha1 = "c5391c6ace3bc430ca630251d02ea9687169ca68" -uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "1.1.2" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" -version = "1.11.0" - -[[deps.Showoff]] -deps = ["Dates", "Grisu"] -git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.0.3" - -[[deps.SimpleBufferStream]] -git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" -uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" -version = "1.2.0" - -[[deps.SimpleNonlinearSolve]] -deps = ["ADTypes", "ArrayInterface", "BracketingNonlinearSolve", "CommonSolve", "ConcreteStructs", "DifferentiationInterface", "FastClosures", "FiniteDiff", "ForwardDiff", "LineSearch", "LinearAlgebra", "MaybeInplace", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase", "Setfield", "StaticArraysCore"] -git-tree-sha1 = "8825064775bf4ae0f22d04ea63979d8c868fd510" -uuid = "727e6d20-b764-4bd8-a329-72de5adea6c7" -version = "2.9.0" - - [deps.SimpleNonlinearSolve.extensions] - SimpleNonlinearSolveChainRulesCoreExt = "ChainRulesCore" - SimpleNonlinearSolveReverseDiffExt = "ReverseDiff" - SimpleNonlinearSolveTrackerExt = "Tracker" - - [deps.SimpleNonlinearSolve.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" - Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" - -[[deps.SimpleUnPack]] -git-tree-sha1 = "58e6353e72cde29b90a69527e56df1b5c3d8c437" -uuid = "ce78b400-467f-4804-87d8-8f486da07d0a" -version = "1.1.0" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" -version = "1.11.0" - -[[deps.SolverCore]] -deps = ["LinearAlgebra", "NLPModels", "Printf"] -git-tree-sha1 = "03a1e0d2d39b9ebc9510f2452c0adfbe887b9cb2" -uuid = "ff4d7338-4cf1-434d-91df-b86cb86fb843" -version = "0.3.8" - -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.2" - -[[deps.SparseArrays]] -deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.12.0" - -[[deps.SparseConnectivityTracer]] -deps = ["ADTypes", "DocStringExtensions", "FillArrays", "LinearAlgebra", "Random", "SparseArrays"] -git-tree-sha1 = "ba6dc9b87304964647bd1c750b903cb360003a36" -uuid = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" -version = "1.1.2" - - [deps.SparseConnectivityTracer.extensions] - SparseConnectivityTracerChainRulesCoreExt = "ChainRulesCore" - SparseConnectivityTracerLogExpFunctionsExt = "LogExpFunctions" - SparseConnectivityTracerNNlibExt = "NNlib" - SparseConnectivityTracerNaNMathExt = "NaNMath" - SparseConnectivityTracerSpecialFunctionsExt = "SpecialFunctions" - - [deps.SparseConnectivityTracer.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688" - NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd" - NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" - SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" - -[[deps.SparseMatrixColorings]] -deps = ["ADTypes", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "Random", "SparseArrays"] -git-tree-sha1 = "d3f3b7bb8a561b5ff20ee7cf9574841ee4e4e137" -uuid = "0a514795-09f3-496d-8182-132a7b665d35" -version = "0.4.22" - - [deps.SparseMatrixColorings.extensions] - SparseMatrixColoringsCUDAExt = "CUDA" - SparseMatrixColoringsCliqueTreesExt = "CliqueTrees" - SparseMatrixColoringsColorsExt = "Colors" - - [deps.SparseMatrixColorings.weakdeps] - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8" - Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" - -[[deps.SpecialFunctions]] -deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "f2685b435df2613e25fc10ad8c26dddb8640f547" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.6.1" -weakdeps = ["ChainRulesCore"] - - [deps.SpecialFunctions.extensions] - SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" - -[[deps.StableRNGs]] -deps = ["Random"] -git-tree-sha1 = "95af145932c2ed859b63329952ce8d633719f091" -uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" -version = "1.0.3" - -[[deps.Static]] -deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools", "SciMLPublic"] -git-tree-sha1 = "49440414711eddc7227724ae6e570c7d5559a086" -uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" -version = "1.3.1" - -[[deps.StaticArrayInterface]] -deps = ["ArrayInterface", "Compat", "IfElse", "LinearAlgebra", "PrecompileTools", "Static"] -git-tree-sha1 = "96381d50f1ce85f2663584c8e886a6ca97e60554" -uuid = "0d7ed370-da01-4f52-bd93-41d350b8b718" -version = "1.8.0" -weakdeps = ["OffsetArrays", "StaticArrays"] - - [deps.StaticArrayInterface.extensions] - StaticArrayInterfaceOffsetArraysExt = "OffsetArrays" - StaticArrayInterfaceStaticArraysExt = "StaticArrays" - -[[deps.StaticArrays]] -deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "b8693004b385c842357406e3af647701fe783f98" -uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.15" -weakdeps = ["ChainRulesCore", "Statistics"] - - [deps.StaticArrays.extensions] - StaticArraysChainRulesCoreExt = "ChainRulesCore" - StaticArraysStatisticsExt = "Statistics" - -[[deps.StaticArraysCore]] -git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a" -uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.4" - -[[deps.Statistics]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.11.1" -weakdeps = ["SparseArrays"] - - [deps.Statistics.extensions] - SparseArraysExt = ["SparseArrays"] - -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "9d72a13a3f4dd3795a195ac5a44d7d6ff5f552ff" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.7.1" - -[[deps.StatsBase]] -deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "a136f98cefaf3e2924a66bd75173d1c891ab7453" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.7" - -[[deps.StrideArraysCore]] -deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] -git-tree-sha1 = "83151ba8065a73f53ca2ae98bc7274d817aa30f2" -uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da" -version = "0.5.8" - -[[deps.StringManipulation]] -deps = ["PrecompileTools"] -git-tree-sha1 = "725421ae8e530ec29bcbdddbe91ff8053421d023" -uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.4.1" - -[[deps.StructTypes]] -deps = ["Dates", "UUIDs"] -git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8" -uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" -version = "1.11.0" - -[[deps.StyledStrings]] -uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" -version = "1.11.0" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" - -[[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] -uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.8.3+2" - -[[deps.Suppressor]] -deps = ["Logging"] -git-tree-sha1 = "6dbb5b635c5437c68c28c2ac9e39b87138f37c0a" -uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb" -version = "0.2.8" - -[[deps.SymbolicIndexingInterface]] -deps = ["Accessors", "ArrayInterface", "RuntimeGeneratedFunctions", "StaticArraysCore"] -git-tree-sha1 = "94c58884e013efff548002e8dc2fdd1cb74dfce5" -uuid = "2efcf032-c050-4f8e-a9bb-153293bab1f5" -version = "0.3.46" -weakdeps = ["PrettyTables"] - - [deps.SymbolicIndexingInterface.extensions] - SymbolicIndexingInterfacePrettyTablesExt = "PrettyTables" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.3" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] -git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.12.1" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -version = "1.10.0" - -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -version = "1.11.0" - -[[deps.ThreadingUtilities]] -deps = ["ManualMemory"] -git-tree-sha1 = "d969183d3d244b6c33796b5ed01ab97328f2db85" -uuid = "8290d209-cae3-49c0-8002-c8c24d57dab5" -version = "0.5.5" - -[[deps.TimerOutputs]] -deps = ["ExprTools", "Printf"] -git-tree-sha1 = "3748bd928e68c7c346b52125cf41fff0de6937d0" -uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" -version = "0.5.29" - - [deps.TimerOutputs.extensions] - FlameGraphsExt = "FlameGraphs" - - [deps.TimerOutputs.weakdeps] - FlameGraphs = "08572546-2f56-4bcf-ba4e-bab62c3a3f89" - -[[deps.TranscodingStreams]] -git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" -uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.11.3" - -[[deps.TruncatedStacktraces]] -deps = ["InteractiveUtils", "MacroTools", "Preferences"] -git-tree-sha1 = "ea3e54c2bdde39062abf5a9758a23735558705e1" -uuid = "781d530d-4396-4725-bb49-402e4bee1e77" -version = "1.4.0" - -[[deps.URIs]] -git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a" -uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.6.1" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" -version = "1.11.0" - -[[deps.UnPack]] -git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" -uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" -version = "1.0.2" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" -version = "1.11.0" - -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unzip]] -git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" -uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" -version = "0.2.0" - -[[deps.Vulkan_Loader_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] -git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" -uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" -version = "1.3.243+0" - -[[deps.Wayland_jll]] -deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" -uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" -version = "1.24.0+0" - -[[deps.WoodburyMatrices]] -deps = ["LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511" -uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6" -version = "1.0.0" - -[[deps.XML2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] -git-tree-sha1 = "80d3930c6347cfce7ccf96bd3bafdf079d9c0390" -uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" -version = "2.13.9+0" - -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "fee71455b0aaa3440dfdd54a9a36ccef829be7d4" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.1+0" - -[[deps.Xorg_libICE_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" -uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" -version = "1.1.2+0" - -[[deps.Xorg_libSM_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] -git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" -uuid = "c834827a-8449-5923-a945-d239c165b7dd" -version = "1.2.6+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "b5899b25d17bf1889d25906fb9deed5da0c15b3b" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.12+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXcursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" -uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" -version = "1.2.4+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "a4c0ee07ad36bf8bbce1c3bb52d21fb1e0b987fb" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.7+0" - -[[deps.Xorg_libXfixes_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" -uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" -version = "6.0.2+0" - -[[deps.Xorg_libXi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] -git-tree-sha1 = "a376af5c7ae60d29825164db40787f15c80c7c54" -uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" -version = "1.8.3+0" - -[[deps.Xorg_libXinerama_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] -git-tree-sha1 = "a5bc75478d323358a90dc36766f3c99ba7feb024" -uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" -version = "1.1.6+0" - -[[deps.Xorg_libXrandr_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "aff463c82a773cb86061bce8d53a0d976854923e" -uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" -version = "1.5.5+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.12+0" - -[[deps.Xorg_libpciaccess_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "4909eb8f1cbf6bd4b1c30dd18b2ead9019ef2fad" -uuid = "a65dc6b1-eb27-53a1-bb3e-dea574b5389e" -version = "0.18.1+0" - -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_libxkbfile_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "e3150c7400c41e207012b41659591f083f3ef795" -uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" -version = "1.1.3+0" - -[[deps.Xorg_xcb_util_cursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] -git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" -uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" -version = "0.1.6+0" - -[[deps.Xorg_xcb_util_image_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" -uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] -git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" -uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_keysyms_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" -uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_renderutil_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" -uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" -version = "0.3.10+0" - -[[deps.Xorg_xcb_util_wm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" -uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" -version = "0.4.2+0" - -[[deps.Xorg_xkbcomp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] -git-tree-sha1 = "801a858fc9fb90c11ffddee1801bb06a738bda9b" -uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" -version = "1.4.7+0" - -[[deps.Xorg_xkeyboard_config_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] -git-tree-sha1 = "00af7ebdc563c9217ecc67776d1bbf037dbcebf4" -uuid = "33bec58e-1273-512f-9401-5d533626f822" -version = "2.44.0+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.3.1+2" - -[[deps.Zstd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308" -uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.7+1" - -[[deps.cminpack_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll"] -git-tree-sha1 = "ca8a038b2cabd4fe3dd206de56ac1285131515a0" -uuid = "b792d7bf-f512-5dba-8a02-6d8084434f1d" -version = "1.3.12+0" - -[[deps.eudev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" -uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" -version = "3.2.14+0" - -[[deps.fzf_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" -uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" -version = "0.61.1+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "371cc681c00a3ccc3fbc5c0fb91f58ba9bec1ecf" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.13.1+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "125eedcb0a4a0bba65b657251ce1d27c8714e9d6" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.4+0" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.15.0+0" - -[[deps.libdecor_jll]] -deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] -git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" -uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" -version = "0.2.2+0" - -[[deps.libevdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" -uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" -version = "1.13.4+0" - -[[deps.libfdk_aac_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libinput_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] -git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" -uuid = "36db933b-70db-51c0-b978-0f229ee0e533" -version = "1.28.1+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "07b6a107d926093898e82b3b1db657ebe33134ec" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.50+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - -[[deps.mtdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" -uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" -version = "1.1.7+0" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.64.0+1" - -[[deps.oneTBB_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] -git-tree-sha1 = "1350188a69a6e46f799d3945beef36435ed7262f" -uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e" -version = "2022.0.0+1" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.5.0+2" - -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" - -[[deps.xkbcommon_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] -git-tree-sha1 = "fbf139bce07a534df0e699dbb5f5cc9346f95cc1" -uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" -version = "1.9.2+0" diff --git a/src/OptimalControl.jl b/src/OptimalControl.jl index 72b3d37db..cf170d09b 100644 --- a/src/OptimalControl.jl +++ b/src/OptimalControl.jl @@ -8,278 +8,42 @@ $(EXPORTS) module OptimalControl using DocStringExtensions - -# CTBase -import CTBase: - CTBase, - ParsingError, - CTException, - AmbiguousDescription, - IncorrectArgument, - IncorrectMethod, - IncorrectOutput, - NotImplemented, - UnauthorizedCall, - ExtensionError -export ParsingError, - CTException, - AmbiguousDescription, - IncorrectArgument, - IncorrectMethod, - IncorrectOutput, - NotImplemented, - UnauthorizedCall, - ExtensionError - -# CTParser -import CTParser: CTParser, @def -export @def - -function __init__() - CTParser.prefix_fun!(:OptimalControl) - CTParser.prefix_exa!(:OptimalControl) - CTParser.e_prefix!(:OptimalControl) -end - -# RecipesBase.plot -import RecipesBase: RecipesBase, plot -export plot - -# CTModels -import CTModels: - CTModels, - # setters - variable!, - time!, - state!, - control!, - dynamics!, - # constraint!, - objective!, - definition!, - time_dependence!, - # model - build, - Model, - PreModel, - Solution, - # getters - constraints, - get_build_examodel, - times, - definition, - dual, - initial_time, - initial_time_name, - final_time, - final_time_name, - time_name, - variable_dimension, - variable_components, - variable_name, - state_dimension, - state_components, - state_name, - control_dimension, - control_components, - control_name, - # constraint, - dynamics, - mayer, - lagrange, - criterion, - has_fixed_final_time, - has_fixed_initial_time, - has_free_final_time, - has_free_initial_time, - has_lagrange_cost, - has_mayer_cost, - is_autonomous, - export_ocp_solution, - import_ocp_solution, - time_grid, - control, - state, - # variable, - costate, - constraints_violation, - # objective, - iterations, - status, - message, - infos, - successful -export Model, Solution -export constraints, - get_build_examodel, - times, - definition, - dual, - initial_time, - initial_time_name, - final_time, - final_time_name, - time_name, - variable_dimension, - variable_components, - variable_name, - state_dimension, - state_components, - state_name, - control_dimension, - control_components, - control_name, - # constraint, - dynamics, - mayer, - lagrange, - criterion, - has_fixed_final_time, - has_fixed_initial_time, - has_free_final_time, - has_free_initial_time, - has_lagrange_cost, - has_mayer_cost, - is_autonomous, - export_ocp_solution, - import_ocp_solution, - time_grid, - control, - state, - # variable, - costate, - constraints_violation, - # objective, - iterations, - status, - message, - infos, - successful - -# CTDirect -import CTDirect: - CTDirect, - direct_transcription, - set_initial_guess, - build_OCP_solution, - nlp_model, - ocp_model -export direct_transcription, set_initial_guess, build_OCP_solution, nlp_model, ocp_model - -# CTFlows -import CTFlows: - CTFlows, - VectorField, - Lift, - Hamiltonian, - HamiltonianLift, - HamiltonianVectorField, - Flow, - ⋅, - Lie, - Poisson, - @Lie, - * # debug: complete? -export VectorField, - Lift, - Hamiltonian, - HamiltonianLift, - HamiltonianVectorField, - Flow, - ⋅, - Lie, - Poisson, - @Lie, - * - -# To trigger CTDirectExtADNLP and CTDirectExtExa -using ADNLPModels: ADNLPModels -import ExaModels: - ExaModels, - ExaModel, - ExaCore, - variable, - constraint, - constraint!, - objective, - solution, - multipliers, - multipliers_L, - multipliers_U, - Constraint - -# Conflicts of functions defined in several packages -# ExaModels.variable, CTModels.variable -# ExaModels.constraint, CTModels.constraint -# ExaModels.constraint!, CTModels.constraint! -# ExaModels.objective, CTModels.objective -""" -$(TYPEDSIGNATURES) - -See CTModels.variable. -""" -variable(ocp::Model) = CTModels.variable(ocp) - -""" -$(TYPEDSIGNATURES) - -Return the variable or `nothing`. - -```@example -julia> v = variable(sol) -``` -""" -variable(sol::Solution) = CTModels.variable(sol) - -""" -$(TYPEDSIGNATURES) - -Get a labelled constraint from the model. Returns a tuple of the form -`(type, f, lb, ub)` where `type` is the type of the constraint, `f` is the function, -`lb` is the lower bound and `ub` is the upper bound. - -The function returns an exception if the label is not found in the model. - -## Arguments - -- `model`: The model from which to retrieve the constraint. -- `label`: The label of the constraint to retrieve. - -## Returns - -- `Tuple`: A tuple containing the type, function, lower bound, and upper bound of the constraint. -""" -constraint(ocp::Model, label::Symbol) = CTModels.constraint(ocp, label) - -""" -$(TYPEDSIGNATURES) - -See CTModels.constraint!. -""" -function constraint!(ocp::PreModel, type::Symbol; kwargs...) - CTModels.constraint!(ocp, type; kwargs...) -end - -""" -$(TYPEDSIGNATURES) - -See CTModels.objective. -""" -objective(ocp::Model) = CTModels.objective(ocp) - -""" -$(TYPEDSIGNATURES) - -Return the objective value. -""" -objective(sol::Solution) = CTModels.objective(sol) - -export variable, constraint, objective - -# CommonSolve -import CommonSolve: CommonSolve, solve -include("solve.jl") -export solve -export available_methods - -end +using Reexport + +import CommonSolve +@reexport import CommonSolve: solve +import CTBase +import CTModels +import CTDirect +import CTSolvers + +# Imports +include(joinpath(@__DIR__, "imports", "ctbase.jl")) +include(joinpath(@__DIR__, "imports", "ctdirect.jl")) +include(joinpath(@__DIR__, "imports", "ctflows.jl")) +include(joinpath(@__DIR__, "imports", "ctmodels.jl")) +include(joinpath(@__DIR__, "imports", "ctparser.jl")) +include(joinpath(@__DIR__, "imports", "ctsolvers.jl")) +include(joinpath(@__DIR__, "imports", "examodels.jl")) + +# helpers +include(joinpath(@__DIR__, "helpers", "kwarg_extraction.jl")) +include(joinpath(@__DIR__, "helpers", "print.jl")) +include(joinpath(@__DIR__, "helpers", "methods.jl")) +include(joinpath(@__DIR__, "helpers", "registry.jl")) +include(joinpath(@__DIR__, "helpers", "component_checks.jl")) +include(joinpath(@__DIR__, "helpers", "strategy_builders.jl")) +include(joinpath(@__DIR__, "helpers", "component_completion.jl")) +include(joinpath(@__DIR__, "helpers", "descriptive_routing.jl")) + +# solve +include(joinpath(@__DIR__, "solve", "mode.jl")) +include(joinpath(@__DIR__, "solve", "mode_detection.jl")) +include(joinpath(@__DIR__, "solve", "dispatch.jl")) +include(joinpath(@__DIR__, "solve", "canonical.jl")) +include(joinpath(@__DIR__, "solve", "explicit.jl")) +include(joinpath(@__DIR__, "solve", "descriptive.jl")) + +export methods # non useful since it is already in Base + +end \ No newline at end of file diff --git a/src/helpers/component_checks.jl b/src/helpers/component_checks.jl new file mode 100644 index 000000000..7097a345a --- /dev/null +++ b/src/helpers/component_checks.jl @@ -0,0 +1,41 @@ +""" +$(TYPEDSIGNATURES) + +Check if all three resolution components are provided. + +This is a pure predicate function with no side effects. It returns `true` if and only if +all three components (discretizer, modeler, solver) are concrete instances (not `nothing`). + +# Arguments +- `discretizer`: Discretization strategy or `nothing` +- `modeler`: NLP modeling strategy or `nothing` +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Bool`: `true` if all components are provided, `false` otherwise + +# Examples +```julia +julia> disc = CTDirect.Collocation() +julia> mod = CTSolvers.ADNLP() +julia> sol = CTSolvers.Ipopt() +julia> OptimalControl._has_complete_components(disc, mod, sol) +true + +julia> OptimalControl._has_complete_components(nothing, mod, sol) +false + +julia> OptimalControl._has_complete_components(disc, nothing, sol) +false +``` + +# See Also +- [`_complete_components`](@ref): Completes missing components via registry +""" +function _has_complete_components( + discretizer::Union{CTDirect.AbstractDiscretizer, Nothing}, + modeler::Union{CTSolvers.AbstractNLPModeler, Nothing}, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +)::Bool + return !isnothing(discretizer) && !isnothing(modeler) && !isnothing(solver) +end diff --git a/src/helpers/component_completion.jl b/src/helpers/component_completion.jl new file mode 100644 index 000000000..906466cec --- /dev/null +++ b/src/helpers/component_completion.jl @@ -0,0 +1,67 @@ +""" +$(TYPEDSIGNATURES) + +Complete missing resolution components using the registry and R3 helpers. + +This function orchestrates the component completion workflow: +1. Extract symbols from provided components using `_build_partial_description()` +2. Complete the method description using `_complete_description()` +3. Build or use strategies for each family using `_build_or_use_strategy()` + +# Arguments +- `discretizer`: Discretization strategy or `nothing` +- `modeler`: NLP modeling strategy or `nothing` +- `solver`: NLP solver strategy or `nothing` +- `registry`: Strategy registry for building missing components + +# Returns +- `NamedTuple{(:discretizer, :modeler, :solver)}`: Complete component triplet + +# Examples +```julia +# Complete from scratch +result = OptimalControl._complete_components(nothing, nothing, nothing, registry) +@test result.discretizer isa CTDirect.AbstractDiscretizer +@test result.modeler isa CTSolvers.AbstractNLPModeler +@test result.solver isa CTSolvers.AbstractNLPSolver + +# Partial completion +disc = CTDirect.Collocation() +result = OptimalControl._complete_components(disc, nothing, nothing, registry) +@test result.discretizer === disc +@test result.modeler isa CTSolvers.AbstractNLPModeler +@test result.solver isa CTSolvers.AbstractNLPSolver +``` + +# See Also +- [`_build_partial_description`](@ref): Extracts symbols from provided components +- [`_complete_description`](@ref): Completes method description via CTBase +- [`_build_or_use_strategy`](@ref): Builds or uses strategy instances +- [`get_strategy_registry`](@ref): Creates the strategy registry +""" +function _complete_components( + discretizer::Union{CTDirect.AbstractDiscretizer, Nothing}, + modeler::Union{CTSolvers.AbstractNLPModeler, Nothing}, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing}, + registry::CTSolvers.StrategyRegistry +)::NamedTuple{(:discretizer, :modeler, :solver)} + + # Step 1: Extract symbols from provided components + partial_description = _build_partial_description(discretizer, modeler, solver) + + # Step 2: Complete the method description + complete_description = _complete_description(partial_description) + + # Step 3: Build or use strategies for each family + final_discretizer = _build_or_use_strategy( + complete_description, discretizer, CTDirect.AbstractDiscretizer, registry + ) + final_modeler = _build_or_use_strategy( + complete_description, modeler, CTSolvers.AbstractNLPModeler, registry + ) + final_solver = _build_or_use_strategy( + complete_description, solver, CTSolvers.AbstractNLPSolver, registry + ) + + return (discretizer=final_discretizer, modeler=final_modeler, solver=final_solver) +end diff --git a/src/helpers/descriptive_routing.jl b/src/helpers/descriptive_routing.jl new file mode 100644 index 000000000..53fa5ef02 --- /dev/null +++ b/src/helpers/descriptive_routing.jl @@ -0,0 +1,263 @@ +# ============================================================================ +# Descriptive mode routing helpers +# ============================================================================ +# +# These helpers encapsulate the option routing logic for solve_descriptive. +# They are kept separate from solve/descriptive.jl to allow direct unit testing +# without requiring a real OCP or solver. +# +# Call chain: +# solve_descriptive +# └─ _route_descriptive_options (R2.1) +# ├─ _descriptive_families (R2.2) +# └─ _descriptive_action_defs (R2.3) +# └─ _build_components_from_routed (R2.4) ← receives ocp for build_initial_guess + +# ---------------------------------------------------------------------------- +# Action option defaults (single source of truth) +# ---------------------------------------------------------------------------- + +const _DEFAULT_DISPLAY::Bool = true +const _DEFAULT_INITIAL_GUESS::Nothing = nothing + +# Aliases for initial_guess (single source of truth) +# _INITIAL_GUESS_ALIASES_ONLY : used in OptionDefinition (name is separate) +# _INITIAL_GUESS_ALIASES : used in _extract_action_kwarg (includes primary name) +const _INITIAL_GUESS_ALIASES_ONLY::Tuple{Symbol} = (:init,) +const _INITIAL_GUESS_ALIASES::Tuple{Symbol, Symbol} = (:initial_guess, :init) + +# Unwrap an OptionValue (from route_all_options) to its raw value. +# Falls back to `fallback` if `opt` is not an OptionValue. +_unwrap_option(opt::CTSolvers.OptionValue, fallback) = opt.value +_unwrap_option(opt, fallback) = opt === nothing ? fallback : opt + +# ---------------------------------------------------------------------------- +# R2.2 — Families +# ---------------------------------------------------------------------------- + +""" +$(TYPEDSIGNATURES) + +Return the strategy families used for option routing in descriptive mode. + +The returned `NamedTuple` maps family names to their abstract types, as expected +by [`CTSolvers.route_all_options`](@ref). + +# Returns +- `NamedTuple`: `(discretizer, modeler, solver)` mapped to their abstract types + +# Example +```julia +julia> fam = OptimalControl._descriptive_families() +(discretizer = CTDirect.AbstractDiscretizer, modeler = CTSolvers.AbstractNLPModeler, solver = CTSolvers.AbstractNLPSolver) +``` + +See also: [`_route_descriptive_options`](@ref) +""" +function _descriptive_families() + return ( + discretizer = CTDirect.AbstractDiscretizer, + modeler = CTSolvers.AbstractNLPModeler, + solver = CTSolvers.AbstractNLPSolver, + ) +end + +# ---------------------------------------------------------------------------- +# R2.3 — Action option definitions +# ---------------------------------------------------------------------------- + +""" +$(TYPEDSIGNATURES) + +Return the action-level option definitions for descriptive mode. + +Action options are solve-level options consumed by the orchestrator before +strategy-specific options are routed. They are extracted from `kwargs` **first** +by [`CTSolvers.route_all_options`](@ref), so they never reach the strategy router. + +Currently defined action options: +- `initial_guess` (aliases: `init`): Initial guess for the OCP solution. + Defaults to `nothing` (automatic generation via [`CTModels.build_initial_guess`](@ref)). +- `display`: Whether to display solve configuration. Defaults to `true`. + +# Priority rule + +If a strategy also declares an option with the same name (e.g., `display`), the +action option takes priority when no [`route_to`](@ref) is used. To explicitly +target a strategy, use `route_to(strategy_id=value)`. + +# Returns +- `Vector{CTSolvers.OptionDefinition}`: Action option definitions + +# Example +```julia +julia> defs = OptimalControl._descriptive_action_defs() +julia> length(defs) +2 +julia> defs[1].name +:initial_guess +julia> defs[1].aliases +(:init,) +``` + +See also: [`_route_descriptive_options`](@ref) +""" +function _descriptive_action_defs()::Vector{CTSolvers.OptionDefinition} + return [ + CTSolvers.OptionDefinition( + name = :initial_guess, + aliases = _INITIAL_GUESS_ALIASES_ONLY, + type = Any, + default = _DEFAULT_INITIAL_GUESS, + description = "Initial guess for the OCP solution", + ), + CTSolvers.OptionDefinition( + name = :display, + aliases = (), + type = Bool, + default = _DEFAULT_DISPLAY, + description = "Display solve configuration", + ), + ] +end + +# ---------------------------------------------------------------------------- +# R2.1 — Option routing +# ---------------------------------------------------------------------------- + +""" +$(TYPEDSIGNATURES) + +Route all keyword options to the appropriate strategy families for descriptive mode. + +This function wraps [`CTSolvers.route_all_options`](@ref) with the +families and action definitions specific to OptimalControl's descriptive mode. + +Options are routed in `:strict` mode: any unknown option raises an +[`CTBase.IncorrectArgument`](@ref). Ambiguous options (belonging to multiple +strategies) must be disambiguated with [`route_to`](@ref). + +# Arguments +- `complete_description`: Complete method triplet `(discretizer_id, modeler_id, solver_id)` +- `registry`: Strategy registry +- `kwargs`: All keyword arguments from the user's `solve` call (action + strategy options) + +# Returns +- `NamedTuple` with fields: + - `action`: action-level options (`initial_guess`, `display`) as `OptionValue` wrappers + - `strategies`: `NamedTuple` with `discretizer`, `modeler`, `solver` sub-tuples + +# Throws +- `CTBase.IncorrectArgument`: If an option is unknown, ambiguous, or routed to the wrong strategy + +# Example +```julia +julia> routed = OptimalControl._route_descriptive_options( + (:collocation, :adnlp, :ipopt), registry, + pairs((; grid_size=100, max_iter=500)) + ) +julia> routed.strategies.discretizer +(grid_size = 100,) +julia> routed.strategies.solver +(max_iter = 500,) +``` + +See also: [`_descriptive_families`](@ref), [`_descriptive_action_defs`](@ref), +[`_build_components_from_routed`](@ref) +""" +function _route_descriptive_options( + complete_description::Tuple{Symbol, Symbol, Symbol}, + registry::CTSolvers.StrategyRegistry, + kwargs, +) + families = _descriptive_families() + action_defs = _descriptive_action_defs() + return CTSolvers.route_all_options( + complete_description, + families, + action_defs, + (; kwargs...), + registry; + source_mode = :description, + ) +end + +# ---------------------------------------------------------------------------- +# R2.4 — Component construction from routed options +# ---------------------------------------------------------------------------- + +""" +$(TYPEDSIGNATURES) + +Build concrete strategy instances and extract action options from a routed options result. + +Each strategy is constructed via +[`CTSolvers.build_strategy_from_method`](@ref) using the options +that were routed to its family by [`_route_descriptive_options`](@ref). + +Action options (`initial_guess`, `display`) are extracted from `routed.action` +and unwrapped from their `OptionValue` wrappers. The initial guess is normalized +via [`CTModels.build_initial_guess`](@ref). + +# Arguments +- `ocp`: The optimal control problem (needed to normalize the initial guess) +- `complete_description`: Complete method triplet `(discretizer_id, modeler_id, solver_id)` +- `registry`: Strategy registry +- `routed`: Result of [`_route_descriptive_options`](@ref) + +# Returns +- `NamedTuple{(:discretizer, :modeler, :solver, :initial_guess, :display)}` + +# Example +```julia +julia> components = OptimalControl._build_components_from_routed( + ocp, (:collocation, :adnlp, :ipopt), registry, routed + ) +julia> components.discretizer isa CTDirect.AbstractDiscretizer +true +julia> components.initial_guess isa CTModels.AbstractInitialGuess +true +``` + +See also: [`_route_descriptive_options`](@ref), +[`CTSolvers.build_strategy_from_method`](@ref) +""" +function _build_components_from_routed( + ocp::CTModels.AbstractModel, + complete_description::Tuple{Symbol, Symbol, Symbol}, + registry::CTSolvers.StrategyRegistry, + routed::NamedTuple, +) + discretizer = CTSolvers.build_strategy_from_method( + complete_description, + CTDirect.AbstractDiscretizer, + registry; + routed.strategies.discretizer..., + ) + modeler = CTSolvers.build_strategy_from_method( + complete_description, + CTSolvers.AbstractNLPModeler, + registry; + routed.strategies.modeler..., + ) + solver = CTSolvers.build_strategy_from_method( + complete_description, + CTSolvers.AbstractNLPSolver, + registry; + routed.strategies.solver..., + ) + + # Extract and unwrap action options (OptionValue → raw value) + init_raw = _unwrap_option(get(routed.action, :initial_guess, nothing), _DEFAULT_INITIAL_GUESS) + normalized_init = CTModels.build_initial_guess(ocp, init_raw) + + display_val = _unwrap_option(get(routed.action, :display, nothing), _DEFAULT_DISPLAY) + + return ( + discretizer = discretizer, + modeler = modeler, + solver = solver, + initial_guess = normalized_init, + display = display_val, + ) +end diff --git a/src/helpers/kwarg_extraction.jl b/src/helpers/kwarg_extraction.jl new file mode 100644 index 000000000..abcfda0ae --- /dev/null +++ b/src/helpers/kwarg_extraction.jl @@ -0,0 +1,91 @@ +""" +$(TYPEDSIGNATURES) + +Extract the first value of abstract type `T` from `kwargs`, or return `nothing`. + +This function enables type-based mode detection: explicit resolution components +(discretizer, modeler, solver) are identified by their abstract type rather than +by their keyword name. This avoids name collisions with strategy-specific options +that might share the same keyword names. + +# Arguments +- `kwargs`: Keyword arguments from a `solve` call (`Base.Pairs`) +- `T`: Abstract type to search for + +# Returns +- `Union{T, Nothing}`: First matching value, or `nothing` if none found + +# Examples +```julia +julia> using CTDirect +julia> disc = CTDirect.Collocation() +julia> kw = pairs((; discretizer=disc, print_level=0)) +julia> OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) +Collocation(...) + +julia> OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPModeler) +nothing +``` + +# See Also +- [`_explicit_or_descriptive`](@ref): Uses this to detect explicit components +- [`_solve(::ExplicitMode, ...)`](@ref): Uses this to extract components for `solve_explicit` +""" +function _extract_kwarg( + kwargs::Base.Pairs, + ::Type{T} +)::Union{T, Nothing} where {T} + for (_, v) in kwargs + v isa T && return v + end + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Extract an action-level option from `kwargs` by trying multiple alias names. + +Returns the value and the remaining kwargs with the matched key removed. +Raises an error if more than one alias is present simultaneously. + +# Arguments +- `kwargs`: Keyword arguments from a `solve` call (`Base.Pairs`) +- `names`: Tuple of accepted names/aliases, in priority order +- `default`: Default value if none of the names is found + +# Returns +- `(value, remaining_kwargs)`: Extracted value and kwargs with the key removed + +# Throws +- `CTBase.IncorrectArgument`: If more than one alias is provided at the same time + +# Examples +```julia +julia> kw = pairs((; init=x0, display=false)) +julia> val, rest = OptimalControl._extract_action_kwarg(kw, (:initial_guess, :init), nothing) +julia> val === x0 +true +``` + +See Also: [`_extract_kwarg`](@ref) +""" +function _extract_action_kwarg(kwargs::Base.Pairs, names::Tuple{Vararg{Symbol}}, default) + present = [n for n in names if haskey(kwargs, n)] + if isempty(present) + return default, kwargs + elseif length(present) == 1 + name = present[1] + value = kwargs[name] + remaining = Base.pairs(NamedTuple(k => v for (k, v) in kwargs if k != name)) + return value, remaining + else + throw(CTBase.IncorrectArgument( + "Conflicting aliases for the same option", + got="multiple aliases $(present) provided simultaneously", + expected="at most one of $(names)", + suggestion="Use only one alias at a time, e.g. `init=x0` or `initial_guess=x0`", + context="solve - action option extraction" + )) + end +end diff --git a/src/helpers/methods.jl b/src/helpers/methods.jl new file mode 100644 index 000000000..86780ecd6 --- /dev/null +++ b/src/helpers/methods.jl @@ -0,0 +1,38 @@ +""" +$(TYPEDSIGNATURES) + +Return the tuple of available method triplets for solving optimal control problems. + +Each triplet consists of `(discretizer_id, modeler_id, solver_id)` where: +- `discretizer_id`: Symbol identifying the discretization strategy +- `modeler_id`: Symbol identifying the NLP modeling strategy +- `solver_id`: Symbol identifying the NLP solver + +# Returns +- `Tuple{Vararg{Tuple{Symbol, Symbol, Symbol}}}`: Available method combinations + +# Examples +```julia +julia> m = methods() +((:collocation, :adnlp, :ipopt), (:collocation, :adnlp, :madnlp), ...) + +julia> length(m) +8 +``` + +# See Also +- [`solve`](@ref): Main solve function that uses these methods +- [`CTBase.complete`](@ref): Completes partial method descriptions +""" +function Base.methods()::Tuple{Vararg{Tuple{Symbol, Symbol, Symbol}}} + return ( + (:collocation, :adnlp, :ipopt ), + (:collocation, :adnlp, :madnlp), + (:collocation, :exa, :ipopt ), + (:collocation, :exa, :madnlp), + (:collocation, :adnlp, :madncl), + (:collocation, :exa, :madncl), + (:collocation, :adnlp, :knitro), + (:collocation, :exa, :knitro), + ) +end diff --git a/src/helpers/print.jl b/src/helpers/print.jl new file mode 100644 index 000000000..b484dbcf2 --- /dev/null +++ b/src/helpers/print.jl @@ -0,0 +1,188 @@ +# Display helpers for OptimalControl + +""" +$(TYPEDSIGNATURES) + +Display the optimal control problem resolution configuration (discretizer → modeler → solver) with user options. + +This function prints a formatted representation of the solving strategy, showing the component +types and their configuration options. The display is compact by default and only shows +user-specified options. + +# Arguments +- `io::IO`: Output stream for printing +- `discretizer::CTDirect.AbstractDiscretizer`: Discretization strategy +- `modeler::CTSolvers.AbstractNLPModeler`: NLP modeling strategy +- `solver::CTSolvers.AbstractNLPSolver`: NLP solver strategy +- `display::Bool`: Whether to print the configuration (default: `true`) +- `show_options::Bool`: Whether to show component options (default: `true`) +- `show_sources::Bool`: Whether to show option sources (default: `false`) + +# Examples +```julia +julia> disc = CTDirect.Collocation() +julia> mod = CTSolvers.ADNLP() +julia> sol = CTSolvers.Ipopt() +julia> OptimalControl.display_ocp_configuration(stdout, disc, mod, sol) +▫ OptimalControl v1.1.8-beta solving with: collocation → adnlp → ipopt + + 📦 Configuration: + ├─ Discretizer: collocation (no user options) + ├─ Modeler: adnlp (no user options) + └─ Solver: ipopt (no user options) +``` + +# Notes +- By default, only user-specified options are displayed +- Set `show_sources=true` to see where options were defined +- Set `show_options=false` to show only component IDs +- The function returns `nothing` and only produces side effects + +See also: [`solve_explicit`](@ref), [`get_strategy_registry`](@ref) +""" +function display_ocp_configuration( + io::IO, + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTSolvers.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool=true, + show_options::Bool=true, + show_sources::Bool=false, +) + display || return nothing + + version_str = string(Base.pkgversion(OptimalControl)) + + # Header with method + print(io, "▫ OptimalControl v", version_str, " solving with: ") + + discretizer_id = OptimalControl.id(typeof(discretizer)) + modeler_id = OptimalControl.id(typeof(modeler)) + solver_id = OptimalControl.id(typeof(solver)) + + printstyled(io, discretizer_id; color=:cyan, bold=true) + print(io, " → ") + printstyled(io, modeler_id; color=:cyan, bold=true) + print(io, " → ") + printstyled(io, solver_id; color=:cyan, bold=true) + + # NOTE: if we want to display extra method hints later, re-enable cleaned_method logic. + # cleaned_method = CTBase.remove(method, (discretizer_id, modeler_id, solver_id)) + # if !isempty(cleaned_method) + # print(io, " (") + # for (i, m) in enumerate(cleaned_method) + # sep = i == length(cleaned_method) ? "" : ", " + # printstyled(io, string(m) * sep; color=:cyan, bold=true) + # end + # print(io, ")") + # end + + println(io) + + # Combined configuration + options (compact default) + println(io, "") + println(io, " 📦 Configuration:") + + discretizer_pkg = OptimalControl.id(typeof(discretizer)) + model_pkg = OptimalControl.id(typeof(modeler)) + solver_pkg = OptimalControl.id(typeof(solver)) + + disc_opts = show_options ? OptimalControl.options(discretizer) : nothing + mod_opts = show_options ? OptimalControl.options(modeler) : nothing + sol_opts = show_options ? OptimalControl.options(solver) : nothing + + function print_component(line_prefix, label, pkg, opts) + print(io, line_prefix) + printstyled(io, label; bold=true) + print(io, ": ") + printstyled(io, pkg; color=:cyan, bold=true) + if show_options && opts !== nothing + user_items = Tuple{Symbol, Any}[] + for (key, opt) in pairs(opts.options) + if OptimalControl.is_user(opts, key) + push!(user_items, (key, opt)) + end + end + sort!(user_items, by = x -> string(x[1])) + n = length(user_items) + if n == 0 + print(io, " (no user options)") + elseif n <= 2 + print(io, " (") + for (i, (key, opt)) in enumerate(user_items) + sep = i == n ? "" : ", " + src = show_sources ? " [" * string(CTSolvers.source(opt)) * "]" : "" + print(io, string(key), " = ", CTSolvers.value(opt), src, sep) + end + print(io, ")") + else + # Multiline with truncation after 3 items + print(io, "\n ") + shown = first(user_items, 3) + for (i, (key, opt)) in enumerate(shown) + sep = i == length(shown) ? "" : ", " + src = show_sources ? " [" * string(CTSolvers.source(opt)) * "]" : "" + print(io, string(key), " = ", CTSolvers.value(opt), src, sep) + end + remaining = n - length(shown) + if remaining > 0 + print(io, ", … (+", remaining, ")") + end + end + end + println(io) + end + + print_component(" ├─ ", "Discretizer", discretizer_pkg, disc_opts) + print_component(" ├─ ", "Modeler", model_pkg, mod_opts) + print_component(" └─ ", "Solver", solver_pkg, sol_opts) + + println(io) + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Display the optimal control problem resolution configuration to standard output. + +This is a convenience method that prints to `stdout` by default. See the main method +for full documentation of all parameters and behavior. + +# Arguments +- `discretizer::CTDirect.AbstractDiscretizer`: Discretization strategy +- `modeler::CTSolvers.AbstractNLPModeler`: NLP modeling strategy +- `solver::CTSolvers.AbstractNLPSolver`: NLP solver strategy +- `display::Bool`: Whether to print the configuration (default: `true`) +- `show_options::Bool`: Whether to show component options (default: `true`) +- `show_sources::Bool`: Whether to show option sources (default: `false`) + +# Examples +```julia +julia> disc = CTDirect.Collocation() +julia> mod = CTSolvers.ADNLP() +julia> sol = CTSolvers.Ipopt() +julia> OptimalControl.display_ocp_configuration(disc, mod, sol) +▫ OptimalControl v1.1.8-beta solving with: collocation → adnlp → ipopt + + 📦 Configuration: + ├─ Discretizer: collocation (no user options) + ├─ Modeler: adnlp (no user options) + └─ Solver: ipopt (no user options) +``` + +See also: [`display_ocp_configuration(io::IO, ...)`](@ref) +""" +function display_ocp_configuration( + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTSolvers.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool=true, + show_options::Bool=true, + show_sources::Bool=false, +) + return display_ocp_configuration( + stdout, discretizer, modeler, solver; + display=display, show_options=show_options, show_sources=show_sources, + ) +end \ No newline at end of file diff --git a/src/helpers/registry.jl b/src/helpers/registry.jl new file mode 100644 index 000000000..93ab7e333 --- /dev/null +++ b/src/helpers/registry.jl @@ -0,0 +1,37 @@ +""" +$(TYPEDSIGNATURES) + +Create and return the strategy registry for the solve system. + +The registry maps abstract strategy families to their concrete implementations: +- `CTDirect.AbstractDiscretizer` → Discretization strategies +- `CTSolvers.AbstractNLPModeler` → NLP modeling strategies +- `CTSolvers.AbstractNLPSolver` → NLP solver strategies + +# Returns +- `CTSolvers.StrategyRegistry`: Registry with all available strategies + +# Examples +```julia +julia> registry = OptimalControl.get_strategy_registry() +StrategyRegistry with 3 families +``` +""" +function get_strategy_registry()::CTSolvers.StrategyRegistry + return CTSolvers.create_registry( + CTDirect.AbstractDiscretizer => ( + CTDirect.Collocation, + # Add other discretizers as they become available + ), + CTSolvers.AbstractNLPModeler => ( + CTSolvers.ADNLP, + CTSolvers.Exa, + ), + CTSolvers.AbstractNLPSolver => ( + CTSolvers.Ipopt, + CTSolvers.MadNLP, + CTSolvers.MadNCL, + CTSolvers.Knitro, + ) + ) +end diff --git a/src/helpers/strategy_builders.jl b/src/helpers/strategy_builders.jl new file mode 100644 index 000000000..387614751 --- /dev/null +++ b/src/helpers/strategy_builders.jl @@ -0,0 +1,346 @@ +""" +$(TYPEDSIGNATURES) + +Extract strategy symbols from provided components to build a partial method description. + +This function extracts the symbolic IDs from concrete strategy instances using +`CTSolvers.id(typeof(component))`. It returns a tuple containing +the symbols of all non-`nothing` components in the order: discretizer, modeler, solver. + +# Arguments +- `discretizer`: Discretization strategy or `nothing` +- `modeler`: NLP modeling strategy or `nothing` +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Tuple{Vararg{Symbol}}`: Tuple of strategy symbols (empty if all `nothing`) + +# Examples +```julia +julia> disc = CTDirect.Collocation() +julia> _build_partial_description(disc, nothing, nothing) +(:collocation,) + +julia> mod = CTSolvers.ADNLP() +julia> sol = CTSolvers.Ipopt() +julia> _build_partial_description(nothing, mod, sol) +(:adnlp, :ipopt) + +julia> _build_partial_description(nothing, nothing, nothing) +() +``` + +# See Also +- [`CTSolvers.id`](@ref): Extracts symbolic ID from strategy types +- [`_complete_description`](@ref): Completes partial description via registry +""" +function _build_partial_description( + discretizer::Union{CTDirect.AbstractDiscretizer, Nothing}, + modeler::Union{CTSolvers.AbstractNLPModeler, Nothing}, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +)::Tuple{Vararg{Symbol}} + return _build_partial_tuple(discretizer, modeler, solver) +end + +# Recursive tuple building with multiple dispatch + +""" +$(TYPEDSIGNATURES) + +Base case for recursive tuple building. + +Returns an empty tuple when no components are provided. +This function serves as the terminal case for the recursive +tuple building algorithm. + +# Returns +- `()`: Empty tuple + +# Notes +- This is the base case for the recursive tuple building algorithm +- Used internally by `_build_partial_description` +- Allocation-free implementation +""" +_build_partial_tuple() = () + +""" +$(TYPEDSIGNATURES) + +Build partial tuple starting with a discretizer component. + +This method handles the case where a discretizer is provided, +extracts its symbolic ID, and recursively processes the remaining +modeler and solver components. + +# Arguments +- `discretizer`: Concrete discretization strategy +- `modeler`: NLP modeling strategy or `nothing` +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Tuple{Vararg{Symbol}}`: Tuple containing discretizer symbol followed by remaining symbols + +# Notes +- Uses `CTSolvers.id` to extract symbolic ID +- Recursive call to process remaining components +- Allocation-free implementation through tuple concatenation +""" +function _build_partial_tuple( + discretizer::CTDirect.AbstractDiscretizer, + modeler::Union{CTSolvers.AbstractNLPModeler, Nothing}, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +) + disc_symbol = (CTSolvers.id(typeof(discretizer)),) + rest_symbols = _build_partial_tuple(modeler, solver) + return (disc_symbol..., rest_symbols...) +end + +""" +$(TYPEDSIGNATURES) + +Skip discretizer and continue with remaining components. + +This method handles the case where no discretizer is provided, +skipping directly to processing the modeler and solver components. + +# Arguments +- `::Nothing`: Indicates no discretizer provided +- `modeler`: NLP modeling strategy or `nothing` +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Tuple{Vararg{Symbol}}`: Tuple containing symbols from modeler and/or solver + +# Notes +- Delegates to recursive processing of remaining components +- Maintains order: modeler then solver +""" +function _build_partial_tuple( + ::Nothing, + modeler::Union{CTSolvers.AbstractNLPModeler, Nothing}, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +) + return _build_partial_tuple(modeler, solver) +end + +""" +$(TYPEDSIGNATURES) + +Build partial tuple starting with a modeler component. + +This method handles the case where a modeler is provided, +extracts its symbolic ID, and recursively processes the solver. + +# Arguments +- `modeler`: Concrete NLP modeling strategy +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Tuple{Vararg{Symbol}}`: Tuple containing modeler symbol followed by solver symbol (if any) + +# Notes +- Uses `CTSolvers.id` to extract symbolic ID +- Recursive call to process solver component +- Allocation-free implementation +""" +function _build_partial_tuple( + modeler::CTSolvers.AbstractNLPModeler, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +) + mod_symbol = (CTSolvers.id(typeof(modeler)),) + rest_symbols = _build_partial_tuple(solver) + return (mod_symbol..., rest_symbols...) +end + +""" +$(TYPEDSIGNATURES) + +Skip modeler and continue with solver component. + +This method handles the case where no modeler is provided, +skipping directly to processing the solver component. + +# Arguments +- `::Nothing`: Indicates no modeler provided +- `solver`: NLP solver strategy or `nothing` + +# Returns +- `Tuple{Vararg{Symbol}}`: Tuple containing solver symbol (if any) + +# Notes +- Delegates to solver processing +- Terminal case in the recursion chain +""" +function _build_partial_tuple( + ::Nothing, + solver::Union{CTSolvers.AbstractNLPSolver, Nothing} +) + return _build_partial_tuple(solver) +end + +""" +$(TYPEDSIGNATURES) + +Terminal case: extract solver symbol. + +This method handles the case where a solver is provided, +extracts its symbolic ID, and returns it as a single-element tuple. + +# Arguments +- `solver`: Concrete NLP solver strategy + +# Returns +- `Tuple{Symbol}`: Single-element tuple containing solver symbol + +# Notes +- Uses `CTSolvers.id` to extract symbolic ID +- Terminal case in the recursion +- Allocation-free implementation +""" +function _build_partial_tuple(solver::CTSolvers.AbstractNLPSolver) + return (CTSolvers.id(typeof(solver)),) +end + +""" +$(TYPEDSIGNATURES) + +Terminal case: no solver provided. + +This method handles the case where no solver is provided, +returning an empty tuple to complete the recursion. + +# Arguments +- `::Nothing`: Indicates no solver provided + +# Returns +- `()`: Empty tuple + +# Notes +- Terminal case in the recursion +- Represents the case where all components are `nothing` +""" +function _build_partial_tuple(::Nothing) + return () +end + +""" +$(TYPEDSIGNATURES) + +Complete a partial method description into a full triplet using CTBase.complete(). + +This function takes a partial description (tuple of strategy symbols) and +completes it to a full (discretizer, modeler, solver) triplet using the +available methods as the completion set. + +# Arguments +- `partial_description`: Tuple of strategy symbols (may be empty or partial) + +# Returns +- `Tuple{Symbol, Symbol, Symbol}`: Complete method triplet + +# Examples +```julia +julia> _complete_description((:collocation,)) +(:collocation, :adnlp, :ipopt) + +julia> _complete_description(()) +(:collocation, :adnlp, :ipopt) # First available method + +julia> _complete_description((:collocation, :exa)) +(:collocation, :exa, :ipopt) +``` + +# See Also +- [`CTBase.complete`](@ref): Generic completion function +- [`methods`](@ref): Available method triplets +- [`_build_partial_description`](@ref): Builds partial description +""" +function _complete_description( + partial_description::Tuple{Vararg{Symbol}} +)::Tuple{Symbol, Symbol, Symbol} + return CTBase.complete(partial_description...; descriptions=OptimalControl.methods()) +end + +""" +$(TYPEDSIGNATURES) + +Generic strategy builder that returns a provided strategy or builds one from a method description. + +This function works for any strategy family (discretizer, modeler, or solver) using +multiple dispatch to handle the two cases: provided strategy vs. building from registry. + +# Arguments +- `complete_description`: Complete method triplet (discretizer, modeler, solver) +- `provided`: Strategy instance or `nothing` +- `family_type`: Abstract strategy type (e.g., `CTDirect.AbstractDiscretizer`) +- `registry`: Strategy registry for building new strategies + +# Returns +- `T`: Strategy instance of the specified family type + +# Examples +```julia +# Use provided strategy +disc = CTDirect.Collocation() +result = _build_or_use_strategy((:collocation, :adnlp, :ipopt), disc, CTDirect.AbstractDiscretizer, registry) +@test result === disc + +# Build from registry +result = _build_or_use_strategy((:collocation, :adnlp, :ipopt), nothing, CTDirect.AbstractDiscretizer, registry) +@test result isa CTDirect.AbstractDiscretizer +``` + +# Notes +- Fast path: when strategy is provided, returns it directly without registry lookup +- Build path: when strategy is `nothing`, constructs from method description using registry +- Type-safe through Julia's multiple dispatch system +- Allocation-free implementation + +See also: [`CTSolvers.build_strategy_from_method`](@ref), [`get_strategy_registry`](@ref), [`_complete_description`](@ref) +""" +function _build_or_use_strategy( + complete_description::Tuple{Symbol, Symbol, Symbol}, + provided::T, + family_type::Type{T}, + registry::CTSolvers.StrategyRegistry +)::T where {T <: CTSolvers.AbstractStrategy} + # Fast path: strategy already provided + return provided +end + +""" +$(TYPEDSIGNATURES) + +Build strategy from registry when no strategy is provided. + +This method handles the case where no strategy is provided (`nothing`), +building a new strategy from the complete method description using the registry. + +# Arguments +- `complete_description`: Complete method triplet for strategy building +- `::Nothing`: Indicates no strategy provided +- `family_type`: Strategy family type to build +- `registry`: Strategy registry for building new strategies + +# Returns +- `T`: Newly built strategy instance + +# Notes +- Uses `CTSolvers.build_strategy_from_method` for construction +- Registry lookup determines the concrete strategy type +- Type-safe through Julia's dispatch system +- Allocation-free when possible (depends on registry implementation) + +See also: [`CTSolvers.build_strategy_from_method`](@ref), [`get_strategy_registry`](@ref) +""" +function _build_or_use_strategy( + complete_description::Tuple{Symbol, Symbol, Symbol}, + ::Nothing, + family_type::Type{T}, + registry::CTSolvers.StrategyRegistry +)::T where {T <: CTSolvers.AbstractStrategy} + # Build path: construct from registry + return CTSolvers.build_strategy_from_method( + complete_description, family_type, registry + ) +end diff --git a/src/imports/ctbase.jl b/src/imports/ctbase.jl new file mode 100644 index 000000000..53a52c424 --- /dev/null +++ b/src/imports/ctbase.jl @@ -0,0 +1,15 @@ +# CTBase reexports + +# Generated code +@reexport import CTBase: + CTBase # for generated code (prefix) + +# Exceptions +import CTBase: + CTException, + IncorrectArgument, + PreconditionError, + NotImplemented, + ParsingError, + AmbiguousDescription, + ExtensionError diff --git a/src/imports/ctdirect.jl b/src/imports/ctdirect.jl new file mode 100644 index 000000000..1f7d10a22 --- /dev/null +++ b/src/imports/ctdirect.jl @@ -0,0 +1,13 @@ +# CTDirect reexports + +# For internal use +import CTDirect + +# Types +import CTDirect: + AbstractDiscretizer, + Collocation + +# Methods +@reexport import CTDirect: + discretize diff --git a/src/imports/ctflows.jl b/src/imports/ctflows.jl new file mode 100644 index 000000000..3e21b2c81 --- /dev/null +++ b/src/imports/ctflows.jl @@ -0,0 +1,17 @@ +# CTFlows reexports + +# Types +import CTFlows: + Hamiltonian, + HamiltonianLift, + HamiltonianVectorField + +# Methods +@reexport import CTFlows: + Lift, + Flow, + ⋅, + Lie, + Poisson, + @Lie, + * \ No newline at end of file diff --git a/src/imports/ctmodels.jl b/src/imports/ctmodels.jl new file mode 100644 index 000000000..a3fd64291 --- /dev/null +++ b/src/imports/ctmodels.jl @@ -0,0 +1,115 @@ +# CTModels reexports + +# For internal use +import CTModels + +# Generated code +@reexport import CTModels: + CTModels # for generated code (prefix) + +# Display +@reexport import RecipesBase: plot + +# Initial guess +import CTModels: + AbstractInitialGuess, + InitialGuess, + build_initial_guess + +# Serialization +@reexport import CTModels: + export_ocp_solution, + import_ocp_solution + +# OCP +import CTModels: + + # api types + Model, + AbstractModel, + AbstractModel, + Solution, + AbstractSolution, + AbstractSolution + +@reexport import CTModels: + + # accessors + constraint, + constraints, + name, + dimension, + components, + initial_time, + final_time, + time_name, + time_grid, + times, + initial_time_name, + final_time_name, + criterion, + has_mayer_cost, + has_lagrange_cost, + is_mayer_cost_defined, + is_lagrange_cost_defined, + has_fixed_initial_time, + has_free_initial_time, + has_fixed_final_time, + has_free_final_time, + is_autonomous, + is_initial_time_fixed, + is_initial_time_free, + is_final_time_fixed, + is_final_time_free, + state_dimension, + control_dimension, + variable_dimension, + state_name, + control_name, + variable_name, + state_components, + control_components, + variable_components, + + # Constraint accessors + path_constraints_nl, + boundary_constraints_nl, + state_constraints_box, + control_constraints_box, + variable_constraints_box, + dim_path_constraints_nl, + dim_boundary_constraints_nl, + dim_state_constraints_box, + dim_control_constraints_box, + dim_variable_constraints_box, + state, + control, + variable, + costate, + objective, + dynamics, + mayer, + lagrange, + definition, + dual, + iterations, + status, + message, + success, + successful, + constraints_violation, + infos, + get_build_examodel, + is_empty, is_empty_time_grid, + index, time, + model, + + # Dual constraints accessors + path_constraints_dual, + boundary_constraints_dual, + state_constraints_lb_dual, + state_constraints_ub_dual, + control_constraints_lb_dual, + control_constraints_ub_dual, + variable_constraints_lb_dual, + variable_constraints_ub_dual diff --git a/src/imports/ctparser.jl b/src/imports/ctparser.jl new file mode 100644 index 000000000..6f69cbd82 --- /dev/null +++ b/src/imports/ctparser.jl @@ -0,0 +1,5 @@ +# CTParser reexports + +@reexport import CTParser: + @def, + @init diff --git a/src/imports/ctsolvers.jl b/src/imports/ctsolvers.jl new file mode 100644 index 000000000..38419fa3c --- /dev/null +++ b/src/imports/ctsolvers.jl @@ -0,0 +1,65 @@ +# CTSolvers reexports + +# For internal use +import CTSolvers + +# DOCP +import CTSolvers: + DiscretizedModel + +@reexport import CTSolvers: + ocp_model, + nlp_model, + ocp_solution + +# Modelers +import CTSolvers: + AbstractNLPModeler, + ADNLP, + Exa + +# Solvers +import CTSolvers: + AbstractNLPSolver, + Ipopt, + MadNLP, + MadNCL, + Knitro + +# Strategies +import CTSolvers: + + # Types + AbstractStrategy, + StrategyRegistry, + StrategyMetadata, + StrategyOptions, + OptionDefinition, + OptionValue, + RoutedOption, + BypassValue + +@reexport import CTSolvers: + + # Metadata + id, + metadata, + + # Display and introspection functions + describe, + options, + option_names, + option_type, + option_description, + option_default, + option_defaults, + option_value, + option_source, + has_option, + is_user, + is_default, + is_computed, + + # Utility functions + route_to, + bypass diff --git a/src/imports/examodels.jl b/src/imports/examodels.jl new file mode 100644 index 000000000..06559031e --- /dev/null +++ b/src/imports/examodels.jl @@ -0,0 +1,5 @@ +# ExaModels reexports + +# Generated code +@reexport import ExaModels: + ExaModels # for generated code (prefix) diff --git a/src/solve/canonical.jl b/src/solve/canonical.jl new file mode 100644 index 000000000..8b15c5fb3 --- /dev/null +++ b/src/solve/canonical.jl @@ -0,0 +1,72 @@ +# ============================================================================ +# Layer 3: Canonical Solve - Pure Execution +# ============================================================================ + +# This file implements the lowest-level solve function that performs actual +# resolution with fully specified, concrete components. + +# ------------------------------------------------------------------------ +# ------------------------------------------------------------------------ +# Canonical solve function - Layer 3 (Pure Execution) +# All inputs are concrete types, no defaults, no normalization + +""" +$(TYPEDSIGNATURES) + +Resolve an optimal control problem using fully specified, concrete components (Layer 3). + +This is the lowest-level execution layer for solving an optimal control problem. It expects all +components (initial guess, discretizer, modeler, and solver) to be fully instantiated and +normalized. It discretizes the problem and passes it to the underlying `solve` pipeline. + +# Arguments +- `ocp::CTModels.AbstractModel`: The optimal control problem to solve +- `initial_guess::CTModels.AbstractInitialGuess`: Normalized initial guess for the solution +- `discretizer::CTDirect.AbstractDiscretizer`: Concrete discretization strategy +- `modeler::CTSolvers.AbstractNLPModeler`: Concrete NLP modeling strategy +- `solver::CTSolvers.AbstractNLPSolver`: Concrete NLP solver strategy +- `display::Bool`: Whether to display the OCP configuration before solving + +# Returns +- `CTModels.AbstractSolution`: The solution to the optimal control problem + +# Example +```julia +# Conceptual usage pattern for Layer 3 solve +ocp = Model(time=:final) +# ... define OCP ... +init = CTModels.build_initial_guess(ocp, nothing) +disc = CTDirect.Collocation(grid_size=100) +mod = CTSolvers.ADNLP() +sol = CTSolvers.Ipopt() + +solution = solve(ocp, init, disc, mod, sol; display=true) +``` + +See also: [`solve_explicit`](@ref), [`solve_descriptive`](@ref) +""" +function CommonSolve.solve( + ocp::CTModels.AbstractModel, + initial_guess::CTModels.AbstractInitialGuess, # Already normalized by Layer 1 + discretizer::CTDirect.AbstractDiscretizer, # Concrete type (no Nothing) + modeler::CTSolvers.AbstractNLPModeler, # Concrete type (no Nothing) + solver::CTSolvers.AbstractNLPSolver; # Concrete type (no Nothing) + display::Bool # Explicit value (no default) +)::CTModels.AbstractSolution + + # 1. Display configuration (compact, user options only) + if display + OptimalControl.display_ocp_configuration( + discretizer, modeler, solver; + display=true, show_options=true, show_sources=false + ) + end + + # 2. Discretize the optimal control problem + discrete_problem = CTDirect.discretize(ocp, discretizer) + + # 3. Solve the discretized optimal control problem + return CommonSolve.solve( + discrete_problem, initial_guess, modeler, solver; display=display + ) +end \ No newline at end of file diff --git a/src/solve/descriptive.jl b/src/solve/descriptive.jl new file mode 100644 index 000000000..6d0f74bab --- /dev/null +++ b/src/solve/descriptive.jl @@ -0,0 +1,68 @@ +""" +$(TYPEDSIGNATURES) + +Resolve an OCP in descriptive mode (Layer 2). + +Accepts a partial or complete symbolic method description and flat keyword options, +then completes the description, routes options to the appropriate strategies, +builds concrete components, and calls the canonical Layer 3 solver. + +# Arguments +- `ocp`: The optimal control problem to solve +- `description`: Symbolic description tokens (e.g., `:collocation`, `:adnlp`, `:ipopt`). + May be empty, partial, or complete — completed via [`_complete_description`](@ref). +- `registry`: Strategy registry for building strategies +- `kwargs...`: All keyword arguments, including action options (`initial_guess`/`init`/`i`, + `display`) and strategy-specific options, optionally disambiguated with [`route_to`](@ref) + +# Returns +- `CTModels.AbstractSolution`: Solution to the optimal control problem + +# Throws +- `CTBase.IncorrectArgument`: If an option is unknown, ambiguous, or routed to the wrong strategy + +# Example +```julia +# Complete description with options +solve(ocp, :collocation, :adnlp, :ipopt; grid_size=100, display=false) + +# Aliases for initial_guess +solve(ocp, :collocation; init=x0, display=false) + +# Disambiguation for ambiguous options +solve(ocp, :collocation, :adnlp, :ipopt; + backend=route_to(adnlp=:sparse, ipopt=:cpu), display=false) +``` + +# See Also +- [`CommonSolve.solve`](@ref): The entry point that dispatches here +- [`_complete_description`](@ref): Completes partial symbolic descriptions +- [`_route_descriptive_options`](@ref): Routes kwargs to strategy families +- [`_build_components_from_routed`](@ref): Builds concrete strategy instances +""" +function solve_descriptive( + ocp::CTModels.AbstractModel, + description::Symbol...; + registry::CTSolvers.StrategyRegistry, + kwargs... +)::CTModels.AbstractSolution + + # 1. Complete partial description → full (discretizer_id, modeler_id, solver_id) triplet + complete_description = _complete_description(description) + + # 2. Route all kwargs to the appropriate strategy families + # Action options (initial_guess/init/i, display) are extracted first + routed = _route_descriptive_options(complete_description, registry, kwargs) + + # 3. Build concrete strategy instances + extract action options + components = _build_components_from_routed(ocp, complete_description, registry, routed) + + # 4. Canonical solve (Layer 3) + return CommonSolve.solve( + ocp, components.initial_guess, + components.discretizer, + components.modeler, + components.solver; + display=components.display, + ) +end diff --git a/src/solve/dispatch.jl b/src/solve/dispatch.jl new file mode 100644 index 000000000..93ed63bc3 --- /dev/null +++ b/src/solve/dispatch.jl @@ -0,0 +1,63 @@ +""" +$(TYPEDSIGNATURES) + +Main entry point for optimal control problem resolution. + +This function orchestrates the complete solve workflow by: +1. Detecting the resolution mode (explicit vs descriptive) from arguments +2. Normalizing the initial guess +3. Creating the strategy registry +4. Dispatching to the appropriate `_solve` method based on the detected mode + +# Arguments +- `ocp`: The optimal control problem to solve +- `description`: Symbolic description tokens (e.g., `:collocation`, `:adnlp`, `:ipopt`) +- `kwargs...`: All keyword arguments. Action options (`initial_guess`/`init`/`i`, `display`) + are extracted by the appropriate Layer 2 function. Explicit components (`discretizer`, + `modeler`, `solver`) are identified by abstract type. + +# Returns +- `CTModels.AbstractSolution`: Solution to the optimal control problem + +# Examples +```julia +# Descriptive mode (symbolic description) +solve(ocp, :collocation, :adnlp, :ipopt) + +# With initial guess aliases +solve(ocp, :collocation; init=x0, display=false) +solve(ocp, :collocation; i=x0) + +# Explicit mode (typed components) +solve(ocp; discretizer=CTDirect.Collocation(), + modeler=CTSolvers.ADNLP(), solver=CTSolvers.Ipopt()) +``` + +# See Also +- [`_explicit_or_descriptive`](@ref): Mode detection and validation +- [`ExplicitMode`](@ref), [`DescriptiveMode`](@ref): Dispatch sentinel types +- [`_solve`](@ref): Mode-specific resolution methods +""" +function CommonSolve.solve( + ocp::CTModels.AbstractModel, + description::Symbol...; + kwargs... +)::CTModels.AbstractSolution + + # 1. Detect mode and validate (raises on conflict) + mode = _explicit_or_descriptive(description, kwargs) + + # 2. Get registry for component completion + registry = _extract_kwarg(kwargs, CTSolvers.StrategyRegistry) + if isnothing(registry) + registry = get_strategy_registry() + end + + # 3. Dispatch — action options (initial_guess, display) are extracted + # by the Layer 2 functions (solve_explicit / solve_descriptive) + if mode isa ExplicitMode + return solve_explicit(ocp; registry=registry, kwargs...) + else + return solve_descriptive(ocp, description...; registry=registry, kwargs...) + end +end diff --git a/src/solve/explicit.jl b/src/solve/explicit.jl new file mode 100644 index 000000000..59dc690f0 --- /dev/null +++ b/src/solve/explicit.jl @@ -0,0 +1,62 @@ +""" +$(TYPEDSIGNATURES) + +Resolve an OCP in explicit mode (Layer 2). + +Receives typed components (`discretizer`, `modeler`, `solver`) as named keyword arguments, +then completes missing components via the registry before calling Layer 3. + +# Arguments +- `ocp`: The optimal control problem to solve +- `registry`: Strategy registry for completing partial components +- `kwargs...`: All keyword arguments. Action options extracted here: + - `initial_guess` (aliases: `init`): Initial guess, default `nothing` + - `display`: Whether to display configuration information, default `true` + - Typed components: `discretizer`, `modeler`, `solver` (identified by abstract type) + +# Returns +- `CTModels.AbstractSolution`: Solution to the optimal control problem + +# See Also +- [`_has_complete_components`](@ref): Checks if all three components are provided +- [`_complete_components`](@ref): Completes missing components via registry +- [`_explicit_or_descriptive`](@ref): Mode detection that routes here +""" +function solve_explicit( + ocp::CTModels.AbstractModel; + registry::CTSolvers.StrategyRegistry, + kwargs... +)::CTModels.AbstractSolution + + # Extract action options with alias support + init_raw, kwargs1 = _extract_action_kwarg( + kwargs, _INITIAL_GUESS_ALIASES, _DEFAULT_INITIAL_GUESS + ) + display_val, _ = _extract_action_kwarg( + kwargs1, (:display,), _DEFAULT_DISPLAY + ) + + # Normalize initial guess + normalized_init = CTModels.build_initial_guess(ocp, init_raw) + + # Extract typed components by abstract type + discretizer = _extract_kwarg(kwargs, CTDirect.AbstractDiscretizer) + modeler = _extract_kwarg(kwargs, CTSolvers.AbstractNLPModeler) + solver = _extract_kwarg(kwargs, CTSolvers.AbstractNLPSolver) + + # Resolve components: use provided ones or complete via registry + components = if _has_complete_components(discretizer, modeler, solver) + (discretizer=discretizer, modeler=modeler, solver=solver) + else + _complete_components(discretizer, modeler, solver, registry) + end + + # Single solve call with resolved components + return CommonSolve.solve( + ocp, normalized_init, + components.discretizer, + components.modeler, + components.solver; + display=display_val + ) +end \ No newline at end of file diff --git a/src/solve/mode.jl b/src/solve/mode.jl new file mode 100644 index 000000000..16b458460 --- /dev/null +++ b/src/solve/mode.jl @@ -0,0 +1,39 @@ +""" +Abstract supertype for solve mode sentinel types. + +Concrete subtypes are used to route resolution to the appropriate mode handler +without `if/else` branching in mode detection. + +# Subtypes +- [`ExplicitMode`](@ref): User provided explicit components (discretizer, modeler, solver) +- [`DescriptiveMode`](@ref): User provided symbolic description (e.g., `:collocation, :adnlp, :ipopt`) + +# See Also +- [`_explicit_or_descriptive`](@ref): Returns the appropriate mode instance +""" +abstract type SolveMode end + +""" +Sentinel type indicating that the user provided explicit resolution components. + +An instance `ExplicitMode()` is returned by [`_explicit_or_descriptive`](@ref) when at +least one of `discretizer`, `modeler`, or `solver` is present in `kwargs` with the +correct abstract type. + +# See Also +- [`DescriptiveMode`](@ref): The alternative mode +- [`_explicit_or_descriptive`](@ref): Mode detection logic +""" +struct ExplicitMode <: SolveMode end + +""" +Sentinel type indicating that the user provided a symbolic description. + +An instance `DescriptiveMode()` is returned by [`_explicit_or_descriptive`](@ref) when +no explicit components are found in `kwargs`. + +# See Also +- [`ExplicitMode`](@ref): The alternative mode +- [`_explicit_or_descriptive`](@ref): Mode detection logic +""" +struct DescriptiveMode <: SolveMode end diff --git a/src/solve/mode_detection.jl b/src/solve/mode_detection.jl new file mode 100644 index 000000000..183536457 --- /dev/null +++ b/src/solve/mode_detection.jl @@ -0,0 +1,69 @@ +""" +$(TYPEDSIGNATURES) + +Detect the resolution mode from `description` and `kwargs`, and validate consistency. + +Returns an instance of [`ExplicitMode`](@ref) if at least one explicit resolution +component (of type `CTDirect.AbstractDiscretizer`, `CTSolvers.AbstractNLPModeler`, or +`CTSolvers.AbstractNLPSolver`) is found in `kwargs`. Returns [`DescriptiveMode`](@ref) +otherwise. + +Raises [`CTBase.IncorrectArgument`](@ref) if both explicit components and a symbolic +description are provided simultaneously. + +# Arguments +- `description`: Tuple of symbolic description tokens (e.g., `(:collocation, :adnlp, :ipopt)`) +- `kwargs`: Keyword arguments from the `solve` call + +# Returns +- `ExplicitMode()` if explicit components are present +- `DescriptiveMode()` if no explicit components are present + +# Throws +- `CTBase.IncorrectArgument`: If explicit components and symbolic description are mixed + +# Examples +```julia +julia> using CTDirect +julia> disc = CTDirect.Collocation() +julia> kw = pairs((; discretizer=disc)) + +julia> OptimalControl._explicit_or_descriptive((), kw) +ExplicitMode() + +julia> OptimalControl._explicit_or_descriptive((:collocation, :adnlp, :ipopt), pairs(NamedTuple())) +DescriptiveMode() + +julia> OptimalControl._explicit_or_descriptive((:collocation,), kw) +# throws CTBase.IncorrectArgument +``` + +# See Also +- [`_extract_kwarg`](@ref): Used internally to detect component types +- [`ExplicitMode`](@ref), [`DescriptiveMode`](@ref): Returned mode types +- [`CommonSolve.solve`](@ref): Calls this function +""" +function _explicit_or_descriptive( + description::Tuple{Vararg{Symbol}}, + kwargs::Base.Pairs +)::SolveMode + + discretizer = _extract_kwarg(kwargs, CTDirect.AbstractDiscretizer) + modeler = _extract_kwarg(kwargs, CTSolvers.AbstractNLPModeler) + solver = _extract_kwarg(kwargs, CTSolvers.AbstractNLPSolver) + + has_explicit = !isnothing(discretizer) || !isnothing(modeler) || !isnothing(solver) + has_description = !isempty(description) + + if has_explicit && has_description + throw(CTBase.IncorrectArgument( + "Cannot mix explicit components with symbolic description", + got="explicit components + symbolic description $(description)", + expected="either explicit components OR symbolic description", + suggestion="Use either solve(ocp; discretizer=..., modeler=..., solver=...) OR solve(ocp, :collocation, :adnlp, :ipopt)", + context="solve function call" + )) + end + + return has_explicit ? ExplicitMode() : DescriptiveMode() +end diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 52425599e..000000000 --- a/test/Project.toml +++ /dev/null @@ -1,26 +0,0 @@ -[deps] -CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" -DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" -ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" -MadNLPMumps = "3b83494e-c0a4-4895-918b-9157a7a085a1" -NLPModelsIpopt = "f4238b75-b362-5c4c-b852-0801c9a21d71" -NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" -OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" -SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[compat] -CTModels = "0.6" -DifferentiationInterface = "0.7" -ForwardDiff = "0.10, 1.0" -LinearAlgebra = "1" -MadNLP = "0.8" -MadNLPMumps = "0.5" -NLPModelsIpopt = "0.11" -NonlinearSolve = "4" -OrdinaryDiffEq = "6" -SplitApplyCombine = "1" -Test = "1" -julia = "1.10" diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..670610017 --- /dev/null +++ b/test/README.md @@ -0,0 +1,143 @@ +# Testing Guide for OptimalControl + +This directory contains the test suite for `OptimalControl.jl`. It follows the testing conventions and infrastructure provided by [CTBase.jl](https://github.com/control-toolbox/CTBase.jl). + +For detailed guidelines on testing and coverage, please refer to: + +- [CTBase Test Coverage Guide](https://control-toolbox.org/CTBase.jl/stable/test-coverage-guide.html) +- [CTBase TestRunner Extension](https://github.com/control-toolbox/CTBase.jl/blob/main/ext/TestRunner.jl) +- [CTBase CoveragePostprocessing](https://github.com/control-toolbox/CTBase.jl/blob/main/ext/CoveragePostprocessing.jl) + +--- + +## 1. Running Tests + +Tests are executed using the standard Julia Test interface, enhanced by `CTBase.TestRunner`. + +### Default Run (All Enabled Tests) + +```bash +julia --project=@. -e 'using Pkg; Pkg.test()' +``` + +### Running Specific Test Groups + +To run only specific test groups (e.g., `reexport`): + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["suite/reexport/*"])' +``` + +Multiple groups can be specified: + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["suite/reexport/*", "suite/other/*"])' +``` + +### Running All Tests (Including Optional/Long Tests) + +```bash +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["all"])' +``` + +--- + +## 2. Coverage + +To run tests with coverage and generate a report: + +```bash +julia --project=@. -e 'using Pkg; Pkg.test("OptimalControl"; coverage=true); include("test/coverage.jl")' +``` + +This will: + +1. Run all tests with coverage tracking +2. Process `.cov` files +3. Move them to `coverage/` directory +4. Generate an HTML report in `coverage/html/` + +--- + +## 3. Adding New Tests + +### File and Function Naming + +All test files must follow this pattern: + +- **File name**: `test_.jl` +- **Entry function**: `test_()` (matching the filename exactly) + +Example: + +```julia +# File: test/suite/reexport/test_ctbase.jl +module TestCtbase + +using Test +using OptimalControl +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_ctbase() + @testset "CTBase reexports" verbose=VERBOSE showtiming=SHOWTIMING begin + # Tests here + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_ctbase() = TestCtbase.test_ctbase() +``` + +### Registering the Test + +Tests are automatically discovered by the `CTBase.TestRunner` extension using the pattern `suite/*/test_*`. + +--- + +## 4. Best Practices & Rules + +### ⚠️ Crucial: Struct Definitions + +**NEVER define `struct`s inside test functions.** All helper types, mocks, and fakes must be defined at the **module top-level**. + +```julia +# ❌ WRONG +function test_something() + @testset "Test" begin + struct FakeType end # WRONG! Causes world-age issues + end +end + +# ✅ CORRECT +module TestSomething + +# TOP-LEVEL: Define all structs here +struct FakeType end + +function test_something() + @testset "Test" begin + obj = FakeType() # Correct + end +end + +end # module +``` + +### Test Structure + +- Use module isolation for each test file +- Separate unit and integration tests with clear comments +- Each test must be independent and deterministic + +### Directory Structure + +Tests are organized under `test/suite/` by **functionality**: + +- `suite/reexport/` - Reexport verification tests + +--- + +For more detailed testing standards, see the [CTBase Test Coverage Guide](https://control-toolbox.org/CTBase.jl/stable/test-coverage-guide.html). diff --git a/test/coverage.jl b/test/coverage.jl new file mode 100644 index 000000000..f74266fe4 --- /dev/null +++ b/test/coverage.jl @@ -0,0 +1,15 @@ +# ============================================================================== +# OptimalControl Coverage Post-Processing +# ============================================================================== +# +# See test/README.md for details. +# +# Usage: +# julia --project=@. -e 'using Pkg; Pkg.test("OptimalControl"; coverage=true); include("test/coverage.jl")' +# +# ============================================================================== + +pushfirst!(LOAD_PATH, @__DIR__) +using Coverage +using CTBase +CTBase.postprocess_coverage(; root_dir=dirname(@__DIR__)) \ No newline at end of file diff --git a/test/helpers/print_utils.jl b/test/helpers/print_utils.jl new file mode 100644 index 000000000..c8fac62b9 --- /dev/null +++ b/test/helpers/print_utils.jl @@ -0,0 +1,345 @@ +""" +Module d'affichage pour tests de solve OptimalControl. + +Responsabilité unique : Formatage et affichage des résultats de tests. +Inspiré de CTBenchmarks.jl pour cohérence avec l'écosystème control-toolbox. + +Exports: +- prettytime: Format temps avec unités adaptatives +- prettymemory: Format mémoire avec unités binaires +- print_test_line: Affiche ligne de tableau alignée +- print_summary: Affiche résumé final +""" +module TestPrintUtils + +using Printf +using OptimalControl + +# Exports explicites (ISP - Interface Segregation) +export prettytime, prettymemory, print_test_header, print_test_line, print_summary + +""" + prettytime(t::Real) -> String + +Format un temps en secondes avec unités adaptatives. + +Responsabilité unique : Conversion temps → string formaté. +Inspiré de CTBenchmarks.jl. + +# Arguments +- `t::Real`: Temps en secondes + +# Returns +- `String`: Temps formaté (e.g., "2.345 s", "123.4 ms") + +# Examples +```julia +julia> prettytime(0.001234) +"1.234 ms" + +julia> prettytime(2.5) +"2.500 s " +``` +""" +function prettytime(t::Real) + t_abs = abs(t) + if t_abs < 1e-6 + value, units = t * 1e9, "ns" + elseif t_abs < 1e-3 + value, units = t * 1e6, "μs" + elseif t_abs < 1 + value, units = t * 1e3, "ms" + else + value, units = t, "s " + end + return string(@sprintf("%.3f", value), " ", units) +end + +""" + prettymemory(bytes::Integer) -> String + +Format une taille mémoire avec unités binaires. + +Responsabilité unique : Conversion bytes → string formaté. +Inspiré de CTBenchmarks.jl. + +# Arguments +- `bytes::Integer`: Taille en bytes + +# Returns +- `String`: Mémoire formatée (e.g., "1.2 MiB", "512 bytes") + +# Examples +```julia +julia> prettymemory(1048576) +"1.00 MiB" + +julia> prettymemory(512) +"512 bytes" +``` +""" +function prettymemory(bytes::Integer) + if bytes < 1024 + return string(bytes, " bytes") + elseif bytes < 1024^2 + value, units = bytes / 1024, "KiB" + elseif bytes < 1024^3 + value, units = bytes / 1024^2, "MiB" + else + value, units = bytes / 1024^3, "GiB" + end + return string(@sprintf("%.2f", value), " ", units) +end + +""" + print_test_header(show_memory::Bool = false) + +Display table header with column names. + +# Arguments +- `show_memory`: Show memory column (default: false) +""" +function print_test_header(show_memory::Bool = false) + println() + printstyled("OptimalControl Solve Tests\n"; color=:cyan, bold=true) + printstyled("==========================\n"; color=:cyan) + println() + + # Table header (aligned with data columns) + print(" ") # Space for the ✓/✗ symbol (2 characters) + print(" │ ") + printstyled(rpad("Type", 4); bold=true) + print(" │ ") + printstyled(rpad("Problem", 8); bold=true) + print(" │ ") + printstyled(rpad("Disc", 8); bold=true) + print(" │ ") + printstyled(rpad("Modeler", 8); bold=true) + print(" │ ") + printstyled(rpad("Solver", 6); bold=true) + print(" │ ") + printstyled(lpad("Time", 12); bold=true) + print(" │ ") + printstyled(lpad("Iters", 5); bold=true) + print(" │ ") + printstyled(lpad("Objective", 14); bold=true) + print(" │ ") + printstyled(lpad("Reference", 14); bold=true) + print(" │ ") + printstyled(lpad("Error", 7); bold=true) + + if show_memory + print(" │ ") + printstyled(lpad("Memory", 10); bold=true) + end + + println() + + # Separator line + print("───") + print("─┼─") + print("────") + print("─┼─") + print("────────") + print("─┼─") + print("────────") + print("─┼─") + print("────────") + print("─┼─") + print("──────") + print("─┼─") + print("────────────") + print("─┼─") + print("─────") + print("─┼─") + print("──────────────") + print("─┼─") + print("──────────────") + print("─┼─") + + if show_memory + print("───────") + print("─┼─") + print("──────────") + else + print("────────") + end + + println() + flush(stdout) +end + +""" + print_test_line( + test_type::String, + problem::String, + discretizer::String, + modeler::String, + solver::String, + success::Bool, + time::Real, + obj::Real, + ref_obj::Real, + iterations::Union{Int, Nothing} = nothing, + memory_bytes::Union{Int, Nothing} = nothing, + show_memory::Bool = false + ) + +Display a formatted table line for a test result. + +Single responsibility: Formatted display of a result line. +Format inspired by print_benchmark_line() in CTBenchmarks.jl. + +# Architecture +- Uses prettytime() and prettymemory() (DRY) +- Fixed columns with rpad/lpad for alignment +- Colors via printstyled for readability +- Flush stdout for real-time display +- Phantom '-' sign for positive objectives (alignment) + +# Output format +``` + ✓ | Beam | midpoint | ADNLP | Ipopt | 2.345 s | 15 | 8.898598e+00 | 8.898598e+00 | 0.00% +``` + +# Arguments +- `test_type`: Test type ("CPU" or "GPU") +- `problem`: Problem name (e.g., "Beam", "Goddard") +- `discretizer`: Discretizer name (e.g., "midpoint", "trapeze") +- `modeler`: Modeler name (e.g., "ADNLP", "Exa") +- `solver`: Solver name (e.g., "Ipopt", "MadNLP") +- `success`: Test success status +- `time`: Execution time in seconds +- `obj`: Obtained objective value +- `ref_obj`: Reference objective value +- `iterations`: Number of iterations (optional) +- `memory_bytes`: Allocated memory in bytes (optional) +- `show_memory`: Show memory (default: false) +""" +function print_test_line( + test_type::String, + problem::String, + discretizer::String, + modeler::String, + solver::String, + success::Bool, + time::Real, + obj::Real, + ref_obj::Real, + iterations::Union{Int, Nothing} = nothing, + memory_bytes::Union{Int, Nothing} = nothing, + show_memory::Bool = false +) + # Relative error calculation + rel_error = abs(obj - ref_obj) / abs(ref_obj) * 100 + + # Colored status (✓ green or ✗ red) + if success + printstyled(" ✓"; color=:green, bold=true) + else + printstyled(" ✗"; color=:red, bold=true) + end + + print(" │ ") + + # Type column: CPU or GPU + printstyled(rpad(test_type, 4); color=:magenta) + print(" │ ") + + # Fixed columns with rpad/lpad (like CTBenchmarks) + # Problem: 8 characters + printstyled(rpad(problem, 8); color=:cyan, bold=true) + print(" │ ") + + # Discretizer: 8 characters + printstyled(rpad(discretizer, 8); color=:blue) + print(" │ ") + + # Modeler: 8 characters + printstyled(rpad(modeler, 8); color=:magenta) + print(" │ ") + + # Solver: 6 characters + printstyled(rpad(solver, 6); color=:yellow) + print(" │ ") + + # Time: right-aligned, 12 characters + print(lpad(prettytime(time), 12)) + print(" │ ") + + # Iterations: right-aligned, 5 characters + iter_str = iterations === nothing ? "N/A" : string(iterations) + print(lpad(iter_str, 5)) + print(" │ ") + + # Objective: scientific format with phantom sign for alignment + # Add space instead of '-' for positive values + obj_str = @sprintf("%.6e", obj) + if obj >= 0 + obj_str = " " * obj_str # Phantom sign + end + print(lpad(obj_str, 14)) + print(" │ ") + + # Reference: scientific format with phantom sign + ref_str = @sprintf("%.6e", ref_obj) + if ref_obj >= 0 + ref_str = " " * ref_str # Phantom sign + end + print(lpad(ref_str, 14)) + print(" │ ") + + # Error: scientific notation with 2 decimal places + err_str = @sprintf("%.2e", rel_error / 100) # Convert to fraction then scientific format + err_color = rel_error < 1 ? :green : (rel_error < 5 ? :yellow : :red) + printstyled(lpad(err_str, 7); color=err_color) + + # Memory: optional, right-aligned, 10 characters + if show_memory + print(" │ ") + if memory_bytes !== nothing + mem_str = prettymemory(memory_bytes) + else + mem_str = "N/A" + end + print(lpad(mem_str, 10)) + end + + println() + flush(stdout) # Real-time display +end + +""" + print_summary(total::Int, passed::Int, total_time::Real) + +Display a final summary with test statistics. + +Single responsibility: Display global summary. + +# Arguments +- `total`: Total number of tests +- `passed`: Number of successful tests +- `total_time`: Total execution time in seconds + +# Output format +``` +✓ Summary: 16/16 tests passed (100.0% success rate) in 45.234 s +``` +""" +function print_summary(total::Int, passed::Int, total_time::Real) + println() + success_rate = (passed / total) * 100 + + # Symbol and color based on result + if passed == total + printstyled("✓ Summary: "; color=:green, bold=true) + else + printstyled("⚠ Summary: "; color=:yellow, bold=true) + end + + # Statistics + println("$passed/$total tests passed ($(round(success_rate, digits=1))% success rate) in $(prettytime(total_time))") + println() +end + +end # module TestPrintUtils diff --git a/test/problems/TestProblems.jl b/test/problems/TestProblems.jl new file mode 100644 index 000000000..2ec09eeae --- /dev/null +++ b/test/problems/TestProblems.jl @@ -0,0 +1,10 @@ +module TestProblems + + using OptimalControl + + include("beam.jl") + include("goddard.jl") + + export Beam, Goddard + +end \ No newline at end of file diff --git a/test/problems/beam.jl b/test/problems/beam.jl new file mode 100644 index 000000000..542d75009 --- /dev/null +++ b/test/problems/beam.jl @@ -0,0 +1,28 @@ +# Beam optimal control problem definition used by tests and examples. +# +# Returns a NamedTuple with fields: +# - ocp :: the CTParser-defined optimal control problem +# - obj :: reference optimal objective value (Ipopt / MadNLP, Collocation) +# - name :: a short problem name +# - init :: NamedTuple of components for CTSolvers.initial_guess +function Beam() + ocp = @def begin + t ∈ [0, 1], time + x ∈ R², state + u ∈ R, control + + x(0) == [0, 1] + x(1) == [0, -1] + 0 ≤ x₁(t) ≤ 0.1 + -10 ≤ u(t) ≤ 10 + + ∂(x₁)(t) == x₂(t) + ∂(x₂)(t) == u(t) + + ∫(u(t)^2) → min + end + + init = (state=[0.05, 0.1], control=0.1) + + return (ocp=ocp, obj=8.898598, name="beam", init=init) +end diff --git a/test/problems/goddard.jl b/test/problems/goddard.jl new file mode 100644 index 000000000..915b30d31 --- /dev/null +++ b/test/problems/goddard.jl @@ -0,0 +1,65 @@ +# Goddard rocket optimal control problem used by CTSolvers tests. + +""" + Goddard(; vmax=0.1, Tmax=3.5) + +Return data for the classical Goddard rocket ascent, formulated as a +*maximization* of the final altitude `r(tf)`. + +The function returns a NamedTuple with fields: + + * `ocp` – CTParser/@def optimal control problem + * `obj` – reference optimal objective value + * `name` – short problem name (`"goddard"`) + * `init` – NamedTuple of components for `CTSolvers.initial_guess`, similar + in spirit to `Beam()`. +""" +function Goddard(; vmax=0.1, Tmax=3.5) + # constants + Cd = 310 + beta = 500 + b = 2 + r0 = 1 + v0 = 0 + m0 = 1 + mf = 0.6 + x0 = [r0, v0, m0] + + @def goddard begin + tf ∈ R, variable + t ∈ [0, tf], time + x ∈ R^3, state + u ∈ R, control + + 0.01 ≤ tf ≤ Inf + + r = x[1] + v = x[2] + m = x[3] + + x(0) == x0 + m(tf) == mf + + r0 ≤ r(t) ≤ r0 + 0.1 + v0 ≤ v(t) ≤ vmax + mf ≤ m(t) ≤ m0 + 0 ≤ u(t) ≤ 1 + + # Component-wise dynamics (Goddard rocket) + D = Cd * v(t)^2 * exp(-beta * (r(t) - r0)) + g = 1 / r(t)^2 + T = Tmax * u(t) + + ∂(r)(t) == v(t) + ∂(v)(t) == (T - D - m(t) * g) / m(t) + ∂(m)(t) == -b * T + + #r(tf) → max + -r(tf) → min + end + + # Components for a reasonable initial guess around a feasible trajectory. + init = (state=[1.01, 0.05, 0.8], control=0.5, variable=[0.1]) + + return (ocp=goddard, obj=-1.01257, name="goddard", init=init) +end diff --git a/test/runtests.jl b/test/runtests.jl index a2c007763..c6e9febbd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,33 +1,63 @@ +# ============================================================================== +# OptimalControl Test Runner +# ============================================================================== +# +# See test/README.md for usage instructions (running specific tests, coverage, etc.) +# +# ============================================================================== + +# Test dependencies using Test +using CTBase using OptimalControl -using NLPModelsIpopt -using MadNLP -using MadNLPMumps -using LinearAlgebra -using OrdinaryDiffEq -using DifferentiationInterface -using ForwardDiff: ForwardDiff -using SplitApplyCombine # for flatten in some tests -using NonlinearSolve - -# NB some direct tests use functional definition and are `using CTModels` - -@testset verbose = true showtiming = true "Optimal control tests" begin - - # ctdirect tests - @testset verbose = true showtiming = true "CTDirect tests" begin - # run all scripts in subfolder suite/ - include.(filter(contains(r".jl$"), readdir("./ctdirect/suite"; join=true))) - end - - # other tests: indirect - include("./indirect/Goddard.jl") - for name in (:goddard_indirect,) - @testset "$(name)" begin - test_name = Symbol(:test_, name) - println("Testing: " * string(name)) - include("./indirect/$(test_name).jl") - @eval $test_name() - end - end + +# Trigger loading of optional extensions +const TestRunner = Base.get_extension(CTBase, :TestRunner) + +# Controls nested testset output formatting (used by individual test files) +module TestOptions +const VERBOSE = true +const SHOWTIMING = true +end +using .TestOptions: VERBOSE, SHOWTIMING + +# CUDA availability check +using CUDA +is_cuda_on() = CUDA.functional() +if is_cuda_on() + @info "✅ CUDA functional, GPU tests enabled" +else + @info "⚠️ CUDA not functional, GPU tests will be skipped" end + +# Run tests using the TestRunner extension +CTBase.run_tests(; + args=String.(ARGS), + testset_name="OptimalControl tests", + available_tests=( + "suite/*/test_*", + ), + filename_builder=name -> Symbol(:test_, name), + funcname_builder=name -> Symbol(:test_, name), + verbose=VERBOSE, + showtiming=SHOWTIMING, + test_dir=@__DIR__, +) + +# If running with coverage enabled, remind the user to run the post-processing script +# because .cov files are flushed at process exit and cannot be cleaned up by this script. +if Base.JLOptions().code_coverage != 0 + println( + """ + +================================================================================ +[OptimalControl] Coverage files generated. + +To process them, move them to the coverage/ directory, and generate a report, +please run: + + julia --project=@. -e 'using Pkg; Pkg.test("OptimalControl"; coverage=true); include("test/coverage.jl")' +================================================================================ +""", + ) +end \ No newline at end of file diff --git a/test/suite/builders/test_options_forwarding.jl b/test/suite/builders/test_options_forwarding.jl new file mode 100644 index 000000000..966890c18 --- /dev/null +++ b/test/suite/builders/test_options_forwarding.jl @@ -0,0 +1,190 @@ +# ============================================================================ +# Strategy Options Forwarding Tests +# ============================================================================ +# This file tests that strategy options (e.g., `display`, `print_level`, `max_iter`) +# are correctly forwarded and processed from the high-level `OptimalControl` +# API down to the underlying `CTSolvers` constructors, preserving their default +# values or correctly overriding them with user inputs. + +module TestOptionsForwarding + +import Test +import OptimalControl +import ADNLPModels +import ExaModels +import CUDA + +# CUDA availability check +is_cuda_on() = CUDA.functional() + +# Include shared test problems via TestProblems module +include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) +import .TestProblems + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_options_forwarding() + Test.@testset "Options forwarding" verbose = VERBOSE showtiming = SHOWTIMING begin + + # ---------------------------------------------------------------- + # Common setup: Beam problem, small grid for speed + # ---------------------------------------------------------------- + pb = TestProblems.Beam() + ocp = pb.ocp + disc = OptimalControl.Collocation(grid_size=50, scheme=:midpoint) + normalized_init = OptimalControl.build_initial_guess(ocp, pb.init) + docp = OptimalControl.discretize(ocp, disc) + + # ================================================================ + # OptimalControl.Exa options + # ================================================================ + Test.@testset "Exa" begin + + # --- base_type: Float32 instead of default Float64 --- + Test.@testset "base_type" begin + Test.@test begin + modeler = OptimalControl.Exa(base_type=Float32) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + eltype(nlp) == Float32 + end + end + + # --- backend: CUDA backend if available --- + if is_cuda_on() + Test.@testset "backend (CUDA)" begin + Test.@test begin + modeler = OptimalControl.Exa(backend=CUDA.CUDABackend()) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + # With CUDA backend, x0 should be CUDA array + nlp.meta.x0 isa CUDA.CuArray + end + end + end + end + + # ================================================================ + # OptimalControl.ADNLP options — basic + # ================================================================ + Test.@testset "ADNLP basic" begin + + # --- name: custom model name --- + Test.@testset "name" begin + Test.@test begin + modeler = OptimalControl.ADNLP(name="BeamTest") + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.meta.name == "BeamTest" + end + end + + # --- show_time: not stored on the model, skip --- + # show_time only controls printing during build and is not + # inspectable on the resulting ADNLPModel. + + # --- backend: :default instead of :optimized --- + # OptimalControl.build_adnlp_model DOES forward the backend option. + # :default uses ForwardDiffADGradient, :optimized uses + # ReverseDiffADGradient — so the adbackend types differ. + Test.@testset "backend" begin + modeler_default = OptimalControl.ADNLP(backend=:default) + modeler_optimized = OptimalControl.ADNLP(backend=:optimized) + nlp_default = OptimalControl.nlp_model(docp, normalized_init, modeler_default) + nlp_optimized = OptimalControl.nlp_model(docp, normalized_init, modeler_optimized) + Test.@test typeof(nlp_default.adbackend) != typeof(nlp_optimized.adbackend) + end + end + + # ================================================================ + # OptimalControl.ADNLP options — advanced backend overrides + # + # CTSolvers v0.2.5-beta now supports backend overrides with both + # Types and instances. These tests verify that non-default backends + # are properly forwarded through the discretization → modeling pipeline. + # ================================================================ + Test.@testset "ADNLP advanced" begin + + # --- gradient_backend: ReverseDiffADGradient instead of default ForwardDiffADGradient --- + Test.@testset "gradient_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + gradient_backend=ADNLPModels.ReverseDiffADGradient, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.gradient_backend isa ADNLPModels.ReverseDiffADGradient + end + end + + # --- hessian_backend: EmptyADbackend instead of default SparseADHessian --- + Test.@testset "hessian_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + hessian_backend=ADNLPModels.EmptyADbackend, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.hessian_backend isa ADNLPModels.EmptyADbackend + end + end + + # --- jacobian_backend: EmptyADbackend instead of default SparseADJacobian --- + Test.@testset "jacobian_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + jacobian_backend=ADNLPModels.EmptyADbackend, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.jacobian_backend isa ADNLPModels.EmptyADbackend + end + end + + # --- hprod_backend: ReverseDiffADHvprod instead of default ForwardDiffADHvprod --- + Test.@testset "hprod_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + hprod_backend=ADNLPModels.ReverseDiffADHvprod, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.hprod_backend isa ADNLPModels.ReverseDiffADHvprod + end + end + + # --- jprod_backend: ReverseDiffADJprod instead of default ForwardDiffADJprod --- + Test.@testset "jprod_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + jprod_backend=ADNLPModels.ReverseDiffADJprod, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.jprod_backend isa ADNLPModels.ReverseDiffADJprod + end + end + + # --- jtprod_backend: ReverseDiffADJtprod instead of default ForwardDiffADJtprod --- + Test.@testset "jtprod_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + jtprod_backend=ADNLPModels.ReverseDiffADJtprod, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.jtprod_backend isa ADNLPModels.ReverseDiffADJtprod + end + end + + # --- ghjvprod_backend: EmptyADbackend instead of default ForwardDiffADGHjvprod --- + Test.@testset "ghjvprod_backend" begin + Test.@test begin + modeler = OptimalControl.ADNLP( + ghjvprod_backend=ADNLPModels.EmptyADbackend, + ) + nlp = OptimalControl.nlp_model(docp, normalized_init, modeler) + nlp.adbackend.ghjvprod_backend isa ADNLPModels.EmptyADbackend + end + end + end + + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_options_forwarding() = TestOptionsForwarding.test_options_forwarding() diff --git a/test/suite/helpers/test_component_checks.jl b/test/suite/helpers/test_component_checks.jl new file mode 100644 index 000000000..e9b7c4c0b --- /dev/null +++ b/test/suite/helpers/test_component_checks.jl @@ -0,0 +1,92 @@ +# ============================================================================ +# Component Checks Helpers Tests +# ============================================================================ +# This file contains unit tests for the `_has_complete_components` helper. +# It verifies the logic that checks whether all required explicit strategy +# components (discretizer, modeler, solver) have been provided by the user. + +module TestComponentChecks + +import Test +import OptimalControl +import CTDirect +import CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ==================================================================== +# TOP-LEVEL: Mock strategies for testing (no side effects) +# ==================================================================== + +struct MockDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end + +struct MockModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end + +struct MockSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +function test_component_checks() + Test.@testset "Component Checks Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # Create mock instances + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + + # ================================================================ + # UNIT TESTS - _has_complete_components + # ================================================================ + + Test.@testset "All Components Provided" begin + Test.@test OptimalControl._has_complete_components(disc, mod, sol) == true + end + + Test.@testset "Missing Discretizer" begin + Test.@test OptimalControl._has_complete_components(nothing, mod, sol) == false + end + + Test.@testset "Missing Modeler" begin + Test.@test OptimalControl._has_complete_components(disc, nothing, sol) == false + end + + Test.@testset "Missing Solver" begin + Test.@test OptimalControl._has_complete_components(disc, mod, nothing) == false + end + + Test.@testset "All Missing" begin + Test.@test OptimalControl._has_complete_components(nothing, nothing, nothing) == false + end + + Test.@testset "Two Missing" begin + Test.@test OptimalControl._has_complete_components(disc, nothing, nothing) == false + Test.@test OptimalControl._has_complete_components(nothing, mod, nothing) == false + Test.@test OptimalControl._has_complete_components(nothing, nothing, sol) == false + end + + Test.@testset "Determinism" begin + result1 = OptimalControl._has_complete_components(disc, mod, sol) + result2 = OptimalControl._has_complete_components(disc, mod, sol) + Test.@test result1 === result2 + end + + Test.@testset "Type Stability" begin + Test.@test_nowarn Test.@inferred OptimalControl._has_complete_components(disc, mod, sol) + Test.@test_nowarn Test.@inferred OptimalControl._has_complete_components(nothing, mod, sol) + end + + Test.@testset "No Allocations" begin + allocs = @allocated OptimalControl._has_complete_components(disc, mod, sol) + Test.@test allocs == 0 + end + end +end + +end # module + +test_component_checks() = TestComponentChecks.test_component_checks() diff --git a/test/suite/helpers/test_component_completion.jl b/test/suite/helpers/test_component_completion.jl new file mode 100644 index 000000000..ed0910032 --- /dev/null +++ b/test/suite/helpers/test_component_completion.jl @@ -0,0 +1,84 @@ +# ============================================================================ +# Component Completion Helpers Tests +# ============================================================================ +# This file contains unit tests for the `_complete_components` helper. +# It verifies that partially provided strategy components are correctly +# completed (instantiated) using the strategy registry to form a full +# `(discretizer, modeler, solver)` triplet. + +module TestComponentCompletion + +import Test +import OptimalControl +import CTDirect +import CTSolvers +import CTModels +import NLPModelsIpopt # Load extension for Ipopt + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_component_completion() + Test.@testset "Component Completion Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # Create registry for tests + registry = OptimalControl.get_strategy_registry() + + # ================================================================ + # INTEGRATION TESTS - _complete_components + # ================================================================ + + Test.@testset "Complete from Scratch" begin + result = OptimalControl._complete_components(nothing, nothing, nothing, registry) + Test.@test result isa NamedTuple{(:discretizer, :modeler, :solver)} + Test.@test result.discretizer isa CTDirect.AbstractDiscretizer + Test.@test result.modeler isa CTSolvers.AbstractNLPModeler + Test.@test result.solver isa CTSolvers.AbstractNLPSolver + end + + Test.@testset "All Components Provided - No Change" begin + # Use real strategies from the registry + disc = CTDirect.Collocation() + mod = CTSolvers.ADNLP() + sol = CTSolvers.Ipopt() + + result = OptimalControl._complete_components(disc, mod, sol, registry) + Test.@test result.discretizer === disc + Test.@test result.modeler === mod + Test.@test result.solver === sol + end + + Test.@testset "Partial Completion - Discretizer Provided" begin + disc = CTDirect.Collocation() + result = OptimalControl._complete_components(disc, nothing, nothing, registry) + Test.@test result.discretizer === disc + Test.@test result.modeler isa CTSolvers.AbstractNLPModeler + Test.@test result.solver isa CTSolvers.AbstractNLPSolver + end + + Test.@testset "Partial Completion - Two Components Provided" begin + disc = CTDirect.Collocation() + sol = CTSolvers.Ipopt() + result = OptimalControl._complete_components(disc, nothing, sol, registry) + Test.@test result.discretizer === disc + Test.@test result.modeler isa CTSolvers.AbstractNLPModeler + Test.@test result.solver === sol + end + + Test.@testset "Return Type Verification" begin + # Verify return types without Test.@inferred (registry lookup prevents full type inference) + result = OptimalControl._complete_components(nothing, nothing, nothing, registry) + Test.@test result isa NamedTuple{(:discretizer, :modeler, :solver)} + + disc = CTDirect.Collocation() + mod = CTSolvers.ADNLP() + sol = CTSolvers.Ipopt() + result = OptimalControl._complete_components(disc, mod, sol, registry) + Test.@test result isa NamedTuple{(:discretizer, :modeler, :solver)} + end + end +end + +end # module + +test_component_completion() = TestComponentCompletion.test_component_completion() diff --git a/test/suite/helpers/test_kwarg_extraction.jl b/test/suite/helpers/test_kwarg_extraction.jl new file mode 100644 index 000000000..65f9d977a --- /dev/null +++ b/test/suite/helpers/test_kwarg_extraction.jl @@ -0,0 +1,145 @@ +# ============================================================================ +# Keyword Argument Extraction Helpers Tests +# ============================================================================ +# This file contains unit tests for helpers that extract specific types from +# keyword arguments (e.g., `_extract_kwarg`) and check for the presence of +# explicit components (`_has_explicit_components`). It ensures reliable +# argument parsing for the main solve dispatch logic. + +module TestKwargExtraction + +import Test +import OptimalControl +import CTDirect +import CTSolvers +import CTBase + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# TOP-LEVEL: mock instances for testing (avoid external dependencies) +struct MockDiscretizer <: CTDirect.AbstractDiscretizer end +struct MockModeler <: CTSolvers.AbstractNLPModeler end +struct MockSolver <: CTSolvers.AbstractNLPSolver end + +const DISC = MockDiscretizer() +const MOD = MockModeler() +const SOL = MockSolver() + +function test_kwarg_extraction() + Test.@testset "KwargExtraction" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Basic extraction + # ==================================================================== + + Test.@testset "Extracts matching type" begin + kw = pairs((; discretizer=DISC, print_level=0)) + result = OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) + Test.@test result === DISC + end + + Test.@testset "Returns nothing when absent" begin + kw = pairs((; print_level=0, max_iter=100)) + result = OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) + Test.@test isnothing(result) + end + + Test.@testset "Returns nothing for empty kwargs" begin + kw = pairs(NamedTuple()) + Test.@test isnothing(OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer)) + Test.@test isnothing(OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPModeler)) + Test.@test isnothing(OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPSolver)) + end + + # ==================================================================== + # UNIT TESTS - All three component types + # ==================================================================== + + 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, CTDirect.AbstractDiscretizer) === DISC + Test.@test OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPModeler) === MOD + Test.@test OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPSolver) === SOL + end + + # ==================================================================== + # UNIT TESTS - Name independence (key design property) + # ==================================================================== + + Test.@testset "Name-independent extraction" begin + # The key is found by TYPE, not by name + kw = pairs((; my_custom_key=DISC, another_key=42)) + result = OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) + Test.@test result === DISC + end + + Test.@testset "Non-matching types ignored" begin + kw = pairs((; x=42, y="hello", z=3.14)) + Test.@test isnothing(OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer)) + Test.@test isnothing(OptimalControl._extract_kwarg(kw, CTSolvers.AbstractNLPModeler)) + end + + # ==================================================================== + # UNIT TESTS - Type safety + # ==================================================================== + + Test.@testset "Return type correctness" begin + kw = pairs((; discretizer=DISC)) + result = OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) + Test.@test result isa Union{CTDirect.AbstractDiscretizer, Nothing} + end + + Test.@testset "Nothing return type" begin + kw = pairs(NamedTuple()) + result = OptimalControl._extract_kwarg(kw, CTDirect.AbstractDiscretizer) + Test.@test result isa Nothing + end + # ==================================================================== + # UNIT TESTS - Action Kwarg Extraction (aliases) + # ==================================================================== + + Test.@testset "Action Kwarg Extraction" begin + Test.@testset "Extracts primary name" begin + kw = pairs((; initial_guess=42, display=false)) + val, rest = OptimalControl._extract_action_kwarg(kw, OptimalControl._INITIAL_GUESS_ALIASES, nothing) + Test.@test val == 42 + Test.@test haskey(rest, :display) + Test.@test !haskey(rest, :initial_guess) + end + + Test.@testset "Extracts alias 1" begin + kw = pairs((; init=42, display=false)) + val, rest = OptimalControl._extract_action_kwarg(kw, OptimalControl._INITIAL_GUESS_ALIASES, nothing) + Test.@test val == 42 + Test.@test haskey(rest, :display) + Test.@test !haskey(rest, :init) + end + + Test.@testset "No alias 'i' (removed)" begin + kw = pairs((; i=42, display=false)) + val, rest = OptimalControl._extract_action_kwarg(kw, OptimalControl._INITIAL_GUESS_ALIASES, nothing) + Test.@test val === nothing # default, since :i is not recognized + Test.@test haskey(rest, :display) + Test.@test haskey(rest, :i) # :i remains in remaining kwargs + end + + Test.@testset "Returns default when not found" begin + kw = pairs((; display=false)) + val, rest = OptimalControl._extract_action_kwarg(kw, OptimalControl._INITIAL_GUESS_ALIASES, :my_default) + Test.@test val === :my_default + Test.@test haskey(rest, :display) + end + + Test.@testset "Throws on multiple aliases present" begin + kw = pairs((; initial_guess=42, init=43)) + Test.@test_throws CTBase.IncorrectArgument OptimalControl._extract_action_kwarg(kw, OptimalControl._INITIAL_GUESS_ALIASES, nothing) + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_kwarg_extraction() = TestKwargExtraction.test_kwarg_extraction() diff --git a/test/suite/helpers/test_methods.jl b/test/suite/helpers/test_methods.jl new file mode 100644 index 000000000..57d0a026c --- /dev/null +++ b/test/suite/helpers/test_methods.jl @@ -0,0 +1,58 @@ +# ============================================================================ +# Available Methods Tests +# ============================================================================ +# This file tests the `methods()` function, verifying that it correctly +# returns the list of all supported solving methods (valid combinations +# of discretizer, modeler, and solver). + +module TestAvailableMethods + +import Test +import OptimalControl + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_methods() + Test.@testset "methods Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS + # ==================================================================== + + Test.@testset "Return Type" begin + methods = OptimalControl.methods() + Test.@test methods isa Tuple + Test.@test all(m -> m isa Tuple{Symbol, Symbol, Symbol}, methods) + end + + Test.@testset "Content Verification" begin + methods = OptimalControl.methods() + + Test.@test (:collocation, :adnlp, :ipopt) in methods + Test.@test (:collocation, :adnlp, :madnlp) in methods + Test.@test (:collocation, :adnlp, :madncl) in methods + Test.@test (:collocation, :adnlp, :knitro) in methods + Test.@test (:collocation, :exa, :ipopt) in methods + Test.@test (:collocation, :exa, :madnlp) in methods + Test.@test (:collocation, :exa, :madncl) in methods + Test.@test (:collocation, :exa, :knitro) in methods + Test.@test length(methods) == 8 + end + + Test.@testset "Uniqueness" begin + methods = OptimalControl.methods() + Test.@test length(methods) == length(unique(methods)) + end + + Test.@testset "Determinism" begin + m1 = OptimalControl.methods() + m2 = OptimalControl.methods() + Test.@test m1 === m2 + end + end +end + +end # module + +test_methods() = TestAvailableMethods.test_methods() diff --git a/test/suite/helpers/test_print.jl b/test/suite/helpers/test_print.jl new file mode 100644 index 000000000..383db7a8d --- /dev/null +++ b/test/suite/helpers/test_print.jl @@ -0,0 +1,69 @@ +# ============================================================================ +# Display and Printing Helpers Tests +# ============================================================================ +# This file tests the `display_ocp_configuration` function and other printing +# utilities. It ensures that the current strategy configuration (components +# and their options) is formatted and displayed correctly to the user. + +module TestPrint + +import Test +import OptimalControl +import NLPModelsIpopt + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# Entry point +function test_print() + Test.@testset "Display helper - compact default" begin + disc = OptimalControl.Collocation(grid_size=5, scheme=:midpoint) + mod = OptimalControl.ADNLP() + sol = OptimalControl.Ipopt(print_level=0) + + io = IOBuffer() + OptimalControl.display_ocp_configuration(io, disc, mod, sol; + display=true, show_options=false, show_sources=false) + out = String(take!(io)) + + Test.@test occursin("Discretizer: collocation", out) + Test.@test occursin("Modeler: adnlp", out) + Test.@test occursin("Solver: ipopt", out) + Test.@test !occursin("[user]", out) # compact mode without sources + end + + Test.@testset "Display helper - hide options" begin + disc = OptimalControl.Collocation(grid_size=5, scheme=:midpoint) + mod = OptimalControl.ADNLP() + sol = OptimalControl.Ipopt(print_level=0) + + io = IOBuffer() + OptimalControl.display_ocp_configuration(io, disc, mod, sol; + display=true, show_options=false, show_sources=false) + out = String(take!(io)) + + Test.@test !occursin("grid_size", out) + Test.@test !occursin("print_level", out) + end + + Test.@testset "Display helper - sources flag" begin + disc = OptimalControl.Collocation(grid_size=5, scheme=:midpoint) + mod = OptimalControl.ADNLP() + sol = OptimalControl.Ipopt(print_level=0) + + io = IOBuffer() + OptimalControl.display_ocp_configuration(io, disc, mod, sol; + display=true, show_options=true, show_sources=true) + out = String(take!(io)) + + # Just ensure it runs and still prints the ids + Test.@test occursin("Discretizer: collocation", out) + Test.@test occursin("Modeler: adnlp", out) + Test.@test occursin("Solver: ipopt", out) + end +end + +end # module + +# Expose entry point +test_print() = TestPrint.test_print() diff --git a/test/suite/helpers/test_registry.jl b/test/suite/helpers/test_registry.jl new file mode 100644 index 000000000..193c32279 --- /dev/null +++ b/test/suite/helpers/test_registry.jl @@ -0,0 +1,68 @@ +# ============================================================================ +# Strategy Registry Setup Tests +# ============================================================================ +# This file tests the `get_strategy_registry` function. It verifies that +# the global strategy registry is correctly populated with all available +# abstract families and their concrete implementations provided by the solver +# ecosystem (CTDirect, CTSolvers). + +module TestRegistry + +import Test +import OptimalControl +import CTSolvers +import CTDirect + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_registry() + Test.@testset "Strategy Registry Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS + # ==================================================================== + + Test.@testset "Registry Creation" begin + registry = OptimalControl.get_strategy_registry() + Test.@test registry isa CTSolvers.StrategyRegistry + end + + Test.@testset "Discretizer Family" begin + registry = OptimalControl.get_strategy_registry() + ids = CTSolvers.strategy_ids(CTDirect.AbstractDiscretizer, registry) + Test.@test :collocation in ids + Test.@test length(ids) >= 1 + end + + Test.@testset "Modeler Family" begin + registry = OptimalControl.get_strategy_registry() + ids = CTSolvers.strategy_ids(CTSolvers.AbstractNLPModeler, registry) + Test.@test :adnlp in ids + Test.@test :exa in ids + Test.@test length(ids) == 2 + end + + Test.@testset "Solver Family" begin + registry = OptimalControl.get_strategy_registry() + ids = CTSolvers.strategy_ids(CTSolvers.AbstractNLPSolver, registry) + Test.@test :ipopt in ids + Test.@test :madnlp in ids + Test.@test :madncl in ids + Test.@test :knitro in ids + Test.@test length(ids) == 4 + end + + Test.@testset "Determinism" begin + r1 = OptimalControl.get_strategy_registry() + r2 = OptimalControl.get_strategy_registry() + ids1 = CTSolvers.strategy_ids(CTSolvers.AbstractNLPSolver, r1) + ids2 = CTSolvers.strategy_ids(CTSolvers.AbstractNLPSolver, r2) + Test.@test ids1 == ids2 + end + end +end + +end # module + +test_registry() = TestRegistry.test_registry() diff --git a/test/suite/helpers/test_strategy_builders.jl b/test/suite/helpers/test_strategy_builders.jl new file mode 100644 index 000000000..03529bc2a --- /dev/null +++ b/test/suite/helpers/test_strategy_builders.jl @@ -0,0 +1,213 @@ +# ============================================================================ +# Strategy Builders Helpers Tests +# ============================================================================ +# This file contains unit tests for the core strategy building helpers: +# `_build_partial_description`, `_complete_description`, and `_build_or_use_strategy`. +# It verifies the logic used to analyze provided components, resolve missing +# parts via the registry, and instantiate the required strategies. + +module TestStrategyBuilders + +import Test +import OptimalControl +import CTDirect +import CTSolvers + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ==================================================================== +# TOP-LEVEL MOCKS +# ==================================================================== + +struct MockDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end + +struct MockModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end + +struct MockSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +CTSolvers.id(::Type{MockDiscretizer}) = :mock_disc +CTSolvers.id(::Type{MockModeler}) = :mock_mod +CTSolvers.id(::Type{MockSolver}) = :mock_sol + +function test_strategy_builders() + Test.@testset "Strategy Builders Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + + # Create mock instances + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + + # ================================================================ + # UNIT TESTS - _build_partial_description + # ================================================================ + + Test.@testset "All Components Provided" begin + result = OptimalControl._build_partial_description(disc, mod, sol) + Test.@test result == (:mock_disc, :mock_mod, :mock_sol) + Test.@test length(result) == 3 + end + + Test.@testset "Only Discretizer" begin + result = OptimalControl._build_partial_description(disc, nothing, nothing) + Test.@test result == (:mock_disc,) + Test.@test length(result) == 1 + end + + Test.@testset "Only Modeler" begin + result = OptimalControl._build_partial_description(nothing, mod, nothing) + Test.@test result == (:mock_mod,) + Test.@test length(result) == 1 + end + + Test.@testset "Only Solver" begin + result = OptimalControl._build_partial_description(nothing, nothing, sol) + Test.@test result == (:mock_sol,) + Test.@test length(result) == 1 + end + + Test.@testset "Discretizer and Modeler" begin + result = OptimalControl._build_partial_description(disc, mod, nothing) + Test.@test result == (:mock_disc, :mock_mod) + Test.@test length(result) == 2 + end + + Test.@testset "Modeler and Solver" begin + result = OptimalControl._build_partial_description(nothing, mod, sol) + Test.@test result == (:mock_mod, :mock_sol) + Test.@test length(result) == 2 + end + + Test.@testset "Discretizer and Solver" begin + result = OptimalControl._build_partial_description(disc, nothing, sol) + Test.@test result == (:mock_disc, :mock_sol) + Test.@test length(result) == 2 + end + + Test.@testset "All Nothing" begin + result = OptimalControl._build_partial_description(nothing, nothing, nothing) + Test.@test result == () + Test.@test length(result) == 0 + end + + Test.@testset "Determinism" begin + # Same inputs should always give same output + result1 = OptimalControl._build_partial_description(disc, mod, sol) + result2 = OptimalControl._build_partial_description(disc, mod, sol) + Test.@test result1 === result2 + end + + Test.@testset "Type Stability" begin + Test.@test_nowarn Test.@inferred OptimalControl._build_partial_description(disc, mod, sol) + Test.@test_nowarn Test.@inferred OptimalControl._build_partial_description(nothing, nothing, nothing) + end + + Test.@testset "No Allocations" begin + # Pure function should not allocate (allow small platform differences) + allocs = @allocated OptimalControl._build_partial_description(disc, mod, sol) + Test.@test allocs <= 32 # Allow small platform-dependent allocations + end + + # ================================================================ + # UNIT TESTS - _complete_description + # ================================================================ + + Test.@testset "Complete Description - Empty" begin + result = OptimalControl._complete_description(()) + Test.@test result isa Tuple{Symbol, Symbol, Symbol} + Test.@test length(result) == 3 + Test.@test result in OptimalControl.methods() + end + + Test.@testset "Complete Description - Partial" begin + result = OptimalControl._complete_description((:collocation,)) + Test.@test result == (:collocation, :adnlp, :ipopt) + Test.@test result in OptimalControl.methods() + end + + Test.@testset "Complete Description - Two Symbols" begin + result = OptimalControl._complete_description((:collocation, :exa)) + Test.@test result == (:collocation, :exa, :ipopt) + Test.@test result in OptimalControl.methods() + end + + Test.@testset "Complete Description - Already Complete" begin + result = OptimalControl._complete_description((:collocation, :adnlp, :ipopt)) + Test.@test result == (:collocation, :adnlp, :ipopt) + Test.@test result in OptimalControl.methods() + end + + Test.@testset "Complete Description - Different Combinations" begin + # Test various partial combinations + combos = [ + (:collocation,), (:collocation, :adnlp), (:collocation, :exa), + (:collocation, :adnlp, :ipopt), (:collocation, :exa, :madnlp) + ] + for combo in combos + result = OptimalControl._complete_description(combo) + Test.@test result isa Tuple{Symbol, Symbol, Symbol} + Test.@test result in OptimalControl.methods() + # Check that the provided symbols are preserved + for (i, sym) in enumerate(combo) + Test.@test result[i] == sym + end + end + end + + Test.@testset "Complete Description - Type Stability" begin + Test.@test_nowarn Test.@inferred OptimalControl._complete_description(()) + Test.@test_nowarn Test.@inferred OptimalControl._complete_description((:collocation,)) + Test.@test_nowarn Test.@inferred OptimalControl._complete_description((:collocation, :adnlp, :ipopt)) + end + + # ================================================================ + # UNIT TESTS - _build_or_use_strategy + # ================================================================ + + # Create registry for _build_or_use_strategy tests + registry = OptimalControl.get_strategy_registry() + + Test.@testset "Build or Use Strategy - Provided Path" begin + # Test discretizer + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + result = OptimalControl._build_or_use_strategy( + (:mock_disc, :mock_mod, :mock_sol), disc, CTDirect.AbstractDiscretizer, registry + ) + Test.@test result === disc + Test.@test result isa MockDiscretizer + + # Test modeler + mod = MockModeler(CTSolvers.StrategyOptions()) + result = OptimalControl._build_or_use_strategy( + (:mock_disc, :mock_mod, :mock_sol), mod, CTSolvers.AbstractNLPModeler, registry + ) + Test.@test result === mod + Test.@test result isa MockModeler + + # Test solver + sol = MockSolver(CTSolvers.StrategyOptions()) + result = OptimalControl._build_or_use_strategy( + (:mock_disc, :mock_mod, :mock_sol), sol, CTSolvers.AbstractNLPSolver, registry + ) + Test.@test result === sol + Test.@test result isa MockSolver + end + + Test.@testset "Build or Use Strategy - Type Stability" begin + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + Test.@test_nowarn Test.@inferred OptimalControl._build_or_use_strategy( + (:mock_disc, :mock_mod, :mock_sol), disc, CTDirect.AbstractDiscretizer, registry + ) + end + end +end + +end # module + +test_strategy_builders() = TestStrategyBuilders.test_strategy_builders() diff --git a/test/suite/reexport/test_ctbase.jl b/test/suite/reexport/test_ctbase.jl new file mode 100644 index 000000000..5de40423d --- /dev/null +++ b/test/suite/reexport/test_ctbase.jl @@ -0,0 +1,49 @@ +# ============================================================================ +# CTBase Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTBase`. It verifies that +# the expected types, functions, and constants are properly exported by +# `OptimalControl` and readily accessible to the end user. + +module TestCtbase + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtbase + +function test_ctbase() + Test.@testset "CTBase reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + + Test.@testset "Generated Code Prefix" begin + Test.@test isdefined(OptimalControl, :CTBase) + Test.@test isdefined(CurrentModule, :CTBase) + Test.@test CTBase isa Module + end + + Test.@testset "Exceptions" begin + for T in ( + OptimalControl.CTException, + OptimalControl.IncorrectArgument, + OptimalControl.PreconditionError, + OptimalControl.NotImplemented, + OptimalControl.ParsingError, + OptimalControl.AmbiguousDescription, + OptimalControl.ExtensionError, + ) + Test.@test isdefined(OptimalControl, nameof(T)) # check if defined in OptimalControl + Test.@test !isdefined(CurrentModule, nameof(T)) # check if exported + Test.@test T isa DataType + end + end + + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctbase() = TestCtbase.test_ctbase() \ No newline at end of file diff --git a/test/suite/reexport/test_ctdirect.jl b/test/suite/reexport/test_ctdirect.jl new file mode 100644 index 000000000..1d142efb5 --- /dev/null +++ b/test/suite/reexport/test_ctdirect.jl @@ -0,0 +1,41 @@ +# ============================================================================ +# CTDirect Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTDirect`. It verifies that +# the expected types and functions related to direct discretization methods +# are properly exported by `OptimalControl`. + +module TestCtdirect + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtdirect + +function test_ctdirect() + Test.@testset "CTDirect reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "Types" begin + for T in ( + OptimalControl.AbstractDiscretizer, + OptimalControl.Collocation, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "Functions" begin + Test.@test isdefined(OptimalControl, :discretize) + Test.@test isdefined(CurrentModule, :discretize) + Test.@test discretize isa Function + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctdirect() = TestCtdirect.test_ctdirect() \ No newline at end of file diff --git a/test/suite/reexport/test_ctflows.jl b/test/suite/reexport/test_ctflows.jl new file mode 100644 index 000000000..fc8d15caf --- /dev/null +++ b/test/suite/reexport/test_ctflows.jl @@ -0,0 +1,62 @@ +# ============================================================================ +# CTFlows Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTFlows`. It verifies that +# the expected types and functions for Hamiltonian flows and dynamics +# are properly exported by `OptimalControl`. + +module TestCtflows + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtflows + +function test_ctflows() + Test.@testset "CTFlows reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "Types" begin + for T in ( + OptimalControl.Hamiltonian, + OptimalControl.HamiltonianLift, + OptimalControl.HamiltonianVectorField, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "Functions" begin + for f in ( + :Lift, + :Flow, + ) + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + Test.@testset "Operators" begin + for op in ( + :⋅, + :Lie, + :Poisson, + :*, + ) + Test.@test isdefined(OptimalControl, op) + Test.@test isdefined(CurrentModule, op) + end + end + Test.@testset "Macros" begin + Test.@test isdefined(OptimalControl, Symbol("@Lie")) + Test.@test isdefined(CurrentModule, Symbol("@Lie")) + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctflows() = TestCtflows.test_ctflows() \ No newline at end of file diff --git a/test/suite/reexport/test_ctmodels.jl b/test/suite/reexport/test_ctmodels.jl new file mode 100644 index 000000000..948186864 --- /dev/null +++ b/test/suite/reexport/test_ctmodels.jl @@ -0,0 +1,158 @@ +# ============================================================================ +# CTModels Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTModels`. It verifies that +# all the core types and functions required to define and manipulate optimal +# control problems (OCPs) are properly exported by `OptimalControl`. + +module TestCtmodels + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtmodels + +function test_ctmodels() + Test.@testset "CTModels reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + + Test.@testset "Generated Code Prefix" begin + Test.@test isdefined(OptimalControl, :CTModels) + Test.@test isdefined(CurrentModule, :CTModels) + Test.@test CTModels isa Module + end + + Test.@testset "Display" begin + Test.@test isdefined(OptimalControl, :plot) + Test.@test isdefined(CurrentModule, :plot) + Test.@test plot isa Function + end + + Test.@testset "Initial Guess Types" begin + for T in ( + OptimalControl.AbstractInitialGuess, + OptimalControl.InitialGuess, + ) + Test.@testset "$(nameof(T))" begin + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + end + + Test.@testset "Initial Guess Functions" begin + for f in ( + :build_initial_guess, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test !isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + + Test.@testset "Serialization Functions" begin + for f in ( + :export_ocp_solution, + :import_ocp_solution, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + + Test.@testset "API Types" begin + for T in ( + OptimalControl.Model, + OptimalControl.AbstractModel, + OptimalControl.AbstractModel, + OptimalControl.Solution, + OptimalControl.AbstractSolution, + OptimalControl.AbstractSolution, + ) + Test.@testset "$(nameof(T))" begin + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + end + + Test.@testset "Accessors" begin + for f in ( + :constraint, :constraints, :name, :dimension, :components, + :initial_time, :final_time, :time_name, :time_grid, :times, + :initial_time_name, :final_time_name, + :criterion, :has_mayer_cost, :has_lagrange_cost, + :is_mayer_cost_defined, :is_lagrange_cost_defined, + :has_fixed_initial_time, :has_free_initial_time, + :has_fixed_final_time, :has_free_final_time, + :is_autonomous, + :is_initial_time_fixed, :is_initial_time_free, + :is_final_time_fixed, :is_final_time_free, + :state_dimension, :control_dimension, :variable_dimension, + :state_name, :control_name, :variable_name, + :state_components, :control_components, :variable_components, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + + Test.@testset "Constraint Accessors" begin + for f in ( + :path_constraints_nl, :boundary_constraints_nl, + :state_constraints_box, :control_constraints_box, :variable_constraints_box, + :dim_path_constraints_nl, :dim_boundary_constraints_nl, + :dim_state_constraints_box, :dim_control_constraints_box, + :dim_variable_constraints_box, + :state, :control, :variable, :costate, :objective, + :dynamics, :mayer, :lagrange, + :definition, :dual, + :iterations, :status, :message, :success, :successful, + :constraints_violation, :infos, + :get_build_examodel, + :is_empty, :is_empty_time_grid, + :index, :time, + :model, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + + Test.@testset "Dual Constraints Accessors" begin + for f in ( + :path_constraints_dual, :boundary_constraints_dual, + :state_constraints_lb_dual, :state_constraints_ub_dual, + :control_constraints_lb_dual, :control_constraints_ub_dual, + :variable_constraints_lb_dual, :variable_constraints_ub_dual, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctmodels() = TestCtmodels.test_ctmodels() \ No newline at end of file diff --git a/test/suite/reexport/test_ctparser.jl b/test/suite/reexport/test_ctparser.jl new file mode 100644 index 000000000..b5ea8d4d3 --- /dev/null +++ b/test/suite/reexport/test_ctparser.jl @@ -0,0 +1,32 @@ +# ============================================================================ +# CTParser Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTParser`. It verifies that +# the `@def` macro and related parsing utilities are properly exported by +# `OptimalControl` for user-friendly problem definition. + +module TestCtparser + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtparser + +function test_ctparser() + Test.@testset "CTParser reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "Macros" begin + Test.@test isdefined(OptimalControl, Symbol("@def")) + Test.@test isdefined(CurrentModule, Symbol("@def")) + Test.@test isdefined(OptimalControl, Symbol("@init")) + Test.@test isdefined(CurrentModule, Symbol("@init")) + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctparser() = TestCtparser.test_ctparser() \ No newline at end of file diff --git a/test/suite/reexport/test_ctsolvers.jl b/test/suite/reexport/test_ctsolvers.jl new file mode 100644 index 000000000..561d0f943 --- /dev/null +++ b/test/suite/reexport/test_ctsolvers.jl @@ -0,0 +1,133 @@ +# ============================================================================ +# CTSolvers Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `CTSolvers`. It verifies that +# the strategy builders, solver types, options, and utilities like `route_to` +# and `bypass` are properly exported by `OptimalControl`. + +module TestCtsolvers + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestCtsolvers + +function test_ctsolvers() + Test.@testset "CTSolvers reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "DOCP Types" begin + for T in ( + OptimalControl.DiscretizedModel, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "DOCP Functions" begin + for f in ( + :ocp_model, + :nlp_model, + :ocp_solution, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + Test.@testset "Modeler Types" begin + for T in ( + OptimalControl.AbstractNLPModeler, + OptimalControl.ADNLP, + OptimalControl.Exa, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "Solver Types" begin + for T in ( + OptimalControl.AbstractNLPSolver, + OptimalControl.Ipopt, + OptimalControl.MadNLP, + OptimalControl.MadNCL, + OptimalControl.Knitro, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "Strategy Types" begin + for T in ( + OptimalControl.AbstractStrategy, + OptimalControl.StrategyRegistry, + OptimalControl.StrategyMetadata, + OptimalControl.StrategyOptions, + OptimalControl.OptionDefinition, + OptimalControl.OptionValue, + OptimalControl.RoutedOption, + OptimalControl.BypassValue, + ) + Test.@test isdefined(OptimalControl, nameof(T)) + Test.@test !isdefined(CurrentModule, nameof(T)) + Test.@test T isa DataType || T isa UnionAll + end + end + Test.@testset "Strategy Metadata Functions" begin + for f in ( + :id, + :metadata, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + Test.@testset "Strategy Introspection Functions" begin + for f in ( + :option_names, + :option_type, + :option_description, + :option_default, + :option_defaults, + :option_value, + :option_source, + :has_option, + :is_user, + :is_default, + :is_computed, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + Test.@testset "Strategy Utility Functions" begin + for f in ( + :route_to, + :bypass, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_ctsolvers() = TestCtsolvers.test_ctsolvers() \ No newline at end of file diff --git a/test/suite/reexport/test_examodels.jl b/test/suite/reexport/test_examodels.jl new file mode 100644 index 000000000..fe0d811df --- /dev/null +++ b/test/suite/reexport/test_examodels.jl @@ -0,0 +1,31 @@ +# ============================================================================ +# ExaModels Reexports Tests +# ============================================================================ +# This file tests the reexport of symbols from `ExaModels`. It verifies that +# the expected types and functions related to the ExaModels backend are +# properly exported by `OptimalControl`. + +module TestExamodels + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestExamodels + +function test_examodels() + Test.@testset "ExaModels reexports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "Generated Code Prefix" begin + Test.@test isdefined(OptimalControl, :ExaModels) + Test.@test isdefined(CurrentModule, :ExaModels) + Test.@test ExaModels isa Module + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_examodels() = TestExamodels.test_examodels() \ No newline at end of file diff --git a/test/suite/reexport/test_optimalcontrol.jl b/test/suite/reexport/test_optimalcontrol.jl new file mode 100644 index 000000000..69c445edb --- /dev/null +++ b/test/suite/reexport/test_optimalcontrol.jl @@ -0,0 +1,37 @@ +# ============================================================================ +# OptimalControl Specific Exports Tests +# ============================================================================ +# This file tests the exports that are specific to the `OptimalControl` package +# itself, ensuring that its native API functions (like `methods`) are correctly +# exposed to the user. + +module TestOptimalControl + +import Test +using OptimalControl # using is mandatory since we test exported symbols + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +const CurrentModule = TestOptimalControl + +function test_optimalcontrol() + Test.@testset "OptimalControl exports" verbose = VERBOSE showtiming = SHOWTIMING begin + Test.@testset "Functions" begin + for f in ( + :methods, + ) + Test.@testset "$f" begin + Test.@test isdefined(OptimalControl, f) + Test.@test isdefined(CurrentModule, f) + Test.@test getfield(OptimalControl, f) isa Function + end + end + end + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_optimalcontrol() = TestOptimalControl.test_optimalcontrol() \ No newline at end of file diff --git a/test/suite/solve/test_bypass.jl b/test/suite/solve/test_bypass.jl new file mode 100644 index 000000000..802033166 --- /dev/null +++ b/test/suite/solve/test_bypass.jl @@ -0,0 +1,260 @@ +# ============================================================================ +# Bypass Mechanism Integration Tests +# ============================================================================ +# This file tests the integration of the bypass mechanism across all solve +# layers (`solve`, `solve_explicit`, `solve_descriptive`). It verifies that +# options wrapped in `bypass(val)` combined with `route_to` correctly +# skip validation and propagate down to the final Layer 3 execution. + +module TestBypassMechanism + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================ +# TOP-LEVEL MOCKS AND TYPES +# ============================================================================ + +# Mock OCP and Initial Guess +struct MockBypassOCP <: CTModels.AbstractModel end +struct MockBypassInit <: CTModels.AbstractInitialGuess end +CTModels.build_initial_guess(::MockBypassOCP, ::Nothing) = MockBypassInit() + +# Mock Strategies +struct MockBypassDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end + +CTSolvers.id(::Type{MockBypassDiscretizer}) = :collocation +CTSolvers.metadata(::Type{MockBypassDiscretizer}) = CTSolvers.StrategyMetadata( + CTSolvers.OptionDefinition(name=:grid_size, type=Int, default=100, description="Grid size"), +) +CTSolvers.options(s::MockBypassDiscretizer) = s.options + +function MockBypassDiscretizer(; kwargs...) + opts = CTSolvers.build_strategy_options(MockBypassDiscretizer; kwargs...) + return MockBypassDiscretizer(opts) +end + +struct MockBypassModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end + +CTSolvers.id(::Type{MockBypassModeler}) = :adnlp +CTSolvers.metadata(::Type{MockBypassModeler}) = CTSolvers.StrategyMetadata( + CTSolvers.OptionDefinition(name=:backend, type=Symbol, default=:dense, description="Backend"), +) +CTSolvers.options(s::MockBypassModeler) = s.options + +function MockBypassModeler(; kwargs...) + opts = CTSolvers.build_strategy_options(MockBypassModeler; kwargs...) + return MockBypassModeler(opts) +end + +struct MockBypassSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +CTSolvers.id(::Type{MockBypassSolver}) = :ipopt +CTSolvers.metadata(::Type{MockBypassSolver}) = CTSolvers.StrategyMetadata( + CTSolvers.OptionDefinition(name=:max_iter, type=Int, default=1000, description="Max iterations"), +) +CTSolvers.options(s::MockBypassSolver) = s.options + +function MockBypassSolver(; kwargs...) + opts = CTSolvers.build_strategy_options(MockBypassSolver; kwargs...) + return MockBypassSolver(opts) +end + +# Registry builder for tests +function build_bypass_mock_registry() + return CTSolvers.create_registry( + CTDirect.AbstractDiscretizer => (MockBypassDiscretizer,), + CTSolvers.AbstractNLPModeler => (MockBypassModeler,), + CTSolvers.AbstractNLPSolver => (MockBypassSolver,) + ) +end + +# Layer 3 override to intercept options +struct MockBypassSolution <: CTModels.AbstractSolution + discretizer::CTDirect.AbstractDiscretizer + modeler::CTSolvers.AbstractNLPModeler + solver::CTSolvers.AbstractNLPSolver +end + +function CommonSolve.solve( + ocp::MockBypassOCP, + init::CTModels.AbstractInitialGuess, + discretizer::CTDirect.AbstractDiscretizer, + modeler::CTSolvers.AbstractNLPModeler, + solver::CTSolvers.AbstractNLPSolver; + display::Bool +)::MockBypassSolution + return MockBypassSolution(discretizer, modeler, solver) +end + +# ============================================================================ +# TESTS +# ============================================================================ + +function test_bypass() + Test.@testset "Bypass Mechanism Tests" verbose=VERBOSE showtiming=SHOWTIMING begin + registry = build_bypass_mock_registry() + ocp = MockBypassOCP() + init = MockBypassInit() + + # ==================================================================== + # Descriptive Mode (`solve_descriptive`) + # ==================================================================== + Test.@testset "Descriptive Mode" begin + Test.@testset "Error without bypass" begin + Test.@test_throws CTBase.Exceptions.IncorrectArgument OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + unknown_opt=42 + ) + end + + Test.@testset "Success with route_to(strategy=bypass(val))" begin + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + unknown_opt=CTSolvers.route_to(ipopt=CTSolvers.bypass(42)) + ) + Test.@test sol isa MockBypassSolution + # The bypassed option should be inside the solver's options + # CTSolvers `build_strategy_options` strips the `BypassValue` + # and returns the raw value in the options. + Test.@test CTSolvers.has_option(sol.solver, :unknown_opt) + Test.@test CTSolvers.option_value(sol.solver, :unknown_opt) == 42 + end + + Test.@testset "Bypass on discretizer" begin + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + disc_custom=CTSolvers.route_to(collocation=CTSolvers.bypass(:fine)) + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.discretizer, :disc_custom) + Test.@test CTSolvers.option_value(sol.discretizer, :disc_custom) == :fine + end + + Test.@testset "Bypass on modeler" begin + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + mod_custom=CTSolvers.route_to(adnlp=CTSolvers.bypass("sparse_mode")) + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.modeler, :mod_custom) + Test.@test CTSolvers.option_value(sol.modeler, :mod_custom) == "sparse_mode" + end + + Test.@testset "Multi-bypass: two strategies simultaneously" begin + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + shared_opt=CTSolvers.route_to( + ipopt=CTSolvers.bypass(100), + adnlp=CTSolvers.bypass(:dense) + ) + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.solver, :shared_opt) + Test.@test CTSolvers.option_value(sol.solver, :shared_opt) == 100 + Test.@test CTSolvers.has_option(sol.modeler, :shared_opt) + Test.@test CTSolvers.option_value(sol.modeler, :shared_opt) == :dense + end + + Test.@testset "Bypass with nothing value" begin + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry, + nullable_opt=CTSolvers.route_to(ipopt=CTSolvers.bypass(nothing)) + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.solver, :nullable_opt) + Test.@test isnothing(CTSolvers.option_value(sol.solver, :nullable_opt)) + end + end + + # ==================================================================== + # Explicit Mode (`solve_explicit`) + # ==================================================================== + Test.@testset "Explicit Mode" begin + Test.@testset "Success with manually bypassed option" begin + solver = MockBypassSolver(unknown_opt=CTSolvers.bypass("passed")) + sol = OptimalControl.solve_explicit( + ocp; + initial_guess=init, + display=false, + registry=registry, + discretizer=MockBypassDiscretizer(), + modeler=MockBypassModeler(), + solver=solver + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.solver, :unknown_opt) + Test.@test CTSolvers.option_value(sol.solver, :unknown_opt) == "passed" + end + end + + # ==================================================================== + # Top-level Dispatch (`solve`) + # ==================================================================== + Test.@testset "Top-level Dispatch" begin + Test.@testset "Descriptive via solve" begin + sol = OptimalControl.solve( + ocp, :collocation, :adnlp, :ipopt; + display=false, + registry=registry, + custom_backend_opt=CTSolvers.route_to(ipopt=CTSolvers.bypass(99)) + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.solver, :custom_backend_opt) + Test.@test CTSolvers.option_value(sol.solver, :custom_backend_opt) == 99 + end + + Test.@testset "Explicit via solve" begin + solver = MockBypassSolver(custom_backend_opt=CTSolvers.bypass(99)) + sol = OptimalControl.solve( + ocp; + display=false, + registry=registry, + discretizer=MockBypassDiscretizer(), + modeler=MockBypassModeler(), + solver=solver + ) + Test.@test sol isa MockBypassSolution + Test.@test CTSolvers.has_option(sol.solver, :custom_backend_opt) + Test.@test CTSolvers.option_value(sol.solver, :custom_backend_opt) == 99 + end + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_bypass() = TestBypassMechanism.test_bypass() diff --git a/test/suite/solve/test_canonical.jl b/test/suite/solve/test_canonical.jl new file mode 100644 index 000000000..9af19c983 --- /dev/null +++ b/test/suite/solve/test_canonical.jl @@ -0,0 +1,216 @@ +# ============================================================================ +# Canonical Solve Tests (Layer 3) +# ============================================================================ +# This file tests the lowest level of the solve pipeline (Layer 3). It verifies +# that the canonical `solve` function correctly executes the resolution when +# provided with fully concrete, instantiated strategy components (discretizer, +# modeler, solver) and real optimal control problems. + +module TestCanonical + +import Test +import OptimalControl + +# Import du module d'affichage (DIP - dépend de l'abstraction) +include(joinpath(@__DIR__, "..", "..", "helpers", "print_utils.jl")) +using .TestPrintUtils + +# Load solver extensions (import only to trigger extensions, avoid name conflicts) +import NLPModelsIpopt +import MadNLP +import MadNLPMumps +import MadNLPGPU +import MadNCL +import CUDA + +# Include shared test problems via TestProblems module +include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) +using .TestProblems + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# Objective tolerance for comparison with reference values +const OBJ_RTOL = 1e-2 + +# CUDA availability check +is_cuda_on() = CUDA.functional() + +function test_canonical() + Test.@testset "Canonical solve" verbose = VERBOSE showtiming = SHOWTIMING begin + + # Initialize statistics + total_tests = 0 + passed_tests = 0 + total_start_time = time() + + # Print header with column names + if VERBOSE + print_test_header(false) # show_memory = false par défaut + end + + # ---------------------------------------------------------------- + # Define strategies + # ---------------------------------------------------------------- + discretizers = [ + ("Collocation/midpoint", OptimalControl.Collocation(grid_size=100, scheme=:midpoint)), + ("Collocation/trapeze", OptimalControl.Collocation(grid_size=100, scheme=:trapeze)), + ] + + modelers = [ + ("ADNLP", OptimalControl.ADNLP()), + ("Exa", OptimalControl.Exa()), + ] + + solvers = [ + ("Ipopt", OptimalControl.Ipopt(print_level=0)), + ("MadNLP", OptimalControl.MadNLP(print_level=MadNLP.ERROR)), + ("MadNCL", OptimalControl.MadNCL(print_level=MadNLP.ERROR)), + ] + + problems = [ + ("Beam", Beam()), + ("Goddard", Goddard()), + ] + + # ---------------------------------------------------------------- + # Test all combinations + # ---------------------------------------------------------------- + for (pname, pb) in problems + Test.@testset "$pname" begin + for (dname, disc) in discretizers + for (mname, mod) in modelers + for (sname, sol) in solvers + # Extract short names for display + d_short = String(split(dname, "/")[2]) # Get "midpoint" or "trapeze" + + # Normalize initial guess before calling canonical solve (Layer 3) + normalized_init = OptimalControl.build_initial_guess(pb.ocp, pb.init) + + # Execute with timing (DRY - single measurement) + timed_result = @timed begin + OptimalControl.solve(pb.ocp, normalized_init, disc, mod, sol; + display=false) + end + + # Extract results + solve_result = timed_result.value + solve_time = timed_result.time + memory_bytes = timed_result.bytes + + success = OptimalControl.successful(solve_result) + obj = success ? OptimalControl.objective(solve_result) : 0.0 + + # Extract iterations using CTModels function + iters = OptimalControl.iterations(solve_result) + + # Display table line (SRP - responsibility delegated) + if VERBOSE + print_test_line( + "CPU", pname, d_short, mname, sname, + success, solve_time, obj, pb.obj, + iters, + memory_bytes > 0 ? memory_bytes : nothing, + false # show_memory = false + ) + end + + # Update statistics + total_tests += 1 + if success + passed_tests += 1 + end + + # Run the actual test assertions + Test.@testset "$dname / $mname / $sname" begin + Test.@test success + if success + Test.@test solve_result isa OptimalControl.AbstractSolution + Test.@test OptimalControl.objective(solve_result) ≈ pb.obj rtol = OBJ_RTOL + end + end + end + end + end + end + end + + # ---------------------------------------------------------------- + # GPU tests (only if CUDA is available) + # ---------------------------------------------------------------- + if is_cuda_on() + gpu_modeler = ("Exa/GPU", OptimalControl.Exa(backend=CUDA.CUDABackend())) + gpu_solver = ("MadNLP/GPU", OptimalControl.MadNLP(print_level=MadNLP.ERROR, linear_solver=MadNLPGPU.CUDSSSolver)) + + for (pname, pb) in problems + Test.@testset "GPU / $pname" begin + for (dname, disc) in discretizers + # Extract short names for display + d_short = String(split(dname, "/")[2]) # Get "midpoint" or "trapeze" + + # Execute with timing (same structure as CPU tests - DRY) + # Normalize initial guess before calling canonical solve (Layer 3) + normalized_init = OptimalControl.build_initial_guess(pb.ocp, pb.init) + + timed_result = @timed begin + OptimalControl.solve(pb.ocp, normalized_init, disc, gpu_modeler[2], gpu_solver[2]; + display=false) + end + + # Extract results + solve_result = timed_result.value + solve_time = timed_result.time + memory_bytes = timed_result.bytes + + success = OptimalControl.successful(solve_result) + obj = success ? OptimalControl.objective(solve_result) : 0.0 + + # Extract iterations using CTModels function + iters = OptimalControl.iterations(solve_result) + + # Display table line (SRP - responsibility delegated) + if VERBOSE + print_test_line( + "GPU", pname, d_short, "Exa", "MadNLP", + success, solve_time, obj, pb.obj, + iters, + memory_bytes > 0 ? memory_bytes : nothing, + false # show_memory = false + ) + end + + # Update statistics + total_tests += 1 + if success + passed_tests += 1 + end + + # Run the actual test assertions + Test.@testset "$dname / $(gpu_modeler[1]) / $(gpu_solver[1])" begin + Test.@test success + if success + Test.@test solve_result isa OptimalControl.AbstractSolution + Test.@test OptimalControl.objective(solve_result) ≈ pb.obj rtol = OBJ_RTOL + end + end + end + end + end + else + println("") + @info "CUDA not functional, skipping GPU tests." + end + + # Print summary (SRP - responsibility delegated) + if VERBOSE + total_time = time() - total_start_time + print_summary(total_tests, passed_tests, total_time) + end + + end +end + +end # module + +# Redefine in outer scope for TestRunner +test_canonical() = TestCanonical.test_canonical() \ No newline at end of file diff --git a/test/suite/solve/test_descriptive_routing.jl b/test/suite/solve/test_descriptive_routing.jl new file mode 100644 index 000000000..1c9df4a74 --- /dev/null +++ b/test/suite/solve/test_descriptive_routing.jl @@ -0,0 +1,401 @@ +# ============================================================================ +# Descriptive Routing Helper Tests +# ============================================================================ +# This file contains unit tests for the descriptive mode routing helpers +# (e.g., `_route_descriptive_options`, `_build_components_from_routed`). +# It uses parametric mock strategies to isolate and verify the routing and +# instantiation logic without relying on heavy solver backends. + +module TestDescriptiveRouting + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================ +# TOP-LEVEL: Mock strategy types for routing tests +# +# We define minimal mock strategies with known option metadata so we can test +# routing behaviour without depending on real backend implementations. +# ============================================================================ + +# --- Abstract families (isolated from real CTDirect/CTSolvers families) --- + +abstract type RoutingMockDiscretizer <: CTDirect.AbstractDiscretizer end +abstract type RoutingMockModeler <: CTSolvers.AbstractNLPModeler end +abstract type RoutingMockSolver <: CTSolvers.AbstractNLPSolver end + +# --- Concrete mock: Collocation-like discretizer --- + +struct MockCollocation <: RoutingMockDiscretizer + options::CTSolvers.StrategyOptions +end + +CTSolvers.Strategies.id(::Type{MockCollocation}) = :collocation +CTSolvers.Strategies.metadata(::Type{MockCollocation}) = CTSolvers.Strategies.StrategyMetadata( + CTSolvers.Options.OptionDefinition( + name = :grid_size, + type = Int, + default = 100, + description = "Number of grid points", + ), +) +CTSolvers.Strategies.options(s::MockCollocation) = s.options + +function MockCollocation(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockCollocation; mode=mode, kwargs...) + return MockCollocation(opts) +end + +# --- Concrete mock: ADNLP-like modeler (with ambiguous :backend option) --- + +struct MockADNLP <: RoutingMockModeler + options::CTSolvers.StrategyOptions +end + +CTSolvers.Strategies.id(::Type{MockADNLP}) = :adnlp +CTSolvers.Strategies.metadata(::Type{MockADNLP}) = CTSolvers.Strategies.StrategyMetadata( + CTSolvers.Options.OptionDefinition( + name = :backend, + type = Symbol, + default = :dense, + description = "NLP backend", + aliases = (:adnlp_backend,), + ), +) +CTSolvers.Strategies.options(s::MockADNLP) = s.options + +function MockADNLP(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockADNLP; mode=mode, kwargs...) + return MockADNLP(opts) +end + +# --- Concrete mock: Ipopt-like solver (with ambiguous :backend + :max_iter) --- + +struct MockIpopt <: RoutingMockSolver + options::CTSolvers.StrategyOptions +end + +CTSolvers.Strategies.id(::Type{MockIpopt}) = :ipopt +CTSolvers.Strategies.metadata(::Type{MockIpopt}) = CTSolvers.Strategies.StrategyMetadata( + CTSolvers.Options.OptionDefinition( + name = :max_iter, + type = Int, + default = 1000, + description = "Maximum iterations", + ), + CTSolvers.Options.OptionDefinition( + name = :backend, + type = Symbol, + default = :cpu, + description = "Solver backend", + aliases = (:ipopt_backend,), + ), +) +CTSolvers.Strategies.options(s::MockIpopt) = s.options + +function MockIpopt(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockIpopt; mode=mode, kwargs...) + return MockIpopt(opts) +end + +# --- Registry and method --- + +const MOCK_REGISTRY = CTSolvers.create_registry( + CTDirect.AbstractDiscretizer => (MockCollocation,), + CTSolvers.AbstractNLPModeler => (MockADNLP,), + CTSolvers.AbstractNLPSolver => (MockIpopt,), +) + +const MOCK_METHOD = (:collocation, :adnlp, :ipopt) + +# ============================================================================ +# TOP-LEVEL: Integration test mock types (Layer 3 short-circuit) +# ============================================================================ + +struct MockOCP2 <: CTModels.AbstractModel end +struct MockInit2 <: CTModels.AbstractInitialGuess end +CTModels.build_initial_guess(::MockOCP2, ::Nothing) = MockInit2() +CTModels.build_initial_guess(::MockOCP2, init::MockInit2) = init +struct MockSolution2 <: CTModels.AbstractSolution + discretizer + modeler + solver +end + +CommonSolve.solve( + ::MockOCP2, ::CTModels.AbstractInitialGuess, + d::RoutingMockDiscretizer, m::RoutingMockModeler, s::RoutingMockSolver; + display::Bool +)::MockSolution2 = MockSolution2(d, m, s) + +# ============================================================================ +# Test function +# ============================================================================ + +function test_descriptive_routing() + Test.@testset "Descriptive Routing Helpers" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS — _descriptive_families + # ==================================================================== + + Test.@testset "_descriptive_families" begin + fam = OptimalControl._descriptive_families() + + Test.@test fam isa NamedTuple + Test.@test haskey(fam, :discretizer) + Test.@test haskey(fam, :modeler) + Test.@test haskey(fam, :solver) + Test.@test fam.discretizer === CTDirect.AbstractDiscretizer + Test.@test fam.modeler === CTSolvers.AbstractNLPModeler + Test.@test fam.solver === CTSolvers.AbstractNLPSolver + end + + # ==================================================================== + # UNIT TESTS — _descriptive_action_defs + # ==================================================================== + + Test.@testset "_descriptive_action_defs" begin + defs = OptimalControl._descriptive_action_defs() + + Test.@test defs isa Vector{CTSolvers.Options.OptionDefinition} + Test.@test length(defs) == 2 + Test.@test defs[1].name == :initial_guess + Test.@test defs[1].aliases == OptimalControl._INITIAL_GUESS_ALIASES_ONLY + Test.@test defs[2].name == :display + end + + # ==================================================================== + # UNIT TESTS — _route_descriptive_options + # ==================================================================== + + Test.@testset "_route_descriptive_options - empty kwargs" begin + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, pairs(NamedTuple()) + ) + + Test.@test haskey(routed, :action) + Test.@test haskey(routed, :strategies) + Test.@test isempty(routed.strategies.discretizer) + Test.@test isempty(routed.strategies.modeler) + Test.@test isempty(routed.strategies.solver) + end + + Test.@testset "_route_descriptive_options - unambiguous auto-routing" begin + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; grid_size=200, max_iter=500)) + ) + + Test.@test routed.strategies.discretizer[:grid_size] == 200 + Test.@test routed.strategies.solver[:max_iter] == 500 + Test.@test isempty(routed.strategies.modeler) + end + + Test.@testset "_route_descriptive_options - single strategy disambiguation" begin + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; backend=CTSolvers.route_to(adnlp=:sparse))) + ) + + Test.@test routed.strategies.modeler[:backend] === :sparse + Test.@test !haskey(routed.strategies.solver, :backend) + end + + Test.@testset "_route_descriptive_options - multi-strategy disambiguation" begin + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; backend=CTSolvers.route_to(adnlp=:sparse, ipopt=:gpu))) + ) + + Test.@test routed.strategies.modeler[:backend] === :sparse + Test.@test routed.strategies.solver[:backend] === :gpu + end + + Test.@testset "_route_descriptive_options - alias auto-routing" begin + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; adnlp_backend=:sparse)) + ) + + Test.@test routed.strategies.modeler[:adnlp_backend] === :sparse + Test.@test !haskey(routed.strategies.solver, :adnlp_backend) + end + + Test.@testset "_route_descriptive_options - error on unknown option" begin + Test.@test_throws CTBase.IncorrectArgument OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; totally_unknown=42)) + ) + end + + Test.@testset "_route_descriptive_options - error on ambiguous option" begin + Test.@test_throws CTBase.IncorrectArgument OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; backend=:sparse)) + ) + end + + # ==================================================================== + # UNIT TESTS — _build_components_from_routed + # ==================================================================== + + Test.@testset "_build_components_from_routed - default options" begin + ocp = MockOCP2() + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, pairs(NamedTuple()) + ) + components = OptimalControl._build_components_from_routed( + ocp, MOCK_METHOD, MOCK_REGISTRY, routed + ) + + Test.@test components.discretizer isa RoutingMockDiscretizer + Test.@test components.modeler isa RoutingMockModeler + Test.@test components.solver isa RoutingMockSolver + Test.@test components.discretizer isa MockCollocation + Test.@test components.modeler isa MockADNLP + Test.@test components.solver isa MockIpopt + Test.@test components.initial_guess isa MockInit2 + Test.@test components.display == true + end + + Test.@testset "_build_components_from_routed - options passed through" begin + ocp = MockOCP2() + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; grid_size=42, max_iter=7)) + ) + components = OptimalControl._build_components_from_routed( + ocp, MOCK_METHOD, MOCK_REGISTRY, routed + ) + + Test.@test CTSolvers.option_value(components.discretizer, :grid_size) == 42 + Test.@test CTSolvers.option_value(components.solver, :max_iter) == 7 + end + + Test.@testset "_build_components_from_routed - disambiguation passed through" begin + ocp = MockOCP2() + routed = OptimalControl._route_descriptive_options( + MOCK_METHOD, MOCK_REGISTRY, + pairs((; backend=CTSolvers.route_to(adnlp=:sparse, ipopt=:gpu))) + ) + components = OptimalControl._build_components_from_routed( + ocp, MOCK_METHOD, MOCK_REGISTRY, routed + ) + + Test.@test CTSolvers.option_value(components.modeler, :backend) === :sparse + Test.@test CTSolvers.option_value(components.solver, :backend) === :gpu + end + + # ==================================================================== + # INTEGRATION TESTS — solve_descriptive end-to-end with mocks + # ==================================================================== + + Test.@testset "solve_descriptive - complete description, no options" begin + ocp = MockOCP2() + + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + display = false, + registry = MOCK_REGISTRY, + ) + + Test.@test sol isa MockSolution2 + Test.@test sol.discretizer isa MockCollocation + Test.@test sol.modeler isa MockADNLP + Test.@test sol.solver isa MockIpopt + end + + Test.@testset "solve_descriptive - partial description completed" begin + ocp = MockOCP2() + + sol = OptimalControl.solve_descriptive( + ocp, :collocation; + display = false, + registry = MOCK_REGISTRY, + ) + + Test.@test sol isa MockSolution2 + Test.@test sol.discretizer isa MockCollocation + Test.@test sol.modeler isa MockADNLP + Test.@test sol.solver isa MockIpopt + end + + Test.@testset "solve_descriptive - options routed correctly" begin + ocp = MockOCP2() + + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + display = false, + registry = MOCK_REGISTRY, + grid_size = 42, + max_iter = 7, + ) + + Test.@test CTSolvers.option_value(sol.discretizer, :grid_size) == 42 + Test.@test CTSolvers.option_value(sol.solver, :max_iter) == 7 + end + + Test.@testset "solve_descriptive - disambiguation via route_to" begin + ocp = MockOCP2() + + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + display = false, + registry = MOCK_REGISTRY, + backend = CTSolvers.route_to(adnlp=:sparse, ipopt=:gpu), + ) + + Test.@test CTSolvers.option_value(sol.modeler, :backend) === :sparse + Test.@test CTSolvers.option_value(sol.solver, :backend) === :gpu + end + + Test.@testset "solve_descriptive - error on unknown option" begin + ocp = MockOCP2() + + Test.@test_throws CTBase.IncorrectArgument OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + display = false, + registry = MOCK_REGISTRY, + bad_option = 99, + ) + end + + Test.@testset "solve_descriptive - error on ambiguous option" begin + ocp = MockOCP2() + + Test.@test_throws CTBase.IncorrectArgument OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + display = false, + registry = MOCK_REGISTRY, + backend = :sparse, + ) + end + + Test.@testset "solve_descriptive - initial_guess alias 'init'" begin + ocp = MockOCP2() + init = MockInit2() + + sol = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + init = init, + display = false, + registry = MOCK_REGISTRY, + ) + Test.@test sol isa MockSolution2 + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_descriptive_routing() = TestDescriptiveRouting.test_descriptive_routing() diff --git a/test/suite/solve/test_dispatch.jl b/test/suite/solve/test_dispatch.jl new file mode 100644 index 000000000..61901c94e --- /dev/null +++ b/test/suite/solve/test_dispatch.jl @@ -0,0 +1,191 @@ +# ============================================================================ +# Solve Dispatch Integration Tests +# ============================================================================ +# This file tests the main `solve` entry point (Layer 1) integration. It verifies +# that the initial guess is properly normalized at the top level and that the +# execution successfully flows through the correct sub-method (explicit or +# descriptive) based on the user's input arguments. + +module TestSolveDispatch + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================ +# TOP-LEVEL: Mock types for contract testing +# ============================================================================ + +struct MockOCP <: CTModels.AbstractModel end +struct MockInit <: CTModels.AbstractInitialGuess end +struct MockSolution <: CTModels.AbstractSolution end +CTModels.build_initial_guess(::MockOCP, ::Nothing) = MockInit() +CTModels.build_initial_guess(::MockOCP, i::MockInit) = i + +struct MockDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end +CTSolvers.Strategies.id(::Type{<:MockDiscretizer}) = :collocation +CTSolvers.Strategies.metadata(::Type{<:MockDiscretizer}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.options(d::MockDiscretizer) = d.options +function MockDiscretizer(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockDiscretizer; mode=mode, kwargs...) + return MockDiscretizer(opts) +end + +struct MockModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end +CTSolvers.Strategies.id(::Type{<:MockModeler}) = :adnlp +CTSolvers.Strategies.metadata(::Type{<:MockModeler}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.options(m::MockModeler) = m.options +function MockModeler(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockModeler; mode=mode, kwargs...) + return MockModeler(opts) +end + +struct MockSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end +CTSolvers.Strategies.id(::Type{<:MockSolver}) = :ipopt +CTSolvers.Strategies.metadata(::Type{<:MockSolver}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.options(s::MockSolver) = s.options +function MockSolver(; mode::Symbol=:strict, kwargs...) + opts = CTSolvers.Strategies.build_strategy_options(MockSolver; mode=mode, kwargs...) + return MockSolver(opts) +end + +# Mock registry: maps mock types so _complete_components builds mocks, not real solvers +function mock_strategy_registry()::CTSolvers.StrategyRegistry + return CTSolvers.create_registry( + CTDirect.AbstractDiscretizer => (MockDiscretizer,), + CTSolvers.AbstractNLPModeler => (MockModeler,), + CTSolvers.AbstractNLPSolver => (MockSolver,) + ) +end + +# Override Layer 3 solve for mocks — returns MockSolution immediately (explicit mode) +function CommonSolve.solve( + ::MockOCP, ::MockInit, + ::MockDiscretizer, ::MockModeler, ::MockSolver; + display::Bool +)::MockSolution + return MockSolution() +end + +# Override Layer 3 for descriptive mode: solve_descriptive builds real mock types via registry +# MockDiscretizer <: AbstractDiscretizer, so this catches those calls too +function CommonSolve.solve( + ::MockOCP, ::CTModels.AbstractInitialGuess, + ::CTDirect.AbstractDiscretizer, ::CTSolvers.AbstractNLPModeler, ::CTSolvers.AbstractNLPSolver; + display::Bool +)::MockSolution + return MockSolution() +end + +function test_solve_dispatch() + Test.@testset "Solve Dispatch" verbose=VERBOSE showtiming=SHOWTIMING begin + + ocp = MockOCP() + init = MockInit() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + registry = mock_strategy_registry() + + # ==================================================================== + # CONTRACT TESTS - solve_explicit: complete components (mock Layer 3) + # ==================================================================== + + Test.@testset "solve_explicit - all three components" begin + result = OptimalControl.solve_explicit( + ocp; + initial_guess=init, + display=false, + registry=registry, + discretizer=disc, modeler=mod, solver=sol + ) + Test.@test result isa MockSolution + end + + Test.@testset "solve_explicit - alias 'init'" begin + result = OptimalControl.solve_explicit( + ocp; + init=init, + display=false, + registry=registry, + discretizer=disc, modeler=mod, solver=sol + ) + Test.@test result isa MockSolution + end + + Test.@testset "solve_explicit - partial components (mock registry completes)" begin + result = OptimalControl.solve_explicit( + ocp; + initial_guess=init, + display=false, + registry=registry, + discretizer=disc, modeler=nothing, solver=nothing + ) + Test.@test result isa MockSolution + end + + # ==================================================================== + # CONTRACT TESTS - solve_descriptive: dispatches correctly + # ==================================================================== + + Test.@testset "solve_descriptive - complete description dispatches" begin + result = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + initial_guess=init, + display=false, + registry=registry + ) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - alias 'init'" begin + result = OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + init=init, + display=false, + registry=registry + ) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - alias 'i' (removed)" begin + # :i is no longer recognized as an alias for initial_guess + Test.@test_throws CTBase.IncorrectArgument begin + OptimalControl.solve_descriptive( + ocp, :collocation, :adnlp, :ipopt; + i=init, + display=false, + registry=registry + ) + end + end + + Test.@testset "solve_descriptive - empty description dispatches" begin + result = OptimalControl.solve_descriptive( + ocp; + initial_guess=init, + display=false, + registry=registry + ) + Test.@test result isa MockSolution + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_dispatch() = TestSolveDispatch.test_solve_dispatch() diff --git a/test/suite/solve/test_dispatch_logic.jl b/test/suite/solve/test_dispatch_logic.jl new file mode 100644 index 000000000..e2b63c5af --- /dev/null +++ b/test/suite/solve/test_dispatch_logic.jl @@ -0,0 +1,263 @@ +# ============================================================================ +# Solve Dispatch Logic Tests +# ============================================================================ +# This file contains unit tests for the top-level `solve` dispatch mechanism. +# It uses a dynamically generated mock registry to verify that the entry point +# correctly analyzes arguments and routes the call to either `solve_explicit` +# or `solve_descriptive`, ensuring the dispatch logic is robust and isolated. + +module TestDispatchLogic + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================ +# TOP-LEVEL: Parametric Mock types +# ============================================================================ + +struct MockOCP <: CTModels.AbstractModel end +struct MockInit <: CTModels.AbstractInitialGuess end +struct MockSolution <: CTModels.AbstractSolution + components::Tuple +end + +# Parametric mocks to simulate ANY strategy ID found in methods.jl +struct MockDiscretizer{ID} <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end + +struct MockModeler{ID} <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end + +struct MockSolver{ID} <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +# ---------------------------------------------------------------------------- +# Strategies Interface Implementation +# ---------------------------------------------------------------------------- + +# ID accessors +CTSolvers.Strategies.id(::Type{MockDiscretizer{ID}}) where {ID} = ID +CTSolvers.Strategies.id(::Type{MockModeler{ID}}) where {ID} = ID +CTSolvers.Strategies.id(::Type{MockSolver{ID}}) where {ID} = ID + +# Metadata (required by registry) +CTSolvers.Strategies.metadata(::Type{<:MockDiscretizer}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.metadata(::Type{<:MockModeler}) = CTSolvers.Strategies.StrategyMetadata() +CTSolvers.Strategies.metadata(::Type{<:MockSolver}) = CTSolvers.Strategies.StrategyMetadata() + +# Options accessors +CTSolvers.Strategies.options(d::MockDiscretizer) = d.options +CTSolvers.Strategies.options(m::MockModeler) = m.options +CTSolvers.Strategies.options(s::MockSolver) = s.options + +# Constructors (required by _build_or_use_strategy) +function MockDiscretizer{ID}(; mode::Symbol=:strict, kwargs...) where {ID} + opts = CTSolvers.Strategies.build_strategy_options(MockDiscretizer{ID}; mode=mode, kwargs...) + return MockDiscretizer{ID}(opts) +end + +function MockModeler{ID}(; mode::Symbol=:strict, kwargs...) where {ID} + opts = CTSolvers.Strategies.build_strategy_options(MockModeler{ID}; mode=mode, kwargs...) + return MockModeler{ID}(opts) +end + +function MockSolver{ID}(; mode::Symbol=:strict, kwargs...) where {ID} + opts = CTSolvers.Strategies.build_strategy_options(MockSolver{ID}; mode=mode, kwargs...) + return MockSolver{ID}(opts) +end + +# ---------------------------------------------------------------------------- +# Mock Registry Builder +# ---------------------------------------------------------------------------- + +function build_mock_registry_from_methods()::CTSolvers.StrategyRegistry + # 1. Get all valid triplets from methods() + # e.g. ((:collocation, :adnlp, :ipopt), ...) + valid_methods = OptimalControl.methods() + + # 2. Extract unique symbols for each category + disc_ids = unique(m[1] for m in valid_methods) + mod_ids = unique(m[2] for m in valid_methods) + sol_ids = unique(m[3] for m in valid_methods) + + # 3. Create tuple of Mock types for each ID + # We need to map AbstractType => (MockType{ID1}, MockType{ID2}, ...) + disc_types = Tuple(MockDiscretizer{id} for id in disc_ids) + mod_types = Tuple(MockModeler{id} for id in mod_ids) + sol_types = Tuple(MockSolver{id} for id in sol_ids) + + # 4. Create registry + return CTSolvers.create_registry( + CTDirect.AbstractDiscretizer => disc_types, + CTSolvers.AbstractNLPModeler => mod_types, + CTSolvers.AbstractNLPSolver => sol_types + ) +end + +# ---------------------------------------------------------------------------- +# Layer 3 Overrides (Mock Resolution) +# ---------------------------------------------------------------------------- + +# Override CommonSolve.solve (Explicit Mode final step) +# This intercepts the call after components have been completed/instantiated. +function CommonSolve.solve( + ::MockOCP, ::MockInit, + d::MockDiscretizer, m::MockModeler, s::MockSolver; + display::Bool +)::MockSolution + return MockSolution((d, m, s)) +end + +# Override OptimalControl.solve_descriptive (Descriptive Mode final step) +# This intercepts the call after mode detection. +function OptimalControl.solve_descriptive( + ocp::MockOCP, description::Symbol...; + initial_guess, display::Bool, registry::CTSolvers.StrategyRegistry, kwargs... +)::MockSolution + # For testing purposes, we return a MockSolution containing the description symbols + # and the registry itself to verify they were passed correctly. + return MockSolution((description, registry)) +end + +# ============================================================================ +# TESTS +# ============================================================================ + +function test_dispatch_logic() + Test.@testset "Dispatch Logic & Completion" verbose=VERBOSE showtiming=SHOWTIMING begin + + ocp = MockOCP() + init = MockInit() + mock_registry = build_mock_registry_from_methods() + + # Iterate over all valid methods defined in OptimalControl + # This ensures we cover every supported combination + for (d_id, m_id, s_id) in OptimalControl.methods() + + method_str = "($d_id, $m_id, $s_id)" + + # ---------------------------------------------------------------- + # TEST 1: Explicit Mode with FULL Components + # ---------------------------------------------------------------- + # Verify that we can explicitly target EVERY method supported. + + Test.@testset "Explicit Full: $method_str" begin + + d_instance = MockDiscretizer{d_id}(CTSolvers.StrategyOptions()) + m_instance = MockModeler{m_id}(CTSolvers.StrategyOptions()) + s_instance = MockSolver{s_id}(CTSolvers.StrategyOptions()) + + sol = OptimalControl.solve( + ocp; + initial_guess=init, + display=false, + registry=mock_registry, + discretizer=d_instance, + modeler=m_instance, + solver=s_instance + ) + + Test.@test sol isa MockSolution + (d_res, m_res, s_res) = sol.components + + Test.@test d_res isa MockDiscretizer{d_id} + Test.@test m_res isa MockModeler{m_id} + Test.@test s_res isa MockSolver{s_id} + end + + # ---------------------------------------------------------------- + # TEST 2: Descriptive Mode + # ---------------------------------------------------------------- + # We pass symbols (:collocation, :adnlp, :ipopt) + # Should dispatch to solve_descriptive with these symbols + + Test.@testset "Descriptive: $method_str" begin + + sol = OptimalControl.solve( + ocp, d_id, m_id, s_id; + initial_guess=init, + display=false, + registry=mock_registry + ) + + Test.@test sol isa MockSolution + (desc_res, reg_res) = sol.components + + # Check that description was passed correctly + Test.@test desc_res == (d_id, m_id, s_id) + + # Check that registry was passed correctly + Test.@test reg_res === mock_registry + end + end + + # ---------------------------------------------------------------- + # TEST 3: Partial Explicit (Defaults) + # ---------------------------------------------------------------- + # Verify that providing partial components triggers completion + # to a valid default (usually the first match). + + Test.@testset "Explicit Partial (Defaults)" begin + # Case: Only Discretizer(:collocation) provided + # Expectation: Defaults to :adnlp, :ipopt (based on methods order) + + d_instance = MockDiscretizer{:collocation}(CTSolvers.StrategyOptions()) + + sol = OptimalControl.solve( + ocp; + initial_guess=init, + display=false, + registry=mock_registry, + discretizer=d_instance + ) + + Test.@test sol isa MockSolution + (d_res, m_res, s_res) = sol.components + + Test.@test d_res isa MockDiscretizer{:collocation} + # Verify it filled in valid components + Test.@test m_res isa MockModeler + Test.@test s_res isa MockSolver + end + + # ---------------------------------------------------------------- + # TEST 4: Default Registry Fallback + # ---------------------------------------------------------------- + # Verify that if we don't pass `registry`, it falls back to the real one. + + Test.@testset "Default Registry Fallback" begin + sol = OptimalControl.solve( + ocp, :foo, :bar; + initial_guess=init, + display=false + ) + + (_, reg_res) = sol.components + # It should NOT be our mock registry + Test.@test reg_res !== mock_registry + + # It should look like the real registry (checking internal families) + # Real registry has CTDirect.AbstractDiscretizer, etc. + families = reg_res.families + Test.@test haskey(families, CTDirect.AbstractDiscretizer) + Test.@test haskey(families, CTSolvers.AbstractNLPModeler) + end + + end +end + +end # module + +# Entry point for TestRunner +test_dispatch_logic() = TestDispatchLogic.test_dispatch_logic() diff --git a/test/suite/solve/test_explicit.jl b/test/suite/solve/test_explicit.jl new file mode 100644 index 000000000..6eb70f840 --- /dev/null +++ b/test/suite/solve/test_explicit.jl @@ -0,0 +1,148 @@ +# ============================================================================ +# Explicit Mode Tests (Layer 2) +# ============================================================================ +# This file tests the `solve_explicit` function. It verifies that when the user +# provides instantiated strategy components (discretizer, modeler, solver) as +# keyword arguments, any missing components are correctly completed via the +# strategy registry before delegating to the canonical Layer 3 solve. + +module TestExplicit + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve + +# +import NLPModelsIpopt +import MadNLP +import MadNLPMumps +import MadNLPGPU +import MadNCL +import CUDA + +# +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ==================================================================== +# TOP-LEVEL MOCKS +# ==================================================================== + +struct MockOCP <: CTModels.AbstractModel end +struct MockInit <: CTModels.AbstractInitialGuess end +struct MockSolution <: CTModels.AbstractSolution end + +# ==================================================================== +# TEST PROBLEMS FOR INTEGRATION TESTS +# ==================================================================== + +# Include shared test problems via TestProblems module +include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) +import .TestProblems + +struct MockDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end + +struct MockModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end + +struct MockSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +CommonSolve.solve( + ::MockOCP, + ::MockInit, + ::MockDiscretizer, + ::MockModeler, + ::MockSolver; + display::Bool +)::MockSolution = MockSolution() + +function test_explicit() + Test.@testset "solve_explicit (contract tests with mocks)" verbose=VERBOSE showtiming=SHOWTIMING begin + ocp = MockOCP() + init = MockInit() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + registry = OptimalControl.get_strategy_registry() + + # ================================================================ + # COMPLETE COMPONENTS PATH + # ================================================================ + Test.@testset "Complete components -> direct path" begin + result = OptimalControl.solve_explicit( + ocp; + initial_guess=init, + discretizer=disc, + modeler=mod, + solver=sol, + display=false, + registry=registry + ) + Test.@test result isa MockSolution + end + + # ================================================================ + # INTEGRATION TESTS WITH REAL STRATEGIES + # ================================================================ + Test.@testset "Integration with real strategies" begin + registry = OptimalControl.get_strategy_registry() + + # Test with real test problems + problems = [ + ("Beam", TestProblems.Beam()), + ("Goddard", TestProblems.Goddard()), + ] + + for (pname, pb) in problems + Test.@testset "$pname" begin + # Build initial guess + init = OptimalControl.build_initial_guess(pb.ocp, pb.init) + + Test.@testset "Complete components - real strategies" begin + result = OptimalControl.solve_explicit( + pb.ocp; + initial_guess=init, + discretizer=CTDirect.Collocation(), + modeler=CTSolvers.ADNLP(), + solver=CTSolvers.Ipopt(), + display=false, + registry=registry + ) + Test.@test result isa CTModels.AbstractSolution + Test.@test OptimalControl.successful(result) + Test.@test OptimalControl.objective(result) ≈ pb.obj rtol=1e-2 + end + + Test.@testset "Partial components - completion" begin + # Test with only discretizer provided + result = OptimalControl.solve_explicit( + pb.ocp; + initial_guess=init, + discretizer=CTDirect.Collocation(), + modeler=nothing, + solver=nothing, + display=false, + registry=registry + ) + Test.@test result isa CTModels.AbstractSolution + Test.@test OptimalControl.successful(result) + end + end + end + + end + end +end + +end # module + +test_explicit() = TestExplicit.test_explicit() diff --git a/test/suite/solve/test_mode.jl b/test/suite/solve/test_mode.jl new file mode 100644 index 000000000..26f02c590 --- /dev/null +++ b/test/suite/solve/test_mode.jl @@ -0,0 +1,77 @@ +# ============================================================================ +# Mode Types and Extraction Tests +# ============================================================================ +# This file tests the basic mode sentinel types (`ExplicitMode`, `DescriptiveMode`) +# and the low-level keyword argument extraction helpers (`_extract_kwarg`, +# `_has_explicit_components`) used by the top-level dispatch system. + +module TestSolveMode + +import Test +import OptimalControl + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_solve_mode() + Test.@testset "SolveMode Types" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Type hierarchy + # ==================================================================== + + Test.@testset "Type hierarchy" begin + Test.@test OptimalControl.ExplicitMode <: OptimalControl.SolveMode + Test.@test OptimalControl.DescriptiveMode <: OptimalControl.SolveMode + Test.@test OptimalControl.SolveMode isa DataType + Test.@test isabstracttype(OptimalControl.SolveMode) + Test.@test !isabstracttype(OptimalControl.ExplicitMode) + Test.@test !isabstracttype(OptimalControl.DescriptiveMode) + end + + # ==================================================================== + # UNIT TESTS - Instantiation + # ==================================================================== + + Test.@testset "Instantiation" begin + em = OptimalControl.ExplicitMode() + dm = OptimalControl.DescriptiveMode() + Test.@test em isa OptimalControl.ExplicitMode + Test.@test em isa OptimalControl.SolveMode + Test.@test dm isa OptimalControl.DescriptiveMode + Test.@test dm isa OptimalControl.SolveMode + end + + # ==================================================================== + # UNIT TESTS - Dispatch + # ==================================================================== + + Test.@testset "Multiple dispatch" begin + # Verify dispatch works correctly on instances + function _mode_name(::OptimalControl.ExplicitMode) + return :explicit + end + function _mode_name(::OptimalControl.DescriptiveMode) + return :descriptive + end + + Test.@test _mode_name(OptimalControl.ExplicitMode()) == :explicit + Test.@test _mode_name(OptimalControl.DescriptiveMode()) == :descriptive + end + + # ==================================================================== + # UNIT TESTS - Distinctness + # ==================================================================== + + Test.@testset "Distinctness" begin + Test.@test OptimalControl.ExplicitMode != OptimalControl.DescriptiveMode + Test.@test !(OptimalControl.ExplicitMode() isa OptimalControl.DescriptiveMode) + Test.@test !(OptimalControl.DescriptiveMode() isa OptimalControl.ExplicitMode) + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_mode() = TestSolveMode.test_solve_mode() diff --git a/test/suite/solve/test_mode_detection.jl b/test/suite/solve/test_mode_detection.jl new file mode 100644 index 000000000..7fa6fb793 --- /dev/null +++ b/test/suite/solve/test_mode_detection.jl @@ -0,0 +1,167 @@ +# ============================================================================ +# Mode Detection Tests +# ============================================================================ +# This file contains unit tests for the `_explicit_or_descriptive` helper +# function. It strictly verifies the logic used to determine whether the user's +# `solve` call is in explicit or descriptive mode based on the provided arguments, +# and ensures that conflicting or mixed arguments throw the appropriate errors. + +module TestModeDetection + +import Test +import OptimalControl +import CTDirect +import CTSolvers +import CTBase + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# TOP-LEVEL: mock instances for testing (avoid external dependencies) +struct MockDiscretizer <: CTDirect.AbstractDiscretizer end +struct MockModeler <: CTSolvers.AbstractNLPModeler end +struct MockSolver <: CTSolvers.AbstractNLPSolver end + +const DISC = MockDiscretizer() +const MOD = MockModeler() +const SOL = MockSolver() + +function test_mode_detection() + Test.@testset "Mode Detection" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - ExplicitMode detection + # ==================================================================== + + Test.@testset "ExplicitMode - discretizer only" begin + kw = pairs((; discretizer=DISC)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + Test.@testset "ExplicitMode - modeler only" begin + kw = pairs((; modeler=MOD)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + Test.@testset "ExplicitMode - solver only" begin + kw = pairs((; solver=SOL)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + Test.@testset "ExplicitMode - all three components" begin + kw = pairs((; discretizer=DISC, modeler=MOD, solver=SOL)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + Test.@testset "ExplicitMode - with extra strategy kwargs" begin + kw = pairs((; discretizer=DISC, print_level=0, max_iter=100)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + # ==================================================================== + # UNIT TESTS - DescriptiveMode detection + # ==================================================================== + + Test.@testset "DescriptiveMode - empty description, no components" begin + kw = pairs(NamedTuple()) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + Test.@testset "DescriptiveMode - with description" begin + kw = pairs(NamedTuple()) + result = OptimalControl._explicit_or_descriptive((:collocation, :adnlp, :ipopt), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + Test.@testset "DescriptiveMode - with strategy-specific kwargs (no components)" begin + kw = pairs((; print_level=0, max_iter=100)) + result = OptimalControl._explicit_or_descriptive((:collocation,), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + # ==================================================================== + # UNIT TESTS - Name independence (key design property) + # ==================================================================== + + Test.@testset "Name-independent detection - component under custom key" begin + # A discretizer stored under a non-standard key name is still detected + kw = pairs((; my_disc=DISC)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.ExplicitMode + end + + Test.@testset "Non-component value named 'discretizer' is ignored" begin + # A kwarg named 'discretizer' but with wrong type is NOT detected as explicit + kw = pairs((; discretizer=:collocation)) # Symbol, not AbstractDiscretizer + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + # ==================================================================== + # UNIT TESTS - Conflict detection (error cases) + # ==================================================================== + + Test.@testset "Conflict: discretizer + description" begin + kw = pairs((; discretizer=DISC)) + Test.@test_throws CTBase.IncorrectArgument begin + OptimalControl._explicit_or_descriptive((:adnlp, :ipopt), kw) + end + end + + Test.@testset "Conflict: solver + description" begin + kw = pairs((; solver=SOL)) + Test.@test_throws CTBase.IncorrectArgument begin + OptimalControl._explicit_or_descriptive((:collocation,), kw) + end + end + + Test.@testset "Conflict: all components + description" begin + kw = pairs((; discretizer=DISC, modeler=MOD, solver=SOL)) + Test.@test_throws CTBase.IncorrectArgument begin + OptimalControl._explicit_or_descriptive((:collocation, :adnlp), kw) + end + end + + Test.@testset "Conflict: custom key component + description" begin + # Even with custom key names, mixing with description is forbidden + kw = pairs((; my_custom_disc=DISC)) + Test.@test_throws CTBase.IncorrectArgument begin + OptimalControl._explicit_or_descriptive((:trapeze,), kw) + end + end + + # ==================================================================== + # UNIT TESTS - Edge cases + # ==================================================================== + + Test.@testset "Edge case: empty kwargs with description" begin + kw = pairs(NamedTuple()) + result = OptimalControl._explicit_or_descriptive((:collocation,), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + Test.@testset "Edge case: empty kwargs, empty description" begin + kw = pairs(NamedTuple()) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + + Test.@testset "Edge case: non-component values only" begin + # Strategy options without component types should not trigger ExplicitMode + kw = pairs((; print_level=0, max_iter=100, tol=1e-6)) + result = OptimalControl._explicit_or_descriptive((), kw) + Test.@test result isa OptimalControl.DescriptiveMode + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_mode_detection() = TestModeDetection.test_mode_detection() diff --git a/test/suite/solve/test_orchestration.jl b/test/suite/solve/test_orchestration.jl new file mode 100644 index 000000000..2acd55ad7 --- /dev/null +++ b/test/suite/solve/test_orchestration.jl @@ -0,0 +1,249 @@ +# ============================================================================ +# Solve Orchestration Integration Tests +# ============================================================================ +# This file provides integration tests for the full solve orchestration pipeline. +# It verifies the interaction between Layer 1 (dispatch), Layer 2 (explicit/descriptive +# component completion and option routing), and a mocked Layer 3 to ensure the +# entire component assembly chain works correctly before execution. + +module TestOrchestration + +import Test +import OptimalControl +import CTModels +import CTDirect +import CTSolvers +import CTBase +import CommonSolve +import NLPModelsIpopt +import MadNLP + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +# ============================================================================ +# TOP-LEVEL: Mock types for contract testing (Layer 3 short-circuited) +# ============================================================================ + +struct MockOCP <: CTModels.AbstractModel end +struct MockInit <: CTModels.AbstractInitialGuess end +struct MockSolution <: CTModels.AbstractSolution end +CTModels.build_initial_guess(::MockOCP, ::Nothing) = MockInit() +CTModels.build_initial_guess(::MockOCP, i::MockInit) = i + +struct MockDiscretizer <: CTDirect.AbstractDiscretizer + options::CTSolvers.StrategyOptions +end +struct MockModeler <: CTSolvers.AbstractNLPModeler + options::CTSolvers.StrategyOptions +end +struct MockSolver <: CTSolvers.AbstractNLPSolver + options::CTSolvers.StrategyOptions +end + +# Short-circuit Layer 3 for mocks (explicit mode: typed mock components) +CommonSolve.solve( + ::MockOCP, ::MockInit, + ::MockDiscretizer, ::MockModeler, ::MockSolver; + display::Bool +)::MockSolution = MockSolution() + +# Short-circuit Layer 3 for mocks (descriptive mode: real abstract component types) +# solve_descriptive builds real CTDirect.Collocation, CTSolvers.ADNLP, etc. +# This override catches those calls for MockOCP without running a real solver. +CommonSolve.solve( + ::MockOCP, ::CTModels.AbstractInitialGuess, + ::CTDirect.AbstractDiscretizer, ::CTSolvers.AbstractNLPModeler, ::CTSolvers.AbstractNLPSolver; + display::Bool +)::MockSolution = MockSolution() + +# ============================================================================ +# TOP-LEVEL: Real test problems for integration tests +# ============================================================================ + +include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) +import .TestProblems + +function test_orchestration() + Test.@testset "Orchestration - CommonSolve.solve" verbose=VERBOSE showtiming=SHOWTIMING begin + + # ==================================================================== + # UNIT TESTS - Mode detection + # ==================================================================== + + Test.@testset "ExplicitMode detection" begin + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + kw = pairs((; discretizer=disc)) + Test.@test OptimalControl._explicit_or_descriptive((), kw) isa OptimalControl.ExplicitMode + end + + Test.@testset "DescriptiveMode detection" begin + kw = pairs(NamedTuple()) + Test.@test OptimalControl._explicit_or_descriptive((:collocation,), kw) isa OptimalControl.DescriptiveMode + end + + Test.@testset "Conflict: explicit + description raises IncorrectArgument" begin + ocp = MockOCP() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + Test.@test_throws CTBase.IncorrectArgument begin + CommonSolve.solve(ocp, :adnlp, :ipopt; discretizer=disc, display=false) + end + end + + # ==================================================================== + # CONTRACT TESTS - solve_explicit path (mocks, Layer 3 short-circuited) + # ==================================================================== + + Test.@testset "solve_explicit - complete components" begin + ocp = MockOCP() + init = MockInit() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + + result = CommonSolve.solve(ocp; + initial_guess=init, + discretizer=disc, modeler=mod, solver=sol, + display=false + ) + Test.@test result isa MockSolution + end + + # ==================================================================== + # CONTRACT TESTS - solve_descriptive path (mock Layer 3 short-circuit) + # ==================================================================== + + Test.@testset "solve_descriptive - complete description dispatches correctly" begin + ocp = MockOCP() + result = CommonSolve.solve(ocp, :collocation, :adnlp, :ipopt; + initial_guess=MockInit(), display=false) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - partial description (:collocation only)" begin + ocp = MockOCP() + result = CommonSolve.solve(ocp, :collocation; + initial_guess=MockInit(), display=false) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - empty description (full defaults)" begin + ocp = MockOCP() + result = CommonSolve.solve(ocp; + initial_guess=MockInit(), display=false) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - alias 'init'" begin + ocp = MockOCP() + result = CommonSolve.solve(ocp, :collocation, :adnlp, :ipopt; + init=MockInit(), display=false) + Test.@test result isa MockSolution + end + + Test.@testset "solve_descriptive - error on unknown option" begin + ocp = MockOCP() + Test.@test_throws CTBase.IncorrectArgument begin + CommonSolve.solve(ocp, :collocation, :adnlp, :ipopt; + initial_guess=MockInit(), display=false, + totally_unknown_option=42) + end + end + + # ==================================================================== + # UNIT TESTS - initial_guess normalization (mocks, no real solver) + # ==================================================================== + + Test.@testset "initial_guess=nothing → default MockInit" begin + ocp = MockOCP() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + result = CommonSolve.solve(ocp; + discretizer=disc, modeler=mod, solver=sol, + display=false + ) + Test.@test result isa MockSolution + end + + Test.@testset "initial_guess as AbstractInitialGuess is forwarded" begin + ocp = MockOCP() + init = MockInit() + disc = MockDiscretizer(CTSolvers.StrategyOptions()) + mod = MockModeler(CTSolvers.StrategyOptions()) + sol = MockSolver(CTSolvers.StrategyOptions()) + result = CommonSolve.solve(ocp; + initial_guess=init, + discretizer=disc, modeler=mod, solver=sol, + display=false + ) + Test.@test result isa MockSolution + end + + # ==================================================================== + # INTEGRATION TESTS - real problems, real strategies + # ==================================================================== + + Test.@testset "Integration - ExplicitMode complete components" begin + pb = TestProblems.Beam() + disc = CTDirect.Collocation(grid_size=10, scheme=:midpoint) + mod = CTSolvers.ADNLP() + sol = CTSolvers.Ipopt(print_level=0, max_iter=0) + + result = CommonSolve.solve(pb.ocp; + initial_guess=pb.init, + discretizer=disc, modeler=mod, solver=sol, + display=false + ) + Test.@test result isa CTModels.AbstractSolution + end + + Test.@testset "Integration - ExplicitMode partial components (registry completes)" begin + pb = TestProblems.Beam() + disc = CTDirect.Collocation(grid_size=10, scheme=:midpoint) + + result = CommonSolve.solve(pb.ocp; + initial_guess=pb.init, discretizer=disc, display=false + ) + Test.@test result isa CTModels.AbstractSolution + end + + Test.@testset "Integration - initial_guess as NamedTuple" begin + pb = TestProblems.Beam() + disc = CTDirect.Collocation(grid_size=10, scheme=:midpoint) + result = CommonSolve.solve(pb.ocp; + initial_guess=pb.init, discretizer=disc, display=false + ) + Test.@test result isa CTModels.AbstractSolution + end + + Test.@testset "Integration - DescriptiveMode complete description" begin + pb = TestProblems.Beam() + result = CommonSolve.solve(pb.ocp, :collocation, :adnlp, :ipopt; + initial_guess=pb.init, display=false, + grid_size=10, print_level=0, max_iter=0) + Test.@test result isa CTModels.AbstractSolution + end + + Test.@testset "Integration - DescriptiveMode partial description" begin + pb = TestProblems.Beam() + result = CommonSolve.solve(pb.ocp, :collocation; + initial_guess=pb.init, display=false, + grid_size=10, print_level=0, max_iter=0) + Test.@test result isa CTModels.AbstractSolution + end + + Test.@testset "Integration - DescriptiveMode empty description (full defaults)" begin + pb = TestProblems.Beam() + result = CommonSolve.solve(pb.ocp; + initial_guess=pb.init, display=false, + grid_size=10, print_level=0, max_iter=0) + Test.@test result isa CTModels.AbstractSolution + end + end +end + +end # module + +# CRITICAL: Redefine in outer scope for TestRunner +test_orchestration() = TestOrchestration.test_orchestration() diff --git a/test/suite/solve/test_solve_modes.jl b/test/suite/solve/test_solve_modes.jl new file mode 100644 index 000000000..9f0ea22e2 --- /dev/null +++ b/test/suite/solve/test_solve_modes.jl @@ -0,0 +1,112 @@ +# ============================================================================ +# End-to-End Solve Modes Tests +# ============================================================================ +# This file tests the high-level `solve` function in both Explicit and +# Descriptive modes using real optimal control problems. +# It verifies that the complete dispatch and routing chain works correctly +# and produces a valid solution (even if not optimal, since we limit iterations). + +module TestSolveModes + +import Test +import OptimalControl + +# Load solver extensions +import NLPModelsIpopt +import MadNLP +import MadNLPMumps + +# Include shared test problems +include(joinpath(@__DIR__, "..", "..", "problems", "TestProblems.jl")) +using .TestProblems + +const VERBOSE = isdefined(Main, :TestOptions) ? Main.TestOptions.VERBOSE : true +const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING : true + +function test_solve_modes() + Test.@testset "Solve Modes Integration" verbose = VERBOSE showtiming = SHOWTIMING begin + + # Use a simple problem for integration tests + pb = Beam() + + # ==================================================================== + # Explicit Mode Integration + # ==================================================================== + Test.@testset "Explicit Mode" begin + # 1. Instantiate concrete components + # We use max_iter=0 to just build and evaluate the problem without solving + disc = OptimalControl.Collocation(grid_size=20, scheme=:midpoint) + mod = OptimalControl.ADNLP() + sol = OptimalControl.Ipopt(print_level=0, max_iter=0) + + # 2. Call solve explicitly + sol_explicit = OptimalControl.solve( + pb.ocp; + discretizer=disc, + modeler=mod, + solver=sol, + display=false + ) + + Test.@test sol_explicit isa OptimalControl.AbstractSolution + end + + # ==================================================================== + # Descriptive Mode Integration + # ==================================================================== + Test.@testset "Descriptive Mode (Complete)" begin + # 1. Call solve with a complete description and options + sol_descriptive = OptimalControl.solve( + pb.ocp, + :collocation, :adnlp, :ipopt; + grid_size=20, + max_iter=0, # Stop immediately + print_level=0, + display=false + ) + + Test.@test sol_descriptive isa OptimalControl.AbstractSolution + end + + # ==================================================================== + # Descriptive Mode Integration (Partial) + # ==================================================================== + Test.@testset "Descriptive Mode (Partial)" begin + # 1. Call solve with a partial description (only discretizer) + # The registry should auto-complete modeler and solver + sol_partial = OptimalControl.solve( + pb.ocp, + :collocation; + grid_size=20, + max_iter=0, # Stop immediately + print_level=0, + display=false + ) + + Test.@test sol_partial isa OptimalControl.AbstractSolution + end + + # ==================================================================== + # Descriptive Mode Integration (Action Option Aliases) + # ==================================================================== + Test.@testset "Descriptive Mode (Action Option Aliases)" begin + Test.@testset "Alias 'init'" begin + sol_init = OptimalControl.solve( + pb.ocp, + :collocation; + init=pb.init, + grid_size=20, + max_iter=0, + print_level=0, + display=false + ) + Test.@test sol_init isa OptimalControl.AbstractSolution + end + end + end +end + +end # module + +# Entry point +test_solve_modes() = TestSolveModes.test_solve_modes()