diff --git a/docs/Project.toml b/docs/Project.toml index 34c79aa18..cb8b7f63f 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,6 +6,7 @@ CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" diff --git a/docs/make.jl b/docs/make.jl index 9b1bffec3..409ef26c4 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -101,7 +101,7 @@ cp("./docs/Project.toml", "./docs/src/assets/Project.toml"; force=true) repo_url = "github.com/control-toolbox/OptimalControl.jl" makedocs(; - draft=false, # if draft is true, then the julia code from .md is not executed + draft=true, # if draft is true, then the julia code from .md is not executed # to disable the draft mode in a specific markdown file, use the following: # ```@meta # Draft = false @@ -125,11 +125,13 @@ makedocs(; ], "Manual" => [ "Define a problem" => "manual-abstract.md", + "Problem characteristics" => "manual-model.md", "Set an initial guess" => "manual-initial-guess.md", "Solve a problem" => "manual-solve.md", + "Solution characteristics" => "manual-solution.md", "Plot a solution" => "manual-plot.md", "Compute flows" => [ - "Getting started" => "manual-flow-api.md", + "Flow API" => "manual-flow-api.md", "From optimal control problems" => "manual-flow-ocp.md", "From Hamiltonian and others" => "manual-flow-others.md", ], diff --git a/docs/src/api-ctbase.md b/docs/src/api-ctbase.md index d7bfe0418..399cfb3c6 100644 --- a/docs/src/api-ctbase.md +++ b/docs/src/api-ctbase.md @@ -29,4 +29,4 @@ M --> B style B fill:#FBF275 ``` -OptimalControl heavily relies on CTBase. Refer to the [CTBase API documentation](@extref CTBase index) for more details. \ No newline at end of file +OptimalControl heavily relies on CTBase. We refer to [CTBase API](@extref CTBase index) for more details. \ No newline at end of file diff --git a/docs/src/api-ctdirect.md b/docs/src/api-ctdirect.md index 2e07a13f4..35faa7710 100644 --- a/docs/src/api-ctdirect.md +++ b/docs/src/api-ctdirect.md @@ -29,4 +29,4 @@ M --> B style D fill:#FBF275 ``` -OptimalControl heavily relies on CTDirect. Refer to the [CTDirect API documentation](@extref CTDirect index) for more details. \ No newline at end of file +OptimalControl heavily relies on CTDirect. We refer to [CTDirect API](@extref CTDirect index) for more details. \ No newline at end of file diff --git a/docs/src/api-ctflows.md b/docs/src/api-ctflows.md index 4bb34184d..6ab4f4b39 100644 --- a/docs/src/api-ctflows.md +++ b/docs/src/api-ctflows.md @@ -29,4 +29,4 @@ M --> B style F fill:#FBF275 ``` -OptimalControl heavily relies on CTFlows. Refer to the [CTFlows API documentation](@extref CTFlows index) for more details. \ No newline at end of file +OptimalControl heavily relies on CTFlows. We refer to [CTFlows API](@extref CTFlows index) for more details. \ No newline at end of file diff --git a/docs/src/api-ctmodels.md b/docs/src/api-ctmodels.md index 1b8dde5bc..e82990794 100644 --- a/docs/src/api-ctmodels.md +++ b/docs/src/api-ctmodels.md @@ -29,4 +29,4 @@ M --> B style M fill:#FBF275 ``` -OptimalControl heavily relies on CTModels. Refer to the [CTModels API documentation](@extref CTModels index) for more details. \ No newline at end of file +OptimalControl heavily relies on CTModels. We refer to [CTModels API](@extref CTModels index) for more details. \ No newline at end of file diff --git a/docs/src/api-ctparser.md b/docs/src/api-ctparser.md index 04ec4916a..d98d243c0 100644 --- a/docs/src/api-ctparser.md +++ b/docs/src/api-ctparser.md @@ -29,4 +29,4 @@ M --> B style P fill:#FBF275 ``` -OptimalControl heavily relies on CTParser. Refer to the [CTParser API documentation](@extref CTParser index) for more details. \ No newline at end of file +OptimalControl heavily relies on CTParser. We refer to [CTParser API](@extref CTParser index) for more details. \ No newline at end of file diff --git a/docs/src/api-optimalcontrol-user.md b/docs/src/api-optimalcontrol-user.md index 10f894f6d..a19300e2f 100644 --- a/docs/src/api-optimalcontrol-user.md +++ b/docs/src/api-optimalcontrol-user.md @@ -28,31 +28,6 @@ Order = [:module] Private = false ``` -## Index - -```@index -Pages = ["api-optimalcontrol-user.md"] -Modules = [ - OptimalControl, - CommonSolve, - RecipesBase, - CTBase, - CTDirect, - CTFlows, - CTModels, - CTParser, - CTFlowsODE, - CTModelsPlots, - CTModelsJSON, - CTModelsJLD, - CTSolveExtIpopt, - CTSolveExtKnitro, - CTSolveExtMadNLP, -] - -Order = [:module, :constant, :type, :function, :macro] -``` - ## Documentation ```@docs; canonical=true @@ -70,15 +45,12 @@ Poisson Solution VectorField available_methods -boundary_constraints_dual build_OCP_solution constraint +constraints constraints_violation control control_components -control_constraints_box -control_constraints_lb_dual -control_constraints_ub_dual control_dimension control_name costate @@ -91,6 +63,7 @@ dynamics export_ocp_solution final_time final_time_name +get_build_examodel has_fixed_final_time has_fixed_initial_time has_free_final_time @@ -107,26 +80,20 @@ lagrange mayer message objective -path_constraints_dual plot(::Solution, ::Symbol...) plot!(::Plots.Plot, ::Solution, ::Symbol...) set_initial_guess solve(::Model, ::Symbol...) state state_components -state_constraints_box -state_constraints_lb_dual -state_constraints_ub_dual state_dimension state_name stopping time_grid time_name +times variable variable_components -variable_constraints_box -variable_constraints_lb_dual -variable_constraints_ub_dual variable_dimension variable_name ⋅ diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml index 277dcd17a..d70310ee5 100644 --- a/docs/src/assets/Manifest.toml +++ b/docs/src/assets/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.11.5" manifest_format = "2.0" -project_hash = "619f0fb19f859094e1399a5b2427138fe211e67a" +project_hash = "aedb19159ec43d1fc78401e06abaeeb58fd73c61" [[deps.ADNLPModels]] deps = ["ADTypes", "ForwardDiff", "LinearAlgebra", "NLPModels", "Requires", "ReverseDiff", "SparseArrays", "SparseConnectivityTracer", "SparseMatrixColorings"] @@ -180,9 +180,9 @@ version = "0.2.6" [[deps.CTBase]] deps = ["DocStringExtensions"] -git-tree-sha1 = "1e0fb19f883cda373412fd40f2ccad71758cb876" +git-tree-sha1 = "ebc7d07d0bf4db7a841c5e7d51b4271bcf1e921c" uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" -version = "0.16.1" +version = "0.16.2" [[deps.CTDirect]] deps = ["ADNLPModels", "CTBase", "CTModels", "CTParser", "DocStringExtensions", "HSL", "MKL", "NLPModelsIpopt", "SparseArrays"] @@ -208,9 +208,9 @@ weakdeps = ["OrdinaryDiffEq"] [[deps.CTModels]] deps = ["CTBase", "DocStringExtensions", "Interpolations", "LinearAlgebra", "MLStyle", "MacroTools", "OrderedCollections", "Parameters", "PrettyTables", "RecipesBase"] -git-tree-sha1 = "dec8ae920442bbb4deedb9ca40619c58702b94a5" +git-tree-sha1 = "73b95a01af8369b5b08d1ecf3edf6b0bb79ce2c3" uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d" -version = "0.5.3" +version = "0.5.4" weakdeps = ["JLD2", "JSON3", "Plots"] [deps.CTModels.extensions] @@ -371,6 +371,12 @@ git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.16.0" +[[deps.DataFrames]] +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "fb61b4812c49343d7ef0b533ba982c46021938a6" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "1.7.0" + [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0" @@ -857,6 +863,19 @@ git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" +[[deps.InlineStrings]] +git-tree-sha1 = "8594fac023c5ce1ef78260f24d1ad18b4327b420" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.4.4" + + [deps.InlineStrings.extensions] + ArrowTypesExt = "ArrowTypes" + ParsersExt = "Parsers" + + [deps.InlineStrings.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" + [[deps.IntelOpenMP_jll]] deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] git-tree-sha1 = "0f14a5456bdc6b9731a5682f439a672750a09e48" @@ -889,6 +908,11 @@ weakdeps = ["Dates", "Test"] InverseFunctionsDatesExt = "Dates" InverseFunctionsTestExt = "Test" +[[deps.InvertedIndices]] +git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.3.1" + [[deps.Ipopt]] deps = ["Ipopt_jll", "LinearAlgebra", "OpenBLAS32_jll", "PrecompileTools"] git-tree-sha1 = "4ad0d2dea51e5d49866b40a2d2521da6a1be7097" @@ -1799,9 +1823,9 @@ version = "1.4.3" [[deps.Plots]] deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "UnitfulLatexify", "Unzip"] -git-tree-sha1 = "809ba625a00c605f8d00cd2a9ae19ce34fc24d68" +git-tree-sha1 = "28ea788b78009c695eb0d637587c81d26bdf0e36" uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.40.13" +version = "1.40.14" [deps.Plots.extensions] FileIOExt = "FileIO" @@ -1829,6 +1853,12 @@ git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" version = "0.2.2" +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.3" + [[deps.PreallocationTools]] deps = ["Adapt", "ArrayInterface", "ForwardDiff"] git-tree-sha1 = "6d98eace73d82e47f5b16c393de198836d9f790a" @@ -2064,6 +2094,12 @@ git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386" uuid = "6c6a2e73-6563-6170-7368-637461726353" version = "1.2.1" +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "712fb0231ee6f9120e005ccd56297abbc053e7e0" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.4.8" + [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index e671a18a1..20693bfa9 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -6,6 +6,7 @@ CTFlows = "1c39547c-7794-42f7-af83-d98194f657c2" CTModels = "34c4fa32-2049-4079-8329-de33c2a22e2d" CTParser = "32681960-a1b1-40db-9bff-a1ca817385d1" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" DocumenterMermaid = "a078cd44-4d9c-4618-b545-3ab9d77f9177" @@ -21,6 +22,7 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" [compat] +ADNLPModels = "0.8" CTBase = "0.16" CTDirect = "0.14" CTFlows = "0.8" @@ -28,12 +30,14 @@ CTModels = "0.5" CTParser = "0.4" CommonSolve = "0.2" Documenter = "1.8" +DocumenterInterLinks = "1" DocumenterMermaid = "0.2" JLD2 = "0.5" JSON3 = "1.14" LinearAlgebra = "1" MadNLP = "0.8" NLPModelsIpopt = "0.10" +NLPModelsKnitro = "0.9" OrdinaryDiffEq = "6.93" Plots = "1.40" Suppressor = "0.2" diff --git a/docs/src/manual-flow-ocp.md b/docs/src/manual-flow-ocp.md index 4b713fb5d..99ef748b1 100644 --- a/docs/src/manual-flow-ocp.md +++ b/docs/src/manual-flow-ocp.md @@ -468,6 +468,22 @@ From the maximizing condition, along a boundary arc, we have $p(t) = 0$. Differe \mu(x) = 2x. ``` +!!! note + + Within OptimalControl.jl, the constraint must be given in the form: + ```julia + c([t, ]x, u[, v]) + ``` + the control law in feedback form must be given as: + ```julia + u([t, ]x, p[, v]) + ``` + and the dual variable: + ```julia + μ([t, ]x, p[, v]) + ``` + The time `t` must be provided when the problem is [non-autonomous](@ref manual-model-time-dependence) and the variable `v` must be given when the optimal control problem contains a [variable](@ref manual-abstract-variable) to optimise. + The optimal control is a concatenation of 3 arcs: a negative bang arc followed by a boundary arc, followed by a positive bang arc. The initial covector is approximately $p(0)=-0.982237546583301$, the first switching time is $t_1 = 0.9$, and the exit time of the boundary is $t_2 = 1.6$. Let us check this by concatenating the three flows. ```@example main diff --git a/docs/src/manual-model.md b/docs/src/manual-model.md new file mode 100644 index 000000000..cd2b503ad --- /dev/null +++ b/docs/src/manual-model.md @@ -0,0 +1,390 @@ +# [The optimal control problem object: structure and usage](@id manual-model) + +```@meta +CollapsedDocStrings = false +``` + +In this manual, we'll first recall the **main functionalities** you can use when working with an optimal control problem (OCP). This includes essential operations like: + +* **Solving an OCP**: How to find the optimal solution for your defined problem. +* **Computing flows from an OCP**: Understanding the dynamics and trajectories derived from the optimal solution. +* **Printing an OCP**: How to display a summary of your problem's definition. + +After covering these core functionalities, we'll delve into the **structure of an OCP**. Since an OCP is structured as a [`Model`](@ref) struct, we'll first explain how to **access its underlying attributes**, such as the problem's dynamics, costs, and constraints. Following this, we'll shift our focus to the **simple properties** inherent to an OCP, learning how to determine aspects like whether the problem: + +* **Is autonomous**: Does its dynamics depend explicitly on time? +* **Has a fixed or free initial/final time**: Is the duration of the control problem predetermined or not? + +--- + +**Content** + +- [Main functionalities](@ref manual-model-main-functionalities) +- [Model struct](@ref manual-model-struct) +- [Attributes and properties](@ref manual-model-attributes) + +--- + +## [Main functionalities](@id manual-model-main-functionalities) + +Let's define a basic optimal control problem. + +```@example main +using OptimalControl + +t0 = 0 +tf = 1 +x0 = [-1, 0] + +ocp = @def begin + t ∈ [ t0, tf ], time + x = (q, v) ∈ R², state + u ∈ R, control + x(t0) == x0 + x(tf) == [0, 0] + ẋ(t) == [v(t), u(t)] + 0.5∫( u(t)^2 ) → min +end +nothing # hide +``` + +To print it, simply: + +```@example main +ocp +``` + +We can now solve the problem (for more details, visit the [solve manual](@ref manual-solve)): + +```@example main +using NLPModelsIpopt +#solve(ocp) +nothing # hide +``` + +You can also compute flows (for more details, see the [flow manual](@ref manual-flow-ocp)) from the optimal control problem, providing a control law in feedback form. The **pseudo-Hamiltonian** of this problem is + +```math + H(x, p, u) = p_q\, q + p_v\, v + p^0 u^2 /2, +``` + +where $p^0 = -1$ since we are in the normal case. From the Pontryagin maximum principle, the maximising control is given in feedback form by + +```math +u(x, p) = p_v +``` + +since $\partial^2_{uu} H = p^0 = - 1 < 0$. + +```@example main +u = (x, p) -> p[2] # control law in feedback form + +using OrdinaryDiffEq # needed to import numerical integrators +f = Flow(ocp, u) # compute the Hamiltonian flow function + +p0 = [12, 6] # initial covector solution +xf, pf = f(t0, x0, p0, tf) # flow from (x0, p0) at time t0 to tf +xf # should be (0, 0) +``` + +!!! note + + A more advanced feature allows for the discretization of the optimal control problem. From the discretized version, you can obtain a Nonlinear Programming problem (or optimization problem) and solve it using any appropriate NLP solver. For more details, visit the [NLP manipulation tutorial](https://control-toolbox.org/Tutorials.jl/stable/tutorial-nlp.html). + +## [Model struct](@id manual-model-struct) + +The optimal control problem `ocp` is a [`Model`](@ref) struct. + +```@docs; canonical=false +Model +``` + +Each field can be accessed directly (`ocp.times`, etc) or by a getter: + +- [`times`](@ref) +- [`state`](@ref) +- [`control`](@ref) +- [`variable`](@ref) +- [`dynamics`](@ref) +- [`objective`](@ref) +- [`constraints`](@ref) +- [`definition`](@ref) +- [`get_build_examodel`](@ref) + +For instance, we can retrieve the `times` and `definition` values. + +```@example main +times(ocp) +``` + +```@example main +definition(ocp) +``` + +!!! note + + We refer to [CTModels API](@extref CTModels Types) for more details about this struct and its fields. + +## [Attributes and properties](@id manual-model-attributes) + +Numerous attributes can be retrieved. To illustrate this, a more complex optimal control problem is defined. + +```@example main +ocp = @def begin + v = (w, tf) ∈ R², variable + s ∈ [0, tf], time + q = (x, y) ∈ R², state + u ∈ R, control + 0 ≤ tf ≤ 2, (1) + u(s) ≥ 0, (cons_u) + x(s) + u(s) ≤ 10, (cons_mixed) + w == 0 + x(0) == -1 + y(0) - tf == 0, (cons_bound) + q(tf) == [0, 0] + q̇(s) == [y(s)+w, u(s)] + 0.5∫( u(s)^2 ) → min +end +nothing # hide +``` + +### Control, state and variable + +You can access the name of the control, state, and variable, along with the names of their components and their dimensions.. + +```@example main +using DataFrames +data = DataFrame( + Data=Vector{Symbol}(), + Name=Vector{String}(), + Components=Vector{Vector{String}}(), + Dimension=Vector{Int}(), +) + +# control +push!(data,( + :control, + control_name(ocp), + control_components(ocp), + control_dimension(ocp), +)) + +# state +push!(data,( + :state, + state_name(ocp), + state_components(ocp), + state_dimension(ocp), +)) + +# variable +push!(data,( + :variable, + variable_name(ocp), + variable_components(ocp), + variable_dimension(ocp), +)) +``` + +!!! note + + The names of the components are used for instance when plotting the solution. See the [plot manual](@ref manual-plot). + +### Constraints + +You can retrieve labelled constraints with the [`constraint`](@ref) function. The `constraint(ocp, label)` method returns a tuple of the form `(type, f, lb, ub)`. +The signature of the function `f` depends on the symbol `type`. For `:boundary` and `:variable` constraints, the signature is `f(x0, xf, v)` where `x0` is the initial state, `xf` the final state and `v` the variable. For other constraints, the signature is `f(t, x, u, v)`. Here, `t` represents time, `x` the state, `u` the control, and `v` the variable. + +```@example main +(type, f, lb, ub) = constraint(ocp, :eq1) +println("type: ", type) +x0 = [0, 1] +xf = [2, 3] +v = [1, 4] +println("val: ", f(x0, xf, v)) +println("lb: ", lb) +println("ub: ", ub) +``` + +```@example main +(type, f, lb, ub) = constraint(ocp, :cons_bound) +println("type: ", type) +println("val: ", f(x0, xf, v)) +println("lb: ", lb) +println("ub: ", ub) +``` + +```@example main +(type, f, lb, ub) = constraint(ocp, :cons_u) +println("type: ", type) +t = 0 +x = [1, 2] +u = 3 +println("val: ", f(t, x, u, v)) +println("lb: ", lb) +println("ub: ", ub) +``` + +```@example main +(type, f, lb, ub) = constraint(ocp, :cons_mixed) +println("type: ", type) +println("val: ", f(t, x, u, v)) +println("lb: ", lb) +println("ub: ", ub) +``` + +!!! note + + To get the dual variable (or Lagrange multiplier) associated to the constraint, use the [`dual`](@ref) method. + +### Dynamics + +The dynamics stored in `ocp` are an [in-place function](https://docs.julialang.org/en/v1/manual/functions/#man-argument-passing) (the first argument is mutated upon call) of the form `f!(dx, t, x, u, v)`. Here, `t` represents time, `x` the state, `u` the control, and `v` the variable, with `dx` being the output value. + +```@example main +f! = dynamics(ocp) +t = 0 +x = [0., 1] +u = 2 +v = [1, 4] +dx = similar(x) +f!(dx, t, x, u, v) +dx +``` + +### Criterion and objective + +The criterion can be `:min` or `:max`. + +```@example main +criterion(ocp) +``` + +The objective function is either in Mayer, Lagrange or Bolza form. + +- Mayer: +```math +g(x(t_0), x(t_f), v) \to \min +``` +- Lagrange: +```math +\int_{t_0}^{t_f} f^0(t, x(t), u(t), v)\, \mathrm{d}t \to \min +``` +- Bolza: +```math +g(x(t_0), x(t_f), v) + \int_{t_0}^{t_f} f^0(t, x(t), u(t), v)\, \mathrm{d}t \to \min +``` + +The objective of problem `ocp` is `0.5∫( u(t)^2 ) → min`, hence, in Lagrange form. The signature of the Mayer part of the objective is `g(x0, xf, v)` but in our case, the method `mayer` will return an error. + +```@repl main +g = mayer(ocp) +``` + +The signature of the Lagrange part of the objective is `f⁰(t, x, u, v)`. + +```@example main +f⁰ = lagrange(ocp) +f⁰(t, x, u, v) +``` + +To avoid having to capture exceptions, you can check the form of the objective: + +```@example main +println("Mayer: ", has_mayer_cost(ocp)) +println("Lagrange: ", has_lagrange_cost(ocp)) +``` + +### Times + +The time variable is not named `t` but `s` in `ocp`. + +```@example main +time_name(ocp) +``` + +The initial time is `0`. + +```@example main +initial_time(ocp) +``` + +Since the initial time has the value `0`, its name is `string(0)`. + +```@example main +initial_time_name(ocp) +``` + +In contrast, the final time is `tf`, since in `ocp` we have `s ∈ [0, tf]`. + +```@example main +final_time_name(ocp) +``` + +To get the value of the final time, since it is part of the variable `v = (w, tf)` of `ocp`, we need to provide a variable to the function `final_time`. + +```@example main +v = [1, 2] +tf = final_time(ocp, v) +``` + +```@repl main +final_time(ocp) +``` + +To check whether the initial or final time is fixed or free (i.e., part of the variable), you can use the following functions: + +```@example main +println("Fixed initial time: ", has_fixed_initial_time(ocp)) +println("Fixed final time: ", has_fixed_final_time(ocp)) +``` + +Or, similarly: + +```@example main +println("Free initial time: ", has_free_initial_time(ocp)) +println("Free final time: ", has_free_final_time(ocp)) +``` + +### [Time dependence](@id manual-model-time-dependence) + +Optimal control problems can be **autonomous** or **non-autonomous**. In an autonomous problem, neither the dynamics nor the Lagrange cost explicitly depends on the time variable. + +The following problem is autonomous. + +```@example main +ocp = @def begin + t ∈ [ 0, 1 ], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) # no explicit dependence on t + x(1) + 0.5∫( u(t)^2 ) → min # no explicit dependence on t +end +is_autonomous(ocp) +``` + +The following problem is non-autonomous since the dynamics depends on `t`. + +```@example main +ocp = @def begin + t ∈ [ 0, 1 ], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) + t # explicit dependence on t + x(1) + 0.5∫( u(t)^2 ) → min +end +is_autonomous(ocp) +``` + +Finally, this last problem is non-autonomous because the Lagrange part of the cost depends on `t`. + +```@example main +ocp = @def begin + t ∈ [ 0, 1 ], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) + x(1) + 0.5∫( t + u(t)^2 ) → min # explicit dependence on t +end +is_autonomous(ocp) +``` \ No newline at end of file diff --git a/docs/src/manual-solution.md b/docs/src/manual-solution.md new file mode 100644 index 000000000..f87285c62 --- /dev/null +++ b/docs/src/manual-solution.md @@ -0,0 +1,217 @@ +# [The optimal control solution object: structure and usage](@id manual-solution) + +```@meta +CollapsedDocStrings = false +Draft = false +``` + +In this manual, we'll first recall the **main functionalities** you can use when working with a solution of an optimal control problem (SOL). This includes essential operations like: + +* **Plotting a SOL**: How to plot the optimal solution for your defined problem. +* **Printing a SOL**: How to display a summary of your solution. + +After covering these core functionalities, we'll delve into the **structure of a SOL**. Since a SOL is structured as a [`Solution`](@ref) struct, we'll first explain how to **access its underlying attributes**. Following this, we'll shift our focus to the **simple properties** inherent to a SOL. + +--- + +**Content** + +- [Main functionalities](@ref manual-solution-main-functionalities) +- [Model struct](@ref manual-solution-struct) +- [Attributes and properties](@ref manual-solution-attributes) + +--- + +## [Main functionalities](@id manual-solution-main-functionalities) + +Let's define a basic optimal control problem. + +```@example main +using OptimalControl + +t0 = 0 +tf = 1 +x0 = [-1, 0] + +ocp = @def begin + t ∈ [ t0, tf ], time + x = (q, v) ∈ R², state + u ∈ R, control + x(t0) == x0 + x(tf) == [0, 0] + ẋ(t) == [v(t), u(t)] + 0.5∫( u(t)^2 ) → min +end +nothing # hide +``` + +We can now solve the problem (for more details, visit the [solve manual](@ref manual-solve)): + +```@example main +using NLPModelsIpopt +sol = solve(ocp) +nothing # hide +``` + +!!! note + + You can export (or save) the solution in a Julia `.jld2` data file and reload it later, and also export a discretised version of the solution in a more portable [JSON](https://en.wikipedia.org/wiki/JSON) format. Note that the optimal control problem is needed when loading a solution. + + See the two functions: + + - [`import_ocp_solution`](@ref), + - [`export_ocp_solution`](@ref). + +To print `sol`, simply: + +```@example main +sol +``` + +For complementary information, you can plot the solution: + +```@example main +using Plots +plot(sol) +``` + +!!! note + + For more details about plotting a solution, visit the [plot manual](@ref manual-plot). + +## [Model struct](@id manual-solution-struct) + +The solution `sol` is a [`Solution`](@ref) struct. + +```@docs; canonical=false +Solution +``` + +Each field can be accessed directly (`ocp.times`, etc) but we recommend to use the sophisticated getters we proveide: the `state(sol::Solution)` method does not return `sol.state` but a function of time that can be called at any time, not only on the grid `time_grid`. + +```@example main +0.25 ∈ time_grid(sol) +``` + +```@example main +x = state(sol) +x(0.25) +``` + +## [Attributes and properties](@id manual-solution-attributes) + +### State, costate, control, variable and objective value + +You can access the values of the state, costate, control and variable by eponymous functions. The returned values are functions of time for the state, costate and control and a scalar or a vector for the variable. + +```@example main +t = 0.25 +x = state(sol) +p = costate(sol) +u = control(sol) +nothing # hide +``` + +Since the state is of dimension 2, evaluating `x(t)` returns a vector: +```@example main +x(t) +``` + +It is the same for the costate: +```@example main +p(t) +``` + +But the control is one-dimensional: +```@example main +u(t) +``` + +There is no variable, hence, an empty vector is returned: +```@example main +v = variable(sol) +``` + +The objective value is accessed by: +```@example main +objective(sol) +``` + +### Infos from the solver + +The problem `ocp` is solved via a direct method (see [solve manual](@ref manual-solve) for details). The solver stores data in `sol`, including the success of the optimization, the iteration count, the time grid used for **discretisation**, and other specific details within the `solver_infos` field. + +```@example main +time_grid(sol) +``` + +```@example main +constraints_violation(sol) +``` + +```@example main +infos(sol) +``` + +```@example main +iterations(sol) +``` + +```@example main +message(sol) +``` + +```@example main +stopping(sol) +``` + +### Dual variables + +You can retrieved dual variables (or Lagrange multipliers) associated to labelled constraint. To illustrate this, we define a problem with constraints: + +```@example main +ocp = @def begin + + tf ∈ R, variable + t ∈ [0, tf], time + x = (q, v) ∈ R², state + u ∈ R, control + + tf ≥ 0, (eq_tf) + -1 ≤ u(t) ≤ 1, (eq_u) + v(t) ≤ 0.75, (eq_v) + + x(0) == [-1, 0], (eq_x0) + q(tf) == 0 + v(tf) == 0 + + ẋ(t) == [v(t), u(t)] + + tf → min + +end +sol = solve(ocp; display=false) +nothing # hide +``` + +Dual variables corresponding to variable and boundary constraints are given as scalar or vectors. + +```@example main +dual(sol, ocp, :eq_tf) +``` + +```@example main +dual(sol, ocp, :eq_x0) +``` + +The other type of constraints are associated to dual variables given as functions of time. + +```@example main +μ_u = dual(sol, ocp, :eq_u) +plot(time_grid(sol), μ_u) +``` + +```@example main +μ_v = dual(sol, ocp, :eq_v) +plot(time_grid(sol), μ_v) +``` \ No newline at end of file diff --git a/docs/src/manual-solve.md b/docs/src/manual-solve.md index 8b49f4f7b..e67a3d797 100644 --- a/docs/src/manual-solve.md +++ b/docs/src/manual-solve.md @@ -122,7 +122,7 @@ The main options for the direct method, with their [default] values, are: For advanced usage, see: - [discrete continuation tutorial](https://control-toolbox.org/Tutorials.jl/stable/tutorial-continuation.html), -- [NLP direct handling tutorial](https://control-toolbox.org/Tutorials.jl/stable/tutorial-nlp.html). +- [NLP manipulation tutorial](https://control-toolbox.org/Tutorials.jl/stable/tutorial-nlp.html). !!! note diff --git a/src/OptimalControl.jl b/src/OptimalControl.jl index 47a1e658e..c680f9ea9 100644 --- a/src/OptimalControl.jl +++ b/src/OptimalControl.jl @@ -32,6 +32,9 @@ import CTModels: PreModel, Solution, # getters + constraints, + get_build_examodel, + times, definition, dual, initial_time, @@ -39,15 +42,12 @@ import CTModels: final_time, final_time_name, time_name, - variable_constraints_box, variable_dimension, variable_components, variable_name, - state_constraints_box, state_dimension, state_components, state_name, - control_constraints_box, control_dimension, control_components, control_name, @@ -68,40 +68,32 @@ import CTModels: constraint, time_grid, control, - control_constraints_lb_dual, - control_constraints_ub_dual, state, - state_constraints_lb_dual, - state_constraints_ub_dual, variable, - variable_constraints_lb_dual, - variable_constraints_ub_dual, costate, constraints_violation, objective, iterations, stopping, message, - infos, - boundary_constraints_dual, - path_constraints_dual + infos export Model, Solution -export definition, +export constraints, + get_build_examodel, + times, + definition, dual, initial_time, initial_time_name, final_time, final_time_name, time_name, - variable_constraints_box, variable_dimension, variable_components, variable_name, - state_constraints_box, state_dimension, state_components, state_name, - control_constraints_box, control_dimension, control_components, control_name, @@ -122,23 +114,15 @@ export definition, constraint, time_grid, control, - control_constraints_lb_dual, - control_constraints_ub_dual, state, - state_constraints_lb_dual, - state_constraints_ub_dual, variable, - variable_constraints_lb_dual, - variable_constraints_ub_dual, costate, constraints_violation, objective, iterations, stopping, message, - infos, - boundary_constraints_dual, - path_constraints_dual + infos # CTParser import CTParser: CTParser, @def @@ -180,4 +164,4 @@ export solve export available_methods include("solve.jl") -end +end \ No newline at end of file