Each of the eight v2.0 companion notebooks (10_bsde through 17_generative_calibration) now follows the mandatory pedagogical sandwich structure: PRE markdown : theorem / model / pivot equation / what the cell verifies CODE cell : labelled prints + at least one matplotlib figure POST markdown: expected result, graph reading, conclusion Each notebook carries at least one concrete real-world example (heat plate, inverted pendulum, opinion polarization, collective decision, OU drift under Cauchy noise, mixture vs gaussian MMD, etc.) Generator script: scripts/enrich_v2_notebooks.py Doc plots refreshed via scripts/inject_doc_plots.py.
174 KiB
174 KiB
In [1]:
import numpy as np
import matplotlib.pyplot as plt
from optimizr import _core as opt
plt.rcParams['figure.figsize'] = (8.5, 4.5)
plt.rcParams['figure.dpi'] = 110
plt.rcParams['axes.grid'] = True
plt.rcParams['grid.alpha'] = 0.3
In [2]:
res = opt.pontryagin_lqr(
a=1.0, b=1.0, q=1.0, r=1.0, s_terminal=0.5,
x0=1.0, t_horizon=5.0, n_steps=400,
)
ts = np.array(res['time_grid'])
P = np.array(res['riccati'])
state = np.array(res['state'])
control = np.array(res['control'])
P_star = 1.0 + np.sqrt(2.0)
print(f"P(0) numérique = {P[0]:.4f}")
print(f"P* analytique = {P_star:.4f}")
print(f"P(T) (terminal) = {P[-1]:.4f}")
print(f"Coût optimal = {res['cost']:.4f}")
fig, axes = plt.subplots(1, 3, figsize=(13, 3.8))
axes[0].plot(ts, P, lw=2)
axes[0].axhline(P_star, ls='--', color='gray',
label=f'P* = {P_star:.3f}')
axes[0].set_xlabel('t'); axes[0].set_ylabel('P(t)')
axes[0].set_title("Riccati"); axes[0].legend()
axes[1].plot(ts, state, lw=2, color='C2')
axes[1].set_xlabel('t'); axes[1].set_ylabel('x(t)')
axes[1].set_title("État optimal")
ts_u = ts[:len(control)]
axes[2].plot(ts_u, control, lw=2, color='C3')
axes[2].set_xlabel('t'); axes[2].set_ylabel('u(t)')
axes[2].set_title("Commande optimale")
fig.tight_layout(); plt.show()
P(0) numérique = 2.7321 P* analytique = 2.4142 P(T) (terminal) = 0.5000 Coût optimal = 2.4701
In [3]:
s_values = [0.1, 0.5, 2.0, 5.0]
T_values = [0.5, 1.0, 2.0, 5.0, 10.0]
P_star = 1.0 + np.sqrt(2.0)
fig, ax = plt.subplots()
for s in s_values:
p0s = []
for T_ in T_values:
r = opt.pontryagin_lqr(1.0, 1.0, 1.0, 1.0, s, 1.0, T_, 200)
p0s.append(r['riccati'][0])
ax.plot(T_values, p0s, 'o-', lw=2, label=f's = {s}')
print(f"s = {s:4.1f} : P(0) à T=10 = {p0s[-1]:.4f}")
ax.axhline(P_star, ls='--', color='black',
label=f'P* = {P_star:.3f}')
ax.set_xlabel('horizon T'); ax.set_ylabel('P(0)')
ax.set_title("Convergence vers le point fixe Riccati")
ax.legend()
fig.tight_layout(); plt.show()
s = 0.1 : P(0) à T=10 = 2.7321 s = 0.5 : P(0) à T=10 = 2.7321 s = 2.0 : P(0) à T=10 = 2.7321 s = 5.0 : P(0) à T=10 = 2.7321
In [4]:
g, ell, m = 9.81, 1.0, 1.0
a, b = g / ell, 1.0 / (m * ell ** 2)
res = opt.pontryagin_lqr(a, b, q=10.0, r=1.0, s_terminal=1.0,
x0=0.3, t_horizon=5.0, n_steps=500)
ts = np.array(res['time_grid'])
theta = np.array(res['state'])
u = np.array(res['control'])
print(f"Angle initial : {theta[0]:.3f} rad ({np.degrees(theta[0]):.1f}°)")
print(f"Angle final : {theta[-1]:.3e} rad")
print(f"Effort max : {np.abs(u).max():.3f}")
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].plot(ts, theta, lw=2, color='C3', label=r'$\theta(t)$')
axes[0].axhline(0, ls='--', color='gray', alpha=0.6)
axes[0].set_xlabel('t (s)'); axes[0].set_ylabel(r'$\theta$ (rad)')
axes[0].set_title("Stabilisation du pendule inversé")
axes[0].legend()
ts_u = ts[:len(u)]
axes[1].plot(ts_u, u, lw=2, color='C2', label='u(t)')
axes[1].set_xlabel('t (s)'); axes[1].set_ylabel('u (couple)')
axes[1].set_title("Couple appliqué (LQR)")
axes[1].legend()
fig.tight_layout(); plt.show()
Angle initial : 0.300 rad (17.2°) Angle final : 5.955e-25 rad Effort max : 6.177