From 39912cc0efce47af350523a1bb8644fb2375e609 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 21 Apr 2026 13:34:16 +0200 Subject: [PATCH 1/2] Update for CTModels v0.9.15-beta API changes - Rename dim_*_constraints_box to dim_dual_*_constraints_box - Add new predicates: has_variable, is_variable, has_control, is_control_free, has_abstract_definition, is_abstractly_defined, is_nonautonomous, is_nonvariable - Add expression() function to extract Expr from AbstractDefinition - Update manual-model.md: definition changes, new Model predicates section, box constraint tuple structure (added aliases field) - Update manual-abstract.md: document box constraint intersection/deduplication - Update manual-solution.md: document signed multiplier convention, add dim_dual_*_constraints_box and dim_{path,boundary}_constraints_nl - Update api/public.md: function list - Update test/README.md and .windsurf/rules/testing.md: add jtest convenience command and tee pattern for test output capture --- Project.toml | 2 +- docs/Project.toml | 4 +- docs/make.jl | 2 +- docs/src/api/public.md | 15 +++- docs/src/assets/Manifest.toml | 100 +++++++++++++-------------- docs/src/assets/Project.toml | 4 +- docs/src/manual-abstract.md | 45 ++++++++++++ docs/src/manual-model.md | 84 ++++++++++++++++++++-- docs/src/manual-solution.md | 75 +++++++++++++++++--- src/imports/ctmodels.jl | 15 +++- test/README.md | 29 ++++++-- test/suite/reexport/test_ctmodels.jl | 15 +++- 12 files changed, 304 insertions(+), 86 deletions(-) diff --git a/Project.toml b/Project.toml index f6a85f750..8b75d1343 100644 --- a/Project.toml +++ b/Project.toml @@ -27,7 +27,7 @@ BenchmarkTools = "1" CTBase = "0.18" CTDirect = "1" CTFlows = "0.8" -CTModels = "0.9, 0.10" +CTModels = "0.10" CTParser = "0.8" CTSolvers = "0.4" CUDA = "5" diff --git a/docs/Project.toml b/docs/Project.toml index 21edf0f91..0310baf65 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -31,7 +31,7 @@ ADNLPModels = "0.8" CTBase = "0.18" CTDirect = "1" CTFlows = "0.8" -CTModels = "0.9" +CTModels = "0.10" CTParser = "0.8" CTSolvers = "0.4" CUDA = "5" @@ -53,4 +53,4 @@ NonlinearSolve = "4" OrdinaryDiffEq = "6" Plots = "1" UnoSolver = "0.3" -julia = "1.10" \ No newline at end of file +julia = "1.10" diff --git a/docs/make.jl b/docs/make.jl index b26d47cbd..764bcac33 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -153,7 +153,7 @@ cp( Draft = false ``` =# -draft = false # Draft mode: if true, @example blocks in markdown are not executed +draft = true # Draft mode: if true, @example blocks in markdown are not executed # ═══════════════════════════════════════════════════════════════════════════════ # Load extensions diff --git a/docs/src/api/public.md b/docs/src/api/public.md index 85709239f..50d35ed6f 100644 --- a/docs/src/api/public.md +++ b/docs/src/api/public.md @@ -55,12 +55,13 @@ costate criterion @def definition +expression describe dim_boundary_constraints_nl -dim_control_constraints_box +dim_dual_control_constraints_box +dim_dual_state_constraints_box +dim_dual_variable_constraints_box dim_path_constraints_nl -dim_state_constraints_box -dim_variable_constraints_box dimension discretize dual @@ -76,6 +77,11 @@ has_free_initial_time has_lagrange_cost has_mayer_cost has_option +has_variable +is_variable +has_control +is_control_free +has_abstract_definition id import_ocp_solution index @@ -86,6 +92,9 @@ initial_time_name is_autonomous is_computed is_default +is_abstractly_defined +is_nonautonomous +is_nonvariable is_empty is_empty_time_grid is_final_time_fixed diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml index a727d2292..b74944f0c 100644 --- a/docs/src/assets/Manifest.toml +++ b/docs/src/assets/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.5" manifest_format = "2.0" -project_hash = "b0d8b73e668b2a300e75cec748fa7e05cad42a09" +project_hash = "505b0cb4420cde5e120adb378ac9d10f3e75d872" [[deps.ADNLPModels]] deps = ["ADTypes", "ForwardDiff", "LinearAlgebra", "NLPModels", "Requires", "ReverseDiff", "SparseArrays", "SparseConnectivityTracer", "SparseMatrixColorings"] @@ -101,9 +101,9 @@ version = "1.1.2" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "78b3a7a536b4b0a747a0f296ea77091ca0a9f9a3" +git-tree-sha1 = "54f895554d05c83e3dd59f6a396671dae8999573" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.23.0" +version = "7.24.0" [deps.ArrayInterface.extensions] ArrayInterfaceAMDGPUExt = "AMDGPU" @@ -226,15 +226,15 @@ version = "0.18.7" [[deps.CTDirect]] deps = ["ADNLPModels", "CTModels", "CTSolvers", "DocStringExtensions", "ExaModels", "SolverCore", "SparseArrays", "SparseConnectivityTracer"] -git-tree-sha1 = "a4d812f60412f47bf29e685a921d0d675f581d55" +git-tree-sha1 = "21b9ea456769176849cdcbd20f04a017526daa76" uuid = "790bbbee-bee9-49ee-8912-a9de031322d5" -version = "1.0.7-beta" +version = "1.0.10" [[deps.CTFlows]] deps = ["CTBase", "CTModels", "DocStringExtensions", "ForwardDiff", "LinearAlgebra", "MLStyle", "MacroTools"] -git-tree-sha1 = "919b47b3c8a9c2fc2c8606ee8fce826bef6d36b2" +git-tree-sha1 = "b555d36b668dc149334a50b835df91a301bad8ac" uuid = "1c39547c-7794-42f7-af83-d98194f657c2" -version = "0.8.23" +version = "0.8.25" weakdeps = ["OrdinaryDiffEq"] [deps.CTFlows.extensions] @@ -242,9 +242,9 @@ weakdeps = ["OrdinaryDiffEq"] [[deps.CTModels]] deps = ["CTBase", "DocStringExtensions", "LinearAlgebra", "MLStyle", "MacroTools", "OrderedCollections", "Parameters", "RecipesBase"] -git-tree-sha1 = "6f8db9437164a844f3dde8f239a82b087d6b6336" +git-tree-sha1 = "a183ce43a9f912a4b62a8af88ba1415c6e2b1521" uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" -version = "0.9.14" +version = "0.10.0" weakdeps = ["JLD2", "JSON3", "Plots"] [deps.CTModels.extensions] @@ -254,15 +254,15 @@ weakdeps = ["JLD2", "JSON3", "Plots"] [[deps.CTParser]] deps = ["CTBase", "DocStringExtensions", "MLStyle", "OrderedCollections", "Parameters", "Unicode"] -git-tree-sha1 = "0a57111b2d95d6272bb76f4f6f7c4fee1e40ce82" +git-tree-sha1 = "67c193f4f1e5167123b917bbbdb26947757ed4b0" uuid = "32681960-a1b1-40db-9bff-a1ca817385d1" -version = "0.8.13" +version = "0.8.14" [[deps.CTSolvers]] deps = ["ADNLPModels", "CTBase", "CTModels", "CommonSolve", "DocStringExtensions", "ExaModels", "KernelAbstractions", "NLPModels", "SolverCore"] -git-tree-sha1 = "22c283a24bd1b51cf2795074a6ec3fe0bff78adb" +git-tree-sha1 = "cc69a7ad7e18ca089e0168c6b361ee1e50a3cfc2" uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6" -version = "0.4.15" +version = "0.4.16" [deps.CTSolvers.extensions] CTSolversCUDA = "CUDA" @@ -306,15 +306,15 @@ version = "5.11.0" [[deps.CUDA_Compiler_jll]] deps = ["Artifacts", "CUDA_Driver_jll", "CUDA_Runtime_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "8c19e97de5b7574672e4a7a3abd55714ad66d59a" +git-tree-sha1 = "b977706846cb0a75d3842a1fed810ab2e6ab2f94" uuid = "d1e2174e-dfdc-576e-b43e-73b79eb1aca8" -version = "0.4.2+0" +version = "0.4.3+0" [[deps.CUDA_Driver_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "TOML"] -git-tree-sha1 = "061f39cc84e99928830aa1005d79f7e99097ba28" +git-tree-sha1 = "3b759ec65ac87ad192c2925114fa5c126657a5bd" uuid = "4ee394cb-3365-5eb0-8335-949819d2adfc" -version = "13.2.0+0" +version = "13.2.1+0" [[deps.CUDA_Runtime_Discovery]] deps = ["Libdl"] @@ -324,9 +324,9 @@ version = "1.0.0" [[deps.CUDA_Runtime_jll]] deps = ["Artifacts", "CUDA_Driver_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "af17d37b5b8b4d7525f8902eba1ef6141a9a7d3b" +git-tree-sha1 = "c0314d9fb0ebd00e404feba4c3fbc04c9975abc1" uuid = "76a88914-d11a-5bdc-97e0-2f5a05c973a2" -version = "0.21.0+0" +version = "0.21.0+1" [[deps.CUDSS]] deps = ["CEnum", "CUDA", "CUDA_Runtime_Discovery", "CUDSS_jll", "GPUToolbox", "LinearAlgebra", "SparseArrays"] @@ -539,10 +539,10 @@ uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" version = "1.9.1" [[deps.DiffEqBase]] -deps = ["ArrayInterface", "BracketingNonlinearSolve", "ConcreteStructs", "DocStringExtensions", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "Logging", "Markdown", "MuladdMacro", "PrecompileTools", "Printf", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "Setfield", "Static", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"] -git-tree-sha1 = "87e2ad6d4ae98505218e2f97cafcfa296dc97d37" +deps = ["ArrayInterface", "BracketingNonlinearSolve", "ConcreteStructs", "DocStringExtensions", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "Logging", "Markdown", "MuladdMacro", "PrecompileTools", "Printf", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "Static", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"] +git-tree-sha1 = "9d333db14895e8c7d4857ed228eb1e72d3b302ec" uuid = "2b5f629d-d688-5b77-993f-72d75c75574e" -version = "6.216.0" +version = "6.218.0" [deps.DiffEqBase.extensions] DiffEqBaseCUDAExt = "CUDA" @@ -859,9 +859,9 @@ version = "1.16.0" [[deps.FiniteDiff]] deps = ["ArrayInterface", "LinearAlgebra", "Setfield"] -git-tree-sha1 = "9340ca07ca27093ff68418b7558ca37b05f8aeb1" +git-tree-sha1 = "73e879af0e767bd6dfade7c5b09d7b05657a8284" uuid = "6a86dc24-6348-571c-b903-95158fe2bd41" -version = "2.29.0" +version = "2.30.0" [deps.FiniteDiff.extensions] FiniteDiffBandedMatricesExt = "BandedMatrices" @@ -921,9 +921,9 @@ version = "1.1.3" [[deps.FunctionWrappersWrappers]] deps = ["FunctionWrappers", "PrecompileTools", "TruncatedStacktraces"] -git-tree-sha1 = "3e13d0b39d117a03d3fb5c88a039e94787a37fcb" +git-tree-sha1 = "ce6762f8f0e7542534f01523ae051e625cbf0468" uuid = "77dc65aa-8811-40c2-897b-53d922fa7daf" -version = "1.4.0" +version = "1.5.0" [deps.FunctionWrappersWrappers.extensions] FunctionWrappersWrappersEnzymeExt = ["Enzyme", "EnzymeCore"] @@ -947,9 +947,9 @@ version = "3.4.1+1" [[deps.GPUArrays]] deps = ["Adapt", "GPUArraysCore", "KernelAbstractions", "LLVM", "LinearAlgebra", "Printf", "Random", "Reexport", "ScopedValues", "Serialization", "SparseArrays", "Statistics"] -git-tree-sha1 = "6487601563e4a1d1dab796e88b4548bf5544209e" +git-tree-sha1 = "99ebe50e3b6537de14d43f1c88963a2e13eff3b7" uuid = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" -version = "11.4.1" +version = "11.5.1" weakdeps = ["JLD2"] [deps.GPUArrays.extensions] @@ -1419,9 +1419,9 @@ version = "2.42.0+0" [[deps.LineSearch]] deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "PrecompileTools", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] -git-tree-sha1 = "69da095e4c24ed3c4a168bb76dc9c620a6d7239c" +git-tree-sha1 = "25454bc65c3eec4656cbe201c3d9336af49178c7" uuid = "87fe0de2-c867-4266-b59a-2f0a94fc965b" -version = "0.1.7" +version = "0.1.8" weakdeps = ["LineSearches"] [deps.LineSearch.extensions] @@ -1587,9 +1587,9 @@ version = "0.5.16" [[deps.MadNCL]] deps = ["Atomix", "KernelAbstractions", "LinearAlgebra", "MadNLP", "NLPModels", "Printf", "Random", "SparseArrays"] -git-tree-sha1 = "8cdb50494fa73f9af44aabadfac51d39413a707e" +git-tree-sha1 = "cbb996d397b63f551bfbeaabfae4af5640326ab2" uuid = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0" -version = "0.2.1" +version = "0.2.2" weakdeps = ["MadNLPGPU"] [deps.MadNCL.extensions] @@ -1761,9 +1761,9 @@ 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", "SciMLLogging", "Setfield", "SimpleNonlinearSolve", "StaticArraysCore", "SymbolicIndexingInterface"] -git-tree-sha1 = "e88921859836899abe94d08ea0fd42137067280e" +git-tree-sha1 = "a2db21951cd1cd46a3ef8ba4bbfc84ddc9a5b3fb" uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" -version = "4.17.1" +version = "4.18.0" [deps.NonlinearSolve.extensions] NonlinearSolveFastLevenbergMarquardtExt = "FastLevenbergMarquardt" @@ -1794,9 +1794,9 @@ version = "4.17.1" [[deps.NonlinearSolveBase]] deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "Compat", "ConcreteStructs", "DifferentiationInterface", "EnzymeCore", "FastClosures", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "LogExpFunctions", "Markdown", "MaybeInplace", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "RecursiveArrayTools", "SciMLBase", "SciMLJacobianOperators", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "StaticArraysCore", "SymbolicIndexingInterface", "TimerOutputs"] -git-tree-sha1 = "5bd437a82d7e5eee049a1b488dedc726f0b84bd4" +git-tree-sha1 = "a19a5df29ef2b197499fc631fa1a59385ae15262" uuid = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" -version = "2.24.0" +version = "2.25.0" [deps.NonlinearSolveBase.extensions] NonlinearSolveBaseBandedMatricesExt = "BandedMatrices" @@ -1895,12 +1895,6 @@ git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" version = "0.5.6+0" -[[deps.OptimalControl]] -deps = ["ADNLPModels", "CTBase", "CTDirect", "CTFlows", "CTModels", "CTParser", "CTSolvers", "CommonSolve", "DocStringExtensions", "Documenter", "ExaModels", "LinearAlgebra", "NLPModels", "RecipesBase", "Reexport", "SolverCore"] -git-tree-sha1 = "8490d74ca7e2dbff82210d08b1cf557168d497e4" -uuid = "5f98b655-cc9a-415a-b60e-744165666948" -version = "2.0.1" - [[deps.Opus_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "e2bb57a313a74b8104064b7efd01406c0a50d2ff" @@ -1931,10 +1925,10 @@ uuid = "6ad6398a-0878-4a85-9266-38940aa047c8" version = "1.26.0" [[deps.OrdinaryDiffEqCore]] -deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "ConcreteStructs", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "EnzymeCore", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappersWrappers", "InteractiveUtils", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "Polyester", "PrecompileTools", "Preferences", "Random", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Static", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"] -git-tree-sha1 = "e81202ab680b4649ea2cb63560b9e57833540cfa" +deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "ConcreteStructs", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "EnzymeCore", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappersWrappers", "InteractiveUtils", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "Polyester", "PrecompileTools", "Preferences", "Random", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Static", "SymbolicIndexingInterface", "TruncatedStacktraces"] +git-tree-sha1 = "2b55a12e68d2e9eb9f06c00cde116356c2d3823d" uuid = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" -version = "3.31.0" +version = "3.33.0" [deps.OrdinaryDiffEqCore.extensions] OrdinaryDiffEqCoreMooncakeExt = "Mooncake" @@ -2088,9 +2082,9 @@ version = "1.14.0" [[deps.OrdinaryDiffEqStabilizedRK]] deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArraysCore"] -git-tree-sha1 = "bb33a312858b1e6663099f231323a78b7bb00eb3" +git-tree-sha1 = "764e11fb6a26ee8f2d2d10778d531fd708f35d28" uuid = "358294b1-0aab-51c3-aafe-ad5ab194a2ad" -version = "1.11.0" +version = "1.11.1" [[deps.OrdinaryDiffEqSymplecticRK]] deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"] @@ -2117,9 +2111,9 @@ 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 = "0662b083e11420952f2e62e17eddae7fc07d5997" +git-tree-sha1 = "58e5ed5e386e156bd93e86b305ebd21ac63d2d04" uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.57.0+0" +version = "1.57.1+0" [[deps.Parameters]] deps = ["OrderedCollections", "UnPack"] @@ -3059,9 +3053,9 @@ version = "0.61.1+0" [[deps.libaom_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "371cc681c00a3ccc3fbc5c0fb91f58ba9bec1ecf" +git-tree-sha1 = "850b06095ee71f0135d644ffd8a52850699581ed" uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.13.1+0" +version = "3.13.3+0" [[deps.libass_jll]] deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] @@ -3106,9 +3100,9 @@ version = "1.28.1+0" [[deps.libpng_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "45a20bd63e4fafc84ed9e4ac4ba15c8a7deff803" +git-tree-sha1 = "e51150d5ab85cee6fc36726850f0e627ad2e4aba" uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.57+0" +version = "1.6.58+0" [[deps.libva_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"] diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index 21edf0f91..0310baf65 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -31,7 +31,7 @@ ADNLPModels = "0.8" CTBase = "0.18" CTDirect = "1" CTFlows = "0.8" -CTModels = "0.9" +CTModels = "0.10" CTParser = "0.8" CTSolvers = "0.4" CUDA = "5" @@ -53,4 +53,4 @@ NonlinearSolve = "4" OrdinaryDiffEq = "6" Plots = "1" UnoSolver = "0.3" -julia = "1.10" \ No newline at end of file +julia = "1.10" diff --git a/docs/src/manual-abstract.md b/docs/src/manual-abstract.md index 93454693a..2df6b9e64 100644 --- a/docs/src/manual-abstract.md +++ b/docs/src/manual-abstract.md @@ -311,6 +311,9 @@ In the example below, there are end ``` +!!! note "Duplicate box constraints" + If the same scalar component of the state, control or variable appears in several **box** constraints (linear range constraints), the effective bounds are the **intersection** of all declared bounds: the effective lower bound is the maximum of declared lower bounds, and the effective upper bound is the minimum of declared upper bounds. A warning is emitted, and an error is thrown if the resulting interval is empty. See [Duplicate box constraints](@ref manual-abstract-box-dedup) below. + !!! note Symbols like `<=` or `>=` are also authorised: @@ -386,6 +389,48 @@ end end ``` +### [Duplicate box constraints](@id manual-abstract-box-dedup) + +**Box constraints** are linear range constraints on a single scalar component of the state, control or variable — that is, constraints of the form `lb ≤ x_i(t) ≤ ub`, `u_i(t) ≤ ub`, `v_i ≥ lb`, etc. (nonlinear path constraints are *not* concerned by this section). + +When the **same scalar component** is targeted by several box-constraint declarations, OptimalControl does **not** keep them as separate constraints. Instead, it merges them by taking the **intersection** of all declared bounds: + +- the effective lower bound is `max` of all declared lower bounds, +- the effective upper bound is `min` of all declared upper bounds, +- a single `@warn` is emitted per duplicated component, listing every contributing label, +- all labels that declared the component are preserved as **aliases** (accessible via the `aliases` field of [`state_constraints_box`](@ref), [`control_constraints_box`](@ref) and [`variable_constraints_box`](@ref); see the [manual on the OCP object](@ref manual-model)), +- if the intersection is empty (`max(lbs) > min(ubs)`), an `IncorrectArgument` exception is thrown. + +For instance, + +```julia +@def begin + t ∈ [0, 1], time + x = (q, v) ∈ R², state + u ∈ R, control + 0 ≤ q(t) ≤ 2, (q_wide) + 1 ≤ q(t) ≤ 3, (q_tight) + ẋ(t) == [v(t), u(t)] + ... +end +``` + +yields the effective constraint `1 ≤ q(t) ≤ 2`, with `aliases = [:q_wide, :q_tight]` for that component, and a warning reporting both labels. + +Conversely, the following declares an empty feasible set and raises an error at build time: + +```julia +@def begin + t ∈ [0, 1], time + x = (q, v) ∈ R², state + u ∈ R, control + 0 ≤ q(t) ≤ 1, (low) + 2 ≤ q(t) ≤ 3, (high) # max(lbs)=2 > min(ubs)=1 ⇒ IncorrectArgument + ẋ(t) == [v(t), u(t)] + ... +end +``` + ## [Mayer cost](@id manual-abstract-mayer) ```julia diff --git a/docs/src/manual-model.md b/docs/src/manual-model.md index 43eb3173d..d7a428651 100644 --- a/docs/src/manual-model.md +++ b/docs/src/manual-model.md @@ -1,5 +1,9 @@ # [The optimal control problem object: structure and usage](@id manual-model) +```@meta +Draft = false +``` + In this manual, we'll first recall the **main functionalities** you can use when working with an optimal control problem (OCP). This includes essential operations like: * **Solving an OCP**: How to find the optimal solution for your defined problem. @@ -281,11 +285,12 @@ state_constraints_box(ocp) # returns box constraints if any ``` !!! note "Tuple structure" - The returned tuple has the structure `(lb, indices, ub, labels)` where: + The returned tuple has the structure `(lb, indices, ub, labels, aliases)` where: - `lb`: vector of lower bounds - `indices`: vector of component indices (1-based) - `ub`: vector of upper bounds - `labels`: vector of constraint labels + - `aliases`: vector of vectors containing all labels that declared each component Get the dimension of state box constraints: @@ -332,11 +337,12 @@ control_constraints_box(ocp) # returns box constraints if any ``` !!! note "Tuple structure" - The returned tuple has the structure `(lb, indices, ub, labels)` where: + The returned tuple has the structure `(lb, indices, ub, labels, aliases)` where: - `lb`: vector of lower bounds - `indices`: vector of component indices (1-based) - `ub`: vector of upper bounds - `labels`: vector of constraint labels + - `aliases`: vector of vectors containing all labels that declared each component Get the dimension of control box constraints: @@ -383,11 +389,12 @@ variable_constraints_box(ocp) # returns box constraints if any ``` !!! note "Tuple structure" - The returned tuple has the structure `(lb, indices, ub, labels)` where: + The returned tuple has the structure `(lb, indices, ub, labels, aliases)` where: - `lb`: vector of lower bounds - `indices`: vector of component indices (1-based) - `ub`: vector of upper bounds - `labels`: vector of constraint labels + - `aliases`: vector of vectors containing all labels that declared each component Get the dimension of variable box constraints: @@ -613,9 +620,20 @@ dim_boundary_constraints_nl(ocp) # number of nonlinear boundary constraints Get the problem definition as a string: ```@example main -definition(ocp) # returns the OCP definition +definition(ocp) # returns the OCP definition as AbstractDefinition ``` +To extract the expression from the definition, use: + +```@example main +expr = expression(ocp) # returns the Expr from the definition +nothing # hide +``` + +!!! note + + The definition is optional and can be `EmptyDefinition`. Use `has_abstract_definition(ocp)` to check if a definition is present. + ### [Time dependence](@id manual-model-time-dependence) Optimal control problems can be **autonomous** or **non-autonomous**. In an autonomous problem, neither the dynamics nor the Lagrange cost explicitly depends on the time variable. @@ -658,3 +676,61 @@ ocp = @def begin end is_autonomous(ocp) ``` + +### Model predicates + +Check various properties of the optimal control problem model. + +#### Variable and control presence + +Check if the problem has optimization variables or control input: + +```@example main +has_variable(ocp) # true if problem has optimization variables +``` + +```@example main +has_control(ocp) # true if problem has control input +``` + +!!! note "Variant methods" + + Alternative methods are also available: + - `is_variable(ocp)` ≡ `has_variable(ocp)` + - `is_nonvariable(ocp)` ≡ `!has_variable(ocp)` + - `is_control_free(ocp)` ≡ `!has_control(ocp)` + +#### Definition presence + +Check if the problem has an abstract definition: + +```@example main +has_abstract_definition(ocp) # true if definition is present (not EmptyDefinition) +``` + +!!! note "Variant methods" + + - `is_abstractly_defined(ocp)` ≡ `has_abstract_definition(ocp)` + +#### Time dependence + +Check if the problem is non-autonomous (time-dependent): + +```@example main +is_nonautonomous(ocp) # true if dynamics or cost depend on time +``` + +!!! note "Variant methods" + + - `is_nonautonomous(ocp)` ≡ `!is_autonomous(ocp)` + +#### [Summary table](@id manual-model-summary-predicates) + +| Method | Returns | Description | +| -------- | --------- | ------------- | +| `has_variable(ocp)` | `Bool` | True if problem has optimization variables | +| `has_control(ocp)` | `Bool` | True if problem has control input | +| `has_abstract_definition(ocp)` | `Bool` | True if definition is present | +| `is_abstractly_defined(ocp)` | `Bool` | Alias for has_abstract_definition | +| `is_nonautonomous(ocp)` | `Bool` | True if time-dependent | +| `is_nonvariable(ocp)` | `Bool` | True if no optimization variables | diff --git a/docs/src/manual-solution.md b/docs/src/manual-solution.md index 2714f562d..e537b463b 100644 --- a/docs/src/manual-solution.md +++ b/docs/src/manual-solution.md @@ -1,5 +1,9 @@ # [The optimal control solution object: structure and usage](@id manual-solution) +```@meta +Draft = false +``` + In this manual, we'll first recall the **main functionalities** you can use when working with a solution of an optimal control problem. This includes essential operations like: * **Plotting a solution**: How to plot the optimal solution for your defined problem. @@ -273,6 +277,28 @@ plot(time_grid(sol), μ_u) plot(time_grid(sol), μ_v) ``` +!!! note "Signed multiplier convention" + + In all cases, `dual(sol, ocp, :label)` returns a **signed multiplier** `μ` (scalar, vector, or function of time, depending on the constraint type). The sign convention is, component-wise: + + - `μ > 0` ⇒ the lower-side constraint is active (e.g. `lb ≤ ...`), + - `μ < 0` ⇒ the upper-side constraint is active (e.g. `... ≤ ub`), + - `μ = 0` ⇒ the constraint is inactive (or the component is never constrained). + + For **nonlinear path and boundary constraints**, the solver already returns a signed multiplier natively; CTModels simply forwards it via `path_constraints_dual(sol)` / `boundary_constraints_dual(sol)`. + + For **box constraints** (state/control/variable components), the solver stores lower- and upper-bound multipliers *separately as non-negative quantities*. CTModels combines them into the signed multiplier explicitly: + + ``` + μ = μ_lb − μ_ub + ``` + + computed per targeted primal component. This is the value returned by `dual(sol, ocp, :label)` for a box-constraint label. + + Rationale for box constraints: after intersection of duplicate box declarations, the solver only sees a single effective bound per component, hence a single signed multiplier per component. If several labels target the same component, each label returns the **same** per-component multiplier (via the `aliases` mechanism; see the [OCP manual](@ref manual-model) and [Duplicate box constraints](@ref manual-abstract-box-dedup)). + + The raw non-negative `*_lb_dual(sol)` / `*_ub_dual(sol)` accessors (see below) remain available if the unsigned components are needed separately. + #### Box constraint duals Get dual variables for box constraints on state, control, and variable: @@ -301,6 +327,22 @@ variable_constraints_lb_dual(sol) # lower bound duals for variable variable_constraints_ub_dual(sol) # upper bound duals for variable ``` +#### Box constraint dual dimensions + +Get the dimensions of the box-constraint dual vectors (one entry per primal component that is box-constrained): + +```@example main +dim_dual_state_constraints_box(sol) # dimension of state box constraint duals +``` + +```@example main +dim_dual_control_constraints_box(sol) # dimension of control box constraint duals +``` + +```@example main +dim_dual_variable_constraints_box(sol) # dimension of variable box constraint duals +``` + #### Nonlinear constraint duals Get dual variables for nonlinear path and boundary constraints: @@ -313,19 +355,34 @@ path_constraints_dual(sol) # duals for nonlinear path constraints boundary_constraints_dual(sol) # duals for nonlinear boundary constraints ``` +Get the dimensions of the nonlinear constraints: + +```@example main +dim_path_constraints_nl(sol) # number of nonlinear path constraints +``` + +```@example main +dim_boundary_constraints_nl(sol) # number of nonlinear boundary constraints +``` + #### Summary table | Method | Returns | Description | | -------- | --------- | ------------- | -| `dual(sol, ocp, label)` | `Real` or `Function` | Dual for labeled constraint | -| `state_constraints_lb_dual(sol)` | Dual values | State lower bound duals | -| `state_constraints_ub_dual(sol)` | Dual values | State upper bound duals | -| `control_constraints_lb_dual(sol)` | Dual values | Control lower bound duals | -| `control_constraints_ub_dual(sol)` | Dual values | Control upper bound duals | -| `variable_constraints_lb_dual(sol)` | Dual values | Variable lower bound duals | -| `variable_constraints_ub_dual(sol)` | Dual values | Variable upper bound duals | -| `path_constraints_dual(sol)` | Dual values | Nonlinear path constraint duals | -| `boundary_constraints_dual(sol)` | Dual values | Nonlinear boundary constraint duals | +| `dual(sol, ocp, label)` | `Real` or `Function` | Signed dual for labeled constraint | +| `state_constraints_lb_dual(sol)` | Dual values | State lower bound duals (non-negative) | +| `state_constraints_ub_dual(sol)` | Dual values | State upper bound duals (non-negative) | +| `control_constraints_lb_dual(sol)` | Dual values | Control lower bound duals (non-negative) | +| `control_constraints_ub_dual(sol)` | Dual values | Control upper bound duals (non-negative) | +| `variable_constraints_lb_dual(sol)` | Dual values | Variable lower bound duals (non-negative) | +| `variable_constraints_ub_dual(sol)` | Dual values | Variable upper bound duals (non-negative) | +| `dim_dual_state_constraints_box(sol)` | `Int` | Dimension of state box constraint duals | +| `dim_dual_control_constraints_box(sol)` | `Int` | Dimension of control box constraint duals | +| `dim_dual_variable_constraints_box(sol)` | `Int` | Dimension of variable box constraint duals | +| `path_constraints_dual(sol)` | Dual values | Nonlinear path constraint duals (signed) | +| `boundary_constraints_dual(sol)` | Dual values | Nonlinear boundary constraint duals (signed) | +| `dim_path_constraints_nl(sol)` | `Int` | Number of nonlinear path constraints | +| `dim_boundary_constraints_nl(sol)` | `Int` | Number of nonlinear boundary constraints | ### Solution metadata diff --git a/src/imports/ctmodels.jl b/src/imports/ctmodels.jl index 60d4029cc..10d250bc5 100644 --- a/src/imports/ctmodels.jl +++ b/src/imports/ctmodels.jl @@ -54,6 +54,14 @@ import CTModels: is_initial_time_free, is_final_time_fixed, is_final_time_free, + has_variable, + is_variable, + has_control, + is_control_free, + has_abstract_definition, + is_abstractly_defined, + is_nonautonomous, + is_nonvariable, state_dimension, control_dimension, variable_dimension, @@ -72,9 +80,9 @@ import CTModels: variable_constraints_box, dim_path_constraints_nl, dim_boundary_constraints_nl, - dim_state_constraints_box, - dim_control_constraints_box, - dim_variable_constraints_box, + dim_dual_state_constraints_box, + dim_dual_control_constraints_box, + dim_dual_variable_constraints_box, state, control, variable, @@ -84,6 +92,7 @@ import CTModels: mayer, lagrange, definition, + expression, dual, iterations, status, diff --git a/test/README.md b/test/README.md index 670610017..d1427dcaa 100644 --- a/test/README.md +++ b/test/README.md @@ -14,30 +14,49 @@ For detailed guidelines on testing and coverage, please refer to: Tests are executed using the standard Julia Test interface, enhanced by `CTBase.TestRunner`. +### Convenience Command: `jtest` + +If the `jtest` command is available (custom script), you can use it for quick test execution: + +```bash +# Run all tests +jtest + +# Run specific test file(s) +jtest test_ctbase test_ctmodels + +# Run test directory +jtest reexport +``` + +If `jtest` is not available, use the manual commands below. + ### Default Run (All Enabled Tests) ```bash -julia --project=@. -e 'using Pkg; Pkg.test()' +julia --project=@. -e 'using Pkg; Pkg.test()' 2>&1 | tee /tmp/test_output.txt ``` +**Note:** Always use `tee` to capture output to `/tmp/test_output.txt` for later inspection with `grep`. + ### 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/*"])' +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["suite/reexport/*"])' 2>&1 | tee /tmp/test_output.txt ``` Multiple groups can be specified: ```bash -julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["suite/reexport/*", "suite/other/*"])' +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["suite/reexport/*", "suite/other/*"])' 2>&1 | tee /tmp/test_output.txt ``` ### Running All Tests (Including Optional/Long Tests) ```bash -julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["all"])' +julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["all"])' 2>&1 | tee /tmp/test_output.txt ``` --- @@ -47,7 +66,7 @@ julia --project=@. -e 'using Pkg; Pkg.test(; test_args=["all"])' To run tests with coverage and generate a report: ```bash -julia --project=@. -e 'using Pkg; Pkg.test("OptimalControl"; coverage=true); include("test/coverage.jl")' +julia --project=@. -e 'using Pkg; Pkg.test("OptimalControl"; coverage=true); include("test/coverage.jl")' 2>&1 | tee /tmp/test_coverage_output.txt ``` This will: diff --git a/test/suite/reexport/test_ctmodels.jl b/test/suite/reexport/test_ctmodels.jl index 21d120094..e71466a5e 100644 --- a/test/suite/reexport/test_ctmodels.jl +++ b/test/suite/reexport/test_ctmodels.jl @@ -107,6 +107,14 @@ function test_ctmodels() :is_initial_time_free, :is_final_time_fixed, :is_final_time_free, + :has_variable, + :is_variable, + :has_control, + :is_control_free, + :has_abstract_definition, + :is_abstractly_defined, + :is_nonautonomous, + :is_nonvariable, :state_dimension, :control_dimension, :variable_dimension, @@ -134,9 +142,9 @@ function test_ctmodels() :variable_constraints_box, :dim_path_constraints_nl, :dim_boundary_constraints_nl, - :dim_state_constraints_box, - :dim_control_constraints_box, - :dim_variable_constraints_box, + :dim_dual_state_constraints_box, + :dim_dual_control_constraints_box, + :dim_dual_variable_constraints_box, :state, :control, :variable, @@ -146,6 +154,7 @@ function test_ctmodels() :mayer, :lagrange, :definition, + :expression, :dual, :iterations, :status, From b39ee75a4527aed15a537d43ff557bcf2fba6807 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Tue, 21 Apr 2026 14:04:34 +0200 Subject: [PATCH 2/2] Restructure documentation TOC and fix constraint dimension functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flatten manual-model.md and manual-solution.md TOC: promote component sections from ### to ## level for better sidebar navigation - Replace static Content lists with Documenter @contents blocks (Depth = 2) - Redistribute Model predicates across logical sections: - has_variable/is_variable/is_nonvariable → Variable section - has_control/is_control_free → Control section - has_abstract_definition/is_abstractly_defined → Problem definition - is_nonautonomous → Time dependence - Remove standalone Model predicates section - Fix constraint dimension functions: - Restore dim_state_constraints_box, dim_control_constraints_box, dim_variable_constraints_box for Model (constraint counts) - Keep dim_dual_*_constraints_box for Solution (dual dimensions) - Update imports, tests, and API documentation to include both families - Remove Draft = false meta blocks from markdown files (use make.jl draft mode) - Enable - Flatten manual-model.md and manual-solution.md TOC: promo --- docs/make.jl | 2 +- docs/src/api/public.md | 3 + docs/src/manual-model.md | 181 ++++++++++++--------------- docs/src/manual-solution.md | 61 ++++----- src/imports/ctmodels.jl | 3 + test/suite/reexport/test_ctmodels.jl | 3 + 6 files changed, 120 insertions(+), 133 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 764bcac33..b26d47cbd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -153,7 +153,7 @@ cp( Draft = false ``` =# -draft = true # Draft mode: if true, @example blocks in markdown are not executed +draft = false # Draft mode: if true, @example blocks in markdown are not executed # ═══════════════════════════════════════════════════════════════════════════════ # Load extensions diff --git a/docs/src/api/public.md b/docs/src/api/public.md index 50d35ed6f..dcbdc4166 100644 --- a/docs/src/api/public.md +++ b/docs/src/api/public.md @@ -58,10 +58,13 @@ definition expression describe dim_boundary_constraints_nl +dim_control_constraints_box dim_dual_control_constraints_box dim_dual_state_constraints_box dim_dual_variable_constraints_box dim_path_constraints_nl +dim_state_constraints_box +dim_variable_constraints_box dimension discretize dual diff --git a/docs/src/manual-model.md b/docs/src/manual-model.md index d7a428651..633e06ccf 100644 --- a/docs/src/manual-model.md +++ b/docs/src/manual-model.md @@ -1,9 +1,5 @@ # [The optimal control problem object: structure and usage](@id manual-model) -```@meta -Draft = false -``` - In this manual, we'll first recall the **main functionalities** you can use when working with an optimal control problem (OCP). This includes essential operations like: * **Solving an OCP**: How to find the optimal solution for your defined problem. @@ -19,9 +15,10 @@ After covering these core functionalities, we'll delve into the **structure of a **Content** -* [Main functionalities](@ref manual-model-main-functionalities) -* [Model struct](@ref manual-model-struct) -* [API Reference by Component](@ref manual-model-api) +```@contents +Pages = ["manual-model.md"] +Depth = 2 +``` --- @@ -124,11 +121,7 @@ definition(ocp) We refer to the CTModels documentation for more details about this struct and its fields. -## [API Reference by Component](@id manual-model-api) - -This section provides a comprehensive reference of all methods available for inspecting and querying optimal control problems. Methods are organized by component for easy navigation. - -To illustrate the various methods, we define a more complex optimal control problem with free final time, variables, and various types of constraints: +To illustrate the various methods in the sections below, we define a more complex optimal control problem with free final time, variables, and various types of constraints: ```@example main ocp = @def begin @@ -149,11 +142,11 @@ end nothing # hide ``` -### Times +## Times The time component defines the temporal domain of the optimal control problem. -#### Times model +### Times model Get the times model: @@ -180,7 +173,7 @@ If you try to get the final time without providing the variable when it's free, final_time(ocp) # error: tf is free, need variable ``` -#### Time variable names +### Time variable names Get the names of the time variable and time bounds: @@ -196,7 +189,7 @@ initial_time_name(ocp) # returns "0" (initial time is fixed at 0) final_time_name(ocp) # returns "tf" (final time is a variable) ``` -#### Time fixedness predicates +### Time fixedness predicates Check whether initial or final times are fixed or free: @@ -225,7 +218,7 @@ has_fixed_final_time(ocp) # false (tf is free in this OCP) has_free_final_time(ocp) # true (tf is part of variable v) ``` -#### Autonomy +### Autonomy Check if the dynamics and Lagrange cost are autonomous (time-independent): @@ -235,7 +228,7 @@ is_autonomous(ocp) # false if dynamics or cost depend on time For more details on autonomy, see the [Time dependence](@ref manual-model-time-dependence) section below. -#### [Summary table](@id manual-model-summary-time) +### [Summary table](@id manual-model-summary-time) | Method | Returns | Description | | -------- | --------- | --------- | @@ -252,11 +245,11 @@ For more details on autonomy, see the [Time dependence](@ref manual-model-time-d | `has_free_final_time(ocp)` | `Bool` | True if tf is free | | `is_autonomous(ocp)` | `Bool` | True if time-independent | -### State +## State The state component represents the state variables of the optimal control problem. -#### State component information +### State component information Get the name, dimension, and component names of the state: @@ -276,7 +269,7 @@ state_components(ocp) # returns ["x", "y"] (component names) The component names are used when plotting the solution. See the [plot manual](@ref manual-plot). -#### State box constraints +### State box constraints Get the box constraints on the state (lower and upper bounds): @@ -298,7 +291,7 @@ Get the dimension of state box constraints: dim_state_constraints_box(ocp) # returns number of box constraints on state ``` -#### [Summary table](@id manual-model-summary-state) +### [Summary table](@id manual-model-summary-state) | Method | Returns | Description | | -------- | --------- | --------- | @@ -308,11 +301,11 @@ dim_state_constraints_box(ocp) # returns number of box constraints on state | `state_constraints_box(ocp)` | Box constraints | State box constraints | | `dim_state_constraints_box(ocp)` | `Int` | Number of state box constraints | -### Control +## Control The control component represents the control variables of the optimal control problem. -#### Control component information +### Control component information Get the name, dimension, and component names of the control: @@ -328,7 +321,7 @@ control_dimension(ocp) # returns 1 (dimension of control) control_components(ocp) # returns ["u"] (component names) ``` -#### Control box constraints +### Control box constraints Get the box constraints on the control: @@ -350,7 +343,19 @@ Get the dimension of control box constraints: dim_control_constraints_box(ocp) # returns number of box constraints on control ``` -#### [Summary table](@id manual-model-summary-control) +### Control presence + +Check whether the problem has a control input: + +```@example main +has_control(ocp) # true if problem has a control input +``` + +!!! note "Variant method" + + - `is_control_free(ocp)` ≡ `!has_control(ocp)` + +### [Summary table](@id manual-model-summary-control) | Method | Returns | Description | | -------- | --------- | --------- | @@ -359,12 +364,14 @@ dim_control_constraints_box(ocp) # returns number of box constraints on control | `control_components(ocp)` | `Vector{String}` | Control component names | | `control_constraints_box(ocp)` | Box constraints | Control box constraints | | `dim_control_constraints_box(ocp)` | `Int` | Number of control box constraints | +| `has_control(ocp)` | `Bool` | True if problem has a control input | +| `is_control_free(ocp)` | `Bool` | True if problem has no control (`≡ !has_control`) | -### Variable +## Variable The variable component represents the optimization variables (parameters) of the optimal control problem. -#### Variable component information +### Variable component information Get the name, dimension, and component names of the variable: @@ -380,7 +387,7 @@ variable_dimension(ocp) # returns 2 (dimension of variable) variable_components(ocp) # returns ["w", "tf"] (component names) ``` -#### Variable box constraints +### Variable box constraints Get the box constraints on the variable: @@ -402,7 +409,20 @@ Get the dimension of variable box constraints: dim_variable_constraints_box(ocp) # returns number of box constraints on variable ``` -#### [Summary table](@id manual-model-summary-variable) +### Variable presence + +Check whether the problem has optimization variables: + +```@example main +has_variable(ocp) # true if problem has optimization variables +``` + +!!! note "Variant methods" + + - `is_variable(ocp)` ≡ `has_variable(ocp)` + - `is_nonvariable(ocp)` ≡ `!has_variable(ocp)` + +### [Summary table](@id manual-model-summary-variable) | Method | Returns | Description | | -------- | --------- | --------- | @@ -411,12 +431,15 @@ dim_variable_constraints_box(ocp) # returns number of box constraints on variab | `variable_components(ocp)` | `Vector{String}` | Variable component names | | `variable_constraints_box(ocp)` | Box constraints | Variable box constraints | | `dim_variable_constraints_box(ocp)` | `Int` | Number of variable box constraints | +| `has_variable(ocp)` | `Bool` | True if problem has optimization variables | +| `is_variable(ocp)` | `Bool` | Alias for `has_variable` | +| `is_nonvariable(ocp)` | `Bool` | True if problem has no variables (`≡ !has_variable`) | -### Dynamics +## Dynamics The dynamics component defines the differential equations governing the state evolution. -#### Dynamics function +### Dynamics function The dynamics are stored as an in-place function of the form `f!(dx, t, x, u, v)`: @@ -438,17 +461,17 @@ The first argument `dx` is mutated upon call and contains the state derivative. * `u`: control * `v`: variable -#### [Summary table](@id manual-model-summary-dynamics) +### [Summary table](@id manual-model-summary-dynamics) | Method | Returns | Description | | -------- | --------- | --------- | | `dynamics(ocp)` | `Function` | In-place dynamics function f!(dx, t, x, u, v) | -### Objective +## Objective The objective component defines the cost function to minimize or maximize. -#### Criterion +### Criterion The criterion indicates whether the problem is a minimization or maximization: @@ -456,7 +479,7 @@ The criterion indicates whether the problem is a minimization or maximization: criterion(ocp) # returns :min or :max ``` -#### Objective form +### Objective form The objective function can be in Mayer form, Lagrange form, or Bolza form (combination of both): @@ -479,7 +502,7 @@ has_lagrange_cost(ocp) # true if Lagrange cost exists - `is_mayer_cost_defined(ocp)` ≡ `has_mayer_cost(ocp)` - `is_lagrange_cost_defined(ocp)` ≡ `has_lagrange_cost(ocp)` -#### Mayer cost +### Mayer cost Get the Mayer cost function with signature `g(x0, xf, v)`: @@ -487,7 +510,7 @@ Get the Mayer cost function with signature `g(x0, xf, v)`: g = mayer(ocp) # error if no Mayer cost ``` -#### Lagrange cost +### Lagrange cost Get the Lagrange cost function with signature `f⁰(t, x, u, v)`: @@ -500,7 +523,7 @@ v = [1.0, 2.0] f⁰(s, q, u, v) # returns the integrand value ``` -#### [Summary table](@id manual-model-summary-objective) +### [Summary table](@id manual-model-summary-objective) | Method | Returns | Description | | -------- | --------- | --------- | @@ -510,11 +533,11 @@ f⁰(s, q, u, v) # returns the integrand value | `mayer(ocp)` | `Function` | Mayer cost function g(x0, xf, v) | | `lagrange(ocp)` | `Function` | Lagrange cost function f⁰(t, x, u, v) | -### Constraints +## Constraints The constraints component defines the constraints on the optimal control problem. -#### Individual constraints +### Individual constraints Retrieve a specific constraint by its label using the `constraint` function. It returns a tuple `(type, f, lb, ub)`: @@ -557,7 +580,7 @@ println("type: ", type) println("val: ", f(s, q, u, v)) ``` -#### All constraints +### All constraints Get all constraints as a collection: @@ -565,7 +588,7 @@ Get all constraints as a collection: constraints(ocp) # returns all constraints ``` -#### Nonlinear constraints +### Nonlinear constraints Get nonlinear path and boundary constraints: @@ -602,7 +625,7 @@ dim_boundary_constraints_nl(ocp) # number of nonlinear boundary constraints To get the dual variable (or Lagrange multiplier) associated to a constraint, use the [`dual`](@ref) method on a solution. -#### [Summary table](@id manual-model-summary-constraints) +### [Summary table](@id manual-model-summary-constraints) | Method | Returns | Description | | -------- | --------- | --------- | @@ -613,9 +636,7 @@ dim_boundary_constraints_nl(ocp) # number of nonlinear boundary constraints | `dim_path_constraints_nl(ocp)` | `Int` | Number of nonlinear path constraints | | `dim_boundary_constraints_nl(ocp)` | `Int` | Number of nonlinear boundary constraints | -### Other accessors - -#### Problem definition +## Problem definition Get the problem definition as a string: @@ -634,7 +655,19 @@ nothing # hide The definition is optional and can be `EmptyDefinition`. Use `has_abstract_definition(ocp)` to check if a definition is present. -### [Time dependence](@id manual-model-time-dependence) +### Definition presence + +Check whether the problem carries an abstract definition: + +```@example main +has_abstract_definition(ocp) # true if definition is present (not EmptyDefinition) +``` + +!!! note "Variant method" + + - `is_abstractly_defined(ocp)` ≡ `has_abstract_definition(ocp)` + +## [Time dependence](@id manual-model-time-dependence) Optimal control problems can be **autonomous** or **non-autonomous**. In an autonomous problem, neither the dynamics nor the Lagrange cost explicitly depends on the time variable. @@ -677,60 +710,12 @@ end is_autonomous(ocp) ``` -### Model predicates - -Check various properties of the optimal control problem model. - -#### Variable and control presence - -Check if the problem has optimization variables or control input: - -```@example main -has_variable(ocp) # true if problem has optimization variables -``` - -```@example main -has_control(ocp) # true if problem has control input -``` - -!!! note "Variant methods" - - Alternative methods are also available: - - `is_variable(ocp)` ≡ `has_variable(ocp)` - - `is_nonvariable(ocp)` ≡ `!has_variable(ocp)` - - `is_control_free(ocp)` ≡ `!has_control(ocp)` - -#### Definition presence - -Check if the problem has an abstract definition: - -```@example main -has_abstract_definition(ocp) # true if definition is present (not EmptyDefinition) -``` - -!!! note "Variant methods" - - - `is_abstractly_defined(ocp)` ≡ `has_abstract_definition(ocp)` - -#### Time dependence - -Check if the problem is non-autonomous (time-dependent): +The variant predicate `is_nonautonomous` is also available and returns the opposite of `is_autonomous`: ```@example main is_nonautonomous(ocp) # true if dynamics or cost depend on time ``` -!!! note "Variant methods" +!!! note "Variant method" - `is_nonautonomous(ocp)` ≡ `!is_autonomous(ocp)` - -#### [Summary table](@id manual-model-summary-predicates) - -| Method | Returns | Description | -| -------- | --------- | ------------- | -| `has_variable(ocp)` | `Bool` | True if problem has optimization variables | -| `has_control(ocp)` | `Bool` | True if problem has control input | -| `has_abstract_definition(ocp)` | `Bool` | True if definition is present | -| `is_abstractly_defined(ocp)` | `Bool` | Alias for has_abstract_definition | -| `is_nonautonomous(ocp)` | `Bool` | True if time-dependent | -| `is_nonvariable(ocp)` | `Bool` | True if no optimization variables | diff --git a/docs/src/manual-solution.md b/docs/src/manual-solution.md index e537b463b..07b5a8099 100644 --- a/docs/src/manual-solution.md +++ b/docs/src/manual-solution.md @@ -1,9 +1,5 @@ # [The optimal control solution object: structure and usage](@id manual-solution) -```@meta -Draft = false -``` - In this manual, we'll first recall the **main functionalities** you can use when working with a solution of an optimal control problem. This includes essential operations like: * **Plotting a solution**: How to plot the optimal solution for your defined problem. @@ -13,11 +9,12 @@ After covering these core functionalities, we'll delve into the **structure of a --- -## Content +**Content** -* [Main functionalities](@ref manual-solution-main-functionalities) -* [Solution struct](@ref manual-solution-struct) -* [API Reference by Component](@ref manual-solution-api) +```@contents +Pages = ["manual-solution.md"] +Depth = 2 +``` --- @@ -103,15 +100,11 @@ You can also retrieve the original optimal control problem from the solution: model(sol) # returns the original OCP model ``` -## [API Reference by Component](@id manual-solution-api) - -This section provides a comprehensive reference of all methods available for inspecting and querying optimal control solutions. Methods are organized by component for easy navigation. - -### Trajectories +## Trajectories The trajectory component provides access to the state, control, variable, and costate trajectories. -#### State trajectory +### State trajectory Get the state trajectory as a function of time: @@ -136,7 +129,7 @@ The state function can be evaluated at any time within the problem horizon, even x(0.25) # still works: interpolated value ``` -#### Control trajectory +### Control trajectory Get the control trajectory as a function of time: @@ -148,7 +141,7 @@ u = control(sol) # returns a function of time u(t) # returns control value at t ``` -#### Variable values +### Variable values Get the optimization variable values: @@ -156,7 +149,7 @@ Get the optimization variable values: v = variable(sol) # returns an empty vector if no variable ``` -#### Costate trajectory +### Costate trajectory Get the costate (adjoint) trajectory as a function of time: @@ -168,7 +161,7 @@ p = costate(sol) # returns a function of time p(t) # returns costate vector at t ``` -#### Time information +### Time information Get time-related information from the solution: @@ -199,7 +192,7 @@ times(sol) # returns the TimesModel struct containing time information !!! note "Trajectory data formats" Trajectories (`state`, `control`, `costate`, `path_constraints_dual`) can be provided either as matrices (rows = time points, columns = components) or as functions `t -> vector` for interpolated or analytical data. -#### [Summary table](@id manual-solution-summary-trajectories) +### [Summary table](@id manual-solution-summary-trajectories) | Method | Returns | Description | | -------- | --------- | ------------- | @@ -210,11 +203,11 @@ times(sol) # returns the TimesModel struct containing time information | `time_grid(sol)` | `Vector{Float64}` | Discretization time grid | | `times(sol)` | `TimesModel` | TimesModel struct containing time information | -### Objective +## Objective The objective component provides access to the objective value. -#### Objective value +### Objective value Get the optimal objective value: @@ -222,13 +215,13 @@ Get the optimal objective value: objective(sol) # returns the objective value ``` -#### [Summary table](@id manual-solution-summary-objective) +### [Summary table](@id manual-solution-summary-objective) | Method | Returns | Description | | -------- | --------- | ------------- | | `objective(sol)` | `Float64` | Objective value | -### Dual variables +## Dual variables The dual variables (Lagrange multipliers) provide sensitivity information about constraints. @@ -253,7 +246,7 @@ sol = solve(ocp; display=false) nothing # hide ``` -#### Dual of labeled constraints +### Dual of labeled constraints Get the dual variable for a specific labeled constraint: @@ -299,7 +292,7 @@ plot(time_grid(sol), μ_v) The raw non-negative `*_lb_dual(sol)` / `*_ub_dual(sol)` accessors (see below) remain available if the unsigned components are needed separately. -#### Box constraint duals +### Box constraint duals Get dual variables for box constraints on state, control, and variable: @@ -327,7 +320,7 @@ variable_constraints_lb_dual(sol) # lower bound duals for variable variable_constraints_ub_dual(sol) # upper bound duals for variable ``` -#### Box constraint dual dimensions +### Box constraint dual dimensions Get the dimensions of the box-constraint dual vectors (one entry per primal component that is box-constrained): @@ -343,7 +336,7 @@ dim_dual_control_constraints_box(sol) # dimension of control box constraint dua dim_dual_variable_constraints_box(sol) # dimension of variable box constraint duals ``` -#### Nonlinear constraint duals +### Nonlinear constraint duals Get dual variables for nonlinear path and boundary constraints: @@ -365,7 +358,7 @@ dim_path_constraints_nl(sol) # number of nonlinear path constraints dim_boundary_constraints_nl(sol) # number of nonlinear boundary constraints ``` -#### Summary table +### Summary table | Method | Returns | Description | | -------- | --------- | ------------- | @@ -384,11 +377,11 @@ dim_boundary_constraints_nl(sol) # number of nonlinear boundary constraints | `dim_path_constraints_nl(sol)` | `Int` | Number of nonlinear path constraints | | `dim_boundary_constraints_nl(sol)` | `Int` | Number of nonlinear boundary constraints | -### Solution metadata +## Solution metadata The solution metadata provides information about the solver performance and status. -#### Solver status +### Solver status Check if the solution was successful: @@ -408,7 +401,7 @@ Get the solver message: message(sol) # returns solver message string ``` -#### Iteration count +### Iteration count Get the number of solver iterations: @@ -416,7 +409,7 @@ Get the number of solver iterations: iterations(sol) # returns iteration count ``` -#### Constraints violation +### Constraints violation Get the maximum constraint violation: @@ -424,7 +417,7 @@ Get the maximum constraint violation: constraints_violation(sol) # returns max violation ``` -#### Additional solver information +### Additional solver information Get additional solver-specific information: @@ -432,7 +425,7 @@ Get additional solver-specific information: infos(sol) # returns dictionary of solver info ``` -#### [Summary table](@id manual-solution-summary-solver) +### [Summary table](@id manual-solution-summary-solver) | Method | Returns | Description | | -------- | --------- | ------------- | diff --git a/src/imports/ctmodels.jl b/src/imports/ctmodels.jl index 10d250bc5..9e2aab40a 100644 --- a/src/imports/ctmodels.jl +++ b/src/imports/ctmodels.jl @@ -80,6 +80,9 @@ import CTModels: variable_constraints_box, dim_path_constraints_nl, dim_boundary_constraints_nl, + dim_state_constraints_box, + dim_control_constraints_box, + dim_variable_constraints_box, dim_dual_state_constraints_box, dim_dual_control_constraints_box, dim_dual_variable_constraints_box, diff --git a/test/suite/reexport/test_ctmodels.jl b/test/suite/reexport/test_ctmodels.jl index e71466a5e..63eb2b81b 100644 --- a/test/suite/reexport/test_ctmodels.jl +++ b/test/suite/reexport/test_ctmodels.jl @@ -142,6 +142,9 @@ function test_ctmodels() :variable_constraints_box, :dim_path_constraints_nl, :dim_boundary_constraints_nl, + :dim_state_constraints_box, + :dim_control_constraints_box, + :dim_variable_constraints_box, :dim_dual_state_constraints_box, :dim_dual_control_constraints_box, :dim_dual_variable_constraints_box,