diff --git a/docs/make.jl b/docs/make.jl
index 5609daab..29ed025b 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -207,6 +207,8 @@ with_api_reference(src_dir, ext_dir) do api_pages
"Basic Examples" => [
"Energy minimisation" => "example-double-integrator-energy.md",
"Time mininimisation" => "example-double-integrator-time.md",
+ "Control-free problems" => "example-control-free.md",
+ "Singular control" => "example-singular-control.md",
],
"Manual" => [
"Define a problem" => "manual-abstract.md",
@@ -221,6 +223,7 @@ with_api_reference(src_dir, ext_dir) do api_pages
],
"Solution characteristics" => "manual-solution.md",
"Plot a solution" => "manual-plot.md",
+ "Differential geometry tools" => "manual-differential-geometry.md",
"Compute flows" => [
"From optimal control problems" => "manual-flow-ocp.md",
"From Hamiltonians and others" => "manual-flow-others.md",
diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml
index 64f46c30..2140cea3 100644
--- a/docs/src/assets/Manifest.toml
+++ b/docs/src/assets/Manifest.toml
@@ -1,6 +1,6 @@
# This file is machine-generated - editing it directly is not advised
-julia_version = "1.12.1"
+julia_version = "1.12.5"
manifest_format = "2.0"
project_hash = "d470f84ad521e691a87a863486162d9840a77916"
@@ -56,9 +56,9 @@ version = "0.4.5"
[[deps.Accessors]]
deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
-git-tree-sha1 = "856ecd7cebb68e5fc87abecd2326ad59f0f911f3"
+git-tree-sha1 = "2eeb2c9bef11013efc6f8f97f32ee59b146b09fb"
uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
-version = "0.1.43"
+version = "0.1.44"
[deps.Accessors.extensions]
AxisKeysExt = "AxisKeys"
@@ -141,9 +141,9 @@ version = "1.11.0"
[[deps.Atomix]]
deps = ["UnsafeAtomics"]
-git-tree-sha1 = "29bb0eb6f578a587a49da16564705968667f5fa8"
+git-tree-sha1 = "b8651b2eb5796a386b0398a20b519a6a6150f75c"
uuid = "a9b6321e-bd34-4604-b9c9-b65b8de01458"
-version = "1.1.2"
+version = "1.1.3"
[deps.Atomix.extensions]
AtomixCUDAExt = "CUDA"
@@ -180,9 +180,9 @@ version = "0.1.6"
[[deps.BracketingNonlinearSolve]]
deps = ["CommonSolve", "ConcreteStructs", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"]
-git-tree-sha1 = "4999dff8efd76814f6662519b985aeda975a1924"
+git-tree-sha1 = "d9b66401c1fa982c7ca984d0566af5a9b3551420"
uuid = "70df07ce-3d50-431d-a3e7-ca6ddb60ac1e"
-version = "1.11.0"
+version = "1.12.0"
weakdeps = ["ChainRulesCore", "ForwardDiff"]
[deps.BracketingNonlinearSolve.extensions]
@@ -208,9 +208,9 @@ version = "0.2.7"
[[deps.CTBase]]
deps = ["DocStringExtensions"]
-git-tree-sha1 = "fac598a2d962f564824d37494a307652941c723d"
+git-tree-sha1 = "2f689cdb1c28d452f143f2c76897cd5273e89cf7"
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
-version = "0.18.6-beta"
+version = "0.18.7"
[deps.CTBase.extensions]
CoveragePostprocessing = ["Coverage"]
@@ -225,16 +225,16 @@ version = "0.18.6-beta"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.CTDirect]]
-deps = ["ADNLPModels", "CTModels", "CTSolvers", "DocStringExtensions", "ExaModels", "SolverCore", "SparseArrays"]
-git-tree-sha1 = "9d9c1c5c1d17cc02692378ae49d03e90b0e469e8"
+deps = ["ADNLPModels", "CTModels", "CTSolvers", "DocStringExtensions", "ExaModels", "SolverCore", "SparseArrays", "SparseConnectivityTracer"]
+git-tree-sha1 = "db1699ffab2f31aeda5dbb61dfa9015773cb70fc"
uuid = "790bbbee-bee9-49ee-8912-a9de031322d5"
-version = "1.0.5"
+version = "1.0.6"
[[deps.CTFlows]]
deps = ["CTBase", "CTModels", "DocStringExtensions", "ForwardDiff", "LinearAlgebra", "MLStyle", "MacroTools"]
-git-tree-sha1 = "71ec5ebc9464b1d79e64ea2e53675fa0506e20c6"
+git-tree-sha1 = "d89a9defa5901dd1d745042c8e1ea5af87977104"
uuid = "1c39547c-7794-42f7-af83-d98194f657c2"
-version = "0.8.16-beta"
+version = "0.8.19-beta"
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 = "c90116a8e74243ae258329325f0b7545cc5385f5"
+git-tree-sha1 = "c878ed38da4040f618a459c30cec8e53286ddc26"
uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d"
-version = "0.9.10-beta"
+version = "0.9.11"
weakdeps = ["JLD2", "JSON3", "Plots"]
[deps.CTModels.extensions]
@@ -260,9 +260,9 @@ version = "0.8.10-beta"
[[deps.CTSolvers]]
deps = ["ADNLPModels", "CTBase", "CTModels", "CommonSolve", "DocStringExtensions", "ExaModels", "KernelAbstractions", "NLPModels", "SolverCore"]
-git-tree-sha1 = "21092a83de84623f3d2e55b301ffa54d8cb10e3b"
+git-tree-sha1 = "46f28bf27a6158333527417d97787b8125b1a07c"
uuid = "d3e8d392-8e4b-4d9b-8e92-d7d4e3650ef6"
-version = "0.4.10-beta"
+version = "0.4.12"
[deps.CTSolvers.extensions]
CTSolversCUDA = "CUDA"
@@ -341,16 +341,16 @@ uuid = "4889d778-9329-5762-9fec-0578a5d30366"
version = "0.7.1+0"
[[deps.Cairo_jll]]
-deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
-git-tree-sha1 = "a21c5464519504e41e0cbc91f0188e8ca23d7440"
+deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
+git-tree-sha1 = "d0efe2c6fdcdaa1c161d206aa8b933788397ec71"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
-version = "1.18.5+1"
+version = "1.18.6+0"
[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra"]
-git-tree-sha1 = "e4c6a16e77171a5f5e25e9646617ab1c276c5607"
+git-tree-sha1 = "12177ad6b3cad7fd50c8b3825ce24a99ad61c18f"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
-version = "1.26.0"
+version = "1.26.1"
weakdeps = ["SparseArrays"]
[deps.ChainRulesCore.extensions]
@@ -512,9 +512,9 @@ version = "1.8.1"
[[deps.DataStructures]]
deps = ["OrderedCollections"]
-git-tree-sha1 = "e357641bb3e0638d353c4b29ea0e40ea644066a6"
+git-tree-sha1 = "e86f4a2805f7f19bec5129bc9150c38208e5dc23"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
-version = "0.19.3"
+version = "0.19.4"
[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
@@ -540,13 +540,14 @@ 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 = "1719cd1b0a12e01775dc6db1577dd6ace1798fee"
+git-tree-sha1 = "803d463050fae6121f5d15fd449e9538cb993ad9"
uuid = "2b5f629d-d688-5b77-993f-72d75c75574e"
-version = "6.210.1"
+version = "6.214.1"
[deps.DiffEqBase.extensions]
DiffEqBaseCUDAExt = "CUDA"
DiffEqBaseChainRulesCoreExt = "ChainRulesCore"
+ DiffEqBaseDynamicQuantitiesExt = "DynamicQuantities"
DiffEqBaseEnzymeExt = ["ChainRulesCore", "Enzyme"]
DiffEqBaseFlexUnitsExt = "FlexUnits"
DiffEqBaseForwardDiffExt = ["ForwardDiff"]
@@ -565,6 +566,7 @@ version = "6.210.1"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
+ DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
FlexUnits = "76e01b6b-c995-4ce6-8559-91e72a3d4e95"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -678,7 +680,7 @@ version = "0.2.0"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
-version = "1.6.0"
+version = "1.7.0"
[[deps.EnumX]]
git-tree-sha1 = "c49898e8438c828577f04b92fc9368c388ac783c"
@@ -686,9 +688,9 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
version = "1.0.7"
[[deps.EnzymeCore]]
-git-tree-sha1 = "990991b8aa76d17693a98e3a915ac7aa49f08d1a"
+git-tree-sha1 = "24bbb6fc8fb87eb71c1f8d00184a60fc22c63903"
uuid = "f151be2c-9106-41f4-ab19-57ee4f262869"
-version = "0.8.18"
+version = "0.8.19"
weakdeps = ["Adapt", "ChainRulesCore"]
[deps.EnzymeCore.extensions]
@@ -779,10 +781,15 @@ uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "8.0.1+1"
[[deps.FastBroadcast]]
-deps = ["ArrayInterface", "LinearAlgebra", "Polyester", "Static", "StaticArrayInterface", "StrideArraysCore"]
-git-tree-sha1 = "ab1b34570bcdf272899062e1a56285a53ecaae08"
+deps = ["ArrayInterface", "LinearAlgebra"]
+git-tree-sha1 = "e3e64918b1604ba8b1734c4a27febdfe5d09e235"
uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898"
-version = "0.3.5"
+version = "1.3.1"
+weakdeps = ["Polyester", "Static"]
+
+ [deps.FastBroadcast.extensions]
+ FastBroadcastPolyesterExt = "Polyester"
+ FastBroadcastStaticExt = "Static"
[[deps.FastClosures]]
git-tree-sha1 = "acebe244d53ee1b461970f8910c235b259e772ef"
@@ -887,9 +894,9 @@ version = "1.3.7"
[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"]
-git-tree-sha1 = "eef4c86803f47dcb61e9b8790ecaa96956fdd8ae"
+git-tree-sha1 = "cddeab6487248a39dae1a960fff0ac17b2a28888"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
-version = "1.3.2"
+version = "1.3.3"
weakdeps = ["StaticArrays"]
[deps.ForwardDiff.extensions]
@@ -897,9 +904,9 @@ weakdeps = ["StaticArrays"]
[[deps.FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31"
+git-tree-sha1 = "70329abc09b886fd2c5d94ad2d9527639c421e3e"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
-version = "2.13.4+0"
+version = "2.14.3+1"
[[deps.FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -913,10 +920,10 @@ uuid = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
version = "1.1.3"
[[deps.FunctionWrappersWrappers]]
-deps = ["FunctionWrappers"]
-git-tree-sha1 = "b104d487b34566608f8b4e1c39fb0b10aa279ff8"
+deps = ["FunctionWrappers", "PrecompileTools", "TruncatedStacktraces"]
+git-tree-sha1 = "6874da243fb93e34201d7d4587ffa0e920682f64"
uuid = "77dc65aa-8811-40c2-897b-53d922fa7daf"
-version = "0.1.3"
+version = "1.0.0"
[[deps.Future]]
deps = ["Random"]
@@ -947,15 +954,15 @@ version = "0.2.0"
[[deps.GPUCompiler]]
deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "PrecompileTools", "Preferences", "Scratch", "Serialization", "TOML", "Tracy", "UUIDs"]
-git-tree-sha1 = "966946d226e8b676ca6409454718accb18c34c54"
+git-tree-sha1 = "fedfe5e7db7035271c3f58359007f971da1dde87"
uuid = "61eb1bfa-7361-4325-ad38-22787b887f55"
-version = "1.8.2"
+version = "1.9.1"
[[deps.GPUToolbox]]
deps = ["LLVM"]
-git-tree-sha1 = "9e9186b09a13b7f094f87d1a9bb266d8780e1b1c"
+git-tree-sha1 = "a589b6c1a0eff953571f5d8b0474f5020831114d"
uuid = "096a3bc2-3ced-46d0-87f4-dd12716f4bfc"
-version = "1.0.0"
+version = "1.1.1"
[[deps.GR]]
deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"]
@@ -1059,9 +1066,9 @@ version = "1.13.1+0"
[[deps.Hwloc_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "XML2_jll", "Xorg_libpciaccess_jll"]
-git-tree-sha1 = "157e2e5838984449e44af851a52fe374d56b9ada"
+git-tree-sha1 = "baaaebd42ed9ee1bd9173cfd56910e55a8622ee1"
uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
-version = "2.13.0+0"
+version = "2.13.0+1"
[[deps.IOCapture]]
deps = ["Logging", "Random"]
@@ -1143,9 +1150,9 @@ version = "1.0.0"
[[deps.JLD2]]
deps = ["ChunkCodecLibZlib", "ChunkCodecLibZstd", "FileIO", "MacroTools", "Mmap", "OrderedCollections", "PrecompileTools", "ScopedValues"]
-git-tree-sha1 = "8f8ff711442d1f4cfc0d86133e7ee03d62ec9b98"
+git-tree-sha1 = "941f87a0ae1b14d1ac2fa57245425b23a9d7a516"
uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
-version = "0.6.3"
+version = "0.6.4"
weakdeps = ["UnPack"]
[deps.JLD2.extensions]
@@ -1165,9 +1172,9 @@ version = "1.7.1"
[[deps.JSON]]
deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
-git-tree-sha1 = "b3ad4a0255688dcb895a52fafbaae3023b588a90"
+git-tree-sha1 = "67c6f1f085cb2671c93fe34244c9cccde30f7a26"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
-version = "1.4.0"
+version = "1.5.0"
[deps.JSON.extensions]
JSONArrowExt = ["ArrowTypes"]
@@ -1230,9 +1237,9 @@ version = "15.1.0"
[[deps.KernelAbstractions]]
deps = ["Adapt", "Atomix", "InteractiveUtils", "MacroTools", "PrecompileTools", "Requires", "StaticArrays", "UUIDs"]
-git-tree-sha1 = "fb14a863240d62fbf5922bf9f8803d7df6c62dc8"
+git-tree-sha1 = "f2e76d3ced51a2a9e185abc0b97494c7273f649f"
uuid = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
-version = "0.9.40"
+version = "0.9.41"
weakdeps = ["EnzymeCore", "LinearAlgebra", "SparseArrays"]
[deps.KernelAbstractions.extensions]
@@ -1254,9 +1261,9 @@ version = "3.100.3+0"
[[deps.LDLFactorizations]]
deps = ["AMD", "LinearAlgebra", "SparseArrays", "Test"]
-git-tree-sha1 = "70f582b446a1c3ad82cf87e62b878668beef9d13"
+git-tree-sha1 = "d75c5cb8d6ac9c359ae9eb8e87e446ba9f221dd4"
uuid = "40e66cde-538c-5869-a4ad-c39174c6795b"
-version = "0.10.1"
+version = "0.10.2"
[[deps.LERC_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
@@ -1291,12 +1298,6 @@ git-tree-sha1 = "eb62a3deb62fc6d8822c0c4bef73e4412419c5d8"
uuid = "1d63c593-3942-5779-bab2-d838dc0a180e"
version = "18.1.8+0"
-[[deps.LZO_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl"]
-git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a"
-uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
-version = "2.10.3+0"
-
[[deps.LaTeXStrings]]
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
@@ -1344,7 +1345,7 @@ version = "0.6.4"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
-version = "8.11.1+1"
+version = "8.15.0+0"
[[deps.LibGit2]]
deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
@@ -1451,10 +1452,10 @@ version = "2.13.0"
Metal = "dde4c033-4e86-420c-a63e-0dd931031962"
[[deps.LinearSolve]]
-deps = ["ArrayInterface", "ChainRulesCore", "ConcreteStructs", "DocStringExtensions", "EnumX", "GPUArraysCore", "InteractiveUtils", "Krylov", "Libdl", "LinearAlgebra", "MKL_jll", "Markdown", "OpenBLAS_jll", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "Setfield", "StaticArraysCore"]
-git-tree-sha1 = "57a7bea58da7de6906f2621294ea35656cb40c5f"
+deps = ["ArrayInterface", "ConcreteStructs", "DocStringExtensions", "EnumX", "GPUArraysCore", "InteractiveUtils", "Krylov", "Libdl", "LinearAlgebra", "MKL_jll", "Markdown", "OpenBLAS_jll", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "Setfield", "StaticArraysCore"]
+git-tree-sha1 = "1ddad5f2b0717f71f1588b3519e2f7d80fc2ce65"
uuid = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
-version = "3.66.0"
+version = "3.69.0"
[deps.LinearSolve.extensions]
LinearSolveAMDGPUExt = "AMDGPU"
@@ -1465,6 +1466,7 @@ version = "3.66.0"
LinearSolveCUDAExt = "CUDA"
LinearSolveCUDSSExt = "CUDSS"
LinearSolveCUSOLVERRFExt = ["CUSOLVERRF", "SparseArrays"]
+ LinearSolveChainRulesCoreExt = "ChainRulesCore"
LinearSolveCliqueTreesExt = ["CliqueTrees", "SparseArrays"]
LinearSolveEnzymeExt = ["EnzymeCore", "SparseArrays"]
LinearSolveFastAlmostBandedMatricesExt = "FastAlmostBandedMatrices"
@@ -1492,6 +1494,7 @@ version = "3.66.0"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
CUDSS = "45b445bb-4962-46a0-9369-b4df9d0f772e"
CUSOLVERRF = "a8cc9031-bad2-4722-94f5-40deabb4245c"
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8"
EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869"
FastAlmostBandedMatrices = "9d29842c-ecb8-4973-b1e9-a27b1157504e"
@@ -1569,9 +1572,9 @@ version = "0.5.16"
[[deps.MadNCL]]
deps = ["Atomix", "KernelAbstractions", "LinearAlgebra", "MadNLP", "NLPModels", "Printf", "Random", "SparseArrays"]
-git-tree-sha1 = "fd3e96ec2e760b48084345414b7943a7b607aa0c"
+git-tree-sha1 = "8cdb50494fa73f9af44aabadfac51d39413a707e"
uuid = "434a0bcb-5a7c-42b2-a9d3-9e3f760e7af0"
-version = "0.2.0"
+version = "0.2.1"
weakdeps = ["MadNLPGPU"]
[deps.MadNCL.extensions]
@@ -1678,7 +1681,7 @@ version = "0.3.7"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
-version = "2025.5.20"
+version = "2025.11.4"
[[deps.MuladdMacro]]
git-tree-sha1 = "cac9cc5499c25554cba55cd3c30543cff5ca4fab"
@@ -1699,9 +1702,9 @@ version = "0.11.2"
[[deps.NLPModelsKnitro]]
deps = ["KNITRO", "NLPModels", "NLPModelsModifiers", "SolverCore"]
-git-tree-sha1 = "f35898a07a02c4f46bc08eca0b19116e894ef464"
+git-tree-sha1 = "90e9e12bf859aa3b9453a852015a552592b8ea95"
uuid = "bec4dd0d-7755-52d5-9a02-22f0ffc7efcb"
-version = "0.10.1"
+version = "0.10.2"
[[deps.NLPModelsModifiers]]
deps = ["FastClosures", "LinearAlgebra", "LinearOperators", "NLPModels", "Printf", "SparseArrays"]
@@ -1742,10 +1745,10 @@ uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.3.0"
[[deps.NonlinearSolve]]
-deps = ["ADTypes", "ArrayInterface", "BracketingNonlinearSolve", "CommonSolve", "ConcreteStructs", "DifferentiationInterface", "FastClosures", "FiniteDiff", "ForwardDiff", "LineSearch", "LinearAlgebra", "LinearSolve", "NonlinearSolveBase", "NonlinearSolveFirstOrder", "NonlinearSolveQuasiNewton", "NonlinearSolveSpectralMethods", "PrecompileTools", "Preferences", "Reexport", "SciMLBase", "SciMLLogging", "SimpleNonlinearSolve", "StaticArraysCore", "SymbolicIndexingInterface"]
-git-tree-sha1 = "d27bcf0cebf8786edcc2eaa4455c959e680334e7"
+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 = "373b688150f160b68083733657246532389e4280"
uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
-version = "4.16.0"
+version = "4.17.0"
[deps.NonlinearSolve.extensions]
NonlinearSolveFastLevenbergMarquardtExt = "FastLevenbergMarquardt"
@@ -1775,10 +1778,10 @@ version = "4.16.0"
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
[[deps.NonlinearSolveBase]]
-deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "Compat", "ConcreteStructs", "DifferentiationInterface", "EnzymeCore", "FastClosures", "LinearAlgebra", "LogExpFunctions", "Markdown", "MaybeInplace", "PreallocationTools", "Preferences", "Printf", "RecursiveArrayTools", "SciMLBase", "SciMLJacobianOperators", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "StaticArraysCore", "SymbolicIndexingInterface", "TimerOutputs"]
-git-tree-sha1 = "4f595a0977d6e048fa1e3c382b088b950f8c7934"
+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 = "7deb924291e30ef27e8823e9d048ffb98ca6ffce"
uuid = "be0214bd-f91f-a760-ac4e-3421ce2b2da0"
-version = "2.15.0"
+version = "2.20.0"
[deps.NonlinearSolveBase.extensions]
NonlinearSolveBaseBandedMatricesExt = "BandedMatrices"
@@ -1869,7 +1872,7 @@ version = "1.6.1"
[[deps.OpenSSL_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
-version = "3.5.1+0"
+version = "3.5.4+0"
[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
@@ -1890,27 +1893,27 @@ version = "1.8.1"
[[deps.OrdinaryDiffEq]]
deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "ExponentialUtilities", "FastBroadcast", "FastClosures", "FillArrays", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "InteractiveUtils", "LineSearches", "LinearAlgebra", "LinearSolve", "Logging", "MacroTools", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqAdamsBashforthMoulton", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqExplicitRK", "OrdinaryDiffEqExponentialRK", "OrdinaryDiffEqExtrapolation", "OrdinaryDiffEqFIRK", "OrdinaryDiffEqFeagin", "OrdinaryDiffEqFunctionMap", "OrdinaryDiffEqHighOrderRK", "OrdinaryDiffEqIMEXMultistep", "OrdinaryDiffEqLinear", "OrdinaryDiffEqLowOrderRK", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqNordsieck", "OrdinaryDiffEqPDIRK", "OrdinaryDiffEqPRK", "OrdinaryDiffEqQPRK", "OrdinaryDiffEqRKN", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqStabilizedIRK", "OrdinaryDiffEqStabilizedRK", "OrdinaryDiffEqSymplecticRK", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "Polyester", "PreallocationTools", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SparseArrays", "Static", "StaticArrayInterface", "StaticArrays", "TruncatedStacktraces"]
-git-tree-sha1 = "924e1db15095c7df6b844231c00c40d756a7553d"
+git-tree-sha1 = "47271adac597af08263b6ea2669e93040b45c4d0"
uuid = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
-version = "6.108.0"
+version = "6.111.0"
[[deps.OrdinaryDiffEqAdamsBashforthMoulton]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqLowOrderRK", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "8307937159c3aeec5f19f4b661d82d96d25a3ff1"
+git-tree-sha1 = "2e44acb684dfcdc2e41851a988733e30b28a8478"
uuid = "89bda076-bce5-4f1c-845f-551c83cdda9a"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqBDF]]
deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqSDIRK", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays", "TruncatedStacktraces"]
-git-tree-sha1 = "22b0c4f7939af140b7f7f4ce2cce90d9f72fa515"
+git-tree-sha1 = "8ebd2eb20a8ebb12911b40282d5b41e2db6d107b"
uuid = "6ad6398a-0878-4a85-9266-38940aa047c8"
-version = "1.22.0"
+version = "1.24.0"
[[deps.OrdinaryDiffEqCore]]
-deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "ConcreteStructs", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "EnzymeCore", "FastBroadcast", "FastClosures", "FastPower", "FillArrays", "FunctionWrappersWrappers", "InteractiveUtils", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "Polyester", "PrecompileTools", "Preferences", "Random", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Static", "StaticArrayInterface", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"]
-git-tree-sha1 = "b4a8d9b96931c2fc69126233bbe6d1a11b053d77"
+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 = "73e431f66fb9854474c0bec1c11053e8889629c1"
uuid = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
-version = "3.22.0"
+version = "3.27.0"
[deps.OrdinaryDiffEqCore.extensions]
OrdinaryDiffEqCoreMooncakeExt = "Mooncake"
@@ -1922,15 +1925,15 @@ version = "3.22.0"
[[deps.OrdinaryDiffEqDefault]]
deps = ["ADTypes", "DiffEqBase", "EnumX", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "PrecompileTools", "Preferences", "Reexport", "SciMLBase"]
-git-tree-sha1 = "0f40d969dd10d1b226c864bf7dc4b4b8933bc130"
+git-tree-sha1 = "5fbd116f93790ce79c4beff8206e1b8e45c492a2"
uuid = "50262376-6c5a-4cf5-baba-aaf4f84d72d7"
-version = "1.13.0"
+version = "1.14.0"
[[deps.OrdinaryDiffEqDifferentiation]]
-deps = ["ADTypes", "ArrayInterface", "ConcreteStructs", "ConstructionBase", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqCore", "SciMLBase", "SciMLOperators", "SparseMatrixColorings", "StaticArrayInterface", "StaticArrays"]
-git-tree-sha1 = "c85968ea296acaff5de6ed0d9b64ddb00f4ea01f"
+deps = ["ADTypes", "ArrayInterface", "ConcreteStructs", "ConstructionBase", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqCore", "SciMLBase", "SciMLOperators", "SparseMatrixColorings", "StaticArrays"]
+git-tree-sha1 = "c7d06493d3327cc45f0d8d7e641203d28779e837"
uuid = "4302a76b-040a-498a-8c04-15b101fed76b"
-version = "2.2.1"
+version = "2.6.0"
weakdeps = ["SparseArrays"]
[deps.OrdinaryDiffEqDifferentiation.extensions]
@@ -1938,153 +1941,153 @@ weakdeps = ["SparseArrays"]
[[deps.OrdinaryDiffEqExplicitRK]]
deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"]
-git-tree-sha1 = "c5b900878b088776b8d1bd5a7b1d94e301e21c4e"
+git-tree-sha1 = "17d8bfeab9d6a7b31d116e950aa7204fd444ec19"
uuid = "9286f039-9fbf-40e8-bf65-aa933bdc4db0"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqExponentialRK]]
deps = ["ADTypes", "DiffEqBase", "ExponentialUtilities", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "RecursiveArrayTools", "Reexport", "SciMLBase"]
-git-tree-sha1 = "72156f954b199ff23dada0e8c0f12c44503b5cf9"
+git-tree-sha1 = "32fb54c8000c82fd32c55f7bf52f4651a3bd0e61"
uuid = "e0540318-69ee-4070-8777-9e2de6de23de"
-version = "1.13.0"
+version = "1.15.0"
[[deps.OrdinaryDiffEqExtrapolation]]
deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "FastPower", "LinearSolve", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"]
-git-tree-sha1 = "129730b7b6cb60cc9c18e0db5861f4a7ed2c30b9"
+git-tree-sha1 = "57bf89f5b144f6e5af1146fb7aee1544c6b76950"
uuid = "becaefa8-8ca2-5cf9-886d-c06f3d2bd2c4"
-version = "1.16.0"
+version = "1.18.0"
[[deps.OrdinaryDiffEqFIRK]]
deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "FastGaussQuadrature", "FastPower", "LinearAlgebra", "LinearSolve", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators"]
-git-tree-sha1 = "342c716e0c15ab44203f68a78f98800ec560df82"
+git-tree-sha1 = "b8af0f1b9925e5ae58a4adf5afc60269a3ac6c44"
uuid = "5960d6e9-dd7a-4743-88e7-cf307b64f125"
-version = "1.23.0"
+version = "1.25.0"
[[deps.OrdinaryDiffEqFeagin]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "b123f64a8635a712ceb037a7d2ffe2a1875325d3"
+git-tree-sha1 = "302999c99bc454cf274d39b9ba3c2415f6fbb1cb"
uuid = "101fe9f7-ebb6-4678-b671-3a81e7194747"
-version = "1.8.0"
+version = "1.10.0"
[[deps.OrdinaryDiffEqFunctionMap]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "cbd291508808caf10cf455f974c2025e886ed2a3"
+git-tree-sha1 = "2d2d59f9e530bca2f99934ecfde5430f6ad54bfe"
uuid = "d3585ca7-f5d3-4ba6-8057-292ed1abd90f"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqHighOrderRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "9584dcc90cf10216de7aa0f2a1edc0f54d254cf6"
+git-tree-sha1 = "46d3c3b871253c21b91fe30257eff55ce61c486a"
uuid = "d28bc4f8-55e1-4f49-af69-84c1a99f0f58"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqIMEXMultistep]]
deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Reexport", "SciMLBase"]
-git-tree-sha1 = "9280abaf9ac36d60dd774113f7ce8a7f826d6e2e"
+git-tree-sha1 = "bd5440f7f09ce75df59e7f5d2f5b8e52d24aae3c"
uuid = "9f002381-b378-40b7-97a6-27a27c83f129"
-version = "1.12.0"
+version = "1.14.0"
[[deps.OrdinaryDiffEqLinear]]
deps = ["DiffEqBase", "ExponentialUtilities", "LinearAlgebra", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators"]
-git-tree-sha1 = "c92913fa5942ed9bc748f3e79a5c693c8ec0c3d7"
+git-tree-sha1 = "a1217ddfc979da3a2d9cf11b083688562e8ef333"
uuid = "521117fe-8c41-49f8-b3b6-30780b3f0fb5"
-version = "1.10.0"
+version = "1.12.0"
[[deps.OrdinaryDiffEqLowOrderRK]]
deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "78223e34d4988070443465cd3f2bdc38d6bd14b0"
+git-tree-sha1 = "ee10d0fa457ee9805dcf1ab20e9a37958952c540"
uuid = "1344f307-1e59-4825-a18e-ace9aa3fa4c6"
-version = "1.10.0"
+version = "1.12.0"
[[deps.OrdinaryDiffEqLowStorageRK]]
deps = ["Adapt", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "StaticArrays"]
-git-tree-sha1 = "bd032c73716bc538033af041ca8903df6c813bfd"
+git-tree-sha1 = "66ebd44d699322583305497eedd371877feb6679"
uuid = "b0944070-b475-4768-8dec-fb6eb410534d"
-version = "1.12.0"
+version = "1.14.0"
[[deps.OrdinaryDiffEqNonlinearSolve]]
deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "FastBroadcast", "FastClosures", "ForwardDiff", "LinearAlgebra", "LinearSolve", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "PreallocationTools", "RecursiveArrayTools", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SparseArrays", "StaticArrays"]
-git-tree-sha1 = "a75727e93ffef0f0bc408372988f7bc0767b1781"
+git-tree-sha1 = "f6722d6a96c27263ac3ea6159a3174914567a807"
uuid = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8"
-version = "1.23.0"
+version = "1.25.0"
[[deps.OrdinaryDiffEqNordsieck]]
deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqTsit5", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "facea9aaf48eed5e9ba66d8b3246e51417c084d0"
+git-tree-sha1 = "21117475cb0d27942d31e4b8a27337392ae680b0"
uuid = "c9986a66-5c92-4813-8696-a7ec84c806c8"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqPDIRK]]
deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "Polyester", "Reexport", "SciMLBase", "StaticArrays"]
-git-tree-sha1 = "c95dd60623e11464e6079b77d2ce604fb399a02d"
+git-tree-sha1 = "274f4faaef2b137bdf05fb221a869d3d773aff22"
uuid = "5dd0a6cf-3d4b-4314-aa06-06d4e299bc89"
-version = "1.11.0"
+version = "1.13.0"
[[deps.OrdinaryDiffEqPRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "Reexport", "SciMLBase"]
-git-tree-sha1 = "baa77b7f874cda1f58f8c793fc7a9778e78a91c5"
+git-tree-sha1 = "1fe4f58f170c68b187ac42dc5f00932bb162ddfa"
uuid = "5b33eab2-c0f1-4480-b2c3-94bc1e80bda1"
-version = "1.8.0"
+version = "1.10.0"
[[deps.OrdinaryDiffEqQPRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "9e351a8f923c843adb48945318437e051f6ee139"
+git-tree-sha1 = "275900d64ab7be5f1bca8c7bfd48166838b6ab44"
uuid = "04162be5-8125-4266-98ed-640baecc6514"
-version = "1.8.0"
+version = "1.10.0"
[[deps.OrdinaryDiffEqRKN]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"]
-git-tree-sha1 = "b086c6d1b4153c9ff4b3f184a9ba7829413cc502"
+git-tree-sha1 = "edf3411246d6030e165a5fba513cbdcde5c72f79"
uuid = "af6ede74-add8-4cfd-b1df-9a4dbb109d7a"
-version = "1.10.0"
+version = "1.12.0"
[[deps.OrdinaryDiffEqRosenbrock]]
deps = ["ADTypes", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "LinearAlgebra", "LinearSolve", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static"]
-git-tree-sha1 = "f11347f3f01a5b00dae2b565e73795ee138cdc68"
+git-tree-sha1 = "88f722a74df3dadaf5f31c454a280f1201a1b368"
uuid = "43230ef6-c299-4910-a778-202eb28ce4ce"
-version = "1.25.0"
+version = "1.28.0"
[[deps.OrdinaryDiffEqSDIRK]]
-deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"]
-git-tree-sha1 = "0b766d820e3b948881f1f246899de9ef3d329224"
+deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"]
+git-tree-sha1 = "c5e26fbadbad137fa2b7e3161a290a1becfec502"
uuid = "2d112036-d095-4a1e-ab9a-08536f3ecdbf"
-version = "1.12.0"
+version = "1.14.0"
[[deps.OrdinaryDiffEqSSPRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "StaticArrays"]
-git-tree-sha1 = "8abc61382a0c6469aa9c3bff2d61c9925a088320"
+git-tree-sha1 = "66a8402e3e50eb07a8d4cb96c730eb8fc2bb372b"
uuid = "669c94d9-1f4b-4b64-b377-1aa079aa2388"
-version = "1.11.0"
+version = "1.13.0"
[[deps.OrdinaryDiffEqStabilizedIRK]]
deps = ["ADTypes", "DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqStabilizedRK", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays"]
-git-tree-sha1 = "cf6856c731ddf9866e3e22612cce5e270f071545"
+git-tree-sha1 = "6688927070213dc11058ee2316027c64eb5052f7"
uuid = "e3e12d00-db14-5390-b879-ac3dd2ef6296"
-version = "1.11.0"
+version = "1.13.0"
[[deps.OrdinaryDiffEqStabilizedRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "RecursiveArrayTools", "Reexport", "SciMLBase", "StaticArrays"]
-git-tree-sha1 = "d156a972fa7bc37bf8377d33a7d51d152e354d4c"
+git-tree-sha1 = "bda2155c5abd7b1e4965d35f930f78e320d7bafe"
uuid = "358294b1-0aab-51c3-aafe-ad5ab194a2ad"
-version = "1.8.0"
+version = "1.10.0"
[[deps.OrdinaryDiffEqSymplecticRK]]
deps = ["DiffEqBase", "FastBroadcast", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "RecursiveArrayTools", "Reexport", "SciMLBase"]
-git-tree-sha1 = "9b783806fe2dc778649231cb3932cb71b63222d9"
+git-tree-sha1 = "b18a4e7973e73e84cfd79222e2389565b323b588"
uuid = "fa646aed-7ef9-47eb-84c4-9443fc8cbfa8"
-version = "1.11.0"
+version = "1.13.0"
[[deps.OrdinaryDiffEqTsit5]]
deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "TruncatedStacktraces"]
-git-tree-sha1 = "8be4cba85586cd2efa6c76d1792c548758610901"
+git-tree-sha1 = "b305a813cc926ce31458e2b955612d9b719f1c38"
uuid = "b1df2697-797e-41e3-8120-5422d3b24e4a"
-version = "1.9.0"
+version = "1.11.0"
[[deps.OrdinaryDiffEqVerner]]
deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "Polyester", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "Static", "TruncatedStacktraces"]
-git-tree-sha1 = "5ca5dbbfea89e14f283ce9fe2301c528ff4ec007"
+git-tree-sha1 = "918dbbaeea6684d4d65907994650d9614bdd2eb2"
uuid = "79d7bb75-1356-48c1-b8c0-6832512096c2"
-version = "1.11.0"
+version = "1.13.0"
[[deps.PCRE2_jll]]
deps = ["Artifacts", "Libdl"]
@@ -2118,7 +2121,7 @@ version = "0.44.2+0"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
-version = "1.12.0"
+version = "1.12.1"
weakdeps = ["REPL"]
[deps.Pkg.extensions]
@@ -2176,9 +2179,9 @@ version = "1.4.3"
[[deps.PreallocationTools]]
deps = ["Adapt", "ArrayInterface", "PrecompileTools"]
-git-tree-sha1 = "dc8d6bde5005a0eac05ae8faf1eceaaca166cfa4"
+git-tree-sha1 = "e16b73bf892c55d16d53c9c0dbd0fb31cb7e25da"
uuid = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
-version = "1.1.2"
+version = "1.2.0"
weakdeps = ["ForwardDiff", "ReverseDiff", "SparseConnectivityTracer"]
[deps.PreallocationTools.extensions]
@@ -2200,9 +2203,9 @@ version = "1.5.2"
[[deps.PrettyTables]]
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
-git-tree-sha1 = "211530a7dc76ab59087f4d4d1fc3f086fbe87594"
+git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
-version = "3.2.3"
+version = "3.3.2"
[deps.PrettyTables.extensions]
PrettyTablesTypstryExt = "Typstry"
@@ -2286,9 +2289,9 @@ version = "0.6.12"
[[deps.RecursiveArrayTools]]
deps = ["Adapt", "ArrayInterface", "DocStringExtensions", "GPUArraysCore", "LinearAlgebra", "PrecompileTools", "RecipesBase", "StaticArraysCore", "SymbolicIndexingInterface"]
-git-tree-sha1 = "18d2a6fd1ea9a8205cadb3a5704f8e51abdd748b"
+git-tree-sha1 = "e4fd3369c78666a65ccec25dba28a0b181434ab2"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
-version = "3.48.0"
+version = "3.52.0"
[deps.RecursiveArrayTools.extensions]
RecursiveArrayToolsFastBroadcastExt = "FastBroadcast"
@@ -2370,9 +2373,9 @@ version = "2025.9.18+0"
[[deps.SciMLBase]]
deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "CommonSolve", "ConstructionBase", "Distributed", "DocStringExtensions", "EnumX", "FunctionWrappersWrappers", "IteratorInterfaceExtensions", "LinearAlgebra", "Logging", "Markdown", "Moshi", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "RecipesBase", "RecursiveArrayTools", "Reexport", "RuntimeGeneratedFunctions", "SciMLLogging", "SciMLOperators", "SciMLPublic", "SciMLStructures", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface"]
-git-tree-sha1 = "0be0208add9b6836a701e0ac3ad30bda72fee51d"
+git-tree-sha1 = "908c0bf271604d09393a21c142116ab26f66f67c"
uuid = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
-version = "2.150.0"
+version = "2.154.0"
[deps.SciMLBase.extensions]
SciMLBaseChainRulesCoreExt = "ChainRulesCore"
@@ -2553,9 +2556,9 @@ version = "1.2.1"
[[deps.SparseMatrixColorings]]
deps = ["ADTypes", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "Random", "SparseArrays"]
-git-tree-sha1 = "fa43a02c01e3e3cb065c89bf9b648b89e3c06f18"
+git-tree-sha1 = "1c1be8c6fdfaf9b6c9e156c509e672953b8e6af7"
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
-version = "0.4.25"
+version = "0.4.26"
[deps.SparseMatrixColorings.extensions]
SparseMatrixColoringsCUDAExt = "CUDA"
@@ -2572,9 +2575,9 @@ version = "0.4.25"
[[deps.SpecialFunctions]]
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
-git-tree-sha1 = "5acc6a41b3082920f79ca3c759acbcecf18a8d78"
+git-tree-sha1 = "2700b235561b0335d5bef7097a111dc513b8655e"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
-version = "2.7.1"
+version = "2.7.2"
weakdeps = ["ChainRulesCore"]
[deps.SpecialFunctions.extensions]
@@ -2664,16 +2667,18 @@ version = "1.11.0"
[[deps.StructUtils]]
deps = ["Dates", "UUIDs"]
-git-tree-sha1 = "28145feabf717c5d65c1d5e09747ee7b1ff3ed13"
+git-tree-sha1 = "fa95b3b097bcef5845c142ea2e085f1b2591e92c"
uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
-version = "2.6.3"
+version = "2.7.1"
[deps.StructUtils.extensions]
StructUtilsMeasurementsExt = ["Measurements"]
+ StructUtilsStaticArraysCoreExt = ["StaticArraysCore"]
StructUtilsTablesExt = ["Tables"]
[deps.StructUtils.weakdeps]
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
+ StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
[[deps.StyledStrings]]
@@ -2800,9 +2805,9 @@ version = "0.4.1"
[[deps.UnoSolver]]
deps = ["LinearAlgebra", "OpenBLAS32_jll", "Uno_jll"]
-git-tree-sha1 = "cf39af9c8be02fd5f5027bc3184e8582b8f6d2a0"
+git-tree-sha1 = "a947341afb633f09cdcf875090981b80b37ec31f"
uuid = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05"
-version = "0.2.7"
+version = "0.2.10"
[deps.UnoSolver.extensions]
UnoSolverMathOptInterfaceExt = "MathOptInterface"
@@ -2814,14 +2819,14 @@ version = "0.2.7"
[[deps.Uno_jll]]
deps = ["ASL_jll", "Artifacts", "CompilerSupportLibraries_jll", "HSL_jll", "HiGHS_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl", "METIS_jll", "MUMPS_seq_jll", "SPRAL_jll", "libblastrampoline_jll"]
-git-tree-sha1 = "30b1deeaeb5de7c0e0e4a7f9a253195147ed0e9e"
+git-tree-sha1 = "3530444c365f78f2d84af6224e4fffbd9e115e49"
uuid = "396d5378-14f1-5ab1-981d-48acd51740ed"
-version = "2.5.0+0"
+version = "2.7.0+0"
[[deps.UnsafeAtomics]]
-git-tree-sha1 = "b13c4edda90890e5b04ba24e20a310fbe6f249ff"
+git-tree-sha1 = "0f30765c32d66d58e41f4cb5624d4fc8a82ec13b"
uuid = "013be700-e6cd-48c3-b4a1-df204f14c38f"
-version = "0.3.0"
+version = "0.3.1"
weakdeps = ["LLVM"]
[deps.UnsafeAtomics.extensions]
@@ -3078,9 +3083,9 @@ version = "1.28.1+0"
[[deps.libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
-git-tree-sha1 = "e015f211ebb898c8180887012b938f3851e719ac"
+git-tree-sha1 = "e2a7072fc0cdd7949528c1455a3e5da4122e1153"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
-version = "1.6.55+0"
+version = "1.6.56+0"
[[deps.libva_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"]
@@ -3112,9 +3117,9 @@ uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
version = "2022.0.0+1"
[[deps.p7zip_jll]]
-deps = ["Artifacts", "Libdl"]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
-version = "17.5.0+2"
+version = "17.7.0+0"
[[deps.x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
diff --git a/docs/src/example-control-free.md b/docs/src/example-control-free.md
new file mode 100644
index 00000000..dc3b3852
--- /dev/null
+++ b/docs/src/example-control-free.md
@@ -0,0 +1,162 @@
+# [Control-free problems](@id example-control-free)
+
+Control-free problems are optimal control problems without a control variable. They are used for **optimizing constant parameters in dynamical systems**, such as:
+
+- Identifying unknown parameters from observed data (parameter estimation)
+- Finding optimal parameters for a given performance criterion
+
+This page demonstrates two simple examples with known analytical solutions.
+
+!!! compat "Upcoming feature"
+
+ The syntax shown here uses a workaround with a dummy control variable (`u ∈ R, control`) and a small penalty term (`1e-5*u(t)^2`) in the cost to force `u ≈ 0`, because the parser does not yet support omitting the control declaration. These workaround lines are marked with `# TO REMOVE WHEN POSSIBLE` and will be removed once native control-free syntax is implemented.
+
+First, we import the necessary packages:
+
+```@example main
+using OptimalControl
+using NLPModelsIpopt
+using Plots
+```
+
+## Example 1: Exponential growth rate estimation
+
+Consider a system with exponential growth:
+
+```math
+\dot{x}(t) = p \cdot x(t), \quad x(0) = 2
+```
+
+where $p$ is an unknown growth rate parameter. We have observed data following $x_{\text{obs}}(t) = 2 e^{0.5 t}$ and want to estimate $p$ by minimizing the squared error:
+
+```math
+\min_{p} \int_0^{10} (x(t) - x_{\text{obs}}(t))^2 \, dt
+```
+
+The analytical solution is $p = 0.5$.
+
+### [Problem definition](@id example-control-free-problem-1)
+
+```@example main
+# observed data (analytical solution)
+data(t) = 2.0 * exp(0.5 * t)
+
+# optimal control problem (parameter estimation)
+ocp1 = @def begin
+ p ∈ R, variable # growth rate to estimate
+ t ∈ [0, 10], time
+ x ∈ R, state
+ u ∈ R, control # TO REMOVE WHEN POSSIBLE
+
+ x(0) == 2.0
+
+ ẋ(t) == p * x(t)
+
+ ∫((x(t) - data(t))^2 + 1e-5*u(t)^2) → min # fit to observed data # TO REMOVE u(t) when possible
+end
+nothing # hide
+```
+
+### [Solution](@id example-control-free-solution-1)
+
+```@example main
+sol1 = solve(ocp1; display=false)
+println("Estimated growth rate: p = ", variable(sol1))
+println("Objective value: ", objective(sol1))
+println("Expected: p = 0.5, objective ≈ 0.0")
+nothing # hide
+```
+
+```@example main
+plot(sol1; size=(800, 400))
+```
+
+The estimated parameter should be very close to $p = 0.5$, and the objective (squared error) should be nearly zero since we're fitting to the exact analytical solution.
+
+## Example 2: Harmonic oscillator pulsation optimization
+
+Consider a harmonic oscillator:
+
+```math
+\ddot{q}(t) = -\omega^2 q(t)
+```
+
+with initial conditions $q(0) = 1$, $\dot{q}(0) = 0$ and final condition $q(1) = 0$. We want to find the minimal pulsation $\omega$ satisfying these constraints:
+
+```math
+ \begin{aligned}
+ & \text{Minimise} && \omega^2 \\
+ & \text{subject to} \\
+ & && \ddot{q}(t) = -\omega^2 q(t), \\[1.0em]
+ & && q(0) = 1, \quad \dot{q}(0) = 0, \\[0.5em]
+ & && q(1) = 0.
+ \end{aligned}
+```
+
+The analytical solution is $\omega = \pi/2 \approx 1.5708$, giving $q(t) = \cos(\pi t / 2)$.
+
+### [Problem definition](@id example-control-free-problem-2)
+
+```@example main
+# optimal control problem (pulsation optimization)
+ocp2 = @def begin
+ ω ∈ R, variable # pulsation to optimize
+ t ∈ [0, 1], time
+ x = (q, v) ∈ R², state
+ u ∈ R, control # TO REMOVE WHEN POSSIBLE
+
+ q(0) == 1.0
+ v(0) == 0.0
+ q(1) == 0.0 # final condition
+
+ ẋ(t) == [v(t), -ω^2 * q(t)]
+
+ ω^2 + 1e-5*∫(u(t)^2) → min # minimize pulsation # TO REMOVE u(t) when possible
+end
+nothing # hide
+```
+
+### [Solution](@id example-control-free-solution-2)
+
+```@example main
+sol2 = solve(ocp2; display=false)
+println("Optimal pulsation: ω = ", variable(sol2))
+println("Objective value: ω² = ", objective(sol2))
+println("Expected: ω = π/2 ≈ 1.5708, ω² ≈ 2.4674")
+nothing # hide
+```
+
+```@example main
+plot(sol2; size=(800, 400))
+```
+
+The optimal pulsation should be close to $\omega = \pi/2 \approx 1.5708$, and the objective $\omega^2 \approx 2.4674$.
+
+## Comparison with analytical solutions
+
+For the harmonic oscillator, we can compare the numerical solution with the analytical one:
+
+```@example main
+# analytical solution
+t_analytical = range(0, 1, 100)
+q_analytical = cos.(π * t_analytical / 2)
+v_analytical = -(π/2) * sin.(π * t_analytical / 2)
+
+# plot comparison
+plt = plot(sol2; size=(800, 600))
+plot!(plt, t_analytical, q_analytical;
+ label="q (analytical)", linestyle=:dash, linewidth=2, subplot=1)
+plot!(plt, t_analytical, v_analytical;
+ label="v (analytical)", linestyle=:dash, linewidth=2, subplot=2)
+```
+
+The numerical and analytical solutions should match very closely.
+
+!!! note "Applications"
+
+ Control-free problems appear in many contexts:
+ - **System identification**: estimating physical parameters (mass, damping, stiffness) from experimental data
+ - **Optimal design**: finding optimal geometric or physical parameters (length, stiffness, etc.)
+ - **Inverse problems**: reconstructing unknown inputs or initial conditions from partial observations
+
+ See the [syntax documentation](@ref manual-abstract-control-free) for more details on defining control-free problems.
diff --git a/docs/src/example-double-integrator-energy.md b/docs/src/example-double-integrator-energy.md
index d35b3976..7d908e6d 100644
--- a/docs/src/example-double-integrator-energy.md
+++ b/docs/src/example-double-integrator-energy.md
@@ -49,8 +49,7 @@ ocp = @def begin
x(t0) == x0
x(tf) == xf
- ∂(q)(t) == v(t)
- ∂(v)(t) == u(t)
+ ẋ(t) == [v(t), u(t)]
0.5∫( u(t)^2 ) → min
end
@@ -68,8 +67,7 @@ nothing # hide
\begin{aligned}
& \text{Minimise} && \frac{1}{2}\int_0^1 u^2(t) \,\mathrm{d}t \\
& \text{subject to} \\
- & && \dot{q}(t) = v(t), \\[0.5em]
- & && \dot{v}(t) = u(t), \\[1.0em]
+ & && \dot{x}(t) = [v(t), u(t)], \\[1.0em]
& && x(0) = (-1,0), \\[0.5em]
& && x(1) = (0,0).
\end{aligned}
@@ -200,8 +198,7 @@ ocp = @def begin
x(t0) == x0
x(tf) == xf
- ∂(q)(t) == v(t)
- ∂(v)(t) == u(t)
+ ẋ(t) == [v(t), u(t)]
0.5∫( u(t)^2 ) → min
end
diff --git a/docs/src/example-double-integrator-time.md b/docs/src/example-double-integrator-time.md
index 984fd081..06c6096c 100644
--- a/docs/src/example-double-integrator-time.md
+++ b/docs/src/example-double-integrator-time.md
@@ -18,9 +18,7 @@ This problem can be interpreted as a simple model for a wagon with constant mass
```
-First, we need to import the [OptimalControl.jl](https://control-toolbox.org/OptimalControl.jl) package to define the
-optimal control problem and [NLPModelsIpopt.jl](https://jso.dev/NLPModelsIpopt.jl) to solve it.
-We also need to import the [Plots.jl](https://docs.juliaplots.org) package to plot the solution.
+First, we need to import the [OptimalControl.jl](https://control-toolbox.org/OptimalControl.jl) package to define the optimal control problem and [NLPModelsIpopt.jl](https://jso.dev/NLPModelsIpopt.jl) to solve it. We also need to import the [Plots.jl](https://docs.juliaplots.org) package to plot the solution.
```@example main
using OptimalControl
@@ -38,24 +36,22 @@ Let us define the problem:
```
```@example main
-ocp = @def begin
+t0 = 0; x0 = [-1, 0]; xf = [0, 0]
+ocp = @def begin
tf ∈ R, variable
- t ∈ [0, tf], time
+ t ∈ [t0, tf], time
x = (q, v) ∈ R², state
u ∈ R, control
-1 ≤ u(t) ≤ 1
- q(0) == -1
- v(0) == 0
- q(tf) == 0
- v(tf) == 0
+ x(t0) == x0
+ x(tf) == xf
- ẋ(t) == [v(t), u(t)]
+ ẋ(t) == [v(t), u(t)]
tf → min
-
end
nothing # hide
```
@@ -71,13 +67,10 @@ nothing # hide
\begin{aligned}
& \text{Minimise} && t_f \\[0.5em]
& \text{subject to} \\[0.5em]
- & && \dot q(t) = v(t), \\
- & && \dot v(t) = u(t), \\[0.5em]
+ & && \dot x(t) = [v(t), u(t)], \\[0.5em]
& && -1 \le u(t) \le 1, \\[0.5em]
- & && q(0) = -1, \\[0.5em]
- & && v(0) = 0, \\[0.5em]
- & && q(t_f) = 0, \\[0.5em]
- & && v(t_f) = 0.
+ & && x(0) = (-1, 0), \\[0.5em]
+ & && x(t_f) = (0, 0).
\end{aligned}
```
@@ -151,9 +144,6 @@ nothing # hide
The shooting function enforces the conditions:
```@example main
-t0 = 0
-x0 = [-1, 0]
-xf = [ 0, 0]
function shoot!(s, p0, t1, tf)
x_t0, p_t0 = x0, p0
x_t1, p_t1 = f_max(t0, x_t0, p_t0, t1)
diff --git a/docs/src/example-singular-control.md b/docs/src/example-singular-control.md
new file mode 100644
index 00000000..fb38d094
--- /dev/null
+++ b/docs/src/example-singular-control.md
@@ -0,0 +1,352 @@
+# [Singular control](@id example-singular-control)
+
+For control-affine systems of the form
+
+```math
+\dot{q}(t) = f_0(q(t)) + u(t) f_1(q(t)), \quad u(t) \in [u_{\min}, u_{\max}],
+```
+
+the pseudo-Hamiltonian is $H = H_0 + u H_1$, where $H_i(q, p) = \langle p, f_i(q) \rangle$ are the Hamiltonian lifts of the vector fields $f_0$ and $f_1$.
+
+When the **switching function** $H_1$ vanishes on a time interval (i.e., $H_1(q(t), p(t)) = 0$ for $t \in [t_1, t_2]$), the arc is called **singular**. On such arcs, the control cannot be determined directly from the maximization condition and must be computed by successive differentiation of $H_1$ along the flow.
+
+This page demonstrates how to compute singular controls both by hand and using differential geometry tools from OptimalControl.jl, then verifies the result numerically using direct and indirect methods.
+
+First, we import the necessary packages:
+
+```@example main
+using OptimalControl
+using NLPModelsIpopt
+using Plots
+```
+
+## Problem definition
+
+We consider a vehicle moving in the plane with drift. The state is $q = (x, y, \theta)$ where $(x, y)$ is the position and $\theta$ is the orientation. The dynamics are:
+
+```math
+\dot{x}(t) = \cos\theta(t), \quad \dot{y}(t) = \sin\theta(t) + x(t), \quad \dot{\theta}(t) = u(t),
+```
+
+with control constraint $u(t) \in [-1, 1]$.
+
+We want to find the time-optimal transfer from the origin $(0, 0)$ with free initial orientation to the target position $(1, 0)$ with free final orientation:
+
+```@example main
+ocp = @def begin
+
+ tf ∈ R, variable
+ t ∈ [0, tf], time
+ q = (x, y, θ) ∈ R³, state
+ u ∈ R, control
+
+ -1 ≤ u(t) ≤ 1 # Control bounds
+ -π/2 ≤ θ(t) ≤ π/2 # State bounds (helps direct method convergence)
+
+ x(0) == 0
+ y(0) == 0
+ x(tf) == 1
+ y(tf) == 0
+
+ ∂(q)(t) == [cos(θ(t)), sin(θ(t)) + x(t), u(t)]
+
+ tf → min
+
+end
+nothing # hide
+```
+
+This is a control-affine system with:
+
+```math
+f_0(q) = \begin{pmatrix} \cos\theta \\ \sin\theta + x \\ 0 \end{pmatrix}, \quad
+f_1(q) = \begin{pmatrix} 0 \\ 0 \\ 1 \end{pmatrix}.
+```
+
+## Direct method
+
+We solve the problem using a direct method:
+
+```@example main
+direct_sol = solve(ocp; display=false)
+println("Optimal time: tf = ", variable(direct_sol))
+nothing # hide
+```
+
+Let's plot the solution:
+
+```@example main
+opt = (state_bounds_style=:none, control_bounds_style=:none)
+plt = plot(direct_sol; label="direct", size=(800, 800), opt...)
+```
+
+## Singular control by hand
+
+The pseudo-Hamiltonian for this time-optimal problem is:
+
+```math
+H(q, p, u) = p_1 \cos\theta + p_2(\sin\theta + x) + p_3 u.
+```
+
+This is control-affine: $H = H_0 + u H_1$ with:
+
+```math
+H_0(q, p) = p_1 \cos\theta + p_2(\sin\theta + x), \quad H_1(q, p) = p_3.
+```
+
+The switching function is $H_1 = p_3$. On a singular arc, we have $H_1 = 0$ and all its time derivatives must vanish.
+
+**First derivative:**
+
+```math
+\dot{H}_1 = \{H, H_1\} = \{H_0, H_1\} =: H_{01}.
+```
+
+Computing the Poisson bracket:
+
+```math
+H_{01} = \frac{\partial H_0}{\partial p_1} \frac{\partial H_1}{\partial x} - \frac{\partial H_0}{\partial x} \frac{\partial H_1}{\partial p_1}
+ + \frac{\partial H_0}{\partial p_2} \frac{\partial H_1}{\partial y} - \frac{\partial H_0}{\partial y} \frac{\partial H_1}{\partial p_2}
+ + \frac{\partial H_0}{\partial p_3} \frac{\partial H_1}{\partial \theta} - \frac{\partial H_0}{\partial \theta} \frac{\partial H_1}{\partial p_3}.
+```
+
+Since $H_1 = p_3$ depends only on $p_3$, the only non-zero contribution comes from the $(\theta, p_3)$ pair:
+
+```math
+H_{01} = \frac{\partial H_0}{\partial \theta} \frac{\partial H_1}{\partial p_3} - \frac{\partial H_0}{\partial p_3} \frac{\partial H_1}{\partial \theta} = (-p_1 \sin\theta + p_2 \cos\theta) \cdot 1 - 0 = -p_1 \sin\theta + p_2 \cos\theta.
+```
+
+On the singular arc, $H_{01} = 0$, which gives the constraint:
+
+```math
+p_2 \cos\theta = p_1 \sin\theta.
+```
+
+**Second derivative:**
+
+```math
+\dot{H}_{01} = \{H, H_{01}\} = \{H_0, H_{01}\} + u \{H_1, H_{01}\} =: H_{001} + u H_{101}.
+```
+
+For the arc to remain singular, $\dot{H}_{01} = 0$, which gives:
+
+```math
+u_s = -\frac{H_{001}}{H_{101}},
+```
+
+whenever $H_{101} \neq 0$. Computing $H_{001} = \{H_0, H_{01}\}$ with $H_{01} = -p_1 \sin\theta + p_2 \cos\theta$:
+
+The only non-zero contribution comes from the $(x, p_1)$ pair:
+
+```math
+H_{001} = \frac{\partial H_0}{\partial x} \frac{\partial H_{01}}{\partial p_1} - \frac{\partial H_0}{\partial p_1} \frac{\partial H_{01}}{\partial x} = p_2 \cdot (-\sin\theta) - \cos\theta \cdot 0 = -p_2 \sin\theta.
+```
+
+Computing $H_{101} = \{H_1, H_{01}\}$ with $H_1 = p_3$ and $H_{01} = -p_1 \sin\theta + p_2 \cos\theta$:
+
+The only non-zero contribution comes from the $(\theta, p_3)$ pair:
+
+```math
+H_{101} = \frac{\partial H_1}{\partial \theta} \frac{\partial H_{01}}{\partial p_3} - \frac{\partial H_1}{\partial p_3} \frac{\partial H_{01}}{\partial \theta} = 0 - 1 \cdot (-p_1 \cos\theta - p_2 \sin\theta) = p_1 \cos\theta + p_2 \sin\theta.
+```
+
+Therefore:
+
+```math
+u_s = -\frac{H_{001}}{H_{101}} = \frac{p_2 \sin\theta}{p_1 \cos\theta + p_2 \sin\theta}.
+```
+
+!!! note "Non-degeneracy condition"
+
+ We can show that $H_{101} \neq 0$ on the singular arc. From the constraint $p_1 \sin\theta = p_2 \cos\theta$, if we had $H_{101} = p_1 \cos\theta + p_2 \sin\theta = 0$, then:
+
+ ```math
+ \begin{pmatrix} \cos\theta & \sin\theta \\ -\sin\theta & \cos\theta \end{pmatrix}
+ \begin{pmatrix} p_1 \\ p_2 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \end{pmatrix}.
+ ```
+
+ Since this matrix has determinant 1 (hence is invertible), we would have $p_1 = p_2 = 0$. Combined with $p_3 = 0$ (from $H_1 = 0$), this gives $p = 0$, which is impossible for a time-minimization problem.
+
+**Simplification using the constraint:**
+
+Multiply numerator and denominator by $\sin\theta$:
+
+```math
+u_s = \frac{p_2 \sin^2\theta}{p_1 \cos\theta \sin\theta + p_2 \sin^2\theta}.
+```
+
+From the constraint $p_1 \sin\theta = p_2 \cos\theta$, we have $p_1 \cos\theta \sin\theta = p_2 \cos^2\theta$. Substituting in the denominator:
+
+```math
+u_s = \frac{p_2 \sin^2\theta}{p_2 \cos^2\theta + p_2 \sin^2\theta} = \frac{p_2 \sin^2\theta}{p_2(\cos^2\theta + \sin^2\theta)} = \sin^2\theta.
+```
+
+So the singular control is:
+
+```math
+u_s(\theta) = \sin^2\theta.
+```
+
+Let's overlay this on the numerical solution:
+
+```@example main
+T = time_grid(direct_sol, :control)
+θ(t) = state(direct_sol)(t)[3]
+us(t) = sin(θ(t))^2
+plot!(plt, T, us; subplot=7, line=:dash, lw=2, label="us (hand)")
+plot(plt[7]; size=(800, 400))
+```
+
+## Singular control via Poisson brackets
+
+We can compute the same result using the differential geometry tools from OptimalControl.jl. See the [differential geometry tools manual](@ref manual-differential-geometry) for detailed explanations.
+
+First, define the vector fields:
+
+```@example main
+F0(q) = [cos(q[3]), sin(q[3]) + q[1], 0]
+F1(q) = [0, 0, 1]
+nothing # hide
+```
+
+Compute their Hamiltonian lifts:
+
+```@example main
+H0 = Lift(F0)
+H1 = Lift(F1)
+nothing # hide
+```
+
+Compute the iterated Poisson brackets:
+
+```@example main
+H01 = @Lie {H0, H1}
+H001 = @Lie {H0, H01}
+H101 = @Lie {H1, H01}
+nothing # hide
+```
+
+The singular control is:
+
+```@example main
+us_bracket(q, p) = -H001(q, p) / H101(q, p)
+nothing # hide
+```
+
+Let's verify this gives the same result:
+
+```@example main
+q(t) = state(direct_sol)(t)
+p(t) = costate(direct_sol)(t)
+us_b(t) = us_bracket(q(t), p(t))
+plot!(plt, T, us_b; subplot=7, line=:dashdot, lw=2, label="us (brackets)")
+plot(plt[7]; size=(800, 400))
+```
+
+Both methods give the same singular control, which matches the numerical solution from the direct method.
+
+## Indirect shooting method
+
+We now solve the problem using an indirect shooting method based on the singular control we computed. This approach is similar to the one used in the [double integrator example](@ref example-double-integrator-energy).
+
+First, import the necessary packages:
+
+```@example main
+using OrdinaryDiffEq
+using NonlinearSolve
+```
+
+Define the singular control in feedback form:
+
+```@example main
+u_indirect(x) = sin(x[3])^2
+nothing # hide
+```
+
+Build the flow for the singular arc:
+
+```@example main
+f = Flow(ocp, (x, p, tf) -> u_indirect(x))
+nothing # hide
+```
+
+Define the shooting function. We have 5 unknowns: the initial costate $p_0 \in \mathbb{R}^3$, the initial orientation $\theta_0$, and the final time $t_f$. We must define 5 equations to solve for these unknowns.
+
+```@example main
+t0 = 0
+
+function shoot!(s, p0, θ0, tf)
+ q_t0, p_t0 = [0, 0, θ0], p0
+ q_tf, p_tf = f(t0, q_t0, p_t0, tf)
+
+ s[1] = q_tf[1] - 1 # x(tf) = 1 (boundary condition)
+ s[2] = q_tf[2] # y(tf) = 0 (boundary condition)
+ s[3] = p_t0[3] # pθ(0) = 0 (transversality condition)
+ s[4] = p_tf[3] # pθ(tf) = 0 (transversality condition)
+
+ # H(tf) = 1 (for time-optimal with p^0 = -1)
+ pxf = p_tf[1]
+ pyf = p_tf[2]
+ θf = q_tf[3]
+ s[5] = pxf * cos(θf) + pyf * (sin(θf) + 1) - 1
+end
+nothing # hide
+```
+
+Use the direct solution to provide an initial guess:
+
+```@example main
+p0 = costate(direct_sol)(t0)
+θ0 = state(direct_sol)(t0)[3]
+tf = variable(direct_sol)
+
+println("Initial guess:")
+println("p0 = ", p0)
+println("θ0 = ", θ0)
+println("tf = ", tf)
+nothing # hide
+```
+
+Set up and solve the nonlinear system:
+
+```@example main
+# Auxiliary in-place NLE function
+nle!(s, ξ, _) = shoot!(s, ξ[1:3], ξ[4], ξ[5])
+
+# Initial guess for the Newton solver
+ξ_guess = [p0..., θ0, tf]
+
+# NLE problem with initial guess
+prob = NonlinearProblem(nle!, ξ_guess)
+
+# Resolution of the shooting equations
+shooting_sol = solve(prob; show_trace=Val(false))
+p0_sol, θ0_sol, tf_sol = shooting_sol.u[1:3], shooting_sol.u[4], shooting_sol.u[5]
+
+println("Shooting solution:")
+println("p0 = ", p0_sol)
+println("θ0 = ", θ0_sol)
+println("tf = ", tf_sol)
+nothing # hide
+```
+
+Reconstruct the indirect solution:
+
+```@example main
+indirect_sol = f((t0, tf_sol), [0, 0, θ0_sol], p0_sol; saveat=range(t0, tf_sol, 100))
+nothing # hide
+```
+
+Plot the indirect solution alongside the direct solution:
+
+```@example main
+plot!(plt, indirect_sol; label="indirect", color=2, linestyle=:dash, opt...)
+```
+
+The indirect and direct solutions match very well, confirming that our singular control computation is correct.
+
+## See also
+
+- [Differential geometry tools](@ref manual-differential-geometry) — Mathematical definitions and usage of `Lift`, `Poisson`, `@Lie`
+- [Goddard tutorial](@extref Tutorials tutorial-goddard) — More complex example with bang, singular, and boundary arcs
+- [Compute flows from optimal control problems](@ref manual-flow-ocp) — Using flows for indirect methods
diff --git a/docs/src/manual-abstract.md b/docs/src/manual-abstract.md
index 1986c883..93454693 100644
--- a/docs/src/manual-abstract.md
+++ b/docs/src/manual-abstract.md
@@ -24,7 +24,8 @@ end
```
!!! warning
- Note that the full code of the definition above is not provided (hence the `...`) The same is true for most examples below (only those without `...` are indeed complete). Also note that problem definitions must at least include definitions for time, state, control, dynamics and cost.
+ - Note that the full code of the definition above is not provided (hence the `...`) The same is true for most examples below (only those without `...` are indeed complete).
+ - Also note that problem definitions must at least include definitions for time, state, dynamics and cost. The control declaration is optional (see [Control-free problems](@ref manual-abstract-control-free)).
Aliases `v₁`, `v₂` (and `v1`, `v2`) are automatically defined and can be used in subsequent expressions instead of `v[1]` and `v[2]`. The user can also define her own aliases for the components (one alias per dimension):
@@ -127,6 +128,45 @@ end
!!! note
One dimensional variable, state or control are treated as scalars (`Real`), not vectors (`Vector`). In Julia, for `x::Real`, it is possible to write `x[1]` (and `x[1][1]`...) so it is OK (though useless) to write `x₁`, `x1` or `x[1]` instead of simply `x` to access the corresponding value. Conversely it is *not* OK to use such an `x` as a vector, for instance as in `...f(x)...` where `f(x::Vector{T}) where {T <: Real}`.
+## [Control-free problems](@id manual-abstract-control-free)
+
+The control declaration is **optional**. You can define problems without control for:
+
+- **Parameter estimation**: Identify unknown parameters in the dynamics from observed data
+- **Dynamic optimization**: Optimize constant parameters subject to ODE constraints
+
+For example, to estimate a growth rate parameter:
+
+```julia
+@def begin
+ p ∈ R, variable # parameter to estimate
+ t ∈ [0, 10], time
+ x ∈ R, state
+ x(0) == 2.0
+ ẋ(t) == p * x(t) # dynamics depends on p
+ ∫(x(t) - data(t))² → min # fit to observed data
+end
+```
+
+Or to optimize the pulsation of a harmonic oscillator:
+
+```julia
+@def begin
+ ω ∈ R, variable # pulsation to optimize
+ t ∈ [0, 1], time
+ x = (q, v) ∈ R², state
+ q(0) == 1.0
+ v(0) == 0.0
+ q(1) == 0.0 # final condition
+ ẋ(t) == [v(t), -ω²*q(t)] # harmonic oscillator
+ ω² → min # minimize pulsation
+end
+```
+
+!!! compat "Upcoming feature"
+
+ Control-free problem syntax (omitting the control declaration) is currently being implemented. For now, use a dummy control with `u ∈ R, control` and `u(t) == 0` as a workaround. See the [Control-free problems example](@ref example-control-free) for executable examples.
+
## [Dynamics](@id manual-abstract-dynamics)
```julia
@@ -136,7 +176,7 @@ end
The dynamics is given in the standard vectorial ODE form:
```math
- \dot{x}(t) = f([t, ]x(t), u(t)[, v])
+ \dot{x}(t) = f([t, ]x(t)[, u(t)][, v])
```
depending on whether it is autonomous / with a variable or not (the parser will detect time and variable dependences,
@@ -534,7 +574,7 @@ end
## Misc
-- Declarations (of variable - if any -, time, state and control) must be done first. Then, dynamics, constraints and cost can be introduced in an arbitrary order.
+- Declarations (of variable - if any -, time, state and control - if any -) must be done first. Then, dynamics, constraints and cost can be introduced in an arbitrary order.
- It is possible to provide numbers / labels (as in math equations) for the constraints to improve readability (this is mostly for future use, typically to retrieve the Lagrange multiplier associated with the discretisation of a given constraint):
```julia
diff --git a/docs/src/manual-differential-geometry.md b/docs/src/manual-differential-geometry.md
new file mode 100644
index 00000000..1875f619
--- /dev/null
+++ b/docs/src/manual-differential-geometry.md
@@ -0,0 +1,634 @@
+# [Differential geometry tools](@id manual-differential-geometry)
+
+Optimal control theory relies on differential geometry tools to analyze Hamiltonian systems, compute singular controls, study controllability, and more. This page introduces the main operators available in OptimalControl.jl: Hamiltonian lift, Lie derivatives, Poisson brackets, Lie brackets, and partial time derivatives.
+
+!!! note "Type qualification"
+
+ Types like `Hamiltonian`, `HamiltonianLift`, `VectorField`, and `HamiltonianVectorField` are **not exported** by OptimalControl.jl. You must qualify them with `OptimalControl.` when using them (e.g., `OptimalControl.VectorField`). Functions and operators (`Lift`, `⋅`, `Lie`, `Poisson`, `@Lie`, `∂ₜ`) are exported and can be used directly.
+
+First, import the package:
+
+```@example main
+using OptimalControl
+```
+
+## Hamiltonian lift
+
+Given a vector field $X: \mathbb{R}^n \to \mathbb{R}^n$, its **Hamiltonian lift** is the function $H_X: \mathbb{R}^n \times (\mathbb{R}^n)^* \to \mathbb{R}$ defined by
+
+```math
+H_X(x, p) = \langle p, X(x) \rangle = \sum_{i=1}^n p_i X_i(x).
+```
+
+### From plain Julia functions
+
+The simplest way to compute a Hamiltonian lift is from a plain Julia function. By default, the function is treated as **autonomous** (time-independent) and **non-variable** (no extra parameter):
+
+```@example main
+# Define a vector field as a Julia function
+X(x) = [x[2], -x[1]]
+
+# Compute its Hamiltonian lift
+H = Lift(X)
+
+# Evaluate at a point (x, p)
+x = [1, 2]
+p = [3, 4]
+H(x, p)
+```
+
+The result is $H(x, p) = p_1 x_2 + p_2 (-x_1) = 3 \times 2 + 4 \times (-1) = 2$.
+
+### From VectorField type
+
+You can also use the `OptimalControl.VectorField` type, which allows more control over the function's properties:
+
+```@example main-1
+using OptimalControl # hide
+# Wrap in VectorField (autonomous, non-variable by default)
+X = OptimalControl.VectorField(x -> [x[2], -x[1]])
+H = Lift(X)
+
+# This returns a HamiltonianLift object
+H([1, 2], [3, 4])
+```
+
+### Non-autonomous case
+
+For time-dependent vector fields, use `autonomous=false`:
+
+```@example main-2
+using OptimalControl # hide
+# Non-autonomous vector field: X(t, x) = [t*x[2], -x[1]]
+X(t, x) = [t * x[2], -x[1]]
+H = Lift(X; autonomous=false)
+
+# Signature is now H(t, x, p)
+H(2, [1, 2], [3, 4])
+```
+
+### Variable case
+
+For vector fields depending on an additional parameter $v$, use `variable=true`:
+
+```@example main-3
+using OptimalControl # hide
+# Variable vector field: X(x, v) = [x[2] + v, -x[1]]
+X(x, v) = [x[2] + v, -x[1]]
+H = Lift(X; variable=true)
+
+# Signature is now H(x, p, v)
+H([1, 2], [3, 4], 1)
+```
+
+## Lie derivative
+
+The **Lie derivative** of a function $f: \mathbb{R}^n \to \mathbb{R}$ along a vector field $X$ is defined by
+
+```math
+(X \cdot f)(x) = f'(x) \cdot X(x) = \sum_{i=1}^n \frac{\partial f}{\partial x_i}(x) X_i(x).
+```
+
+This represents the directional derivative of $f$ along $X$.
+
+### [From plain Julia functions](@id lie-from-functions)
+
+When using plain Julia functions, they are treated as autonomous and non-variable:
+
+```@example main-4
+using OptimalControl # hide
+# Vector field and scalar function
+X(x) = [x[2], -x[1]]
+f(x) = x[1]^2 + x[2]^2
+
+# Lie derivative (using dot operator)
+Xf = X ⋅ f
+
+# Evaluate at a point
+Xf([1, 2])
+```
+
+For the harmonic oscillator with $X(x) = (x_2, -x_1)$ and energy $f(x) = x_1^2 + x_2^2$:
+
+```math
+(X \cdot f)(x) = 2x_1 x_2 + 2x_2(-x_1) = 0,
+```
+
+which confirms that energy is conserved along trajectories.
+
+### [From VectorField type](@id lie-from-vectorfield)
+
+```@example main-5
+using OptimalControl # hide
+# Using VectorField type
+X = OptimalControl.VectorField(x -> [x[2], -x[1]])
+g(x) = x[1]^2 + x[2]^2
+
+# Lie derivative
+Xg = X ⋅ g
+Xg([1, 2])
+```
+
+### Alternative syntax
+
+The `Lie` function is equivalent to the `⋅` operator:
+
+```@example main-5
+# These are equivalent
+Xg1 = X ⋅ g
+Xg2 = Lie(X, g)
+
+Xg1([1, 2]) == Xg2([1, 2])
+```
+
+### With keyword arguments
+
+For non-autonomous or variable cases, use the `Lie` function with keyword arguments:
+
+```@example main-6
+using OptimalControl # hide
+# Non-autonomous case
+X(t, x) = [t + x[2], -x[1]]
+f(t, x) = t + x[1]^2 + x[2]^2
+
+Xf = Lie(X, f; autonomous=false)
+Xf(1, [1, 2])
+```
+
+```@example main-7
+using OptimalControl # hide
+# Variable case
+X(x, v) = [x[2] + v, -x[1]]
+f(x, v) = x[1]^2 + x[2]^2 + v
+
+Xf = Lie(X, f; variable=true)
+Xf([1, 2], 1)
+```
+
+### With VectorField type
+
+You can also create the VectorField explicitly with the keywords, then use it without keywords in the Lie function:
+
+```@example main-7a
+using OptimalControl # hide
+# Non-autonomous VectorField created with keywords
+X = OptimalControl.VectorField((t, x) -> [t + x[2], -x[1]]; autonomous=false)
+f(t, x) = t + x[1]^2 + x[2]^2
+
+# No keywords needed here - the VectorField already knows its properties
+Xf = Lie(X, f)
+Xf(1, [1, 2])
+```
+
+```@example main-7b
+using OptimalControl # hide
+# Variable VectorField created with keywords
+X = OptimalControl.VectorField((x, v) -> [x[2] + v, -x[1]]; variable=true)
+f(x, v) = x[1]^2 + x[2]^2 + v
+
+# No keywords needed here
+Xf = Lie(X, f)
+Xf([1, 2], 1)
+```
+
+## Poisson bracket
+
+For two functions $f, g: \mathbb{R}^n \times (\mathbb{R}^n)^* \to \mathbb{R}$, the **Poisson bracket** is defined by
+
+```math
+\{f, g\}(x, p) = \sum_{i=1}^n \left( \frac{\partial f}{\partial p_i} \frac{\partial g}{\partial x_i} - \frac{\partial f}{\partial x_i} \frac{\partial g}{\partial p_i} \right).
+```
+
+### Properties
+
+The Poisson bracket satisfies:
+
+- **Bilinearity**: $\{af + bg, h\} = a\{f, h\} + b\{g, h\}$ for scalars $a, b$
+- **Antisymmetry**: $\{f, g\} = -\{g, f\}$
+- **Leibniz rule**: $\{fg, h\} = f\{g, h\} + g\{f, h\}$
+- **Jacobi identity**: $\{\{f, g\}, h\} + \{\{h, f\}, g\} + \{\{g, h\}, f\} = 0$
+
+### [From plain Julia functions](@id poisson-from-functions)
+
+```@example main-8
+using OptimalControl # hide
+# Define two Hamiltonian functions
+f(x, p) = p[1] * x[2] + p[2] * x[1]
+g(x, p) = x[1]^2 + p[2]^2
+
+# Compute the Poisson bracket
+H = Poisson(f, g)
+
+# Evaluate at a point
+x = [1, 2]
+p = [3, 4]
+H(x, p)
+```
+
+### Verify antisymmetry
+
+```@example main-8
+Hfg = Poisson(f, g)
+Hgf = Poisson(g, f)
+
+println("Poisson(f, g) = ", Hfg(x, p))
+println("Poisson(g, f) = ", Hgf(x, p))
+println("Sum = ", Hfg(x, p) + Hgf(x, p))
+```
+
+### From Hamiltonian type
+
+```@example main-8
+# Wrap in Hamiltonian type
+F = OptimalControl.Hamiltonian(f)
+G = OptimalControl.Hamiltonian(g)
+
+H = Poisson(F, G)
+H(x, p)
+```
+
+### [With keyword arguments](@id poisson-kwargs)
+
+```@example main-9
+using OptimalControl # hide
+# Non-autonomous case
+f(t, x, p) = t + p[1] * x[2] + p[2] * x[1]
+g(t, x, p) = t^2 + x[1]^2 + p[2]^2
+
+H = Poisson(f, g; autonomous=false)
+H(1, [1, 2], [3, 4])
+```
+
+### With Hamiltonian type and keywords
+
+You can also create Hamiltonian objects explicitly with keywords, then use them without keywords in the Poisson function. **Important**: both Hamiltonians must have the same time and variable dependencies:
+
+```@example main-9a
+using OptimalControl # hide
+# Non-autonomous Hamiltonians created with keywords
+f(t, x, p) = t + p[1] * x[2] + p[2] * x[1]
+g(t, x, p) = t^2 + x[1]^2 + p[2]^2
+
+F = OptimalControl.Hamiltonian(f; autonomous=false)
+G = OptimalControl.Hamiltonian(g; autonomous=false)
+
+# No keywords needed here - both Hamiltonians are already non-autonomous
+H = Poisson(F, G)
+H(1, [1, 2], [3, 4])
+```
+
+```@example main-9b
+using OptimalControl # hide
+# Variable Hamiltonians created with keywords
+f(x, p, v) = x[1]^2 + p[2]^2 + v
+g(x, p, v) = x[2]^2 + p[1]^2 + 2*v
+
+F = OptimalControl.Hamiltonian(f; variable=true)
+G = OptimalControl.Hamiltonian(g; variable=true)
+
+# Both are variable, so the Poisson bracket is also variable
+H = Poisson(F, G)
+H([1, 2], [3, 4], 1)
+```
+
+### Relation to Hamiltonian vector fields
+
+The Poisson bracket is closely related to the Lie derivative. If $\vec{H} = (\nabla_p H, -\nabla_x H)$ denotes the Hamiltonian vector field associated to $H$, then
+
+```math
+\{H, G\} = \vec{H} \cdot G.
+```
+
+This means the Poisson bracket of $H$ and $G$ equals the Lie derivative of $G$ along the Hamiltonian vector field of $H$.
+
+### Poisson bracket of Hamiltonian lifts
+
+When computing the Poisson bracket of two Hamiltonian lifts, the result is the Hamiltonian lift of the Lie bracket of the underlying vector fields:
+
+```@example main-10
+using OptimalControl # hide
+# Two vector fields
+X(x) = [x[1]^2, x[2]^2]
+Y(x) = [x[2], -x[1]]
+
+# Their Hamiltonian lifts
+HX = Lift(X)
+HY = Lift(Y)
+
+# Poisson bracket of lifts
+H = Poisson(HX, HY)
+H([1, 2], [3, 4])
+```
+
+This satisfies: $\{H_X, H_Y\} = H_{[X,Y]}$ where $[X,Y]$ is the Lie bracket of vector fields (see next section).
+
+## Lie bracket of vector fields
+
+For two vector fields $X, Y: \mathbb{R}^n \to \mathbb{R}^n$, the **Lie bracket** is the vector field $[X, Y]$ defined by
+
+```math
+[X, Y](x) = Y'(x) \cdot X(x) - X'(x) \cdot Y(x),
+```
+
+where $X'(x)$ denotes the Jacobian matrix of $X$ at $x$.
+
+### [From VectorField type](@id bracket-from-vectorfield)
+
+```@example main-11
+using OptimalControl # hide
+# Define two vector fields
+X = OptimalControl.VectorField(x -> [x[2], -x[1]])
+Y = OptimalControl.VectorField(x -> [x[1], x[2]])
+
+# Compute the Lie bracket
+Z = Lie(X, Y)
+
+# Evaluate at a point
+Z([1, 2])
+```
+
+### Relation to Poisson brackets
+
+If $H_X = \text{Lift}(X)$ and $H_Y = \text{Lift}(Y)$ are the Hamiltonian lifts, then:
+
+```math
+\{H_X, H_Y\} = H_{[X,Y]}.
+```
+
+Let's verify this numerically:
+
+```@example main-11
+# Hamiltonian lifts
+HX = Lift(x -> X(x))
+HY = Lift(x -> Y(x))
+
+# Poisson bracket of the lifts
+HXY = Poisson(HX, HY)
+
+# Lift of the Lie bracket
+HZ = Lift(x -> Z(x))
+
+# Compare at a point
+x = [1, 2]
+p = [3, 4]
+
+println("Poisson bracket: ", HXY(x, p))
+println("Lift of Lie bracket: ", HZ(x, p))
+```
+
+## The `@Lie` macro
+
+The `@Lie` macro provides a convenient syntax for computing Lie brackets (for vector fields) and Poisson brackets (for Hamiltonians).
+
+!!! warning "Important distinction"
+
+ - **Square brackets `[...]`** denote **Lie brackets** and work with:
+ - `VectorField` objects
+ - Plain Julia functions (automatically wrapped as `VectorField`)
+ - **Curly braces `{...}`** denote **Poisson brackets** and work with:
+ - Plain Julia functions (automatically wrapped as `Hamiltonian`)
+ - `Hamiltonian` objects
+
+ When using **only plain functions** (no `VectorField` or `Hamiltonian` objects), specify `autonomous` and/or `variable` keywords as needed to match your function signature. Keywords are optional - if not specified, they use the default values (`autonomous=true`, `variable=false`). If you mix plain functions with `VectorField` or `Hamiltonian` objects, the keywords are inferred from the `VectorField` or `Hamiltonian` objects.
+
+### Lie brackets with VectorField
+
+```@example main-12
+using OptimalControl # hide
+# Define vector fields
+F1 = OptimalControl.VectorField(x -> [0, -x[3], x[2]])
+F2 = OptimalControl.VectorField(x -> [x[3], 0, -x[1]])
+
+# Compute Lie bracket using macro
+F12 = @Lie [F1, F2]
+
+# Evaluate
+F12([1, 2, 3])
+```
+
+### Nested Lie brackets
+
+```@example main-12
+F3 = OptimalControl.VectorField(x -> [x[1], x[2], x[3]])
+F123 = @Lie [[F1, F2], F3]
+F123([1, 2, 3])
+```
+
+### Lie brackets with plain Julia functions
+
+You can also use plain Julia functions directly with the `@Lie` macro. The functions will be automatically wrapped in `VectorField` objects:
+
+```@example main-12a
+using OptimalControl # hide
+# Define plain Julia functions
+X(x) = [x[2], -x[1]]
+Y(x) = [x[1], x[2]]
+
+# Compute Lie bracket using macro with plain functions
+Z = @Lie [X, Y]
+
+# Evaluate
+Z([1, 2])
+```
+
+### With keyword arguments for plain functions
+
+For non-autonomous or variable cases, specify the keywords:
+
+```@example main-12b
+using OptimalControl # hide
+# Non-autonomous plain functions
+X(t, x) = [t + x[2], -x[1]]
+Y(t, x) = [x[1], t*x[2]]
+
+# Use autonomous=false keyword
+Z = @Lie [X, Y] autonomous=false
+Z(1, [1, 2])
+```
+
+```@example main-12c
+using OptimalControl # hide
+# Variable plain functions
+X(x, v) = [x[2] + v, -x[1]]
+Y(x, v) = [x[1], x[2] + v]
+
+# Use variable=true keyword
+Z = @Lie [X, Y] variable=true
+Z([1, 2], 1)
+```
+
+### Nested brackets with plain functions
+
+```@example main-12d
+using OptimalControl # hide
+X(x) = [0, -x[3], x[2]]
+Y(x) = [x[3], 0, -x[1]]
+Z(x) = [x[1], x[2], x[3]]
+
+# Nested Lie brackets
+nested = @Lie [[X, Y], Z]
+nested([1, 2, 3])
+```
+
+!!! tip "Plain functions vs VectorField"
+
+ Using plain functions with `@Lie [X, Y]` is convenient for quick computations. However, if you need to reuse the same vector field multiple times or want explicit control over the autonomy/variability, consider creating `VectorField` objects explicitly:
+
+ ```julia
+ # Explicit VectorField (keywords at creation)
+ X = OptimalControl.VectorField((t, x) -> [t + x[2], -x[1]]; autonomous=false)
+ Y = OptimalControl.VectorField((t, x) -> [x[1], t*x[2]]; autonomous=false)
+ Z = @Lie [X, Y] # No keywords needed
+
+ # Plain functions (keywords at macro call)
+ X(t, x) = [t + x[2], -x[1]]
+ Y(t, x) = [x[1], t*x[2]]
+ Z = @Lie [X, Y] autonomous=false
+ ```
+
+### Poisson brackets from plain functions
+
+For Hamiltonian functions (plain Julia functions), use curly braces `{_, _}`:
+
+```@example main-13
+using OptimalControl # hide
+# Define Hamiltonian functions
+H0(x, p) = p[1] * x[2] + p[2] * (-x[1])
+H1(x, p) = p[2]
+
+# Compute Poisson bracket
+H01 = @Lie {H0, H1}
+
+# Evaluate
+H01([1, 2], [3, 4])
+```
+
+### Iterated Poisson brackets
+
+The macro is particularly useful for computing iterated brackets, which appear in singular control analysis:
+
+```@example main-13
+# First-order bracket
+H01 = @Lie {H0, H1}
+
+# Second-order brackets
+H001 = @Lie {H0, H01}
+H101 = @Lie {H1, H01}
+
+# Evaluate
+x = [1, 2]
+p = [3, 4]
+
+println("H01(x, p) = ", H01(x, p))
+println("H001(x, p) = ", H001(x, p))
+println("H101(x, p) = ", H101(x, p))
+```
+
+These iterated brackets are used to compute singular controls. For a pseudo-Hamiltonian of the form $H = H_0 + u H_1$, if the switching function $H_1$ vanishes on an interval (singular arc), the control is given by
+
+```math
+u_s = -\frac{H_{001}}{H_{101}},
+```
+
+provided $H_{101} \neq 0$. See the [singular control example](@ref example-singular-control) for a complete application.
+
+### [With keyword arguments](@id macro-kwargs)
+
+For non-autonomous functions, specify `autonomous=false`:
+
+```@example main-14
+using OptimalControl # hide
+# Non-autonomous Hamiltonians
+H0(t, x, p) = t + p[1] * x[2] + p[2] * (-x[1])
+H1(t, x, p) = p[2]
+
+# Poisson bracket with keyword
+H01 = @Lie {H0, H1} autonomous=false
+
+# Evaluate
+H01(1, [1, 2], [3, 4])
+```
+
+### Poisson brackets from Hamiltonian type
+
+```@example main-15
+using OptimalControl # hide
+# Using Hamiltonian type
+H1 = OptimalControl.Hamiltonian((x, p) -> x[1]^2 + p[2]^2)
+H2 = OptimalControl.Hamiltonian((x, p) -> x[2]^2 + p[1]^2)
+
+# Macro works with Hamiltonian objects too
+H12 = @Lie {H1, H2}
+H12([1, 1], [3, 2])
+```
+
+## Partial time derivative
+
+For non-autonomous functions $f(t, x, \ldots)$, the **partial derivative with respect to time** is computed using the `∂ₜ` operator:
+
+```math
+(\partial_t f)(t, x, \ldots) = \frac{\partial f}{\partial t}(t, x, \ldots).
+```
+
+### Basic usage
+
+```@example main-16
+using OptimalControl # hide
+# Define a time-dependent function
+f(t, x) = t * x
+
+# Compute partial time derivative
+df = ∂ₜ(f)
+
+# Evaluate
+df(0, 8)
+```
+
+```@example main-16
+df(2, 3)
+```
+
+### More complex example
+
+```@example main-17
+using OptimalControl # hide
+# Function with multiple arguments
+g(t, x, p) = t^2 + x[1] * p[1] + x[2] * p[2]
+
+# Partial derivative
+dg = ∂ₜ(g)
+
+# At t=3, ∂g/∂t = 2t = 6
+dg(3, [1, 2], [4, 5])
+```
+
+### Relation to total time derivative
+
+For a non-autonomous Hamiltonian $H(t, x, p)$ and a function $G(t, x, p)$, the **total time derivative** along the Hamiltonian flow is:
+
+```math
+\frac{d}{dt} G(t, x(t), p(t)) = \partial_t G + \{H, G\}.
+```
+
+This is the sum of:
+
+- The **partial time derivative** $\partial_t G$ (explicit time dependence)
+- The **Poisson bracket** $\{H, G\}$ (evolution along the flow)
+
+This relation is fundamental in non-autonomous optimal control theory.
+
+## Summary
+
+| Function/Operator | Mathematical notation | Julia syntax |
+| ----------------- | --------------------- | ------------ |
+| Hamiltonian lift | $H_X(x,p) = \langle p, X(x) \rangle$ | `H = Lift(X)` |
+| Lie derivative | $(X \cdot f)(x) = f'(x) \cdot X(x)$ | `X ⋅ f` or `Lie(X, f)` |
+| Poisson bracket | $\{f,g\}(x,p) = \langle \nabla_p f, \nabla_x g \rangle - \langle \nabla_x f, \nabla_p g \rangle$ | `Poisson(f, g)` or `@Lie {f, g}` |
+| Lie bracket | $[X,Y](x) = Y'(x) X(x) - X'(x) Y(x)$ | `Lie(X, Y)` or `@Lie [X, Y]` |
+| Partial time derivative | $\partial_t f(t, x, \ldots)$ | `∂ₜ(f)` |
+
+## See also
+
+- [Compute flows from Hamiltonians and others](@ref manual-flow-others) — Using flows with Hamiltonian vector fields
+- [Singular control example](@ref example-singular-control) — Application to computing singular controls
+- [Goddard tutorial](@extref Tutorials tutorial-goddard) — Complex example with bang, singular, and boundary arcs
diff --git a/docs/src/manual-initial-guess.md b/docs/src/manual-initial-guess.md
index 43c76255..e970eca2 100644
--- a/docs/src/manual-initial-guess.md
+++ b/docs/src/manual-initial-guess.md
@@ -63,6 +63,20 @@ nothing # hide
We first solve the problem without giving an initial guess.
This will default to initialize all variables to 0.1.
+To visualize the default initial guess before solving, we can run the solver with `max_iter=0`:
+
+```@example main
+# visualize the default initial guess (no iterations)
+sol_init = solve(ocp1; init=nothing, max_iter=0, display=false)
+plot(sol_init; size=(600, 450))
+```
+
+!!! tip "Visualizing any initial guess"
+
+ This technique works with any initial guess specification. By setting `max_iter=0`, the solver stops immediately after initialization, allowing you to visualize the initial guess before the optimization process begins.
+
+Now let us solve the problem completely:
+
```@example main
# solve the optimal control problem without initial guess
sol = solve(ocp1; display=false)
diff --git a/docs/src/manual-model.md b/docs/src/manual-model.md
index a36a6dd9..43eb3173 100644
--- a/docs/src/manual-model.md
+++ b/docs/src/manual-model.md
@@ -70,7 +70,7 @@ where $p^0 = -1$ since we are in the normal case. From the Pontryagin maximum pr
u(x, p) = p_v
```
-since $\partial^2_{uu} H = p^0 = - 1 < 0$.
+since $\partial^2_{uu} H = p^0 = - 1 < 0$.
```@example main
u = (x, p) -> p[2] # control law in feedback form
@@ -231,7 +231,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
+#### [Summary table](@id manual-model-summary-time)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -293,7 +293,7 @@ Get the dimension of state box constraints:
dim_state_constraints_box(ocp) # returns number of box constraints on state
```
-#### Summary table
+#### [Summary table](@id manual-model-summary-state)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -344,7 +344,7 @@ Get the dimension of control box constraints:
dim_control_constraints_box(ocp) # returns number of box constraints on control
```
-#### Summary table
+#### [Summary table](@id manual-model-summary-control)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -395,7 +395,7 @@ Get the dimension of variable box constraints:
dim_variable_constraints_box(ocp) # returns number of box constraints on variable
```
-#### Summary table
+#### [Summary table](@id manual-model-summary-variable)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -431,7 +431,7 @@ The first argument `dx` is mutated upon call and contains the state derivative.
* `u`: control
* `v`: variable
-#### Summary table
+#### [Summary table](@id manual-model-summary-dynamics)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -493,7 +493,7 @@ v = [1.0, 2.0]
f⁰(s, q, u, v) # returns the integrand value
```
-#### Summary table
+#### [Summary table](@id manual-model-summary-objective)
| Method | Returns | Description |
| -------- | --------- | --------- |
@@ -595,7 +595,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
+#### [Summary table](@id manual-model-summary-constraints)
| Method | Returns | Description |
| -------- | --------- | --------- |
diff --git a/docs/src/manual-plot.md b/docs/src/manual-plot.md
index dc02e0c0..796ddc9d 100644
--- a/docs/src/manual-plot.md
+++ b/docs/src/manual-plot.md
@@ -22,12 +22,12 @@ The table below summarizes the main plotting arguments and links to the correspo
| Section | Relevant Arguments |
| :---------------------------------------------------| :-------------------------------------------------------------------------------------------- |
-| [Basic concepts](@ref manual-plot-basic) | `size`, `state_style`, `costate_style`, `control_style`, `time_style`, `kwargs...` |
-| [Split vs. group layout](@ref manual-plot-layout) | `layout` |
-| [Plotting control norm](@ref manual-plot-control) | `control` |
-| [Normalised time](@ref manual-plot-time) | `time` |
-| [Constraints](@ref manual-plot-constraints) | `state_bounds_style`, `control_bounds_style`, `path_style`, `path_bounds_style`, `dual_style` |
-| [What to plot](@ref manual-plot-select) | `description...` |
+| [Basic concepts](@ref manual-plot-basic) | `size`, `state_style`, `costate_style`, `control_style`, `time_style`, `kwargs...` |
+| [Split vs. group layout](@ref manual-plot-layout) | `layout` |
+| [Plotting control norm](@ref manual-plot-control) | `control` |
+| [Normalised time](@ref manual-plot-time) | `time` |
+| [Constraints](@ref manual-plot-constraints) | `state_bounds_style`, `control_bounds_style`, `path_style`, `path_bounds_style`, `dual_style` |
+| [What to plot](@ref manual-plot-select) | `description...` |
You can plot solutions obtained from the `solve` function or from a flow computed using an optimal control problem and a control law. See the [Basic Concepts](@ref manual-plot-basic) and [From Flow function](@ref manual-plot-flow) sections for details.
@@ -104,6 +104,7 @@ plotattr("color") # Specific Attribute Example
You can also visit the Plot documentation online to get the descriptions of the attributes:
- To pass attributes to the plot, see the [attributes plot](https://docs.juliaplots.org/latest/generated/attributes_plot/) documentation. For instance, you can specify the size of the figure.
+
```@raw html
List of plot attributes.
```
@@ -117,7 +118,9 @@ end # hide
```@raw html
```
+
- You can pass attributes to all subplots at once by referring to the [attributes subplot](https://docs.juliaplots.org/latest/generated/attributes_subplot/) documentation. For example, you can specify the location of the legends.
+
```@raw html
List of subplot attributes.
```
@@ -131,7 +134,9 @@ end # hide
```@raw html
```
+
- Similarly, you can pass axis attributes to all subplots. See the [attributes axis](https://docs.juliaplots.org/latest/generated/attributes_axis/) documentation. For example, you can remove the grid from every subplot.
+
```@raw html
List of axis attributes.
```
@@ -145,7 +150,9 @@ end # hide
```@raw html
```
+
- Finally, you can pass series attributes to all subplots. Refer to the [attributes series](https://docs.juliaplots.org/latest/generated/attributes_series/) documentation. For instance, you can set the width of the curves using `linewidth`.
+
```@raw html
List of series attributes.
```
@@ -174,7 +181,7 @@ plot(sol;
control_style = (color=:red, linewidth=2)) # style: control trajectory
```
-Vertical axes at the initial and final times are automatically plotted. The style can me modified with the `time_style` keyword argument.
+Vertical axes at the initial and final times are automatically plotted. The style can me modified with the `time_style` keyword argument.
Additionally, you can choose not to display for instance the state and the costate trajectories by setting their styles to `:none`. You can set to `:none` any style.
```@example main
@@ -235,7 +242,7 @@ If you prefer to get a more compact figure, you can use the `layout` optional ke
```@example main
plot(sol; layout=:group)
```
-
+
The default layout value is `:split` which corresponds to the grid of subplots presented above.
```@example main
@@ -316,6 +323,7 @@ plot(plt[1]) # x₁
plt = plot(sol)
plot(plt[2]) # x₂
```
+
```@example main
plt = plot(sol)
plot(plt[3]) # p₁
diff --git a/docs/src/manual-solution.md b/docs/src/manual-solution.md
index 770dd669..2714f562 100644
--- a/docs/src/manual-solution.md
+++ b/docs/src/manual-solution.md
@@ -9,7 +9,7 @@ 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)
@@ -195,7 +195,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
+#### [Summary table](@id manual-solution-summary-trajectories)
| Method | Returns | Description |
| -------- | --------- | ------------- |
@@ -218,7 +218,7 @@ Get the optimal objective value:
objective(sol) # returns the objective value
```
-#### Summary table
+#### [Summary table](@id manual-solution-summary-objective)
| Method | Returns | Description |
| -------- | --------- | ------------- |
@@ -375,7 +375,7 @@ Get additional solver-specific information:
infos(sol) # returns dictionary of solver info
```
-#### Summary table
+#### [Summary table](@id manual-solution-summary-solver)
| Method | Returns | Description |
| -------- | --------- | ------------- |
diff --git a/docs/src/manual-solve-explicit.md b/docs/src/manual-solve-explicit.md
index 7faa5d1f..665f4922 100644
--- a/docs/src/manual-solve-explicit.md
+++ b/docs/src/manual-solve-explicit.md
@@ -42,8 +42,7 @@ The mode is **automatically detected**: if any of `discretizer`, `modeler`, or `
### Creating strategy instances
-Each strategy is constructed with its options as keyword arguments.
-First, load the required solver packages:
+Each strategy is constructed with its options as keyword arguments. First, load the required solver packages:
```julia
# Load solver packages (only what you need)
diff --git a/src/imports/ctflows.jl b/src/imports/ctflows.jl
index 8ddeedc3..7a61d6e5 100644
--- a/src/imports/ctflows.jl
+++ b/src/imports/ctflows.jl
@@ -4,7 +4,7 @@
@reexport import CTFlows: CTFlows # for generated code (prefix)
# Types
-import CTFlows: Hamiltonian, HamiltonianLift, HamiltonianVectorField
+import CTFlows: Hamiltonian, HamiltonianLift, HamiltonianVectorField, VectorField
# Methods
-@reexport import CTFlows: Lift, Flow, ⋅, Lie, Poisson, @Lie, *
+@reexport import CTFlows: Lift, Flow, ⋅, Lie, Poisson, @Lie, *, ∂ₜ
diff --git a/test/helpers/print_utils.jl b/test/helpers/print_utils.jl
index 537ac9ea..7db896da 100644
--- a/test/helpers/print_utils.jl
+++ b/test/helpers/print_utils.jl
@@ -182,7 +182,9 @@ end
ref_obj::Union{Real, Nothing},
iterations::Union{Int, Nothing} = nothing,
memory_bytes::Union{Int, Nothing} = nothing,
- show_memory::Bool = false
+ show_memory::Bool = false,
+ rtol::Real = 1e-2,
+ atol::Real = 1e-3
)
Display a formatted table line for a test result.
@@ -215,6 +217,8 @@ Format inspired by print_benchmark_line() in CTBenchmarks.jl.
- `iterations`: Number of iterations (optional)
- `memory_bytes`: Allocated memory in bytes (optional)
- `show_memory`: Show memory (default: false)
+- `rtol`: Relative tolerance for color thresholds (default: 1e-2)
+- `atol`: Absolute tolerance for color thresholds (default: 1e-3)
"""
function print_test_line(
test_type::String,
@@ -229,9 +233,17 @@ function print_test_line(
iterations::Union{Int,Nothing}=nothing,
memory_bytes::Union{Int,Nothing}=nothing,
show_memory::Bool=false,
+ rtol::Real=1e-2,
+ atol::Real=1e-3,
)
- # Relative error calculation
- rel_error = ref_obj === nothing ? missing : abs(obj - ref_obj) / abs(ref_obj) * 100
+ # Error calculation: use absolute error when ref is near zero, otherwise relative error
+ error_value = if ref_obj === nothing
+ missing
+ elseif abs(ref_obj) < 1e-6
+ abs(obj - ref_obj) # Absolute error for near-zero reference
+ else
+ abs(obj - ref_obj) / abs(ref_obj) * 100 # Relative error in %
+ end
# Colored status (✓ green or ✗ red)
if success
@@ -289,12 +301,27 @@ function print_test_line(
print(lpad(ref_str, 14))
print(" │ ")
- # Error: scientific notation with 2 decimal places
- err_str = rel_error === missing ? "N/A" : @sprintf("%.2e", rel_error / 100) # Convert to fraction then scientific format
- err_color = if rel_error === missing
+ # Error: display format depends on whether it's absolute or relative
+ err_str = if error_value === missing
+ "N/A"
+ elseif ref_obj !== nothing && abs(ref_obj) < 1e-6
+ # Absolute error: display as scientific notation
+ @sprintf("%.2e", error_value)
+ else
+ # Relative error: display as scientific notation (convert % to fraction)
+ @sprintf("%.2e", error_value / 100)
+ end
+
+ # Color thresholds based on provided tolerances
+ err_color = if error_value === missing
:white
+ elseif ref_obj !== nothing && abs(ref_obj) < 1e-6
+ # Absolute error: green if < atol, yellow if < 5*atol, red otherwise
+ (error_value < atol ? :green : (error_value < 5 * atol ? :yellow : :red))
else
- (rel_error < 1 ? :green : (rel_error < 5 ? :yellow : :red))
+ # Relative error (in %): green if < rtol*100, yellow if < 5*rtol*100, red otherwise
+ rtol_percent = rtol * 100
+ (error_value < rtol_percent ? :green : (error_value < 5 * rtol_percent ? :yellow : :red))
end
printstyled(lpad(err_str, 7); color=err_color)
diff --git a/test/problems/TestProblems.jl b/test/problems/TestProblems.jl
index 810755ce..a60069f9 100644
--- a/test/problems/TestProblems.jl
+++ b/test/problems/TestProblems.jl
@@ -7,9 +7,11 @@ include("goddard.jl")
include("double_integrator.jl")
include("quadrotor.jl")
include("transfer.jl")
+include("control_free.jl")
export Beam, Goddard
export DoubleIntegratorTime, DoubleIntegratorEnergy, DoubleIntegratorEnergyConstrained
export Quadrotor, Transfer
+export ExponentialGrowth, HarmonicOscillator
end
diff --git a/test/problems/control_free.jl b/test/problems/control_free.jl
new file mode 100644
index 00000000..19ce173e
--- /dev/null
+++ b/test/problems/control_free.jl
@@ -0,0 +1,94 @@
+# Control-free optimal control problems for testing parameter estimation
+# and dynamic optimization capabilities.
+
+using OptimalControl
+
+"""
+ ExponentialGrowth()
+
+Return data for the exponential growth rate estimation problem.
+
+The problem consists in estimating the growth rate parameter `p` for:
+- ẋ(t) = p * x(t)
+- x(0) = 2.0
+
+by minimizing the squared error with observed data x_obs(t) = 2.0 * exp(0.5 * t):
+- ∫₀¹⁰ (x(t) - x_obs(t))² dt → min
+
+The function returns a NamedTuple with fields:
+ * `ocp` – CTParser/@def optimal control problem
+ * `obj` – reference optimal objective value (≈0.0, perfect fit)
+ * `name` – short problem name
+ * `p_expected` – expected parameter value (0.5)
+"""
+function ExponentialGrowth()
+ # observed data (analytical solution)
+ data(t) = 2.0 * exp(0.5 * t)
+
+ @def ocp begin
+ p ∈ R, variable # growth rate to estimate
+ t ∈ [0, 10], time
+ x ∈ R, state
+ u ∈ R, control # TO REMOVE WHEN POSSIBLE
+
+ x(0) == 2.0
+
+ ẋ(t) == p * x(t)
+
+ ∫((x(t) - data(t))^2 + 1e-5*u(t)^2) → min # fit to observed data # TO REMOVE u(t) when possible
+ end
+
+ return (
+ ocp=ocp,
+ obj=0.0, # perfect fit expected
+ name="exponential_growth",
+ init=nothing,
+ p_expected=0.5,
+ )
+end
+
+"""
+ HarmonicOscillator()
+
+Return data for the harmonic oscillator pulsation optimization problem.
+
+The problem consists in finding the minimal pulsation ω for:
+- q̈(t) = -ω² * q(t)
+- q(0) = 1.0, v(0) = 0.0
+- q(1) = 0.0
+
+by minimizing ω²:
+- ω² → min
+
+The analytical solution is ω = π/2 ≈ 1.5708.
+
+The function returns a NamedTuple with fields:
+ * `ocp` – CTParser/@def optimal control problem
+ * `obj` – reference optimal objective value (π²/4 ≈ 2.4674)
+ * `name` – short problem name
+ * `ω_expected` – expected pulsation value (π/2)
+"""
+function HarmonicOscillator()
+ @def ocp begin
+ ω ∈ R, variable # pulsation to optimize
+ t ∈ [0, 1], time
+ x = (q, v) ∈ R², state
+ u ∈ R, control # TO REMOVE WHEN POSSIBLE
+
+ q(0) == 1.0
+ v(0) == 0.0
+ q(1) == 0.0 # final condition
+
+ ẋ(t) == [v(t), -ω^2 * q(t)]
+
+ ω^2 + 1e-5*∫(u(t)^2) → min # minimize pulsation # TO REMOVE u(t) when possible
+ end
+
+ return (
+ ocp=ocp,
+ obj=π^2 / 4, # ω² = (π/2)² = π²/4 ≈ 2.4674
+ name="harmonic_oscillator",
+ init=nothing,
+ ω_expected=π / 2, # ≈ 1.5708
+ )
+end
diff --git a/test/suite/reexport/test_ctflows.jl b/test/suite/reexport/test_ctflows.jl
index 3e5fcfbf..66ca73f3 100644
--- a/test/suite/reexport/test_ctflows.jl
+++ b/test/suite/reexport/test_ctflows.jl
@@ -23,6 +23,7 @@ function test_ctflows()
OptimalControl.Hamiltonian,
OptimalControl.HamiltonianLift,
OptimalControl.HamiltonianVectorField,
+ OptimalControl.VectorField,
)
Test.@test isdefined(OptimalControl, nameof(T))
Test.@test !isdefined(CurrentModule, nameof(T))
@@ -37,7 +38,7 @@ function test_ctflows()
end
end
Test.@testset "Operators" begin
- for op in (:⋅, :Lie, :Poisson, :*)
+ for op in (:⋅, :Lie, :Poisson, :*, :∂ₜ)
Test.@test isdefined(OptimalControl, op)
Test.@test isdefined(CurrentModule, op)
end
@@ -128,7 +129,7 @@ function test_ctflows()
end
Test.@testset "@Lie Macro" begin
- # Test basic macro usage
+ # Test basic macro usage with VectorField
X1 = CTFlows.VectorField(x -> [x[2], -x[1]])
X2 = CTFlows.VectorField(x -> [x[1], x[2]])
@@ -140,6 +141,108 @@ function test_ctflows()
Test.@test lie_macro_result([1, 2]) isa Vector
end
+ Test.@testset "@Lie Macro with Plain Functions" begin
+ # ================================================================
+ # Autonomous plain functions
+ # ================================================================
+ Test.@testset "Autonomous plain functions" begin
+ X(x) = [x[2], -x[1]]
+ Y(x) = [x[1], x[2]]
+
+ # Lie bracket with macro
+ Z = @Lie [X, Y]
+ Test.@test Z isa CTFlows.VectorField
+ Test.@test Z([1, 2]) isa Vector
+
+ # Should give same result as with VectorField objects
+ X_vf = CTFlows.VectorField(X)
+ Y_vf = CTFlows.VectorField(Y)
+ Z_vf = @Lie [X_vf, Y_vf]
+ Test.@test Z([1, 2]) ≈ Z_vf([1, 2])
+ end
+
+ # ================================================================
+ # Non-autonomous plain functions
+ # ================================================================
+ Test.@testset "Non-autonomous plain functions" begin
+ X(t, x) = [t + x[2], -x[1]]
+ Y(t, x) = [x[1], t * x[2]]
+
+ Z = @Lie [X, Y] autonomous = false
+ Test.@test Z isa CTFlows.VectorField
+ Test.@test Z(1, [1, 2]) isa Vector
+
+ # Verify against VectorField version
+ X_vf = CTFlows.VectorField(X; autonomous=false)
+ Y_vf = CTFlows.VectorField(Y; autonomous=false)
+ Z_vf = @Lie [X_vf, Y_vf]
+ Test.@test Z(1, [1, 2]) ≈ Z_vf(1, [1, 2])
+ end
+
+ # ================================================================
+ # Variable plain functions
+ # ================================================================
+ Test.@testset "Variable plain functions" begin
+ X(x, v) = [x[2] + v, -x[1]]
+ Y(x, v) = [x[1], x[2] + v]
+
+ Z = @Lie [X, Y] variable = true
+ Test.@test Z isa CTFlows.VectorField
+ Test.@test Z([1, 2], 1) isa Vector
+
+ # Verify against VectorField version
+ X_vf = CTFlows.VectorField(X; variable=true)
+ Y_vf = CTFlows.VectorField(Y; variable=true)
+ Z_vf = @Lie [X_vf, Y_vf]
+ Test.@test Z([1, 2], 1) ≈ Z_vf([1, 2], 1)
+ end
+
+ # ================================================================
+ # Non-autonomous + variable plain functions
+ # ================================================================
+ Test.@testset "Non-autonomous variable plain functions" begin
+ X(t, x, v) = [t + x[2] + v, -x[1]]
+ Y(t, x, v) = [x[1], t * x[2] + v]
+
+ Z = @Lie [X, Y] autonomous = false variable = true
+ Test.@test Z isa CTFlows.VectorField
+ Test.@test Z(1, [1, 2], 1) isa Vector
+
+ # Verify against VectorField version
+ X_vf = CTFlows.VectorField(X; autonomous=false, variable=true)
+ Y_vf = CTFlows.VectorField(Y; autonomous=false, variable=true)
+ Z_vf = @Lie [X_vf, Y_vf]
+ Test.@test Z(1, [1, 2], 1) ≈ Z_vf(1, [1, 2], 1)
+ end
+
+ # ================================================================
+ # Nested Lie brackets with plain functions
+ # ================================================================
+ Test.@testset "Nested brackets with plain functions" begin
+ X(x) = [x[2], -x[1]]
+ Y(x) = [x[1], x[2]]
+ Z_func(x) = [0, x[1]]
+
+ # [[X, Y], Z]
+ nested = @Lie [[X, Y], Z_func]
+ Test.@test nested isa CTFlows.VectorField
+ Test.@test nested([1, 2]) isa Vector
+ end
+
+ # ================================================================
+ # Mixed: plain function + VectorField
+ # ================================================================
+ Test.@testset "Mixed plain function and VectorField" begin
+ X(x) = [x[2], -x[1]]
+ Y_vf = CTFlows.VectorField(x -> [x[1], x[2]])
+
+ # Should work with one plain function and one VectorField
+ Z = @Lie [X, Y_vf]
+ Test.@test Z isa CTFlows.VectorField
+ Test.@test Z([1, 2]) isa Vector
+ end
+ end
+
Test.@testset "Complex Signature Tests" begin
# Test with different arities and keyword arguments
@@ -222,6 +325,37 @@ function test_ctflows()
result3 = @Lie {H1, H2}
Test.@test result3 isa CTFlows.Hamiltonian
end
+
+ Test.@testset "VectorField Construction" begin
+ # Test VectorField construction patterns
+ X1 = OptimalControl.VectorField(x -> [x[2], -x[1]])
+ Test.@test X1 isa OptimalControl.VectorField
+ Test.@test X1([1, 2]) isa Vector
+
+ # Non-autonomous
+ X2 = OptimalControl.VectorField((t, x) -> [t + x[2], -x[1]]; autonomous=false)
+ Test.@test X2 isa OptimalControl.VectorField
+ Test.@test X2(1.0, [1, 2]) isa Vector
+
+ # Variable
+ X3 = OptimalControl.VectorField((x, v) -> [x[2] + v, -x[1]]; variable=true)
+ Test.@test X3 isa OptimalControl.VectorField
+ Test.@test X3([1, 2], 1.0) isa Vector
+ end
+
+ Test.@testset "∂ₜ Operator" begin
+ # Test partial time derivative
+ f = (t, x) -> t * x
+ df = ∂ₜ(f)
+ Test.@test df isa Function
+ Test.@test df(0, 8) ≈ 8
+ Test.@test df(2, 3) ≈ 3
+
+ # More complex function
+ g = (t, x, p) -> t^2 + x[1]*p[1]
+ dg = ∂ₜ(g)
+ Test.@test dg(3, [1, 2], [4, 5]) ≈ 6
+ end
end
end
end
diff --git a/test/suite/solve/test_canonical.jl b/test/suite/solve/test_canonical.jl
index 57e3af21..414bdac0 100644
--- a/test/suite/solve/test_canonical.jl
+++ b/test/suite/solve/test_canonical.jl
@@ -32,6 +32,7 @@ const SHOWTIMING = isdefined(Main, :TestOptions) ? Main.TestOptions.SHOWTIMING :
# Objective tolerance for comparison with reference values
const OBJ_RTOL = 1e-2
+const OBJ_ATOL = 1e-3 # Absolute tolerance for small objectives
# CUDA availability check
is_cuda_on() = CUDA.functional()
@@ -97,6 +98,8 @@ function run_test(
iters,
memory_bytes > 0 ? memory_bytes : nothing,
false, # show_memory = false
+ OBJ_RTOL, # rtol for color thresholds
+ OBJ_ATOL, # atol for color thresholds
)
end
@@ -111,7 +114,12 @@ function run_test(
Test.@test success
if success
Test.@test solve_result isa OptimalControl.AbstractSolution
- Test.@test OptimalControl.objective(solve_result) ≈ pb.obj rtol = OBJ_RTOL
+ # Use absolute tolerance when reference objective is near zero
+ if abs(pb.obj) < 1e-6
+ Test.@test OptimalControl.objective(solve_result) ≈ pb.obj atol = OBJ_ATOL
+ else
+ Test.@test OptimalControl.objective(solve_result) ≈ pb.obj rtol = OBJ_RTOL
+ end
end
end
end
@@ -191,13 +199,15 @@ function test_canonical()
]
problems = [
+ # ("Quadrotor", Quadrotor()), # some DomainError some times
+ # ("Transfer", Transfer()), # debug: add later (currently issue with :exa)
("Beam", Beam()),
("Goddard", Goddard()),
- # ("Quadrotor", Quadrotor()), # some DomainError some times
("DI_Time", DoubleIntegratorTime()),
("DI_Energy", DoubleIntegratorEnergy()),
("DI_EnergyCons", DoubleIntegratorEnergyConstrained()),
- # ("Transfer", Transfer()), # debug: add later (currently issue with :exa)
+ ("ExpGrowth", ExponentialGrowth()),
+ ("HarmOsc", HarmonicOscillator()),
]
# Use Refs for statistics to pass to helper functions