docs(v2.0.0-alpha.3): inline plot injection across the entire doc tree
Add scripts/inject_doc_plots.py that scans every .md and .rst page under docs/source/, executes each Python code-block in an isolated namespace with a non-interactive matplotlib backend, captures every figure produced, and inserts an inline image directive immediately after the code-block. Markers AUTO-PLOT-BEGIN/END make the injection idempotent on re-runs. Blocks that fail to execute or produce no figure are left untouched. Add a transparent __getattr__ fallback in python/optimizr/__init__.py that forwards any unresolved top-level attribute to the compiled _core extension. This lets all v1.x and v2.0 doc samples that use 'from optimizr import X' (estimate_ou_params_py, linear_bsde_constant_coeffs, mmd_gaussian, ...) execute as written. Augment the OU Parameter Estimation example (docs/source/algorithms/optimal_control.md) with a two-panel visualization (simulated path plus empirical/theoretical autocorrelation). Net effect: 14 doc pages now display matplotlib plots inline directly under the code that produced them -- including the OU page, point processes, Grid Search, HMM, MCMC, plus the 8 v2.0 RST pages.
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -40,6 +40,16 @@ Generic interacting-agent simulator (`consensus_dynamics`) — linear bounded-co
|
|||||||
ax.set_title('Bounded-confidence consensus, α = 0.3')
|
ax.set_title('Bounded-confidence consensus, α = 0.3')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/agent_based/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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)
|
ax.set_title('Convergence rate vs averaging weight α'); ax.legend(); ax.grid(alpha=0.3)
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/agent_based/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -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)
|
ax.legend(); ax.grid(alpha=0.3)
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/bsde/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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()
|
ax.set_title('Crank–Nicolson convergence'); ax.grid(which='both', alpha=0.3); ax.legend()
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/bsde/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -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.set_title('Gaussian-kernel MMD vs translation (σ = 1)')
|
||||||
ax.grid(alpha=0.3); fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/generative_calibration_hooks/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
@@ -49,6 +59,16 @@ Bandwidth dependence
|
|||||||
ax.set_title('MMD as a function of kernel bandwidth')
|
ax.set_title('MMD as a function of kernel bandwidth')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/generative_calibration_hooks/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -305,6 +305,13 @@ plt.ylabel('Learning Rate')
|
|||||||
plt.title('Grid Search: Loss Surface')
|
plt.title('Grid Search: Loss Surface')
|
||||||
plt.savefig('grid_search_heatmap.png', dpi=150)
|
plt.savefig('grid_search_heatmap.png', dpi=150)
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Output:**
|
**Output:**
|
||||||
|
|
||||||
|
|||||||
@@ -447,6 +447,13 @@ axes[1].set_yticklabels(['Bull', 'Bear'])
|
|||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.savefig('hmm_states.png', dpi=150)
|
plt.savefig('hmm_states.png', dpi=150)
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Output:**
|
**Output:**
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
ax.set_title('Mean-reverting McKean–Vlasov — 200 particles')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/mckean_vlasov/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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')
|
ax.set_title('Marginal density at t = 0 and t = T')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/mckean_vlasov/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -295,6 +295,13 @@ axes[1, 1].set_title('Posterior: σ')
|
|||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.savefig('mcmc_posterior.png', dpi=150)
|
plt.savefig('mcmc_posterior.png', dpi=150)
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -261,15 +261,17 @@ Estimates Ornstein-Uhlenbeck process parameters from time series data.
|
|||||||
```python
|
```python
|
||||||
from optimizr import estimate_ou_params_py
|
from optimizr import estimate_ou_params_py
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
# Simulate OU process (for testing)
|
# Simulate OU process (for testing)
|
||||||
dt = 1/252 # Daily data
|
dt = 1/252 # Daily data
|
||||||
T = 1000
|
T = 1000
|
||||||
kappa_true, theta_true, sigma_true = 3.0, 0.0, 0.2
|
kappa_true, theta_true, sigma_true = 3.0, 0.0, 0.2
|
||||||
|
rng = np.random.default_rng(0)
|
||||||
spread = [0.0]
|
spread = [0.0]
|
||||||
for _ in range(T-1):
|
for _ in range(T-1):
|
||||||
dx = kappa_true * (theta_true - spread[-1]) * dt + \
|
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.append(spread[-1] + dx)
|
||||||
|
|
||||||
spread = np.array(spread)
|
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"True: κ={kappa_true:.2f}, θ={theta_true:.3f}, σ={sigma_true:.3f}")
|
||||||
print(f"Estimated: κ={kappa:.2f}, θ={theta:.3f}, σ={sigma:.3f}")
|
print(f"Estimated: κ={kappa:.2f}, θ={theta:.3f}, σ={sigma:.3f}")
|
||||||
print(f"Half-life: {half_life:.1f} periods ({half_life*252:.1f} days)")
|
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()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
**Method**: Maximum likelihood estimation (MLE) using analytical formulas for discrete-time OU process.
|
**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.legend()
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
**Metrics interpretation**:
|
**Metrics interpretation**:
|
||||||
- `total_return`: Should be positive with low transaction costs
|
- `total_return`: Should be positive with low transaction costs
|
||||||
|
|||||||
@@ -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()
|
ax.set_title('Pure-diffusion Fokker–Planck'); ax.grid(alpha=0.3); ax.legend()
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/pde/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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])
|
axes[1].set_title('error vs analytic'); plt.colorbar(im1, ax=axes[1])
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/pde/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
@@ -104,6 +124,16 @@ Heat-only relaxation ($H = 0$, σ² > 0) preserves a constant value, while a qua
|
|||||||
plt.colorbar(im, ax=ax)
|
plt.colorbar(im, ax=ax)
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/pde/plot_03.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -451,6 +451,10 @@ plt.suptitle("Fractional Brownian Motion Paths")
|
|||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.show()
|
plt.show()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
### Mixed fBM for Aggregate Order Flow
|
### Mixed fBM for Aggregate Order Flow
|
||||||
|
|
||||||
@@ -518,6 +522,10 @@ plt.title('Power-law decay in scaling limit')
|
|||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.show()
|
plt.show()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
ax.set_title('Riccati fixed point γ=φ=A=1')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/quadratic_impact_control/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
@@ -60,6 +70,16 @@ Vary $A$, fix $γ = 1$, $φ = 0.25$, $T = 1$.
|
|||||||
ax.set_title('Riccati sensitivity to terminal weight')
|
ax.set_title('Riccati sensitivity to terminal weight')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/quadratic_impact_control/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ Synthetic stationary process with 5 % outliers
|
|||||||
ax.set_title('Synthetic series with heavy-tailed innovations')
|
ax.set_title('Synthetic series with heavy-tailed innovations')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/robust_drift/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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')
|
ax.legend(); ax.grid(alpha=0.3); ax.set_title('Robust vs OLS drift estimate')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/robust_drift/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -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')
|
ax.set_title('Snell envelope — free switching')
|
||||||
fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/stochastic_control/plot_01.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
: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)
|
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()
|
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
|
.. image:: ../_static/v2/stochastic_control/plot_02.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
@@ -96,6 +114,15 @@ Affine premium $δ_±(λ) = α_± + κ_± λ$. First-order condition: $\lambda^
|
|||||||
ax.set_title('Optimal upward intensity vs value-function gradient')
|
ax.set_title('Optimal upward intensity vs value-function gradient')
|
||||||
ax.grid(alpha=0.3); fig.tight_layout(); plt.show()
|
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
|
.. image:: ../_static/v2/stochastic_control/plot_03.png
|
||||||
:align: center
|
:align: center
|
||||||
:width: 80%
|
:width: 80%
|
||||||
|
|||||||
@@ -207,6 +207,10 @@ plt.suptitle("Fractional Brownian Motion: Three Regimes")
|
|||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.show()
|
plt.show()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -401,6 +405,10 @@ plt.title('Scaling Function (Theorem 3.1)')
|
|||||||
plt.grid(True, alpha=0.3)
|
plt.grid(True, alpha=0.3)
|
||||||
plt.show()
|
plt.show()
|
||||||
```
|
```
|
||||||
|
<!-- AUTO-PLOT-BEGIN -->
|
||||||
|

|
||||||
|
<!-- AUTO-PLOT-END -->
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,20 @@ __all__ = [
|
|||||||
"min_variance_weights",
|
"min_variance_weights",
|
||||||
"erc_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}")
|
||||||
|
|||||||
@@ -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/<page-slug>/<idx>.png`.
|
||||||
|
3. Rewrite the page so that, immediately after the executable code-block, a
|
||||||
|
markdown image (``) 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 = "<!-- AUTO-PLOT-BEGIN -->"
|
||||||
|
MD_END = "<!-- AUTO-PLOT-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, "<doc-block>", "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<head>```(?:python|py)\s*\n)(?P<body>.*?)(?P<tail>```)",
|
||||||
|
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"")
|
||||||
|
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<indent>[ \t]*)\.\. code-block::[ \t]+python\s*\n"
|
||||||
|
r"(?:[ \t]*:.*\n)*"
|
||||||
|
r"\n?"
|
||||||
|
r"(?P<body>(?:(?:\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/<page_slug>
|
||||||
|
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()
|
||||||