Skip to content

Commit 2e1b6e1

Browse files
committed
update doc manual plot
1 parent 903e84d commit 2e1b6e1

1 file changed

Lines changed: 128 additions & 60 deletions

File tree

docs/src/tutorial-plot.md

Lines changed: 128 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,69 @@
11
# [How to plot a solution](@id tutorial-plot)
22

3-
In this tutorial we explain the different ways to plot a solution of an optimal control problem.
3+
In this tutorial, we explain the different options for plotting the solution of an optimal control problem using the `plot` and `plot!` functions, which are extensions of the [Plots.jl](https://docs.juliaplots.org) package. Use `plot` to create a new plot object, and `plot!` to add to an existing one:
44

5-
Let us start by importing the package to define the problem and solve it.
5+
```julia
6+
plot(args...; kw...) # creates a new Plot, and set it to be the `current`
7+
plot!(args...; kw...) # modifies Plot `current()`
8+
plot!(plt, args...; kw...) # modifies Plot `plt`
9+
```
10+
11+
More precisely, the signature of `plot` is as follows.
12+
13+
```julia
14+
function plot(
15+
sol; # optimal control solution
16+
layout, # layout of the subplots
17+
control, # plot the norm or components of the control
18+
time, # normalise the time or not
19+
size, # size of the figure
20+
solution_label, # suffix for the labels
21+
state_style, # style for the state trajectory
22+
costate_style, # style for the costate trajectory
23+
control_style, # style for the control trajectory
24+
kwargs..., # attributes from Plots
25+
)
26+
```
27+
28+
In the following, we detail the roles of the arguments.
29+
30+
| Section | Arguments |
31+
| :------ | :------ |
32+
| [Basic concepts](@ref tutorial-plot-basic) | `size`, `state_style`, `costate_style`, `control_style`, `kwargs...` |
33+
| [Split versus group layout](@ref tutorial-plot-layout) | `layout` |
34+
| [Plot the norm of the control](@ref tutorial-plot-control) | `control` |
35+
| [Normalised time](@ref tutorial-plot-time) | `time` |
36+
| [Add a plot](@ref tutorial-plot-add) | `solution_label` |
37+
38+
You can plot a solution obtained from the `solve` function, as well as from the flow computed using an optimal control problem and a control law. See, respectively, [Basic Concepts](@ref tutorial-plot-basic) and [From Flow](@ref tutorial-plot-flow) sections for more details.
39+
40+
You can also retrieve the state, the costate and the control to create your own plots, see [Custom plot](@ref tutorial-plot-custom) section.
41+
42+
## The problem and the solution
43+
44+
Let us start by importing the packages needed to define and solve the problem.
645

746
```@example main
847
using OptimalControl
948
using NLPModelsIpopt
1049
```
1150

12-
Then, we define a simple optimal control problem and solve it.
51+
We consider the simple optimal control problem from the [basic example tutorial](@ref tutorial-double-integrator-energy).
1352

1453
```@example main
54+
const t0 = 0 # initial time
55+
const tf = 1 # final time
56+
const x0 = [ -1, 0 ] # initial condition
57+
const xf = [ 0, 0 ] # final condition
58+
1559
ocp = @def begin
1660
17-
t ∈ [0, 1], time
61+
t ∈ [t0, tf], time
1862
x ∈ R², state
1963
u ∈ R, control
2064
21-
x(0) == [-1, 0]
22-
x(1) == [0, 0]
65+
x(t0) == x0
66+
x(tf) == xf
2367
2468
ẋ(t) == [x₂(t), u(t)]
2569
@@ -31,92 +75,122 @@ sol = solve(ocp, display=false)
3175
nothing # hide
3276
```
3377

34-
## First ways to plot
78+
## [Basic concepts](@id tutorial-plot-basic)
3579

36-
The simplest way to plot the solution is to use the `plot` function with only the solution as argument.
80+
The simplest way to plot the solution is to use the `plot` function with the solution as the only argument.
3781

38-
!!! note "The plot function"
82+
!!! warning
3983

40-
The plot function on a solution of an optimal control problem is an extension of the plot function from the package Plots.jl. Hence, we need to import this package to plot a solution.
84+
The `plot` function for a solution of an optimal control problem extends the `plot` function from Plots. Therefore, you need to import this package in order to plot a solution.
4185

4286
```@example main
4387
using Plots
4488
plot(sol)
4589
```
4690

47-
As you can see, it produces a grid of subplots. The left column contains the state trajectories, the right column the costate trajectories, and at the bottom we have the control trajectory.
91+
In the figure above, we have a grid of subplots: the left column displays the state component trajectories, the right column shows the costate component trajectories, and the bottom row contains the control component trajectories.
92+
93+
As in Plots, input data is passed positionally (for example, `sol` in `plot(sol)`), and attributes are passed as keyword arguments (for example, `plot(sol; color = :blue)`). After executing `using Plots` in the REPL, you can use the `plotattr()` function to print a list of all available attributes for series, plots, subplots, or axes.
94+
95+
```julia
96+
# Valid Operations
97+
plotattr(:Plot)
98+
plotattr(:Series)
99+
plotattr(:Subplot)
100+
plotattr(:Axis)
101+
```
102+
103+
Once you have the list of attributes, you can either use the aliases of a specific attribute or inspect a specific attribute to display its aliases and description.
104+
105+
```@repl main
106+
plotattr("color") # Specific Attribute Example
107+
```
108+
109+
!!! warning
110+
111+
Some attributes have different default values in OptimalControl compared to Plots. For instance, the default figure size is 600x400 in Plots, while in OptimalControl, it depends on the number of states and controls.
48112

49-
Attributes from [Plots.jl](https://docs.juliaplots.org) can be passed to the `plot` function:
113+
You can also visit the Plot documentation online to get the descriptions of the attributes:
114+
115+
- 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.
116+
- 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.
117+
- 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.
118+
- 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`.
50119

51-
- In addition to `sol` you can pass attributes to the full plot, see the [attributes plot documentation](https://docs.juliaplots.org/latest/generated/attributes_plot/) from Plots.jl for more details. For instance, you can specify the size of the figure.
52-
- You can also pass attributes to the subplots, see the [attributes subplot documentation](https://docs.juliaplots.org/latest/generated/attributes_subplot/) from Plots.jl for more details. However, it will affect all the subplots. For instance, you can specify the location of the legend.
53-
- In the same way, you can pass axis attributes to the subplots, see the [attributes axis documentation](https://docs.juliaplots.org/latest/generated/attributes_axis/) from Plots.jl for more details. It will also affect all the subplots. For instance, you can remove the grid.
54-
- In the same way, you can pass series attributes to the all the subplots, see the [attributes series documentation](https://docs.juliaplots.org/latest/generated/attributes_series/) from Plots.jl for more details. It will also affect all the subplots. For instance, you can set the width of the curves with `linewidth`.
55120

56121
```@example main
57122
plot(sol, size=(700, 450), legend=:bottomright, grid=false, linewidth=2)
58123
```
59124

60-
To specify series attributes to a specific subplot, you can use the optional keyword arguments `state_style`, `costate_style` and `control_style` which correspond respectively to the state, costate and control trajectories. See the [attribute series documentation](https://docs.juliaplots.org/latest/generated/attributes_series/) from Plots.jl for more details. For instance, you can specify the color of the state trajectories and more.
125+
To specify series attributes for a specific group of subplots (state, costate or control), you can use the optional keyword arguments `state_style`, `costate_style`, and `control_style`, which correspond to the state, costate, and control trajectories, respectively.
61126

62127
```@example main
63128
plot(sol;
64-
state_style = (color=:blue,),
65-
costate_style = (color=:black, linestyle=:dash),
66-
control_style = (color=:red, linewidth=2))
129+
state_style = (color=:blue,), # style of the state trajectory
130+
costate_style = (color=:black, linestyle=:dash), # style of the costate trajectory
131+
control_style = (color=:red, linewidth=2)) # style of the control trajectory
67132
```
68133

69-
## From Flow
134+
## [From Flow](@id tutorial-plot-flow)
70135

71-
The previous resolution of the optimal control problem was done with the `solve` function. If you use an indirect shooting method and solve shooting equations, you may want to plot the associated solution. To do so, you need to use the `Flow` function to reconstruct the solution. See the [Indirect Simple Shooting](@ref tutorial-indirect-simple-shooting) tutorial for an example. In our example, you must provide the maximising control $(x, p) \mapsto p_2$ together with the optimal control problem.
136+
The previous solution of the optimal control problem was obtained using the `solve` function. If you prefer using an indirect shooting method and solving shooting equations, you may also want to plot the associated solution. To do this, you need to use the `Flow` function to reconstruct the solution. See the manual on [how to compute flows](@ref manual-flow) for more details. In our case, you must provide the maximizing control $(x, p) \mapsto p_2$ along with the optimal control problem. For an introduction to simple indirect shooting, see the [indirect simple shooting](@ref tutorial-indirect-simple-shooting) tutorial for an example.
72137

73138
!!! tip "Interactions with an optimal control solution"
74139

75-
Please check [`state`](@ref), [`costate`](@ref), [`control`](@ref) and [`variable`](@ref) to get data from the solution. The functions `state`, `costate` and `control` return functions of time and `variable` returns a vector.
140+
Please check [`state`](@ref), [`costate`](@ref), [`control`](@ref), and [`variable`](@ref) to retrieve data from the solution. The functions `state`, `costate`, and `control` return functions of time, while `variable` returns a vector.
76141

77142
```@example main
78143
using OrdinaryDiffEq
79-
t0 = 0
80-
tf = 1
81-
x0 = [ -1, 0 ]
82-
p0 = costate(sol)(t0)
83-
f = Flow(ocp, (x, p) -> p[2])
84-
sol_flow = f( (t0, tf), x0, p0 )
85-
plot(sol_flow)
144+
145+
p = costate(sol) # costate as a function of time
146+
p0 = p(t0) # costate solution at the initial time
147+
f = Flow(ocp, (x, p) -> p[2]) # flow from an ocp and a control law
148+
149+
sol_flow = f( (t0, tf), x0, p0 ) # compute the solution
150+
plot(sol_flow) # plot the solution from a flow
86151
```
87152

88-
You can notice that the time grid has very few points. To have a better visualisation (the accuracy won't change), you can give a finer grid.
153+
We may notice that the time grid contains very few points. This is evident from the subplot of $x_2$, or by retrieving the time grid directly from the solution.
89154

90155
```@example main
91-
sol_flow = f( (t0, tf), x0, p0; saveat=range(t0, tf, 100) )
156+
time_grid(sol_flow)
157+
```
158+
159+
To improve visualization (without changing the accuracy), you can provide a finer grid.
160+
161+
```@example main
162+
fine_grid = range(t0, tf, 100)
163+
sol_flow = f( (t0, tf), x0, p0; saveat=fine_grid )
92164
plot(sol_flow)
93165
```
94166

95-
## Split versus group layout
167+
## [Split versus group layout](@id tutorial-plot-layout)
96168

97169
If you prefer to get a more compact figure, you can use the `layout` optional keyword argument with `:group` value. It will group the state, costate and control trajectories in one subplot for each.
98170

99171
```@example main
100172
plot(sol; layout=:group, size=(800, 300))
101173
```
174+
175+
The default layout value is `:split` which corresponds to the grid of subplots presented above.
102176

103-
!!! note "Default layout value"
104-
105-
The default layout value is `:split` which corresponds to the grid of subplots presented above.
177+
```@example main
178+
plot(sol; layout=:split)
179+
```
106180

107-
## Additional plots
181+
## [Add a plot](@id tutorial-plot-add)
108182

109183
You can plot the solution of a second optimal control problem on the same figure if it has the same number of states, costates and controls. For instance, consider the same optimal control problem but with a different initial condition.
110184

111185
```@example main
112186
ocp = @def begin
113187
114-
t ∈ [0, 1], time
188+
t ∈ [t0, tf], time
115189
x ∈ R², state
116190
u ∈ R, control
117191
118-
x(0) == [-0.5, -0.5]
119-
x(1) == [0, 0]
192+
x(t0) == [-0.5, -0.5]
193+
x(tf) == xf
120194
121195
ẋ(t) == [x₂(t), u(t)]
122196
@@ -130,28 +204,27 @@ nothing # hide
130204
We first plot the solution of the first optimal control problem, then, we plot the solution of the second optimal control problem on the same figure, but with dashed lines.
131205

132206
```@example main
133-
# first plot
134207
plt = plot(sol; solution_label="(sol1)", size=(700, 500))
135-
136-
# second plot
137208
plot!(plt, sol2; solution_label="(sol2)", linestyle=:dash)
138209
```
139210

140-
## Plot the norm of the control
211+
## [Plot the norm of the control](@id tutorial-plot-control)
141212

142-
For some problem, it is interesting to plot the norm of the control. You can do it by using the `control` optional keyword argument with `:norm` value. The default value is `:components`.
213+
For some problem, it is interesting to plot the (Euclidean) norm of the control. You can do it by using the `control` optional keyword argument with `:norm` value.
143214

144215
```@example main
145216
plot(sol; control=:norm, size=(800, 300), layout=:group)
146217
```
147218

148-
## Custom plot
219+
The default value is `:components`.
149220

150-
You can of course create your own plots by getting the `state`, `costate` and `control` from the optimal control solution. For instance, let us plot the norm of the control for the orbital transfer problem.
221+
```@example main
222+
plot(sol; control=:components, size=(800, 300), layout=:group)
223+
```
151224

152-
!!! tip "Interactions with an optimal control solution"
225+
## [Custom plot](@id tutorial-plot-custom)
153226

154-
Additionally to [`state`](@ref), [`costate`](@ref), [`control`](@ref) and [`variable`](@ref), the function [`time_grid`](@ref) returns the discretized time grid returned by the solver.
227+
You can, of course, create your own plots by extracting the `state`, `costate`, and `control` from the optimal control solution. For instance, let us plot the norm of the control.
155228

156229
```@example main
157230
using LinearAlgebra
@@ -167,32 +240,27 @@ plot(t, norm∘u; label="‖u‖")
167240
- The `norm` function is from `LinearAlgebra.jl`.
168241
- The `∘` operator is the composition operator. Hence, `norm∘u` is the function `t -> norm(u(t))`.
169242

170-
## Normalized time
243+
## [Normalised time](@id tutorial-plot-time)
171244

172-
We consider a [LQR example](@ref) and solve the problem for different values of the final time `tf`. Then, we plot the solutions on the same figure considering a normalized time $s=(t-t_0)/(t_f-t_0)$, thanks to the keyword argument `time=:normalize` of the `plot` function.
245+
We consider a [LQR example](@ref) and solve the problem for different values of the final time `tf`. Then, we plot the solutions on the same figure using a normalized time $s = (t - t_0) / (t_f - t_0)$, enabled by the keyword argument `time = :normalize` (or `:normalise`) in the `plot` function.
173246

174247
```@example main
175-
176-
# parameters
177-
x0 = [ 0
178-
1 ]
179-
180-
# definition
248+
# definition of the problem, parameterised by the final time
181249
function lqr(tf)
182250
183251
ocp = @def begin
184252
t ∈ [0, tf], time
185253
x ∈ R², state
186254
u ∈ R, control
187-
x(0) == x0
255+
x(0) == [0, 1]
188256
ẋ(t) == [x₂(t), - x₁(t) + u(t)]
189257
∫( 0.5(x₁(t)^2 + x₂(t)^2 + u(t)^2) ) → min
190258
end
191259
192260
return ocp
193261
end;
194262
195-
# solve
263+
# solve the problems and store them
196264
solutions = []
197265
tfs = [3, 5, 30]
198266
for tf ∈ tfs
@@ -206,7 +274,7 @@ for sol ∈ solutions[2:end]
206274
plot!(plt, sol; time=:normalize)
207275
end
208276
209-
# make a custom plot from created plots: only state and control are plotted
277+
# make a custom plot: keep only state and control
210278
N = length(tfs)
211279
px1 = plot(plt[1]; legend=false, xlabel="s", ylabel="x₁")
212280
px2 = plot(plt[2]; label=reshape(["tf = $tf" for tf ∈ tfs], (1, N)), xlabel="s", ylabel="x₂")

0 commit comments

Comments
 (0)