Spaces:
Running
Running
Merge branch 'marimo-team:main' into feat/issue#18/polars-data-wrangling
Browse files- README.md +1 -0
- optimization/01_least_squares.py +123 -0
- optimization/02_linear_program.py +269 -0
- optimization/03_minimum_fuel_optimal_control.py +172 -0
- optimization/04_quadratic_program.py +265 -0
- optimization/README.md +31 -0
- probability/01_sets.py +297 -0
- probability/README.md +22 -0
README.md
CHANGED
@@ -26,6 +26,7 @@ notebooks for educators, students, and practitioners.
|
|
26 |
|
27 |
- ๐ฒ Probability
|
28 |
- ๐ Linear algebra
|
|
|
29 |
- โ๏ธ Polars
|
30 |
- ๐ฅ Pytorch
|
31 |
|
|
|
26 |
|
27 |
- ๐ฒ Probability
|
28 |
- ๐ Linear algebra
|
29 |
+
- โ๏ธ Optimization
|
30 |
- โ๏ธ Polars
|
31 |
- ๐ฅ Pytorch
|
32 |
|
optimization/01_least_squares.py
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# /// script
|
2 |
+
# requires-python = ">=3.11"
|
3 |
+
# dependencies = [
|
4 |
+
# "cvxpy==1.6.0",
|
5 |
+
# "marimo",
|
6 |
+
# "numpy==2.2.2",
|
7 |
+
# ]
|
8 |
+
# ///
|
9 |
+
|
10 |
+
import marimo
|
11 |
+
|
12 |
+
__generated_with = "0.11.0"
|
13 |
+
app = marimo.App()
|
14 |
+
|
15 |
+
|
16 |
+
@app.cell(hide_code=True)
|
17 |
+
def _(mo):
|
18 |
+
mo.md(
|
19 |
+
r"""
|
20 |
+
# Least squares
|
21 |
+
|
22 |
+
In a least-squares problem, we have measurements $A \in \mathcal{R}^{m \times
|
23 |
+
n}$ (i.e., $m$ rows and $n$ columns) and $b \in \mathcal{R}^m$. We seek a vector
|
24 |
+
$x \in \mathcal{R}^{n}$ such that $Ax$ is close to $b$. The matrices $A$ and $b$ are problem data or constants, and $x$ is the variable we are solving for.
|
25 |
+
|
26 |
+
Closeness is defined as the sum of the squared differences:
|
27 |
+
|
28 |
+
\[ \sum_{i=1}^m (a_i^Tx - b_i)^2, \]
|
29 |
+
|
30 |
+
also known as the $\ell_2$-norm squared, $\|Ax - b\|_2^2$.
|
31 |
+
|
32 |
+
For example, we might have a dataset of $m$ users, each represented by $n$ features. Each row $a_i^T$ of $A$ is the feature vector for user $i$, while the corresponding entry $b_i$ of $b$ is the measurement we want to predict from $a_i^T$, such as ad spending. The prediction for user $i$ is given by $a_i^Tx$.
|
33 |
+
|
34 |
+
We find the optimal value of $x$ by solving the optimization problem
|
35 |
+
|
36 |
+
\[
|
37 |
+
\begin{array}{ll}
|
38 |
+
\text{minimize} & \|Ax - b\|_2^2.
|
39 |
+
\end{array}
|
40 |
+
\]
|
41 |
+
|
42 |
+
Let $x^\star$ denote the optimal $x$. The quantity $r = Ax^\star - b$ is known as the residual. If $\|r\|_2 = 0$, we have a perfect fit.
|
43 |
+
"""
|
44 |
+
)
|
45 |
+
return
|
46 |
+
|
47 |
+
|
48 |
+
@app.cell(hide_code=True)
|
49 |
+
def _(mo):
|
50 |
+
mo.md(
|
51 |
+
r"""
|
52 |
+
## Example
|
53 |
+
|
54 |
+
In this example, we use the Python library [CVXPY](https://github.com/cvxpy/cvxpy) to construct and solve a least-squares problems.
|
55 |
+
"""
|
56 |
+
)
|
57 |
+
return
|
58 |
+
|
59 |
+
|
60 |
+
@app.cell
|
61 |
+
def _():
|
62 |
+
import cvxpy as cp
|
63 |
+
import numpy as np
|
64 |
+
return cp, np
|
65 |
+
|
66 |
+
|
67 |
+
@app.cell
|
68 |
+
def _():
|
69 |
+
m = 20
|
70 |
+
n = 15
|
71 |
+
return m, n
|
72 |
+
|
73 |
+
|
74 |
+
@app.cell
|
75 |
+
def _(m, n, np):
|
76 |
+
np.random.seed(0)
|
77 |
+
A = np.random.randn(m, n)
|
78 |
+
b = np.random.randn(m)
|
79 |
+
return A, b
|
80 |
+
|
81 |
+
|
82 |
+
@app.cell
|
83 |
+
def _(A, b, cp, n):
|
84 |
+
x = cp.Variable(n)
|
85 |
+
objective = cp.sum_squares(A @ x - b)
|
86 |
+
problem = cp.Problem(cp.Minimize(objective))
|
87 |
+
optimal_value = problem.solve()
|
88 |
+
return objective, optimal_value, problem, x
|
89 |
+
|
90 |
+
|
91 |
+
@app.cell
|
92 |
+
def _(A, b, cp, mo, optimal_value, x):
|
93 |
+
mo.md(
|
94 |
+
f"""
|
95 |
+
- The optimal value is **{optimal_value:.04f}**.
|
96 |
+
- The optimal value of $x$ is {mo.as_html(list(x.value))}
|
97 |
+
- The norm of the residual is **{cp.norm(A @ x - b, p=2).value:0.4f}**
|
98 |
+
"""
|
99 |
+
)
|
100 |
+
return
|
101 |
+
|
102 |
+
|
103 |
+
@app.cell(hide_code=True)
|
104 |
+
def _(mo):
|
105 |
+
mo.md(
|
106 |
+
r"""
|
107 |
+
## Further reading
|
108 |
+
|
109 |
+
For a primer on least squares, with many real-world examples, check out the free book
|
110 |
+
[Vectors, Matrices, and Least Squares](https://web.stanford.edu/~boyd/vmls/), which is used for undergraduate linear algebra education at Stanford.
|
111 |
+
"""
|
112 |
+
)
|
113 |
+
return
|
114 |
+
|
115 |
+
|
116 |
+
@app.cell
|
117 |
+
def _():
|
118 |
+
import marimo as mo
|
119 |
+
return (mo,)
|
120 |
+
|
121 |
+
|
122 |
+
if __name__ == "__main__":
|
123 |
+
app.run()
|
optimization/02_linear_program.py
ADDED
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# /// script
|
2 |
+
# requires-python = ">=3.13"
|
3 |
+
# dependencies = [
|
4 |
+
# "cvxpy==1.6.0",
|
5 |
+
# "marimo",
|
6 |
+
# "matplotlib==3.10.0",
|
7 |
+
# "numpy==2.2.2",
|
8 |
+
# "wigglystuff==0.1.9",
|
9 |
+
# ]
|
10 |
+
# ///
|
11 |
+
|
12 |
+
import marimo
|
13 |
+
|
14 |
+
__generated_with = "0.11.0"
|
15 |
+
app = marimo.App()
|
16 |
+
|
17 |
+
|
18 |
+
@app.cell(hide_code=True)
|
19 |
+
def _(mo):
|
20 |
+
mo.md(
|
21 |
+
r"""
|
22 |
+
# Linear program
|
23 |
+
|
24 |
+
A linear program is an optimization problem with a linear objective and affine
|
25 |
+
inequality constraints. A common standard form is the following:
|
26 |
+
|
27 |
+
\[
|
28 |
+
\begin{array}{ll}
|
29 |
+
\text{minimize} & c^Tx \\
|
30 |
+
\text{subject to} & Ax \leq b.
|
31 |
+
\end{array}
|
32 |
+
\]
|
33 |
+
|
34 |
+
Here $A \in \mathcal{R}^{m \times n}$, $b \in \mathcal{R}^m$, and $c \in \mathcal{R}^n$ are problem data and $x \in \mathcal{R}^{n}$ is the optimization variable. The inequality constraint $Ax \leq b$ is elementwise.
|
35 |
+
|
36 |
+
For example, we might have $n$ different products, each constructed out of $m$ components. Each entry $A_{ij}$ is the amount of component $i$ required to build one unit of product $j$. Each entry $b_i$ is the total amount of component $i$ available. We lose $c_j$ for each unit of product $j$ ($c_j < 0$ indicates profit). Our goal then is to choose how many units of each product $j$ to make, $x_j$, in order to minimize loss without exceeding our budget for any component.
|
37 |
+
|
38 |
+
In addition to a solution $x^\star$, we obtain a dual solution $\lambda^\star$. A positive entry $\lambda^\star_i$ indicates that the constraint $a_i^Tx \leq b_i$ holds with equality for $x^\star$ and suggests that changing $b_i$ would change the optimal value.
|
39 |
+
|
40 |
+
**Why linear programming?** Linear programming is a way to achieve an optimal outcome, such as maximum utility or lowest cost, subject to a linear objective function and affine constraints. Developed in the 20th century, linear programming is widely used today to solve problems in resource allocation, scheduling, transportation, and more. The discovery of polynomial-time algorithms to solve linear programs was of tremendous worldwide importance and entered the public discourse, even making the front page of the New York Times.
|
41 |
+
|
42 |
+
In the late 20th and early 21st century, researchers generalized linear programming to a much wider class of problems called convex optimization problems. Nearly all convex optimization problems can be solved efficiently and reliably, and even more difficult problems are readily solved by a sequence of convex optimization problems. Today, convex optimization is used to fit machine learning models, land rockets in real-time at SpaceX, plan trajectories for self-driving cars at Waymo, execute many billions of dollars of financial trades a day, and much more.
|
43 |
+
|
44 |
+
This marimo learn course uses CVXPY, a modeling language for convex optimization problems developed originally at Stanford, to construct and solve convex programs.
|
45 |
+
"""
|
46 |
+
)
|
47 |
+
return
|
48 |
+
|
49 |
+
|
50 |
+
@app.cell(hide_code=True)
|
51 |
+
def _(mo):
|
52 |
+
mo.md(
|
53 |
+
f"""
|
54 |
+
{mo.image("https://www.debugmind.com/wp-content/uploads/2020/01/spacex-1.jpg")}
|
55 |
+
_SpaceX solves convex optimization problems onboard to land its rockets, using CVXGEN, a code generator for quadratic programming developed at Stephen Boydโs Stanford lab. Photo by SpaceX, licensed CC BY-NC 2.0._
|
56 |
+
"""
|
57 |
+
)
|
58 |
+
return
|
59 |
+
|
60 |
+
|
61 |
+
@app.cell(hide_code=True)
|
62 |
+
def _(mo):
|
63 |
+
mo.md(
|
64 |
+
r"""
|
65 |
+
## Example
|
66 |
+
|
67 |
+
Here we use CVXPY to construct and solve a linear program.
|
68 |
+
"""
|
69 |
+
)
|
70 |
+
return
|
71 |
+
|
72 |
+
|
73 |
+
@app.cell
|
74 |
+
def _():
|
75 |
+
import cvxpy as cp
|
76 |
+
import numpy as np
|
77 |
+
return cp, np
|
78 |
+
|
79 |
+
|
80 |
+
@app.cell(hide_code=True)
|
81 |
+
def _(np):
|
82 |
+
A = np.array(
|
83 |
+
[
|
84 |
+
[0.76103773, 0.12167502],
|
85 |
+
[0.44386323, 0.33367433],
|
86 |
+
[1.49407907, -0.20515826],
|
87 |
+
[0.3130677, -0.85409574],
|
88 |
+
[-2.55298982, 0.6536186],
|
89 |
+
[0.8644362, -0.74216502],
|
90 |
+
[2.26975462, -1.45436567],
|
91 |
+
[0.04575852, -0.18718385],
|
92 |
+
[1.53277921, 1.46935877],
|
93 |
+
[0.15494743, 0.37816252],
|
94 |
+
]
|
95 |
+
)
|
96 |
+
|
97 |
+
b = np.array(
|
98 |
+
[
|
99 |
+
2.05062369,
|
100 |
+
0.94934659,
|
101 |
+
0.89559424,
|
102 |
+
1.04389978,
|
103 |
+
2.45035643,
|
104 |
+
-0.95479445,
|
105 |
+
-0.83801349,
|
106 |
+
-0.26562529,
|
107 |
+
2.35763652,
|
108 |
+
0.98286942,
|
109 |
+
]
|
110 |
+
)
|
111 |
+
return A, b
|
112 |
+
|
113 |
+
|
114 |
+
@app.cell(hide_code=True)
|
115 |
+
def _(mo):
|
116 |
+
mo.md(r"""We've randomly generated problem data $A$ and $B$. The vector for $c$ is shown below. Try playing with the value of $c$ by dragging the components, and see how the level curves change in the visualization below.""")
|
117 |
+
return
|
118 |
+
|
119 |
+
|
120 |
+
@app.cell
|
121 |
+
def _(mo, np):
|
122 |
+
from wigglystuff import Matrix
|
123 |
+
|
124 |
+
c_widget = mo.ui.anywidget(Matrix(matrix=np.array([[0.1, -0.2]]), step=0.01))
|
125 |
+
c_widget
|
126 |
+
return Matrix, c_widget
|
127 |
+
|
128 |
+
|
129 |
+
@app.cell
|
130 |
+
def _(c_widget, np):
|
131 |
+
c = np.array(c_widget.value["matrix"][0])
|
132 |
+
return (c,)
|
133 |
+
|
134 |
+
|
135 |
+
@app.cell
|
136 |
+
def _(A, b, c, cp):
|
137 |
+
x = cp.Variable(A.shape[1])
|
138 |
+
prob = cp.Problem(cp.Minimize(c.T @ x), [A @ x <= b])
|
139 |
+
_ = prob.solve()
|
140 |
+
x_star = x.value
|
141 |
+
return prob, x, x_star
|
142 |
+
|
143 |
+
|
144 |
+
@app.cell(hide_code=True)
|
145 |
+
def _(mo):
|
146 |
+
mo.md(r"""Below, we plot the feasible region of the problem โ the intersection of the inequalities โ and the level curves of the objective function. The optimal value $x^\star$ is the point farthest in the feasible region in the direction $-c$.""")
|
147 |
+
return
|
148 |
+
|
149 |
+
|
150 |
+
@app.cell(hide_code=True)
|
151 |
+
def _(A, b, c, make_plot, x_star):
|
152 |
+
make_plot(A, b, c, x_star)
|
153 |
+
return
|
154 |
+
|
155 |
+
|
156 |
+
@app.cell(hide_code=True)
|
157 |
+
def _(np):
|
158 |
+
import matplotlib.pyplot as plt
|
159 |
+
|
160 |
+
|
161 |
+
def make_plot(A, b, c, x_star):
|
162 |
+
# Define a grid over a region that covers the feasible set.
|
163 |
+
# You might need to adjust these limits.
|
164 |
+
x_vals = np.linspace(-1, 1, 400)
|
165 |
+
y_vals = np.linspace(1, 3, 400)
|
166 |
+
X, Y = np.meshgrid(x_vals, y_vals)
|
167 |
+
|
168 |
+
# Flatten the grid points into an (N,2) array.
|
169 |
+
points = np.vstack([X.ravel(), Y.ravel()]).T
|
170 |
+
|
171 |
+
# For each point, check if it satisfies all the constraints: A @ x <= b.
|
172 |
+
# A dot product: shape of A @ x.T will be (m, N). We add a little tolerance.
|
173 |
+
feasible = np.all(np.dot(A, points.T) <= (b[:, None] + 1e-8), axis=0)
|
174 |
+
feasible = feasible.reshape(X.shape)
|
175 |
+
|
176 |
+
# Create the figure with a white background.
|
177 |
+
fig = plt.figure(figsize=(8, 6), facecolor="white")
|
178 |
+
ax = fig.add_subplot(111)
|
179 |
+
ax.set_facecolor("white")
|
180 |
+
|
181 |
+
# Plot the feasible region.
|
182 |
+
# Since "feasible" is a boolean array (False=0, True=1), we set contour levels so that
|
183 |
+
# the region with value 1 (feasible) gets filled.
|
184 |
+
ax.contourf(
|
185 |
+
X,
|
186 |
+
Y,
|
187 |
+
feasible.astype(float),
|
188 |
+
levels=[-0.5, 0.5, 1.5],
|
189 |
+
colors=["white", "gray"],
|
190 |
+
alpha=0.5,
|
191 |
+
)
|
192 |
+
|
193 |
+
# Plot level curves of the objective function c^T x.
|
194 |
+
# Compute c^T x over the grid:
|
195 |
+
Z = c[0] * X + c[1] * Y
|
196 |
+
# Choose several levels for the iso-cost lines.
|
197 |
+
levels = np.linspace(np.min(Z), np.max(Z), 20)
|
198 |
+
contours = ax.contour(
|
199 |
+
X, Y, Z, levels=levels, colors="gray", linestyles="--", linewidths=1
|
200 |
+
)
|
201 |
+
|
202 |
+
# Draw the vector -c as an arrow starting at x_star.
|
203 |
+
norm_c = np.linalg.norm(c)
|
204 |
+
if norm_c > 0:
|
205 |
+
head_width = norm_c * 0.1
|
206 |
+
head_length = norm_c * 0.1
|
207 |
+
# The arrow starts at x_star and points in the -c direction.
|
208 |
+
ax.arrow(
|
209 |
+
x_star[0],
|
210 |
+
x_star[1],
|
211 |
+
-c[0],
|
212 |
+
-c[1],
|
213 |
+
head_width=head_width,
|
214 |
+
head_length=head_length,
|
215 |
+
fc="black",
|
216 |
+
ec="black",
|
217 |
+
length_includes_head=True,
|
218 |
+
)
|
219 |
+
# Label the arrow near its tip.
|
220 |
+
ax.text(
|
221 |
+
x_star[0] - c[0] * 1.05,
|
222 |
+
x_star[1] - c[1] * 1.05,
|
223 |
+
r"$-c$",
|
224 |
+
color="black",
|
225 |
+
fontsize=12,
|
226 |
+
)
|
227 |
+
|
228 |
+
# Optionally, mark and label the point x_star.
|
229 |
+
ax.plot(x_star[0], x_star[1], "ko", markersize=5)
|
230 |
+
ax.text(
|
231 |
+
x_star[0],
|
232 |
+
x_star[1],
|
233 |
+
r"$\mathbf{x}^\star$",
|
234 |
+
color="black",
|
235 |
+
fontsize=12,
|
236 |
+
verticalalignment="bottom",
|
237 |
+
horizontalalignment="right",
|
238 |
+
)
|
239 |
+
# Label the axes and set title.
|
240 |
+
ax.set_xlabel("$x_1$")
|
241 |
+
ax.set_ylabel("$x_2$")
|
242 |
+
ax.set_title("Feasible Region and Level Curves of $c^Tx$")
|
243 |
+
ax.set_xlim(np.min(x_vals), np.max(x_vals))
|
244 |
+
ax.set_ylim(np.min(y_vals), np.max(y_vals))
|
245 |
+
return ax
|
246 |
+
return make_plot, plt
|
247 |
+
|
248 |
+
|
249 |
+
@app.cell(hide_code=True)
|
250 |
+
def _(mo, prob, x):
|
251 |
+
mo.md(
|
252 |
+
f"""
|
253 |
+
The optimal value is {prob.value:.04f}.
|
254 |
+
|
255 |
+
A solution $x$ is {mo.as_html(list(x.value))}
|
256 |
+
A dual solution is is {mo.as_html(list(prob.constraints[0].dual_value))}
|
257 |
+
"""
|
258 |
+
)
|
259 |
+
return
|
260 |
+
|
261 |
+
|
262 |
+
@app.cell
|
263 |
+
def _():
|
264 |
+
import marimo as mo
|
265 |
+
return (mo,)
|
266 |
+
|
267 |
+
|
268 |
+
if __name__ == "__main__":
|
269 |
+
app.run()
|
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()
|
optimization/04_quadratic_program.py
ADDED
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# /// script
|
2 |
+
# requires-python = ">=3.13"
|
3 |
+
# dependencies = [
|
4 |
+
# "cvxpy==1.6.0",
|
5 |
+
# "marimo",
|
6 |
+
# "matplotlib==3.10.0",
|
7 |
+
# "numpy==2.2.2",
|
8 |
+
# "wigglystuff==0.1.9",
|
9 |
+
# ]
|
10 |
+
# ///
|
11 |
+
|
12 |
+
import marimo
|
13 |
+
|
14 |
+
__generated_with = "0.11.0"
|
15 |
+
app = marimo.App()
|
16 |
+
|
17 |
+
|
18 |
+
@app.cell(hide_code=True)
|
19 |
+
def _(mo):
|
20 |
+
mo.md(
|
21 |
+
r"""
|
22 |
+
# Quadratic program
|
23 |
+
|
24 |
+
A quadratic program is an optimization problem with a quadratic objective and
|
25 |
+
affine equality and inequality constraints. A common standard form is the
|
26 |
+
following:
|
27 |
+
|
28 |
+
\[
|
29 |
+
\begin{array}{ll}
|
30 |
+
\text{minimize} & (1/2)x^TPx + q^Tx\\
|
31 |
+
\text{subject to} & Gx \leq h \\
|
32 |
+
& Ax = b.
|
33 |
+
\end{array}
|
34 |
+
\]
|
35 |
+
|
36 |
+
Here $P \in \mathcal{S}^{n}_+$, $q \in \mathcal{R}^n$, $G \in \mathcal{R}^{m \times n}$, $h \in \mathcal{R}^m$, $A \in \mathcal{R}^{p \times n}$, and $b \in \mathcal{R}^p$ are problem data and $x \in \mathcal{R}^{n}$ is the optimization variable. The inequality constraint $Gx \leq h$ is elementwise.
|
37 |
+
|
38 |
+
**Why quadratic programming?** Quadratic programs are convex optimization problems that generalize both least-squares and linear programming.They can be solved efficiently and reliably, even in real-time.
|
39 |
+
|
40 |
+
**An example from finance.** A simple example of a quadratic program arises in finance. Suppose we have $n$ different stocks, an estimate $r \in \mathcal{R}^n$ of the expected return on each stock, and an estimate $\Sigma \in \mathcal{S}^{n}_+$ of the covariance of the returns. Then we solve the optimization problem
|
41 |
+
|
42 |
+
\[
|
43 |
+
\begin{array}{ll}
|
44 |
+
\text{minimize} & (1/2)x^T\Sigma x - r^Tx\\
|
45 |
+
\text{subject to} & x \geq 0 \\
|
46 |
+
& \mathbf{1}^Tx = 1,
|
47 |
+
\end{array}
|
48 |
+
\]
|
49 |
+
|
50 |
+
to find a nonnegative portfolio allocation $x \in \mathcal{R}^n_+$ that optimally balances expected return and variance of return.
|
51 |
+
|
52 |
+
When we solve a quadratic program, in addition to a solution $x^\star$, we obtain a dual solution $\lambda^\star$ corresponding to the inequality constraints. A positive entry $\lambda^\star_i$ indicates that the constraint $g_i^Tx \leq h_i$ holds with equality for $x^\star$ and suggests that changing $h_i$ would change the optimal value.
|
53 |
+
"""
|
54 |
+
)
|
55 |
+
return
|
56 |
+
|
57 |
+
|
58 |
+
@app.cell(hide_code=True)
|
59 |
+
def _(mo):
|
60 |
+
mo.md(
|
61 |
+
r"""
|
62 |
+
## Example
|
63 |
+
|
64 |
+
In this example, we use CVXPY to construct and solve a quadratic program.
|
65 |
+
"""
|
66 |
+
)
|
67 |
+
return
|
68 |
+
|
69 |
+
|
70 |
+
@app.cell
|
71 |
+
def _():
|
72 |
+
import cvxpy as cp
|
73 |
+
import numpy as np
|
74 |
+
return cp, np
|
75 |
+
|
76 |
+
|
77 |
+
@app.cell(hide_code=True)
|
78 |
+
def _(mo):
|
79 |
+
mo.md("""First we generate synthetic data. In this problem, we don't include equality constraints, only inequality.""")
|
80 |
+
return
|
81 |
+
|
82 |
+
|
83 |
+
@app.cell
|
84 |
+
def _(np):
|
85 |
+
m = 4
|
86 |
+
n = 2
|
87 |
+
|
88 |
+
np.random.seed(1)
|
89 |
+
q = np.random.randn(n)
|
90 |
+
G = np.random.randn(m, n)
|
91 |
+
h = G @ np.random.randn(n)
|
92 |
+
return G, h, m, n, q
|
93 |
+
|
94 |
+
|
95 |
+
@app.cell(hide_code=True)
|
96 |
+
def _(mo, np):
|
97 |
+
import wigglystuff
|
98 |
+
|
99 |
+
P_widget = mo.ui.anywidget(
|
100 |
+
wigglystuff.Matrix(np.array([[4.0, -1.4], [-1.4, 4]]), step=0.1)
|
101 |
+
)
|
102 |
+
|
103 |
+
mo.md(
|
104 |
+
f"""
|
105 |
+
The quadratic form $P$ is equal to the symmetrized version of this
|
106 |
+
matrix:
|
107 |
+
|
108 |
+
{P_widget.center()}
|
109 |
+
"""
|
110 |
+
)
|
111 |
+
return P_widget, wigglystuff
|
112 |
+
|
113 |
+
|
114 |
+
@app.cell
|
115 |
+
def _(P_widget, np):
|
116 |
+
P = 0.5 * (np.array(P_widget.matrix) + np.array(P_widget.matrix).T)
|
117 |
+
return (P,)
|
118 |
+
|
119 |
+
|
120 |
+
@app.cell(hide_code=True)
|
121 |
+
def _(mo):
|
122 |
+
mo.md(r"""Next, we specify the problem. Notice that we use the `quad_form` function from CVXPY to create the quadratic form $x^TPx$.""")
|
123 |
+
return
|
124 |
+
|
125 |
+
|
126 |
+
@app.cell
|
127 |
+
def _(G, P, cp, h, n, q):
|
128 |
+
x = cp.Variable(n)
|
129 |
+
|
130 |
+
problem = cp.Problem(
|
131 |
+
cp.Minimize((1 / 2) * cp.quad_form(x, P) + q.T @ x),
|
132 |
+
[G @ x <= h],
|
133 |
+
)
|
134 |
+
_ = problem.solve()
|
135 |
+
return problem, x
|
136 |
+
|
137 |
+
|
138 |
+
@app.cell(hide_code=True)
|
139 |
+
def _(mo, problem, x):
|
140 |
+
mo.md(
|
141 |
+
f"""
|
142 |
+
The optimal value is {problem.value:.04f}.
|
143 |
+
|
144 |
+
A solution $x$ is {mo.as_html(list(x.value))}
|
145 |
+
A dual solution is is {mo.as_html(list(problem.constraints[0].dual_value))}
|
146 |
+
"""
|
147 |
+
)
|
148 |
+
return
|
149 |
+
|
150 |
+
|
151 |
+
@app.cell
|
152 |
+
def _(G, P, h, plot_contours, q, x):
|
153 |
+
plot_contours(P, G, h, q, x.value)
|
154 |
+
return
|
155 |
+
|
156 |
+
|
157 |
+
@app.cell(hide_code=True)
|
158 |
+
def _(mo):
|
159 |
+
mo.md(
|
160 |
+
r"""
|
161 |
+
In this plot, the gray shaded region is the feasible region (points satisfying the inequality), and the ellipses are level curves of the quadratic form.
|
162 |
+
|
163 |
+
**๐ Try it!** Try changing the entries of $P$ above with your mouse. How do the
|
164 |
+
level curves and the optimal value of $x$ change? Can you explain what you see?
|
165 |
+
"""
|
166 |
+
)
|
167 |
+
return
|
168 |
+
|
169 |
+
|
170 |
+
@app.cell(hide_code=True)
|
171 |
+
def _(P, mo):
|
172 |
+
mo.md(
|
173 |
+
rf"""
|
174 |
+
The above contour lines were generated with
|
175 |
+
|
176 |
+
\[
|
177 |
+
P= \begin{{bmatrix}}
|
178 |
+
{P[0, 0]:.01f} & {P[0, 1]:.01f} \\
|
179 |
+
{P[1, 0]:.01f} & {P[1, 1]:.01f} \\
|
180 |
+
\end{{bmatrix}}
|
181 |
+
\]
|
182 |
+
"""
|
183 |
+
)
|
184 |
+
return
|
185 |
+
|
186 |
+
|
187 |
+
@app.cell(hide_code=True)
|
188 |
+
def _(np):
|
189 |
+
def plot_contours(P, G, h, q, x_star):
|
190 |
+
import matplotlib.pyplot as plt
|
191 |
+
|
192 |
+
# Create a grid of x and y values.
|
193 |
+
x = np.linspace(-5, 5, 400)
|
194 |
+
y = np.linspace(-5, 5, 400)
|
195 |
+
X, Y = np.meshgrid(x, y)
|
196 |
+
|
197 |
+
# Compute the quadratic form Q(x, y) = a*x^2 + 2*b*x*y + c*y^2.
|
198 |
+
# Here, a = P[0,0], b = P[0,1] (and P[1,0]), c = P[1,1]
|
199 |
+
Z = (
|
200 |
+
0.5 * (P[0, 0] * X**2 + 2 * P[0, 1] * X * Y + P[1, 1] * Y**2)
|
201 |
+
+ q[0] * X
|
202 |
+
+ q[1] * Y
|
203 |
+
)
|
204 |
+
|
205 |
+
# --- Evaluate the constraints on the grid ---
|
206 |
+
# We stack X and Y to get a list of (x,y) points.
|
207 |
+
points = np.vstack([X.ravel(), Y.ravel()]).T
|
208 |
+
|
209 |
+
# Start with all points feasible
|
210 |
+
feasible = np.ones(points.shape[0], dtype=bool)
|
211 |
+
|
212 |
+
# Apply the inequality constraints Gx <= h.
|
213 |
+
# Each row of G and corresponding h defines a condition.
|
214 |
+
for i in range(G.shape[0]):
|
215 |
+
# For a given point x, the condition is: G[i,0]*x + G[i,1]*y <= h[i]
|
216 |
+
feasible &= points.dot(G[i]) <= h[i] + 1e-8 # small tolerance
|
217 |
+
# Reshape the boolean mask back to grid shape.
|
218 |
+
feasible_grid = feasible.reshape(X.shape)
|
219 |
+
|
220 |
+
# --- Plot the feasible region and contour lines---
|
221 |
+
plt.figure(figsize=(8, 6))
|
222 |
+
|
223 |
+
# Use contourf to fill the region where feasible_grid is True.
|
224 |
+
# We define two levels, so that points that are True (feasible) get one
|
225 |
+
# color.
|
226 |
+
plt.contourf(
|
227 |
+
X,
|
228 |
+
Y,
|
229 |
+
feasible_grid,
|
230 |
+
levels=[-0.5, 0.5, 1.5],
|
231 |
+
colors=["white", "gray"],
|
232 |
+
alpha=0.5,
|
233 |
+
)
|
234 |
+
|
235 |
+
contours = plt.contour(X, Y, Z, levels=10, cmap="viridis")
|
236 |
+
plt.clabel(contours, inline=True, fontsize=8)
|
237 |
+
plt.title("Feasible region and level curves")
|
238 |
+
plt.xlabel("$x_1$")
|
239 |
+
plt.ylabel("$y_2$")
|
240 |
+
# plt.colorbar(contours, label='Q(x, y)')
|
241 |
+
|
242 |
+
ax = plt.gca()
|
243 |
+
# Optionally, mark and label the point x_star.
|
244 |
+
ax.plot(x_star[0], x_star[1], "ko", markersize=5)
|
245 |
+
ax.text(
|
246 |
+
x_star[0],
|
247 |
+
x_star[1],
|
248 |
+
r"$\mathbf{x}^\star$",
|
249 |
+
color="black",
|
250 |
+
fontsize=12,
|
251 |
+
verticalalignment="bottom",
|
252 |
+
horizontalalignment="right",
|
253 |
+
)
|
254 |
+
return plt.gca()
|
255 |
+
return (plot_contours,)
|
256 |
+
|
257 |
+
|
258 |
+
@app.cell
|
259 |
+
def _():
|
260 |
+
import marimo as mo
|
261 |
+
return (mo,)
|
262 |
+
|
263 |
+
|
264 |
+
if __name__ == "__main__":
|
265 |
+
app.run()
|
optimization/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Learn optimization
|
2 |
+
|
3 |
+
๐ง _This collection is a work in progress. Check back later for new notebooks!_
|
4 |
+
|
5 |
+
This collection of marimo notebooks teaches you the basics of mathematical
|
6 |
+
optimization.
|
7 |
+
|
8 |
+
After working through these notebooks, you'll understand how to create
|
9 |
+
and solve optimization problems using the Python library
|
10 |
+
[CVXPY](https://github.com/cvxpy/cvxpy), as well as how to apply what you've
|
11 |
+
learned to real-world problems such as portfolio allocation in finance,
|
12 |
+
resource allocation, and more.
|
13 |
+
|
14 |
+

|
15 |
+
|
16 |
+
_SpaceX solves convex optimization problems onboard to land its rockets, using CVXGEN, a code generator for quadratic programming developed at Stephen Boydโs Stanford lab. Photo by SpaceX, licensed CC BY-NC 2.0._
|
17 |
+
|
18 |
+
**Running notebooks.** To run a notebook locally, use
|
19 |
+
|
20 |
+
```bash
|
21 |
+
uvx marimo edit <URL>
|
22 |
+
```
|
23 |
+
|
24 |
+
For example, run the least-squares tutorial with
|
25 |
+
|
26 |
+
```bash
|
27 |
+
uvx marimo edit https://github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py
|
28 |
+
```
|
29 |
+
|
30 |
+
You can also open notebooks in our online playground by appending `marimo.app/`
|
31 |
+
to a notebook's URL: [marimo.app/github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py](https://marimo.app/https://github.com/marimo-team/learn/blob/main/optimization/01_least_squares.py).
|
probability/01_sets.py
ADDED
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# /// script
|
2 |
+
# requires-python = ">=3.10"
|
3 |
+
# dependencies = [
|
4 |
+
# "marimo",
|
5 |
+
# ]
|
6 |
+
# ///
|
7 |
+
|
8 |
+
import marimo
|
9 |
+
|
10 |
+
__generated_with = "0.11.0"
|
11 |
+
app = marimo.App()
|
12 |
+
|
13 |
+
|
14 |
+
@app.cell(hide_code=True)
|
15 |
+
def _(mo):
|
16 |
+
mo.md(
|
17 |
+
r"""
|
18 |
+
# Sets
|
19 |
+
|
20 |
+
Probability is the study of "events", assigning numerical values to how likely
|
21 |
+
events are to occur. For example, probability lets us quantify how likely it is for it to rain or shine on a given day.
|
22 |
+
|
23 |
+
|
24 |
+
Typically we reason about _sets_ of events. In mathematics,
|
25 |
+
a set is a collection of elements, with no element included more than once.
|
26 |
+
Elements can be any kind of object.
|
27 |
+
|
28 |
+
For example:
|
29 |
+
|
30 |
+
- โ๏ธ Weather events: $\{\text{Rain}, \text{Overcast}, \text{Clear}\}$
|
31 |
+
- ๐ฒ Die rolls: $\{1, 2, 3, 4, 5, 6\}$
|
32 |
+
- ๐ช Pairs of coin flips = $\{ \text{(Heads, Heads)}, \text{(Heads, Tails)}, \text{(Tails, Tails)} \text{(Tails, Heads)}\}$
|
33 |
+
|
34 |
+
Sets are the building blocks of probability, and will arise frequently in our study.
|
35 |
+
"""
|
36 |
+
)
|
37 |
+
return
|
38 |
+
|
39 |
+
|
40 |
+
@app.cell(hide_code=True)
|
41 |
+
def _(mo):
|
42 |
+
mo.md(r"""## Set operations""")
|
43 |
+
return
|
44 |
+
|
45 |
+
|
46 |
+
@app.cell(hide_code=True)
|
47 |
+
def _(mo):
|
48 |
+
mo.md(r"""In Python, sets are made with the `set` function:""")
|
49 |
+
return
|
50 |
+
|
51 |
+
|
52 |
+
@app.cell
|
53 |
+
def _():
|
54 |
+
A = set([2, 3, 5, 7])
|
55 |
+
A
|
56 |
+
return (A,)
|
57 |
+
|
58 |
+
|
59 |
+
@app.cell
|
60 |
+
def _():
|
61 |
+
B = set([0, 1, 2, 3, 5, 8])
|
62 |
+
B
|
63 |
+
return (B,)
|
64 |
+
|
65 |
+
|
66 |
+
@app.cell(hide_code=True)
|
67 |
+
def _(mo):
|
68 |
+
mo.md(
|
69 |
+
r"""
|
70 |
+
Below we explain common operations on sets.
|
71 |
+
|
72 |
+
_**Try it!** Try modifying the definitions of `A` and `B` above, and see how the results change below._
|
73 |
+
|
74 |
+
The **union** $A \cup B$ of sets $A$ and $B$ is the set of elements in $A$, $B$, or both.
|
75 |
+
"""
|
76 |
+
)
|
77 |
+
return
|
78 |
+
|
79 |
+
|
80 |
+
@app.cell
|
81 |
+
def _(A, B):
|
82 |
+
A | B
|
83 |
+
return
|
84 |
+
|
85 |
+
|
86 |
+
@app.cell(hide_code=True)
|
87 |
+
def _(mo):
|
88 |
+
mo.md(r"""The **intersection** $A \cap B$ is the set of elements in both $A$ and $B$""")
|
89 |
+
return
|
90 |
+
|
91 |
+
|
92 |
+
@app.cell
|
93 |
+
def _(A, B):
|
94 |
+
A & B
|
95 |
+
return
|
96 |
+
|
97 |
+
|
98 |
+
@app.cell(hide_code=True)
|
99 |
+
def _(mo):
|
100 |
+
mo.md(r"""The **difference** $A \setminus B$ is the set of elements in $A$ that are not in $B$.""")
|
101 |
+
return
|
102 |
+
|
103 |
+
|
104 |
+
@app.cell
|
105 |
+
def _(A, B):
|
106 |
+
A - B
|
107 |
+
return
|
108 |
+
|
109 |
+
|
110 |
+
@app.cell(hide_code=True)
|
111 |
+
def _(mo):
|
112 |
+
mo.md(
|
113 |
+
"""
|
114 |
+
### ๐ฌ An interactive example
|
115 |
+
|
116 |
+
Here's a simple example that classifies TV shows into sets by genre, and uses these sets to recommend shows to a user based on their preferences.
|
117 |
+
"""
|
118 |
+
)
|
119 |
+
return
|
120 |
+
|
121 |
+
|
122 |
+
@app.cell(hide_code=True)
|
123 |
+
def _(mo):
|
124 |
+
viewer_type = mo.ui.radio(
|
125 |
+
options={
|
126 |
+
"I like action and drama!": "New Viewer",
|
127 |
+
"I only like action shows": "Action Fan",
|
128 |
+
"I only like dramas": "Drama Fan",
|
129 |
+
},
|
130 |
+
value="I like action and drama!",
|
131 |
+
label="Which genre do you prefer?",
|
132 |
+
)
|
133 |
+
return (viewer_type,)
|
134 |
+
|
135 |
+
|
136 |
+
@app.cell(hide_code=True)
|
137 |
+
def _(viewer_type):
|
138 |
+
viewer_type
|
139 |
+
return
|
140 |
+
|
141 |
+
|
142 |
+
@app.cell
|
143 |
+
def _():
|
144 |
+
action_shows = {"Stranger Things", "The Witcher", "Money Heist"}
|
145 |
+
drama_shows = {"The Crown", "Money Heist", "Bridgerton"}
|
146 |
+
return action_shows, drama_shows
|
147 |
+
|
148 |
+
|
149 |
+
@app.cell
|
150 |
+
def _(action_shows, drama_shows):
|
151 |
+
recommendations = {
|
152 |
+
"New Viewer": action_shows | drama_shows, # Union for new viewers
|
153 |
+
"Action Fan": action_shows - drama_shows, # Unique action shows
|
154 |
+
"Drama Fan": drama_shows - action_shows, # Unique drama shows
|
155 |
+
}
|
156 |
+
return (recommendations,)
|
157 |
+
|
158 |
+
|
159 |
+
@app.cell(hide_code=True)
|
160 |
+
def _(mo, recommendations, viewer_type):
|
161 |
+
result = recommendations[viewer_type.value]
|
162 |
+
|
163 |
+
explanation = {
|
164 |
+
"New Viewer": "You get everything to explore!",
|
165 |
+
"Action Fan": "Pure action, no drama!",
|
166 |
+
"Drama Fan": "Drama-focused selections!",
|
167 |
+
}
|
168 |
+
|
169 |
+
mo.md(f"""
|
170 |
+
**๐ฌ Recommended shows.** Based on your preference for **{viewer_type.value}**,
|
171 |
+
we recommend:
|
172 |
+
|
173 |
+
{", ".join(result)}
|
174 |
+
|
175 |
+
**Why these shows?**
|
176 |
+
{explanation[viewer_type.value]}
|
177 |
+
""")
|
178 |
+
return explanation, result
|
179 |
+
|
180 |
+
|
181 |
+
@app.cell(hide_code=True)
|
182 |
+
def _(mo):
|
183 |
+
mo.md("""
|
184 |
+
### Exercise
|
185 |
+
|
186 |
+
Given these sets:
|
187 |
+
|
188 |
+
- A = {๐ฎ, ๐ฑ, ๐ป}
|
189 |
+
|
190 |
+
- B = {๐ฑ, ๐ป, ๐จ๏ธ}
|
191 |
+
|
192 |
+
- C = {๐ป, ๐จ๏ธ, โจ๏ธ}
|
193 |
+
|
194 |
+
Can you:
|
195 |
+
|
196 |
+
1. Find all elements that are in A or B
|
197 |
+
|
198 |
+
2. Find elements common to all three sets
|
199 |
+
|
200 |
+
3. Find elements in A that aren't in C
|
201 |
+
|
202 |
+
<details>
|
203 |
+
|
204 |
+
<summary>Check your answers!</summary>
|
205 |
+
|
206 |
+
1. A โช B = {๐ฎ, ๐ฑ, ๐ป, ๐จ๏ธ}<br>
|
207 |
+
2. A โฉ B โฉ C = {๐ป}<br>
|
208 |
+
3. A - C = {๐ฎ, ๐ฑ}
|
209 |
+
|
210 |
+
</details>
|
211 |
+
""")
|
212 |
+
return
|
213 |
+
|
214 |
+
|
215 |
+
@app.cell(hide_code=True)
|
216 |
+
def _(mo):
|
217 |
+
mo.md(
|
218 |
+
r"""
|
219 |
+
## ๐งฎ Set properties
|
220 |
+
|
221 |
+
Here are some important properties of the set operations:
|
222 |
+
|
223 |
+
1. **Commutative**: $A \cup B = B \cup A$
|
224 |
+
2. **Associative**: $(A \cup B) \cup C = A \cup (B \cup C)$
|
225 |
+
3. **Distributive**: $A \cup (B \cap C) = (A \cup B) \cap (A \cup C)$
|
226 |
+
"""
|
227 |
+
)
|
228 |
+
return
|
229 |
+
|
230 |
+
|
231 |
+
@app.cell(hide_code=True)
|
232 |
+
def _(mo):
|
233 |
+
mo.md(
|
234 |
+
r"""
|
235 |
+
## Set builder notation
|
236 |
+
|
237 |
+
To compactly describe the elements in a set, we can use **set builder notation**, which specifies conditions that must be true for elements to be in the set.
|
238 |
+
|
239 |
+
For example, here is how to specify the set of positive numbers less than 10:
|
240 |
+
|
241 |
+
\[
|
242 |
+
\{x \mid 0 < x < 10 \}
|
243 |
+
\]
|
244 |
+
|
245 |
+
The predicate to the right of the vertical bar $\mid$ specifies conditions that must be true for an element to be in the set; the expression to the left of $\mid$ specifies the value being included.
|
246 |
+
|
247 |
+
In Python, set builder notation is called a "set comprehension."
|
248 |
+
"""
|
249 |
+
)
|
250 |
+
return
|
251 |
+
|
252 |
+
|
253 |
+
@app.cell
|
254 |
+
def _():
|
255 |
+
def predicate(x):
|
256 |
+
return x > 0 and x < 10
|
257 |
+
return (predicate,)
|
258 |
+
|
259 |
+
|
260 |
+
@app.cell
|
261 |
+
def _(predicate):
|
262 |
+
set(x for x in range(100) if predicate(x))
|
263 |
+
return
|
264 |
+
|
265 |
+
|
266 |
+
@app.cell(hide_code=True)
|
267 |
+
def _(mo):
|
268 |
+
mo.md("""**Try it!** Try modifying the `predicate` function above and see how the set changes.""")
|
269 |
+
return
|
270 |
+
|
271 |
+
|
272 |
+
@app.cell(hide_code=True)
|
273 |
+
def _(mo):
|
274 |
+
mo.md("""
|
275 |
+
## Summary
|
276 |
+
|
277 |
+
You've learned:
|
278 |
+
|
279 |
+
- Basic set operations
|
280 |
+
- Set properties
|
281 |
+
- Real-world applications
|
282 |
+
|
283 |
+
In the next lesson, we'll define probability from the ground up, using sets.
|
284 |
+
|
285 |
+
Remember: In probability, every event is a set, and every set can be an event!
|
286 |
+
""")
|
287 |
+
return
|
288 |
+
|
289 |
+
|
290 |
+
@app.cell
|
291 |
+
def _():
|
292 |
+
import marimo as mo
|
293 |
+
return (mo,)
|
294 |
+
|
295 |
+
|
296 |
+
if __name__ == "__main__":
|
297 |
+
app.run()
|
probability/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Learn probability
|
2 |
+
|
3 |
+
๐ง _This collection is a work in progress. Check back later for new noteboks._
|
4 |
+
|
5 |
+
This collection of marimo notebooks teaches the fundamentals of probability,
|
6 |
+
with an emphasis on computation with Python.
|
7 |
+
|
8 |
+
|
9 |
+
**Running notebooks.** To run a notebook locally, use
|
10 |
+
|
11 |
+
```bash
|
12 |
+
uvx marimo edit <URL>
|
13 |
+
```
|
14 |
+
|
15 |
+
For example, run the numbers tutorial with
|
16 |
+
|
17 |
+
```bash
|
18 |
+
uvx marimo edit https://github.com/marimo-team/learn/blob/main/probability/01_sets.py
|
19 |
+
```
|
20 |
+
|
21 |
+
You can also open notebooks in our online playground by appending `marimo.app/`
|
22 |
+
to a notebook's URL: [marimo.app/https://github.com/marimo-team/learn/blob/main/probability/01_sets.py](https://marimo.app/https://github.com/marimo-team/learn/blob/main/probability/01_sets.py).
|