diff --git a/.github/workflows/Breakage.yml b/.github/workflows/Breakage.yml index edb57e3a5..73a75c057 100644 --- a/.github/workflows/Breakage.yml +++ b/.github/workflows/Breakage.yml @@ -5,7 +5,7 @@ name: Breakage # no access to secrets on: pull_request: - types: [labeled, opened, synchronize, reopened] + types: [labeled, synchronize, reopened] jobs: break-pkg: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 71202ce28..6372a6561 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,7 +6,7 @@ on: - main tags: '*' pull_request: - types: [labeled, opened, synchronize, reopened] + types: [labeled, synchronize, reopened] jobs: test-cpu-github: diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 4e8ec56c9..f131174a8 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -7,7 +7,7 @@ on: tags: - 'v[0-9]+\.[0-9]+\.[0-9]+' pull_request: - types: [labeled, opened, synchronize, reopened] + types: [labeled, synchronize, reopened] jobs: call: diff --git a/Project.toml b/Project.toml index b10cf5900..c89f16312 100644 --- a/Project.toml +++ b/Project.toml @@ -18,10 +18,8 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" ExaModels = "1037b233-b668-4ce9-9b63-f9f681f55dd2" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -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" @@ -45,7 +43,6 @@ LinearAlgebra = "1" MadNCL = "0.2" MadNLP = "0.9, 0.10" MadNLPGPU = "0.8, 0.10" -NLPModels = "0.21" NLPModelsIpopt = "0.11" NonlinearSolve = "4" OrdinaryDiffEq = "6, 7" @@ -74,9 +71,10 @@ OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" +SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" [targets] -test = ["BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "OrdinaryDiffEqTsit5", "Printf", "SciMLBase", "SplitApplyCombine", "Test", "UnoSolver"] +test = ["BenchmarkTools", "CUDA", "CUDSS", "DiffEqBase", "MadNCL", "MadNLP", "MadNLPGPU", "NLPModelsIpopt", "NonlinearSolve", "OrdinaryDiffEq", "OrdinaryDiffEqTsit5", "Printf", "SciMLBase", "SolverCore", "SplitApplyCombine", "Test", "UnoSolver"] diff --git a/README.md b/README.md index 40d521726..d9b517bbc 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ Let us model and solve a simple optimal control problem, then plot the solution: ```julia using OptimalControl -using NLPModelsIpopt -using Plots +using NLPModelsIpopt # activates the Ipopt solver extension (required for solve) +using Plots # activates the plotting extension (required for plot) ocp = @def begin t ∈ [0, 1], time diff --git a/_typos.toml b/_typos.toml index 2c7b65bba..447df6725 100644 --- a/_typos.toml +++ b/_typos.toml @@ -3,6 +3,7 @@ locale = "en" extend-ignore-re = [ "ded", "ois", + "Mor", "mis-", ] diff --git a/docs/Project.toml b/docs/Project.toml index 7bd92d6e5..11a80c3c8 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -9,7 +9,6 @@ CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" @@ -40,7 +39,6 @@ CTParser = "0.8" CTSolvers = "0.4" CUDA = "5" CommonSolve = "0.2" -DataFrames = "1" Documenter = "1" DocumenterInterLinks = "1" DocumenterVitepress = "0.3" diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml index a97b8026e..91842758b 100644 --- a/docs/src/assets/Manifest.toml +++ b/docs/src/assets/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.6" manifest_format = "2.0" -project_hash = "f162caf2a328205a3f9b513ec41770aa8fc3ac1b" +project_hash = "314ad4c57dce34bf5d5df4ae6539ac86b0000f01" [[deps.ADNLPModels]] deps = ["ADTypes", "ForwardDiff", "LinearAlgebra", "NLPModels", "Requires", "ReverseDiff", "SparseArrays", "SparseConnectivityTracer", "SparseMatrixColorings"] @@ -512,12 +512,6 @@ 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 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.2" - [[deps.DataStructures]] deps = ["OrderedCollections"] git-tree-sha1 = "b0bc6d2cad1fed8b7fd59a1551a991cb3d2809e6" @@ -1110,19 +1104,6 @@ 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" @@ -1144,11 +1125,6 @@ weakdeps = ["Dates", "Test"] 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 = "f8443766032a81e1f2cddfd4f624a5650067f0d0" @@ -2235,12 +2211,6 @@ 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 = "920abd8738c02528d1078885e07bbd57939fc949" @@ -2545,12 +2515,6 @@ git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" uuid = "6c6a2e73-6563-6170-7368-637461726353" version = "1.3.0" -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.10" - [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 7bd92d6e5..11a80c3c8 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -9,7 +9,6 @@ CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" CTSolvers = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" @@ -40,7 +39,6 @@ CTParser = "0.8" CTSolvers = "0.4" CUDA = "5" CommonSolve = "0.2" -DataFrames = "1" Documenter = "1" DocumenterInterLinks = "1" DocumenterVitepress = "0.3" diff --git a/docs/src/index.md b/docs/src/index.md index b28124fa9..9a02e324b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -25,8 +25,8 @@ Let us model, solve and plot a simple optimal control problem. ```julia using OptimalControl -using NLPModelsIpopt -using Plots +using NLPModelsIpopt # activates the Ipopt solver extension (required for solve) +using Plots # activates the plotting extension (required for plot) ocp = @def begin t ∈ [0, 1], time diff --git a/joss/paper.bib b/joss/paper.bib index 2f4d3a85b..90f2e0dbb 100644 --- a/joss/paper.bib +++ b/joss/paper.bib @@ -9,11 +9,11 @@ @misc{caillau2023juliacon @software{joss_oc_example, author = {Caillau, Jean-Baptiste and Cots, Olivier and Gergaud, Joseph and Martinon, Pierre and Sed, Sophia}, title = {joss-oc-example: illustrative example companion to the {OptimalControl.jl} {JOSS} paper}, - version = {v1.0.0}, year = {2026}, publisher = {Zenodo}, - doi = {10.5281/zenodo.19663650}, - url = {https://doi.org/10.5281/zenodo.19663650} + note = {All-versions (concept) DOI, resolving to the latest release}, + doi = {10.5281/zenodo.19663649}, + url = {https://doi.org/10.5281/zenodo.19663649} } @article{rackauckas2017differentialequations, @@ -475,6 +475,17 @@ @article{pal2024nonlinearsolve keywords = {Nonlinear Systems, Root Finding, Sparsity Detection, Automatic Differentiation, JuliaLang} } +@techreport{More1980, + author = {Mor{\'e}, Jorge J. and Garbow, Burton S. and Hillstrom, Kenneth E.}, + title = {User Guide for {MINPACK}-1}, + institution = {Argonne National Laboratory}, + address = {Argonne, IL, USA}, + number = {ANL-80-74}, + year = {1980}, + doi = {10.2172/6997568}, + url = {https://doi.org/10.2172/6997568} +} + @software{Orban_NLPModelsIpopt_jl, author = {Orban, Dominique and Soares Siqueira, Abel and contributors}, license = {MPL-2.0}, diff --git a/joss/paper.md b/joss/paper.md index 6ae99a03c..67a2fe2cf 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -48,15 +48,23 @@ The package features an expressive domain-specific language (DSL) built around t The ecosystem includes extensive [tutorial resources](https://control-toolbox.org/Tutorials.jl), a benchmark problem collection ([OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl) [@OptimalControlProblems_jl] with formulations in OptimalControl DSL and [JuMP](https://jump.dev/JuMP.jl) [@Lubin2023]), and performance comparison tools ([CTBenchmarks.jl](https://control-toolbox.org/CTBenchmarks.jl)). Integration with Julia's ecosystem enables access to state-of-the-art tools: NLP solvers [IPOPT](https://coin-or.github.io/Ipopt) [@Wachter2006] and [MadNLP.jl](https://github.com/MadNLP/MadNLP.jl) [@SHIN2024110651; @SHIN2021693], automatic differentiation and NLP modeling through JuliaSmoothOptimizers' [ADNLPModels](https://jso.dev/ADNLPModels.jl) [@ADNLPModels_jl], GPU acceleration via [ExaModels.jl](https://github.com/exanauts/ExaModels.jl) [@shin2023accelerating], numerical integration from SciML's [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs) [@rackauckas2017differentialequations], and visualization through [Plots.jl](https://docs.juliaplots.org) [@PlotsJL]. +# Problem Class + +The package currently targets single-phase problems governed by explicit ODEs, written in Bolza form, + +$$g(t_0, x(t_0), t_f, x(t_f), v) + \int_{t_0}^{t_f} f^0(t, x(t), u(t), v)\, \mathrm{d}t \longrightarrow \min,$$ + +subject to the dynamics $\dot{x}(t) = f(t, x(t), u(t), v)$, where $x(t) \in \mathbb{R}^n$ is the state, $u(t) \in \mathbb{R}^m$ the control, and $v \in \mathbb{R}^q$ an optimisation variable, that is a finite-dimensional parameter optimised together with the trajectory. Mayer ($f^0 = 0$) and Lagrange ($g = 0$) costs are the corresponding special cases, and maximisation is also supported. Both autonomous and non-autonomous dynamics are handled, the dependences being detected by the parser. The time bounds $t_0$ and $t_f$ may be fixed or be components of $v$, which covers free initial and / or final time, in particular minimum time problems. Admissible constraints are of five types — boundary, variable, control, state, and mixed (control and state), the last three being *path* constraints, imposed at all times — and may be equalities or one- or two-sided inequalities, either box constraints on state, control and variable (for which dedicated bounds are passed to the NLP solver) or nonlinear. The variable may appear in any of them. The current implementation is restricted to *single-phase* problems with *explicit* ODE dynamics: multiphase problems, differential-algebraic equations, and hybrid or nonsmooth systems are not supported. Optimization of PDE or stochastic systems is out of scope as well (see the design trade-offs below). + # Statement of Need OptimalControl.jl is, to our knowledge, the only Julia package that unifies both direct and indirect methods for optimal control within a single, coherent framework. This unified approach addresses a gap in the current optimal control software landscape. Existing tools are fragmented across programming languages and paradigms. Legacy packages such as [HamPath](https://gitlab.inria.fr/ct/hampath) [@caillau2012differential], [NutoPy](https://ct.gitlabpages.inria.fr/nutopy) [@CAILLAU202213], and [COTCOT](https://github.com/mctao-inria/cotcot) [@bonnard2007second] implement sophisticated indirect methods but rely on Fortran implementations with MATLAB or Python interfaces, leading to complex and less extensible workflows. Proprietary solvers like [GPOPS-II](https://gpops2.com) [@Patterson2014] limit transparency and reproducibility. Open-source tools such as [BOCOP](https://github.com/control-toolbox/bocop) [@Bonnans2017], [ACADO](https://acado.github.io) [@Houska2011], and [acados](https://docs.acados.org) [@Verschueren2021] provide valuable direct method implementations but are not natively integrated into a modern, high-level scientific computing ecosystem. Python-based tools such as [Dymos](https://openmdao.github.io/dymos) [@Falck2021], [Pyomo.DAE](https://pyomo.readthedocs.io/en/stable/modeling_extensions/dae.html) [@Nicholson2018], [do-mpc](https://www.do-mpc.com) [@Fiedler2023], and [GEKKO](https://gekko.readthedocs.io) [@Beal2018] offer complementary capabilities but are similarly limited to direct transcription approaches within the Python ecosystem. -Within Julia, most existing packages target specific domains: [RobustAndOptimalControl.jl](https://juliacontrol.github.io/RobustAndOptimalControl.jl) for linear systems, [QuantumControl.jl](https://juliaquantumcontrol.github.io/QuantumControl.jl) and [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) for quantum optimal control, [DirectTrajectoryOptimization.jl](https://github.com/thowell/DirectTrajectoryOptimization.jl) for trajectory problems, [LinearMPC.jl](https://darnstrom.github.io/LinearMPC.jl/stable/) [@arnstrom2022daqp] and [ModelPredictiveControl.jl](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/) [@Gagnon_ModelPredictiveControl_jl_advanced_process_2024] for model predictive control, and [InfiniteOpt.jl](https://infiniteopt.github.io/InfiniteOpt.jl) [@pulsipher2022unifying] for infinite-dimensional optimization. In contrast with InfiniteOpt.jl that is designed as an extension of [JuMP.jl](https://jump.dev/JuMP.jl), our package provides a modeler-agnostic approach, accepting general Julia code and leveraging various optimization solvers. Besides, contributing to general-purpose NLP modeling frameworks like JuMP or [CasADi](https://web.casadi.org) [@Andersson2019] would not address the specific needs of optimal control: these tools lack native support for shooting methods based on Pontryagin's Maximum Principle and that heavily rely on differential geometric primitives. +Within Julia, most existing packages target specific domains: [RobustAndOptimalControl.jl](https://juliacontrol.github.io/RobustAndOptimalControl.jl) for linear systems, [QuantumControl.jl](https://juliaquantumcontrol.github.io/QuantumControl.jl) [@QuantumControl_jl] and [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) for quantum optimal control, [DirectTrajectoryOptimization.jl](https://github.com/thowell/DirectTrajectoryOptimization.jl) for trajectory problems, [LinearMPC.jl](https://darnstrom.github.io/LinearMPC.jl/stable/) [@arnstrom2022daqp] and [ModelPredictiveControl.jl](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/) [@Gagnon_ModelPredictiveControl_jl_advanced_process_2024] for model predictive control, and [InfiniteOpt.jl](https://infiniteopt.github.io/InfiniteOpt.jl) [@pulsipher2022unifying] for infinite-dimensional optimization. In contrast to [InfiniteOpt.jl](https://infiniteopt.github.io/InfiniteOpt.jl), which is designed as an extension of [JuMP.jl](https://jump.dev/JuMP.jl), our package provides a modeler-agnostic approach, accepting general Julia code and leveraging various optimization solvers. Moreover, contributing to general-purpose NLP modeling frameworks like [JuMP](https://jump.dev/JuMP.jl) or [CasADi](https://web.casadi.org) [@Andersson2019] would not address the specific needs of optimal control: these tools lack native support for shooting methods based on Pontryagin's Maximum Principle, which heavily rely on differential geometric primitives. -OptimalControl.jl fills this gap by providing a DSL that matches mathematical notation, multiple discretization schemes and shooting methods, with planned support for homotopy continuation methods. The modeler-solver separation enables complementary use with InfiniteOpt.jl (JuMP-based) through alternative NLP modeling backends (currently ADNLPModels, ExaModels) and solvers. GPU acceleration and an ecosystem with domain-specific applications, tutorials, and benchmarking tools complete the offering. Target users include researchers and engineers working in optimal control, control theorists developing new algorithms, and students learning optimal control through interactive tutorials. +OptimalControl.jl fills this gap by providing a DSL that matches mathematical notation, multiple discretization schemes and shooting methods, with planned support for homotopy continuation methods. The modeler-solver separation enables complementary use with [InfiniteOpt.jl](https://infiniteopt.github.io/InfiniteOpt.jl) (JuMP-based) through alternative NLP modeling backends (currently [ADNLPModels](https://jso.dev/ADNLPModels.jl), [ExaModels](https://github.com/exanauts/ExaModels.jl)) and solvers. GPU acceleration and an ecosystem with domain-specific applications, tutorials, and benchmarking tools complete the offering. Target users include researchers and engineers working in optimal control, control theorists developing new algorithms, and students learning optimal control through interactive tutorials. # State of the Field @@ -64,9 +72,9 @@ OptimalControl.jl requires Julia version 1.10 or later and is registered in the - **Legacy tools (COTCOT, HamPath, NutoPy)**: These Fortran packages excel at indirect methods and homotopy continuation but require multi-language setup (Fortran plus MATLAB / Python). OptimalControl.jl provides both direct and indirect methods in pure Julia with straightforward installation via package manager. -- **Direct method tools (BOCOP, ACADO, GPOPS-II, acados, nosnoc)**: Strong direct method implementations: [GPOPS-II](https://gpops2.com) [@Patterson2014] delivers mature methods with MATLAB and C++ implementations but is proprietary; [acados](https://docs.acados.org) [@Verschueren2021] targets real-time MPC on embedded systems; [nosnoc](https://github.com/nosnoc/nosnoc) [@Nurkanovic2022] specializes in nonsmooth optimal control; [CasADi](https://web.casadi.org) [@Andersson2019], used as symbolic backend by several of these tools, is a general NLP modeler rather than an optimal control solver. OptimalControl.jl offers an open-source alternative with expressive DSL, native Julia ecosystem integration, GPU support, and unified direct and indirect approaches. +- **Direct-method tools (BOCOP, ACADO, GPOPS-II, acados, nosnoc)**: Strong direct-method implementations: [GPOPS-II](https://gpops2.com) delivers mature methods with MATLAB and C++ implementations but is proprietary; [acados](https://docs.acados.org) targets real-time MPC on embedded systems; [nosnoc](https://github.com/nosnoc/nosnoc) [@Nurkanovic2022] specializes in nonsmooth optimal control; [CasADi](https://web.casadi.org), used as symbolic backend by several of these tools, is a general NLP modeler rather than an optimal control solver. OptimalControl.jl offers an open-source alternative with expressive DSL, native Julia ecosystem integration, GPU support, and unified direct and indirect approaches. -- **Julia packages**: RobustAndOptimalControl.jl targets linear systems; QuantumControl.jl, Piccolo.jl and DirectTrajectoryOptimization.jl serve specific domains; [LinearMPC.jl](https://darnstrom.github.io/LinearMPC.jl/stable/) [@arnstrom2022daqp] and [ModelPredictiveControl.jl](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/) [@Gagnon_ModelPredictiveControl_jl_advanced_process_2024] focus on model predictive control. InfiniteOpt.jl addresses a very rich range of problems, including optimization on PDE's or with chance constraints, focusing on direct transcription methods. OptimalControl.jl supports both CPU and GPU execution (currently NVIDIA-only, via ExaModels.jl + MadNLP.jl), and adds tools to do shooting in a unified framework plus systematic benchmarking through OptimalControlProblems.jl and CTBenchmarks.jl. +- **Julia packages**: [RobustAndOptimalControl.jl](https://juliacontrol.github.io/RobustAndOptimalControl.jl) targets linear systems; [QuantumControl.jl](https://juliaquantumcontrol.github.io/QuantumControl.jl), [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) and [DirectTrajectoryOptimization.jl](https://github.com/thowell/DirectTrajectoryOptimization.jl) serve specific domains; [LinearMPC.jl](https://darnstrom.github.io/LinearMPC.jl/stable/) and [ModelPredictiveControl.jl](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/) focus on model predictive control. [InfiniteOpt.jl](https://infiniteopt.github.io/InfiniteOpt.jl) addresses a very rich range of problems, including optimization on PDEs or with chance constraints, focusing on direct transcription methods. OptimalControl.jl supports both CPU and GPU execution (currently NVIDIA-only, via [ExaModels.jl](https://github.com/exanauts/ExaModels.jl) + [MadNLP.jl](https://github.com/MadNLP/MadNLP.jl)), and adds tools to do shooting in a unified framework plus systematic benchmarking through [OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl) and [CTBenchmarks.jl](https://control-toolbox.org/CTBenchmarks.jl). # Illustrative Example @@ -82,19 +90,19 @@ The package architecture balances expressiveness, performance, and extensibility - **Discretization**: Direct transcription converts continuous problems into NLPs. Supporting multiple discretization schemes (Euler, midpoint, trapezoidal, Gauss-Legendre) required careful abstraction to share transcription logic while allowing scheme-specific implementations. -- **Modelers and solvers**: We chose a clear modeler-solver separation: discretization produces ADNLPModels (able to deal with arbitrary user defined functions) or ExaModels [@shin2023accelerating] instances compatible with multiple NLP solvers (IPOPT via NLPModelsIpopt.jl [@Orban_NLPModelsIpopt_jl], Knitro [@Byrd2006], MadNLP, and [Uno](https://github.com/cvanaret/Uno) [@VanaretLeyffer2026]). These NLP solvers rely on linear solvers such as [MUMPS.jl](https://github.com/JuliaSmoothOptimizers/MUMPS.jl) [@Montoison_MUMPS_jl; @MUMPS:1; @MUMPS:2] for CPU computations or [CUDSS.jl](https://github.com/exanauts/CUDSS.jl) [@CUDSS_nvidia] for GPU acceleration with MadNLP. Automatic differentiation via ForwardDiff.jl [@RevelsLubinPapamarkou2016] and DifferentiationInterface.jl [@dalle2026commoninterfaceautomaticdifferentiation] avoids manual derivative coding. +- **Modelers and solvers**: We chose a clear modeler-solver separation: discretization produces [ADNLPModels](https://jso.dev/ADNLPModels.jl) (able to deal with arbitrary user-defined functions) or [ExaModels](https://github.com/exanauts/ExaModels.jl) instances compatible with multiple NLP solvers ([IPOPT](https://coin-or.github.io/Ipopt) via [NLPModelsIpopt.jl](https://jso.dev/NLPModelsIpopt.jl) [@Orban_NLPModelsIpopt_jl], [Knitro](https://www.artelys.com/solvers/knitro) [@Byrd2006], [MadNLP](https://github.com/MadNLP/MadNLP.jl), and [Uno](https://github.com/cvanaret/Uno) [@VanaretLeyffer2026]). These NLP solvers rely on linear solvers such as [MUMPS.jl](https://github.com/JuliaSmoothOptimizers/MUMPS.jl) [@Montoison_MUMPS_jl; @MUMPS:1; @MUMPS:2] for CPU computations or [CUDSS.jl](https://github.com/exanauts/CUDSS.jl) [@CUDSS_nvidia] for GPU acceleration with MadNLP. Automatic differentiation via [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) [@RevelsLubinPapamarkou2016] and [DifferentiationInterface.jl](https://github.com/JuliaDiff/DifferentiationInterface.jl) [@dalle2026commoninterfaceautomaticdifferentiation] avoids manual derivative coding. -- **Indirect methods**: Hamiltonian flows integrate with DifferentialEquations.jl to access adaptive stepping, event handling, and multiple ODE solvers without reimplementation. Shooting methods rely on NonlinearSolve.jl [@pal2024nonlinearsolve] or [MINPACK.jl](https://github.com/sglyon/MINPACK.jl) for root-finding. Future extensions will incorporate homotopy continuation methods leveraging bifurcation analysis tools like [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) [@veltz:hal-02902346]. Currently, indirect methods are limited to Hamiltonian flows, where the feedback control law depends on both state and costate; support for non-Hamiltonian flows (open-loop or state-feedback control laws) is under active development. +- **Indirect methods**: Hamiltonian flows integrate with [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs) to access adaptive stepping, event handling, and multiple ODE solvers without reimplementation. Shooting methods rely on [NonlinearSolve.jl](https://docs.sciml.ai/NonlinearSolve) [@pal2024nonlinearsolve] or [MINPACK.jl](https://github.com/sglyon/MINPACK.jl) [@More1980] for root-finding. Future extensions will incorporate homotopy continuation methods leveraging bifurcation analysis tools like [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) [@veltz:hal-02902346]. Currently, indirect methods are limited to Hamiltonian flows, where the feedback control law depends on both state and costate; support for non-Hamiltonian flows (open-loop or state-feedback control laws) is under active development. -- **Testing and quality assurance**: The multi-repository structure is matched by a layered testing strategy. Each sub-package is tested independently, combining unit tests, integration tests, and code-quality checks, while OptimalControl.jl adds strong end-to-end integration tests that solve complete optimal control problems by both direct and indirect methods. Continuous integration runs across Linux, macOS, and Windows, on both CPU and GPU (through a self-hosted CUDA runner), using reusable GitHub Actions workflows centralized in [CTActions](https://github.com/control-toolbox/CTActions) and shared across the ecosystem. Code coverage is tracked on Codecov, downstream packages and applications are guarded against regressions through dedicated breakage tests triggered on pull requests, and beta versions are distributed during development via a local registry, [ct-registry](https://github.com/control-toolbox/ct-registry). Non-regression on complex problems is increasingly driven by [OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl), used as a curated problem bank. +- **Testing and quality assurance**: The multi-repository structure is matched by a layered testing strategy. Each sub-package is tested independently, combining unit tests, integration tests, and code-quality checks, while OptimalControl.jl adds strong end-to-end integration tests that solve complete optimal control problems by both direct and indirect methods. Continuous integration runs across Linux, macOS, and Windows, on both CPU and GPU (through a self-hosted CUDA runner), using reusable GitHub Actions workflows centralized in [CTActions](https://github.com/control-toolbox/CTActions) and shared across the ecosystem. Code coverage is tracked on Codecov, downstream packages and applications are guarded against regressions through dedicated breakage tests triggered on pull requests, and beta versions are distributed during development via a local registry, [ct-registry](https://github.com/control-toolbox/ct-registry). Regression testing on complex optimal control instances is increasingly supported by [OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl), which serves as a curated problem bank. **Key design trade-offs:** 1. **DSL vs. programmatic API**: We prioritized a mathematical DSL to reduce cognitive load for domain experts, accepting increased parsing complexity handled internally. Currently we do not address optimization of PDE or stochastic systems. -2. **GPU acceleration strategy**: The modeler-solver separation enables a straightforward CPU-to-GPU transition. Users select the ExaModels modeler with MadNLP and the CUDSS linear solver for GPU execution, or simply append the `:gpu` token to the `solve` call. This modular approach minimizes maintenance burden while enabling GPU performance without reimplementing transcription logic. GPU support is currently limited to NVIDIA hardware and to the ExaModels + MadNLP modeler-solver combination. +2. **GPU acceleration strategy**: The modeler-solver separation enables a straightforward CPU-to-GPU transition. Users select the [ExaModels](https://github.com/exanauts/ExaModels.jl) modeler with [MadNLP](https://github.com/MadNLP/MadNLP.jl) and the [CUDSS](https://github.com/exanauts/CUDSS.jl) linear solver for GPU execution, or simply append the `:gpu` token to the `solve` call. This modular approach minimizes maintenance burden while enabling GPU performance without reimplementing transcription logic. GPU support is currently limited to NVIDIA hardware and to the [ExaModels](https://github.com/exanauts/ExaModels.jl) + [MadNLP](https://github.com/MadNLP/MadNLP.jl) modeler-solver combination. -3. **Method coverage**: Supporting both direct and indirect approaches increases code complexity but serves distinct user needs: direct methods for constrained problems with many variables, indirect methods for theoretical analysis and smaller problems requiring high accuracy. Both approaches resort to iterative solvers and may converge or not, *e.g.* depending on the initial guess. In the case of optimization solvers, the full output status of the solver is returned allowing *a posteriori* analysis. +3. **Method coverage**: Supporting both direct and indirect approaches increases code complexity but serves distinct user needs: direct methods for constrained problems with many variables, indirect methods for theoretical analysis and smaller problems requiring high accuracy. Both approaches rely on iterative solvers and may or may not converge, *e.g.*, depending on the initial guess. In the case of optimization solvers, the full output status of the solver is returned allowing *a posteriori* analysis. # Research Impact Statement @@ -104,9 +112,9 @@ Research by close collaborators further demonstrates the package's versatility a The [control-toolbox organization](https://control-toolbox.org) hosts domain-specific application packages built on OptimalControl.jl: medical imaging optimization ([MagneticResonanceImaging.jl](https://control-toolbox.org/MagneticResonanceImaging.jl)), gene regulatory networks ([PWLdynamics.jl](https://agustinyabo.github.io/PWLdynamics.jl)), spacecraft orbital transfers ([Kepler.jl](https://control-toolbox.org/Kepler.jl)), epidemiological modeling ([SIRcontrol.jl](https://anasxbouali.github.io/SIRcontrol.jl)), and variational calculus ([CalculusOfVariations.jl](https://control-toolbox.org/CalculusOfVariations.jl)). -The package serves educational purposes through [Tutorials.jl](https://control-toolbox.org/Tutorials.jl) covering topics from linear-quadratic regulators to Model Predictive Control. These resources are used in academic courses and workshops; the package was notably presented at JuliaCon 2023 [@caillau2023juliacon]. [OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl) provides standardized test problems formulated in both OptimalControl DSL and JuMP, enabling systematic performance comparisons through [CTBenchmarks.jl](https://control-toolbox.org/CTBenchmarks.jl). +The package also serves educational purposes through [Tutorials.jl](https://control-toolbox.org/Tutorials.jl), which covers topics from linear-quadratic regulators to Model Predictive Control. These resources are used in academic courses and workshops; the package was notably presented at JuliaCon 2023 [@caillau2023juliacon]. [OptimalControlProblems.jl](https://control-toolbox.org/OptimalControlProblems.jl) provides standardized test problems formulated in both OptimalControl DSL and [JuMP](https://jump.dev/JuMP.jl), enabling systematic performance comparisons through [CTBenchmarks.jl](https://control-toolbox.org/CTBenchmarks.jl). -Development involves international collaborations with CNES (French space agency), Thales Alenia Space, Inria, and CNRS. The package development began in September 2022, with the first public release in February 2023. As of March 2026, OptimalControl.jl has published more than 40 releases with contributions from multiple developers across the control-toolbox ecosystem packages, demonstrating sustained community engagement. +Development involves international collaborations with CNES (French space agency), Thales Alenia Space, Inria, and CNRS. The package development began in September 2022, with the first public release in February 2023. As of March 2026, OptimalControl.jl has published more than 40 releases with contributions from multiple developers across the control-toolbox ecosystem, demonstrating sustained community engagement. # AI Usage Disclosure diff --git a/joss/paper.pdf b/joss/paper.pdf index 992b1ce00..fb2382f2d 100644 Binary files a/joss/paper.pdf and b/joss/paper.pdf differ diff --git a/test/suite/helpers/test_kwarg_extraction.jl b/test/suite/helpers/test_kwarg_extraction.jl index dae02d0a5..2503bddba 100644 --- a/test/suite/helpers/test_kwarg_extraction.jl +++ b/test/suite/helpers/test_kwarg_extraction.jl @@ -10,7 +10,6 @@ module TestKwargExtraction using Test: Test using OptimalControl: OptimalControl -using CTDirect: CTDirect using CTSolvers: CTSolvers using CTBase: CTBase @@ -175,9 +174,10 @@ function test_kwarg_extraction() ) Test.@test allocs == 0 - # Type stability (Julia 1.10 fails @inferred here: it widens the - # return type to Union{Nothing,AbstractDiscretizer} where 1.11's - # inference narrows it to the concrete branch actually taken) + # Type stability. On Julia < 1.11, inference for this call gives the + # wider `Union{Nothing, AbstractDiscretizer}` instead of the concrete + # runtime type, so `@inferred` fails — a compiler precision + # difference, not a logic bug; the strict check only holds from 1.11 on. if VERSION >= v"1.11" Test.@test_nowarn Test.@inferred OptimalControl._extract_kwarg( kw, CTSolvers.DOCP.AbstractDiscretizer @@ -195,9 +195,7 @@ function test_kwarg_extraction() ) Test.@test allocs == 0 - # Type stability (Julia 1.10 fails @inferred here: it widens the - # return type to Union{Nothing,AbstractDiscretizer} where 1.11's - # inference narrows it to the concrete branch actually taken) + # Type stability — see comment above, strict check only holds from 1.11 on. if VERSION >= v"1.11" Test.@test_nowarn Test.@inferred OptimalControl._extract_kwarg( kw, CTSolvers.DOCP.AbstractDiscretizer @@ -229,7 +227,7 @@ function test_kwarg_extraction() ) Test.@test allocs < 1000 # Small allocation acceptable for large kwargs - # Type stability (see the 1.10-vs-1.11 inference note above) + # Type stability — see comment above, strict check only holds from 1.11 on. if VERSION >= v"1.11" Test.@test_nowarn Test.@inferred OptimalControl._extract_kwarg( large_kw, CTSolvers.DOCP.AbstractDiscretizer