Akshay Agrawal commited on
Commit
89f83d9
·
1 Parent(s): 5a78583

minimum fuel optimal control

Browse files
optimization/03_minimum_fuel_optimal_control.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import marimo
2
+
3
+ __generated_with = "0.11.0"
4
+ app = marimo.App()
5
+
6
+
7
+ @app.cell
8
+ def _():
9
+ import marimo as mo
10
+ return (mo,)
11
+
12
+
13
+ @app.cell(hide_code=True)
14
+ def _(mo):
15
+ mo.md(
16
+ r"""
17
+ # Minimal fuel optimal control
18
+
19
+ This notebook includes an application of linear programming to controlling a
20
+ physical system, adapted from [Convex
21
+ Optimization](https://web.stanford.edu/~boyd/cvxbook/) by Boyd and Vandenberghe.
22
+
23
+ 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
24
+ of the system is given by the linear recurrence
25
+
26
+ \[
27
+ x(t + 1) = Ax(t) + bu(t), \quad t = 0, \ldots, T - 1,
28
+ \]
29
+
30
+ 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.
31
+
32
+ The _minimum fuel optimal control problem_ is to choose the inputs $u(0), \ldots, u(T - 1)$ so as to achieve
33
+ a given desired state $x_\text{des} = x(T)$ while minimizing the total fuel consumed
34
+
35
+ \[
36
+ F = \sum_{t=0}^{T - 1} f(u(t)).
37
+ \]
38
+
39
+ 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
40
+
41
+ \[
42
+ f(a) = \begin{cases}
43
+ |a| & |a| \leq 1 \\
44
+ 2|a| - 1 & |a| > 1.
45
+ \end{cases}
46
+ \]
47
+
48
+ 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.
49
+
50
+ **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$.
51
+
52
+ First, we create the **problem data**.
53
+ """
54
+ )
55
+ return
56
+
57
+
58
+ @app.cell
59
+ def _():
60
+ import numpy as np
61
+ return (np,)
62
+
63
+
64
+ @app.cell
65
+ def _():
66
+ n, T = 3, 30
67
+ return T, n
68
+
69
+
70
+ @app.cell
71
+ def _(np):
72
+ A = np.array([[-1, 0.4, 0.8], [1, 0, 0], [0, 1, 0]])
73
+ b = np.array([[1, 0, 0.3]]).T
74
+ return A, b
75
+
76
+
77
+ @app.cell(hide_code=True)
78
+ def _(mo, n, np):
79
+ import wigglystuff
80
+
81
+ x0_widget = mo.ui.anywidget(wigglystuff.Matrix(np.zeros((1, n))))
82
+ xdes_widget = mo.ui.anywidget(wigglystuff.Matrix(np.array([[7, 2, -6]])))
83
+
84
+ _a = mo.md(
85
+ rf"""
86
+
87
+ Choose a value for $x_0$ ...
88
+
89
+ {x0_widget}
90
+ """
91
+ )
92
+
93
+ _b = mo.md(
94
+ rf"""
95
+ ... and for $x_\text{{des}}$
96
+
97
+ {xdes_widget}
98
+ """
99
+ )
100
+
101
+ mo.hstack([_a, _b], justify="space-around")
102
+ return wigglystuff, x0_widget, xdes_widget
103
+
104
+
105
+ @app.cell
106
+ def _(x0_widget, xdes_widget):
107
+ x0 = x0_widget.matrix
108
+ xdes = xdes_widget.matrix
109
+ return x0, xdes
110
+
111
+
112
+ @app.cell(hide_code=True)
113
+ def _(mo):
114
+ 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.)""")
115
+ return
116
+
117
+
118
+ @app.cell
119
+ def _():
120
+ import cvxpy as cp
121
+ return (cp,)
122
+
123
+
124
+ @app.cell
125
+ def _(A, T, b, cp, mo, n, x0, xdes):
126
+ X, u = cp.Variable(shape=(n, T + 1)), cp.Variable(shape=(1, T))
127
+
128
+ objective = cp.sum(cp.maximum(cp.abs(u), 2 * cp.abs(u) - 1))
129
+ constraints = [
130
+ X[:, 1:] == A @ X[:, :-1] + b @ u,
131
+ X[:, 0] == x0,
132
+ X[:, -1] == xdes,
133
+ ]
134
+
135
+ fuel_used = cp.Problem(cp.Minimize(objective), constraints).solve()
136
+ mo.md(f"Achieved a fuel usage of {fuel_used:.02f}. 🚀")
137
+ return X, constraints, fuel_used, objective, u
138
+
139
+
140
+ @app.cell(hide_code=True)
141
+ def _(mo):
142
+ mo.md(
143
+ """
144
+ Finally, we plot the chosen inputs over time.
145
+
146
+ **🌊 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$.
147
+ """
148
+ )
149
+ return
150
+
151
+
152
+ @app.cell
153
+ def _(plot_solution, u):
154
+ plot_solution(u)
155
+ return
156
+
157
+
158
+ @app.cell
159
+ def _(T, cp, np):
160
+ def plot_solution(u: cp.Variable):
161
+ import matplotlib.pyplot as plt
162
+
163
+ plt.step(np.arange(T), u.T.value)
164
+ plt.axis("tight")
165
+ plt.xlabel("$t$")
166
+ plt.ylabel("$u$")
167
+ return plt.gca()
168
+ return (plot_solution,)
169
+
170
+
171
+ if __name__ == "__main__":
172
+ app.run()