diff --git a/docs/source/_static/auto/algorithms__agent_based/block_03_fig_01.png b/docs/source/_static/auto/algorithms__agent_based/block_03_fig_01.png new file mode 100644 index 0000000..727a634 Binary files /dev/null and b/docs/source/_static/auto/algorithms__agent_based/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__agent_based/block_04_fig_01.png b/docs/source/_static/auto/algorithms__agent_based/block_04_fig_01.png new file mode 100644 index 0000000..c643edc Binary files /dev/null and b/docs/source/_static/auto/algorithms__agent_based/block_04_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__bsde/block_03_fig_01.png b/docs/source/_static/auto/algorithms__bsde/block_03_fig_01.png new file mode 100644 index 0000000..55283bf Binary files /dev/null and b/docs/source/_static/auto/algorithms__bsde/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__bsde/block_05_fig_01.png b/docs/source/_static/auto/algorithms__bsde/block_05_fig_01.png new file mode 100644 index 0000000..4ba0748 Binary files /dev/null and b/docs/source/_static/auto/algorithms__bsde/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_03_fig_01.png b/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_03_fig_01.png new file mode 100644 index 0000000..ab18ccd Binary files /dev/null and b/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_04_fig_01.png b/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_04_fig_01.png new file mode 100644 index 0000000..5a847a4 Binary files /dev/null and b/docs/source/_static/auto/algorithms__generative_calibration_hooks/block_04_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__grid_search/block_08_fig_01.png b/docs/source/_static/auto/algorithms__grid_search/block_08_fig_01.png new file mode 100644 index 0000000..ee953b2 Binary files /dev/null and b/docs/source/_static/auto/algorithms__grid_search/block_08_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__hmm/block_05_fig_01.png b/docs/source/_static/auto/algorithms__hmm/block_05_fig_01.png new file mode 100644 index 0000000..4ca934e Binary files /dev/null and b/docs/source/_static/auto/algorithms__hmm/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__mckean_vlasov/block_03_fig_01.png b/docs/source/_static/auto/algorithms__mckean_vlasov/block_03_fig_01.png new file mode 100644 index 0000000..b1da6ce Binary files /dev/null and b/docs/source/_static/auto/algorithms__mckean_vlasov/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__mckean_vlasov/block_04_fig_01.png b/docs/source/_static/auto/algorithms__mckean_vlasov/block_04_fig_01.png new file mode 100644 index 0000000..c9e9cb1 Binary files /dev/null and b/docs/source/_static/auto/algorithms__mckean_vlasov/block_04_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__mcmc/block_03_fig_01.png b/docs/source/_static/auto/algorithms__mcmc/block_03_fig_01.png new file mode 100644 index 0000000..03b830f Binary files /dev/null and b/docs/source/_static/auto/algorithms__mcmc/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__optimal_control/block_03_fig_01.png b/docs/source/_static/auto/algorithms__optimal_control/block_03_fig_01.png new file mode 100644 index 0000000..c321410 Binary files /dev/null and b/docs/source/_static/auto/algorithms__optimal_control/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__optimal_control/block_06_fig_01.png b/docs/source/_static/auto/algorithms__optimal_control/block_06_fig_01.png new file mode 100644 index 0000000..6ba568f Binary files /dev/null and b/docs/source/_static/auto/algorithms__optimal_control/block_06_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__pde/block_03_fig_01.png b/docs/source/_static/auto/algorithms__pde/block_03_fig_01.png new file mode 100644 index 0000000..4bdd9d8 Binary files /dev/null and b/docs/source/_static/auto/algorithms__pde/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__pde/block_05_fig_01.png b/docs/source/_static/auto/algorithms__pde/block_05_fig_01.png new file mode 100644 index 0000000..ab30ce8 Binary files /dev/null and b/docs/source/_static/auto/algorithms__pde/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__pde/block_07_fig_01.png b/docs/source/_static/auto/algorithms__pde/block_07_fig_01.png new file mode 100644 index 0000000..8bc4d62 Binary files /dev/null and b/docs/source/_static/auto/algorithms__pde/block_07_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__point_processes/block_03_fig_01.png b/docs/source/_static/auto/algorithms__point_processes/block_03_fig_01.png new file mode 100644 index 0000000..d267efe Binary files /dev/null and b/docs/source/_static/auto/algorithms__point_processes/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__point_processes/block_05_fig_01.png b/docs/source/_static/auto/algorithms__point_processes/block_05_fig_01.png new file mode 100644 index 0000000..db14598 Binary files /dev/null and b/docs/source/_static/auto/algorithms__point_processes/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__quadratic_impact_control/block_03_fig_01.png b/docs/source/_static/auto/algorithms__quadratic_impact_control/block_03_fig_01.png new file mode 100644 index 0000000..300c7e5 Binary files /dev/null and b/docs/source/_static/auto/algorithms__quadratic_impact_control/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__quadratic_impact_control/block_04_fig_01.png b/docs/source/_static/auto/algorithms__quadratic_impact_control/block_04_fig_01.png new file mode 100644 index 0000000..c9ee83b Binary files /dev/null and b/docs/source/_static/auto/algorithms__quadratic_impact_control/block_04_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__robust_drift/block_03_fig_01.png b/docs/source/_static/auto/algorithms__robust_drift/block_03_fig_01.png new file mode 100644 index 0000000..f2f4b74 Binary files /dev/null and b/docs/source/_static/auto/algorithms__robust_drift/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__robust_drift/block_05_fig_01.png b/docs/source/_static/auto/algorithms__robust_drift/block_05_fig_01.png new file mode 100644 index 0000000..8b05312 Binary files /dev/null and b/docs/source/_static/auto/algorithms__robust_drift/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__stochastic_control/block_03_fig_01.png b/docs/source/_static/auto/algorithms__stochastic_control/block_03_fig_01.png new file mode 100644 index 0000000..21a652e Binary files /dev/null and b/docs/source/_static/auto/algorithms__stochastic_control/block_03_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__stochastic_control/block_05_fig_01.png b/docs/source/_static/auto/algorithms__stochastic_control/block_05_fig_01.png new file mode 100644 index 0000000..ce9cf39 Binary files /dev/null and b/docs/source/_static/auto/algorithms__stochastic_control/block_05_fig_01.png differ diff --git a/docs/source/_static/auto/algorithms__stochastic_control/block_06_fig_01.png b/docs/source/_static/auto/algorithms__stochastic_control/block_06_fig_01.png new file mode 100644 index 0000000..b97dfff Binary files /dev/null and b/docs/source/_static/auto/algorithms__stochastic_control/block_06_fig_01.png differ diff --git a/docs/source/_static/auto/api__point_processes/block_04_fig_01.png b/docs/source/_static/auto/api__point_processes/block_04_fig_01.png new file mode 100644 index 0000000..84009cc Binary files /dev/null and b/docs/source/_static/auto/api__point_processes/block_04_fig_01.png differ diff --git a/docs/source/_static/auto/api__point_processes/block_09_fig_01.png b/docs/source/_static/auto/api__point_processes/block_09_fig_01.png new file mode 100644 index 0000000..d517282 Binary files /dev/null and b/docs/source/_static/auto/api__point_processes/block_09_fig_01.png differ diff --git a/docs/source/algorithms/agent_based.rst b/docs/source/algorithms/agent_based.rst index ebf4db7..4b8a1e1 100644 --- a/docs/source/algorithms/agent_based.rst +++ b/docs/source/algorithms/agent_based.rst @@ -40,6 +40,16 @@ Generic interacting-agent simulator (`consensus_dynamics`) — linear bounded-co ax.set_title('Bounded-confidence consensus, α = 0.3') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__agent_based/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/agent_based/plot_01.png :align: center :width: 80% @@ -56,6 +66,16 @@ Generic interacting-agent simulator (`consensus_dynamics`) — linear bounded-co ax.set_title('Convergence rate vs averaging weight α'); ax.legend(); ax.grid(alpha=0.3) fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__agent_based/block_04_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/agent_based/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/bsde.rst b/docs/source/algorithms/bsde.rst index 10aba0b..bdffae7 100644 --- a/docs/source/algorithms/bsde.rst +++ b/docs/source/algorithms/bsde.rst @@ -48,6 +48,16 @@ With $a(t) \equiv -\rho$, $b = c = 0$ and $Y_T = 1$ the analytic deterministic s ax.legend(); ax.grid(alpha=0.3) fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__bsde/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/bsde/plot_01.png :align: center :width: 80% @@ -76,6 +86,16 @@ Crank–Nicolson is second-order in `Δt`. ax.set_title('Crank–Nicolson convergence'); ax.grid(which='both', alpha=0.3); ax.legend() fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__bsde/block_05_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/bsde/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/generative_calibration_hooks.rst b/docs/source/algorithms/generative_calibration_hooks.rst index 71e9719..90dc110 100644 --- a/docs/source/algorithms/generative_calibration_hooks.rst +++ b/docs/source/algorithms/generative_calibration_hooks.rst @@ -32,6 +32,16 @@ Maximum-Mean-Discrepancy distance with Gaussian kernel (`mmd_gaussian`). Self-d ax.set_title('Gaussian-kernel MMD vs translation (σ = 1)') ax.grid(alpha=0.3); fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__generative_calibration_hooks/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/generative_calibration_hooks/plot_01.png :align: center :width: 80% @@ -49,6 +59,16 @@ Bandwidth dependence ax.set_title('MMD as a function of kernel bandwidth') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__generative_calibration_hooks/block_04_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/generative_calibration_hooks/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/grid_search.md b/docs/source/algorithms/grid_search.md index e9985b3..638e906 100644 --- a/docs/source/algorithms/grid_search.md +++ b/docs/source/algorithms/grid_search.md @@ -305,6 +305,13 @@ plt.ylabel('Learning Rate') plt.title('Grid Search: Loss Surface') plt.savefig('grid_search_heatmap.png', dpi=150) ``` + +![Generated plot](../_static/auto/algorithms__grid_search/block_08_fig_01.png) + + + + + **Output:** diff --git a/docs/source/algorithms/hmm.md b/docs/source/algorithms/hmm.md index aa8fdae..3d7aee3 100644 --- a/docs/source/algorithms/hmm.md +++ b/docs/source/algorithms/hmm.md @@ -447,6 +447,13 @@ axes[1].set_yticklabels(['Bull', 'Bear']) plt.tight_layout() plt.savefig('hmm_states.png', dpi=150) ``` + +![Generated plot](../_static/auto/algorithms__hmm/block_05_fig_01.png) + + + + + **Output:** diff --git a/docs/source/algorithms/mckean_vlasov.rst b/docs/source/algorithms/mckean_vlasov.rst index 9520da5..2930956 100644 --- a/docs/source/algorithms/mckean_vlasov.rst +++ b/docs/source/algorithms/mckean_vlasov.rst @@ -41,6 +41,16 @@ Interacting-particle Euler scheme for $dX_t = θ(\bar X_t - X_t) dt + σ dW_t$ ( ax.set_title('Mean-reverting McKean–Vlasov — 200 particles') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__mckean_vlasov/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/mckean_vlasov/plot_01.png :align: center :width: 80% @@ -54,6 +64,16 @@ Interacting-particle Euler scheme for $dX_t = θ(\bar X_t - X_t) dt + σ dW_t$ ( ax.set_title('Marginal density at t = 0 and t = T') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__mckean_vlasov/block_04_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/mckean_vlasov/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/mcmc.md b/docs/source/algorithms/mcmc.md index 59becdc..e136758 100644 --- a/docs/source/algorithms/mcmc.md +++ b/docs/source/algorithms/mcmc.md @@ -295,6 +295,13 @@ axes[1, 1].set_title('Posterior: σ') plt.tight_layout() plt.savefig('mcmc_posterior.png', dpi=150) ``` + +![Generated plot](../_static/auto/algorithms__mcmc/block_03_fig_01.png) + + + + + --- diff --git a/docs/source/algorithms/optimal_control.md b/docs/source/algorithms/optimal_control.md index 60c21b8..bc85f5b 100644 --- a/docs/source/algorithms/optimal_control.md +++ b/docs/source/algorithms/optimal_control.md @@ -261,15 +261,17 @@ Estimates Ornstein-Uhlenbeck process parameters from time series data. ```python from optimizr import estimate_ou_params_py import numpy as np +import matplotlib.pyplot as plt # Simulate OU process (for testing) dt = 1/252 # Daily data T = 1000 kappa_true, theta_true, sigma_true = 3.0, 0.0, 0.2 +rng = np.random.default_rng(0) spread = [0.0] for _ in range(T-1): dx = kappa_true * (theta_true - spread[-1]) * dt + \ - sigma_true * np.sqrt(dt) * np.random.randn() + sigma_true * np.sqrt(dt) * rng.standard_normal() spread.append(spread[-1] + dx) spread = np.array(spread) @@ -280,7 +282,36 @@ kappa, theta, sigma, half_life = estimate_ou_params_py(spread, dt=dt) print(f"True: κ={kappa_true:.2f}, θ={theta_true:.3f}, σ={sigma_true:.3f}") print(f"Estimated: κ={kappa:.2f}, θ={theta:.3f}, σ={sigma:.3f}") print(f"Half-life: {half_life:.1f} periods ({half_life*252:.1f} days)") + +# Visualise the simulated path together with the estimated mean-reversion +# level and the decay envelope implied by the fitted half-life. +t_axis = np.arange(len(spread)) * dt * 252 # in days +fig, axes = plt.subplots(1, 2, figsize=(11, 4)) +axes[0].plot(t_axis, spread, lw=0.7, label="simulated path") +axes[0].axhline(theta_true, color="k", ls=":", label="true θ") +axes[0].axhline(theta, color="red", ls="--", label="estimated θ") +axes[0].set_xlabel("days"); axes[0].set_ylabel("spread") +axes[0].set_title("OU simulation vs estimated long-run mean") +axes[0].legend(); axes[0].grid(alpha=0.3) + +# Empirical autocorrelation vs theoretical exp(-κ τ). +lags = np.arange(0, 60) +x = spread - spread.mean() +acf = np.array([ + (x[: len(x) - k] @ x[k:]) / (x @ x) for k in lags +]) +axes[1].plot(lags, acf, "o-", label="empirical ACF") +axes[1].plot(lags, np.exp(-kappa * lags * dt), "--", + label=r"theoretical $e^{-\kappa\,\tau}$") +axes[1].set_xlabel("lag (days)"); axes[1].set_ylabel("autocorrelation") +axes[1].set_title("Mean-reversion fingerprint") +axes[1].legend(); axes[1].grid(alpha=0.3) +fig.tight_layout(); plt.show() ``` + +![Generated plot](../_static/auto/algorithms__optimal_control/block_03_fig_01.png) + + **Method**: Maximum likelihood estimation (MLE) using analytical formulas for discrete-time OU process. @@ -431,6 +462,10 @@ plt.plot(pnl_path, label='P&L') plt.legend() plt.tight_layout() ``` + +![Generated plot](../_static/auto/algorithms__optimal_control/block_06_fig_01.png) + + **Metrics interpretation**: - `total_return`: Should be positive with low transaction costs diff --git a/docs/source/algorithms/pde.rst b/docs/source/algorithms/pde.rst index 90ec597..6c8dd72 100644 --- a/docs/source/algorithms/pde.rst +++ b/docs/source/algorithms/pde.rst @@ -47,6 +47,16 @@ $\partial_t m = \tfrac12 \partial_{xx} m$ with Gaussian initial density should r ax.set_title('Pure-diffusion Fokker–Planck'); ax.grid(alpha=0.3); ax.legend() fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__pde/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/pde/plot_01.png :align: center :width: 80% @@ -78,6 +88,16 @@ $-\Delta u = 2\pi^2 \sin(\pi x)\sin(\pi y)$ on the unit square with zero Dirichl axes[1].set_title('error vs analytic'); plt.colorbar(im1, ax=axes[1]) fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__pde/block_05_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/pde/plot_02.png :align: center :width: 80% @@ -104,6 +124,16 @@ Heat-only relaxation ($H = 0$, σ² > 0) preserves a constant value, while a qua plt.colorbar(im, ax=ax) fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__pde/block_07_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/pde/plot_03.png :align: center :width: 80% diff --git a/docs/source/algorithms/point_processes.md b/docs/source/algorithms/point_processes.md index 43e475d..99f88b0 100644 --- a/docs/source/algorithms/point_processes.md +++ b/docs/source/algorithms/point_processes.md @@ -451,6 +451,10 @@ plt.suptitle("Fractional Brownian Motion Paths") plt.tight_layout() plt.show() ``` + +![Generated plot](../_static/auto/algorithms__point_processes/block_03_fig_01.png) + + ### Mixed fBM for Aggregate Order Flow @@ -518,6 +522,10 @@ plt.title('Power-law decay in scaling limit') plt.tight_layout() plt.show() ``` + +![Generated plot](../_static/auto/algorithms__point_processes/block_05_fig_01.png) + + --- diff --git a/docs/source/algorithms/quadratic_impact_control.rst b/docs/source/algorithms/quadratic_impact_control.rst index b2c0263..199a67a 100644 --- a/docs/source/algorithms/quadratic_impact_control.rst +++ b/docs/source/algorithms/quadratic_impact_control.rst @@ -41,6 +41,16 @@ $h'(t) = h(t)^2/γ - φ$ with $h(T) = A$. When $γ = φ = A = 1$ the right-hand ax.set_title('Riccati fixed point γ=φ=A=1') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__quadratic_impact_control/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/quadratic_impact_control/plot_01.png :align: center :width: 80% @@ -60,6 +70,16 @@ Vary $A$, fix $γ = 1$, $φ = 0.25$, $T = 1$. ax.set_title('Riccati sensitivity to terminal weight') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__quadratic_impact_control/block_04_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/quadratic_impact_control/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/robust_drift.rst b/docs/source/algorithms/robust_drift.rst index e19adfb..24218f0 100644 --- a/docs/source/algorithms/robust_drift.rst +++ b/docs/source/algorithms/robust_drift.rst @@ -43,6 +43,16 @@ Synthetic stationary process with 5 % outliers ax.set_title('Synthetic series with heavy-tailed innovations') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__robust_drift/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/robust_drift/plot_01.png :align: center :width: 80% @@ -71,6 +81,16 @@ Synthetic stationary process with 5 % outliers ax.legend(); ax.grid(alpha=0.3); ax.set_title('Robust vs OLS drift estimate') fig.tight_layout(); plt.show() + + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__robust_drift/block_05_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/robust_drift/plot_02.png :align: center :width: 80% diff --git a/docs/source/algorithms/stochastic_control.rst b/docs/source/algorithms/stochastic_control.rst index 17445d2..0791256 100644 --- a/docs/source/algorithms/stochastic_control.rst +++ b/docs/source/algorithms/stochastic_control.rst @@ -43,6 +43,15 @@ Two modes; only mode 1 pays a unit reward. Free switching should give `V_0(0) = ax.set_title('Snell envelope — free switching') fig.tight_layout(); plt.show() + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__stochastic_control/block_03_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/stochastic_control/plot_01.png :align: center :width: 80% @@ -73,6 +82,15 @@ Closed-form Riccati for $a=q=0$, $b=r=s_T=1$, $T=1$ is $P(t) = 1/(1 + (T - t))$, axes[2].plot(tg[:-1], u); axes[2].set_title('feedback u(t) = -(b/r) P(t) x(t)'); axes[2].set_xlabel('t'); axes[2].grid(alpha=0.3) fig.tight_layout(); plt.show() + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__stochastic_control/block_05_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/stochastic_control/plot_02.png :align: center :width: 80% @@ -96,6 +114,15 @@ Affine premium $δ_±(λ) = α_± + κ_± λ$. First-order condition: $\lambda^ ax.set_title('Optimal upward intensity vs value-function gradient') ax.grid(alpha=0.3); fig.tight_layout(); plt.show() + + + +.. AUTO-PLOT-BEGIN +.. image:: ../_static/auto/algorithms__stochastic_control/block_06_fig_01.png + :align: center + :width: 80% + +.. AUTO-PLOT-END .. image:: ../_static/v2/stochastic_control/plot_03.png :align: center :width: 80% diff --git a/docs/source/api/point_processes.md b/docs/source/api/point_processes.md index 984ae4c..31a3018 100644 --- a/docs/source/api/point_processes.md +++ b/docs/source/api/point_processes.md @@ -207,6 +207,10 @@ plt.suptitle("Fractional Brownian Motion: Three Regimes") plt.tight_layout() plt.show() ``` + +![Generated plot](../_static/auto/api__point_processes/block_04_fig_01.png) + + --- @@ -401,6 +405,10 @@ plt.title('Scaling Function (Theorem 3.1)') plt.grid(True, alpha=0.3) plt.show() ``` + +![Generated plot](../_static/auto/api__point_processes/block_09_fig_01.png) + + --- diff --git a/python/optimizr/__init__.py b/python/optimizr/__init__.py index 0f12426..fde4892 100644 --- a/python/optimizr/__init__.py +++ b/python/optimizr/__init__.py @@ -103,3 +103,20 @@ __all__ = [ "min_variance_weights", "erc_weights", ] + + +def __getattr__(name): + """Transparent fallback: forward any unresolved top-level attribute access + to the compiled `_core` extension. This keeps `from optimizr import X` + working for every Rust-backed function (v1.x and v2.0 primitives) without + having to enumerate the full list above.""" + try: + from optimizr import _core as _ext + except ImportError as exc: # pragma: no cover + raise AttributeError( + f"module 'optimizr' has no attribute {name!r} " + f"(_core extension is not built: {exc})" + ) from exc + if hasattr(_ext, name): + return getattr(_ext, name) + raise AttributeError(f"module 'optimizr' has no attribute {name!r}") diff --git a/scripts/inject_doc_plots.py b/scripts/inject_doc_plots.py new file mode 100644 index 0000000..ea91913 --- /dev/null +++ b/scripts/inject_doc_plots.py @@ -0,0 +1,270 @@ +"""Inject matplotlib plots inline below every executable Python code-block +across the entire `docs/source/` tree. + +For each `.md` and `.rst` file: + +1. Parse out the Python code-blocks (fenced ```python``` for Markdown, + `.. code-block:: python` for reStructuredText). +2. Execute each block in its own namespace with a non-interactive matplotlib + backend. Every figure produced by the block is saved as a PNG under + `docs/source/_static/auto//.png`. +3. Rewrite the page so that, immediately after the executable code-block, a + markdown image (`![caption](path)`) or RST `.. image::` directive points + to the captured PNG. +4. Blocks that fail to execute (missing imports, illustrative pseudo-code, + external data dependencies …) are left untouched — no image is inserted + and the existing source is preserved. + +Idempotent: previously injected image directives are detected and refreshed +in place rather than duplicated. + +The script is purely additive on the v2.0.0 branch — it touches no Rust or +binding code, only the doc tree and the auto-generated `_static/auto` +folder. +""" +from __future__ import annotations + +import io +import os +import re +import sys +import traceback +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 + +ROOT = Path(__file__).resolve().parent.parent +DOC_SRC = ROOT / "docs" / "source" +STATIC_DIR = DOC_SRC / "_static" / "auto" +SKIP_DIRS = {"_static", "_build", "_templates"} + +# A small allow-list of pages we KNOW exercise the bindings in a way that is +# self-contained. Every other page is still scanned, but if a code block +# fails we silently leave it alone. + +# Auto-injected marker so that re-runs are idempotent. +MD_BEGIN = "" +MD_END = "" +RST_BEGIN = ".. AUTO-PLOT-BEGIN" +RST_END = ".. AUTO-PLOT-END" + + +# --------------------------------------------------------------------------- + +def slugify(path: Path) -> str: + rel = path.relative_to(DOC_SRC).with_suffix("") + return str(rel).replace(os.sep, "__") + + +def execute_block(code: str, page_ns: dict) -> list[Path] | None: + """Run `code` in `page_ns`. Return a list of PNG paths for the figures + it created, or `None` if the block raised.""" + plt.close("all") + buf_out, buf_err = io.StringIO(), io.StringIO() + try: + with redirect_stdout(buf_out), redirect_stderr(buf_err): + exec(compile(code, "", "exec"), page_ns) + except BaseException: # noqa: BLE001 — also catch pyo3 PanicException + return None + return list(plt.get_fignums()) + + +def save_figs(page_slug: str, block_idx: int) -> list[str]: + out_dir = STATIC_DIR / page_slug + out_dir.mkdir(parents=True, exist_ok=True) + written: list[str] = [] + for i, num in enumerate(plt.get_fignums(), start=1): + fig = plt.figure(num) + fname = f"block_{block_idx:02d}_fig_{i:02d}.png" + fpath = out_dir / fname + fig.savefig(fpath, bbox_inches="tight", dpi=110) + written.append(fpath.as_posix()) + plt.close("all") + return written + + +def relative_to_doc(target: Path, page_path: Path) -> str: + return os.path.relpath(target, page_path.parent).replace(os.sep, "/") + + +# --------------------------------------------------------------------------- +# Markdown +# --------------------------------------------------------------------------- + +MD_BLOCK_RE = re.compile( + r"(?P```(?:python|py)\s*\n)(?P.*?)(?P```)", + re.DOTALL, +) +MD_AUTO_BLOCK_RE = re.compile( + re.escape(MD_BEGIN) + r".*?" + re.escape(MD_END) + r"\n?", + re.DOTALL, +) + + +def process_markdown(path: Path) -> bool: + text = path.read_text() + # Strip previously-injected auto blocks so we re-run from a clean slate. + text = MD_AUTO_BLOCK_RE.sub("", text) + + page_slug = slugify(path) + page_ns: dict = {"__name__": "__doc__"} + out_chunks: list[str] = [] + last = 0 + block_idx = 0 + n_injected = 0 + + for m in MD_BLOCK_RE.finditer(text): + block_idx += 1 + out_chunks.append(text[last:m.end()]) + last = m.end() + + code = m.group("body") + nums = execute_block(code, page_ns) + if not nums: + continue + pngs = save_figs(page_slug, block_idx) + if not pngs: + continue + + block = ["", MD_BEGIN] + for png in pngs: + rel = relative_to_doc(Path(png), path) + block.append(f"![Generated plot]({rel})") + block.append(MD_END) + block.append("") + out_chunks.append("\n".join(block)) + n_injected += len(pngs) + + out_chunks.append(text[last:]) + new_text = "".join(out_chunks) + if new_text != path.read_text(): + path.write_text(new_text) + return n_injected > 0 + + +# --------------------------------------------------------------------------- +# reStructuredText +# --------------------------------------------------------------------------- + +RST_BLOCK_RE = re.compile( + r"(?P[ \t]*)\.\. code-block::[ \t]+python\s*\n" + r"(?:[ \t]*:.*\n)*" + r"\n?" + r"(?P(?:(?:\1[ \t]+.*|[ \t]*)\n)+)", +) +RST_AUTO_BLOCK_RE = re.compile( + re.escape(RST_BEGIN) + r".*?" + re.escape(RST_END) + r"\n?", + re.DOTALL, +) + + +def dedent_rst_body(body: str, indent: str) -> str: + inner_indent = indent + " " + out = [] + for line in body.splitlines(): + if line.startswith(inner_indent): + out.append(line[len(inner_indent):]) + elif line.strip() == "": + out.append("") + else: + # End of code-block (different indentation): stop early. + break + return "\n".join(out) + + +def process_rst(path: Path) -> bool: + text = path.read_text() + text = RST_AUTO_BLOCK_RE.sub("", text) + + page_slug = slugify(path) + page_ns: dict = {"__name__": "__doc__"} + out_chunks: list[str] = [] + last = 0 + block_idx = 0 + n_injected = 0 + + for m in RST_BLOCK_RE.finditer(text): + block_idx += 1 + out_chunks.append(text[last:m.end()]) + last = m.end() + + indent = m.group("indent") + code = dedent_rst_body(m.group("body"), indent) + nums = execute_block(code, page_ns) + if not nums: + continue + pngs = save_figs(page_slug, block_idx) + if not pngs: + continue + + lines = ["", f"{indent}{RST_BEGIN}"] + for png in pngs: + rel = relative_to_doc(Path(png), path) + lines.append(f"{indent}.. image:: {rel}") + lines.append(f"{indent} :align: center") + lines.append(f"{indent} :width: 80%") + lines.append("") + lines.append(f"{indent}{RST_END}") + lines.append("") + out_chunks.append("\n".join(lines)) + n_injected += len(pngs) + + out_chunks.append(text[last:]) + new_text = "".join(out_chunks) + if new_text != path.read_text(): + path.write_text(new_text) + return n_injected > 0 + + +# --------------------------------------------------------------------------- + +def iter_doc_pages(): + for dirpath, dirnames, filenames in os.walk(DOC_SRC): + # Prune + rel = Path(dirpath).relative_to(DOC_SRC) + parts = set(rel.parts) + if parts & SKIP_DIRS: + dirnames.clear() + continue + for fn in filenames: + if fn.endswith((".md", ".rst")) and not fn.endswith(".backup"): + yield Path(dirpath) / fn + + +def main() -> None: + STATIC_DIR.mkdir(parents=True, exist_ok=True) + n_pages = 0 + n_with_plots = 0 + total_imgs = 0 + for page in sorted(iter_doc_pages()): + n_pages += 1 + try: + if page.suffix == ".md": + injected = process_markdown(page) + else: + injected = process_rst(page) + except Exception: # noqa: BLE001 + print(f"!! {page.relative_to(DOC_SRC)} crashed:") + traceback.print_exc() + continue + if injected: + n_with_plots += 1 + # Count images in _static/auto/ + img_dir = STATIC_DIR / slugify(page) + if img_dir.exists(): + n_imgs = len(list(img_dir.glob("*.png"))) + total_imgs += n_imgs + print(f" {page.relative_to(DOC_SRC)} -> {n_imgs} plot(s)") + else: + print(f" {page.relative_to(DOC_SRC)} -> no plots") + print() + print(f"Scanned {n_pages} pages, {n_with_plots} got new images, " + f"{total_imgs} PNGs total.") + + +if __name__ == "__main__": + main()