-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy path03_minimum_fuel_optimal_control.py
172 lines (121 loc) · 4.41 KB
/
03_minimum_fuel_optimal_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import marimo
__generated_with = "0.11.0"
app = marimo.App()
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
# Minimal fuel optimal control
This notebook includes an application of linear programming to controlling a
physical system, adapted from [Convex
Optimization](https://web.stanford.edu/~boyd/cvxbook/) by Boyd and Vandenberghe.
We consider a linear dynamical system with state $x(t) \in \mathbf{R}^n$, for $t = 0, \ldots, T$. At each time step $t = 0, \ldots, T - 1$, an actuator or input signal $u(t)$ is applied, affecting the state. The dynamics
of the system is given by the linear recurrence
\[
x(t + 1) = Ax(t) + bu(t), \quad t = 0, \ldots, T - 1,
\]
where $A \in \mathbf{R}^{n \times n}$ and $b \in \mathbf{R}^n$ are given and encode how the system evolves. The initial state $x(0)$ is also given.
The _minimum fuel optimal control problem_ is to choose the inputs $u(0), \ldots, u(T - 1)$ so as to achieve
a given desired state $x_\text{des} = x(T)$ while minimizing the total fuel consumed
\[
F = \sum_{t=0}^{T - 1} f(u(t)).
\]
The function $f : \mathbf{R} \to \mathbf{R}$ tells us how much fuel is consumed as a function of the input, and is given by
\[
f(a) = \begin{cases}
|a| & |a| \leq 1 \\
2|a| - 1 & |a| > 1.
\end{cases}
\]
This means the fuel use is proportional to the magnitude of the signal between $-1$ and $1$, but for larger signals the marginal fuel efficiency is half.
**This notebook.** In this notebook we use CVXPY to formulate the minimum fuel optimal control problem as a linear program. The notebook lets you play with the initial and target states, letting you see how they affect the planned trajectory of inputs $u$.
First, we create the **problem data**.
"""
)
return
@app.cell
def _():
import numpy as np
return (np,)
@app.cell
def _():
n, T = 3, 30
return T, n
@app.cell
def _(np):
A = np.array([[-1, 0.4, 0.8], [1, 0, 0], [0, 1, 0]])
b = np.array([[1, 0, 0.3]]).T
return A, b
@app.cell(hide_code=True)
def _(mo, n, np):
import wigglystuff
x0_widget = mo.ui.anywidget(wigglystuff.Matrix(np.zeros((1, n))))
xdes_widget = mo.ui.anywidget(wigglystuff.Matrix(np.array([[7, 2, -6]])))
_a = mo.md(
rf"""
Choose a value for $x_0$ ...
{x0_widget}
"""
)
_b = mo.md(
rf"""
... and for $x_\text{{des}}$
{xdes_widget}
"""
)
mo.hstack([_a, _b], justify="space-around")
return wigglystuff, x0_widget, xdes_widget
@app.cell
def _(x0_widget, xdes_widget):
x0 = x0_widget.matrix
xdes = xdes_widget.matrix
return x0, xdes
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""**Next, we specify the problem as a linear program using CVXPY.** This problem is linear because the objective and constraints are affine. (In fact, the objective is piecewise affine, but CVXPY rewrites it to be affine for you.)""")
return
@app.cell
def _():
import cvxpy as cp
return (cp,)
@app.cell
def _(A, T, b, cp, mo, n, x0, xdes):
X, u = cp.Variable(shape=(n, T + 1)), cp.Variable(shape=(1, T))
objective = cp.sum(cp.maximum(cp.abs(u), 2 * cp.abs(u) - 1))
constraints = [
X[:, 1:] == A @ X[:, :-1] + b @ u,
X[:, 0] == x0,
X[:, -1] == xdes,
]
fuel_used = cp.Problem(cp.Minimize(objective), constraints).solve()
mo.md(f"Achieved a fuel usage of {fuel_used:.02f}. 🚀")
return X, constraints, fuel_used, objective, u
@app.cell(hide_code=True)
def _(mo):
mo.md(
"""
Finally, we plot the chosen inputs over time.
**🌊 Try it!** Change the initial and desired states; how do fuel usage and controls change? Can you explain what you see? You can also try experimenting with the value of $T$.
"""
)
return
@app.cell
def _(plot_solution, u):
plot_solution(u)
return
@app.cell
def _(T, cp, np):
def plot_solution(u: cp.Variable):
import matplotlib.pyplot as plt
plt.step(np.arange(T), u.T.value)
plt.axis("tight")
plt.xlabel("$t$")
plt.ylabel("$u$")
return plt.gca()
return (plot_solution,)
if __name__ == "__main__":
app.run()