Spaces:
Sleeping
Sleeping
File size: 4,512 Bytes
89f83d9 |
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 173 |
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()
|