Adds Python bindings (behind feature='python-bindings') for graph,
risk_measures, topology, volterra, signatures.
Companion notebooks under examples/notebooks/:
- 05_graph.ipynb (Laplacians + spectral clustering)
- 06_risk_measures.ipynb (VaR / CVaR + simplex projection)
- 07_topology.ipynb (Vietoris-Rips + persistent homology)
- 08_volterra.ipynb (fractional ODE, Markovian lift, Volterra,
Fourier inversion)
- 09_signatures.ipynb (path / log / random / kernel signatures)
All notebooks executed end-to-end against analytic ground truth
(closed-form solutions, Mittag-Leffler, exp(-t), unit-circle homology,
identical-path signature kernel).
Built and validated via: maturin develop --release --features python-bindings.
Workflow generated by 5 parallel optimizRs subagents (.github/agents/).
324 KiB
324 KiB
In [1]:
import numpy as np
import matplotlib.pyplot as plt
from optimizr import _core as opt
from scipy.special import gamma as Gamma
rng = np.random.default_rng(0)
errors = {}In [2]:
def mittag_leffler(alpha, z, n_terms=200):
z = np.asarray(z, dtype=float)
out = np.zeros_like(z)
term = np.ones_like(z)
for k in range(n_terms):
out = out + term / Gamma(alpha * k + 1.0)
term = term * z
return out
T, N = 2.0, 800
alphas = [0.3, 0.5, 0.7, 0.9]
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
max_err = 0.0
for a in alphas:
res = opt.solve_fractional_ode(1.0, a, T, N, lambda t, h: -h)
t = np.asarray(res["t_grid"]) ; h_num = np.asarray(res["h"])
h_exact = mittag_leffler(a, -t**a)
err = np.max(np.abs(h_num - h_exact))
max_err = max(max_err, err)
ax[0].plot(t, h_num, label=f"alpha={a} num")
ax[0].plot(t, h_exact, '--', alpha=0.6, label=f"alpha={a} exact")
ax[1].semilogy(t[1:], np.abs(h_num - h_exact)[1:], label=f"alpha={a}")
ax[0].set_xlabel("t"); ax[0].set_ylabel("h(t)"); ax[0].legend(fontsize=7); ax[0].set_title("D^a h = -h, h(0)=1")
ax[1].set_xlabel("t"); ax[1].set_ylabel("|h_num - E_a(-t^a)|"); ax[1].legend(fontsize=7); ax[1].set_title("pointwise error (log)")
plt.tight_layout(); plt.show()
errors['solve_fractional_ode'] = max_err
assert max_err < 5e-2, f"max err = {max_err}"
print(f"max error vs Mittag-Leffler = {max_err:.3e}")max error vs Mittag-Leffler = 5.625e-04
In [3]:
H = 0.1
rough_kernel = lambda t: t ** (H - 0.5) / Gamma(H + 0.5)
t_samples = np.geomspace(1e-3, 1.0, 200).tolist()
lift = opt.geometric_grid_lift(rough_kernel, t_samples, 12, 1e-2, 1e4, 20000)
gammas = np.asarray(lift["gammas"]) ; weights = np.asarray(lift["weights"])
t_eval = np.geomspace(1e-3, 1.0, 400)
k_target = np.array([rough_kernel(tt) for tt in t_eval])
k_lift = np.array([np.sum(weights * np.exp(-gammas * tt)) for tt in t_eval])
rel_err = np.max(np.abs(k_lift - k_target) / np.abs(k_target))
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].loglog(t_eval, k_target, label='target K(t)')
ax[0].loglog(t_eval, k_lift, '--', label='lift sum c_j exp(-g_j t)')
ax[0].set_xlabel('t'); ax[0].set_ylabel('K(t)'); ax[0].legend(); ax[0].set_title(f'Rough kernel H={H}')
ax[1].loglog(t_eval, np.abs(k_lift - k_target) / np.abs(k_target))
ax[1].set_xlabel('t'); ax[1].set_ylabel('relative error'); ax[1].set_title('lift relative error')
plt.tight_layout(); plt.show()
errors['geometric_grid_lift'] = rel_err
assert rel_err < 0.5, f'relative err = {rel_err}'
print(f'max relative error on rough kernel = {rel_err:.3e}')max relative error on rough kernel = 1.688e-02
In [4]:
T, N = 2.0, 2000
res = opt.solve_volterra(lambda t: 1.0, lambda dt, y: y, T, N, 100, 1e-13)
t = np.asarray(res["t_grid"]) ; y_num = np.asarray(res["y"])
y_exact = np.exp(t)
err = np.abs(y_num - y_exact)
max_err = float(err.max())
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].plot(t, y_num, label='numerical')
ax[0].plot(t, y_exact, '--', label='exp(t)')
ax[0].set_xlabel('t'); ax[0].set_ylabel('y(t)'); ax[0].legend(); ax[0].set_title('Volterra: y = 1 + int_0^t y(s) ds')
ax[1].semilogy(t[1:], err[1:])
ax[1].set_xlabel('t'); ax[1].set_ylabel('|y_num - exp(t)|'); ax[1].set_title('pointwise error (log)')
plt.tight_layout(); plt.show()
errors['solve_volterra'] = max_err
assert max_err < 1e-2, f'max err = {max_err}'
print(f'max error vs exp(t) = {max_err:.3e}')max error vs exp(t) = 1.232e-06
In [5]:
def phi_normal(u):
# Standard normal phi(u) = exp(-u^2 / 2), purely real.
return (float(np.exp(-0.5 * u * u)), 0.0)
x_grid = np.linspace(-5.0, 5.0, 401).tolist()
res = opt.fourier_invert(phi_normal, x_grid, 25.0, 4000)
x = np.asarray(res["x_grid"]) ; f_num = np.asarray(res["density"])
f_exact = (1.0 / np.sqrt(2.0 * np.pi)) * np.exp(-0.5 * x * x)
err = np.abs(f_num - f_exact)
max_err = float(err.max())
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].plot(x, f_num, label='Fourier inversion')
ax[0].plot(x, f_exact, '--', label='exact N(0,1)')
ax[0].set_xlabel('x'); ax[0].set_ylabel('f(x)'); ax[0].legend(); ax[0].set_title('Density recovery')
ax[1].semilogy(x, err)
ax[1].set_xlabel('x'); ax[1].set_ylabel('|f_num - f_exact|'); ax[1].set_title('pointwise error (log)')
plt.tight_layout(); plt.show()
errors['fourier_invert'] = max_err
assert max_err < 1e-3, f'max err = {max_err}'
print(f'max error vs N(0,1) density = {max_err:.3e}')max error vs N(0,1) density = 1.665e-15
In [6]:
for name, e in errors.items():
print(f'{name:30s} max error = {e:.3e}')
print('\nVerified against analytic ground truth -- max error =', max(errors.values()))solve_fractional_ode max error = 5.625e-04 geometric_grid_lift max error = 1.688e-02 solve_volterra max error = 1.232e-06 fourier_invert max error = 1.665e-15 Verified against analytic ground truth -- max error = 0.016883453274562938