From 1799a2fa7b02746fd8d4ee740719d5cf25aa755e Mon Sep 17 00:00:00 2001 From: ThotDjehuty Date: Thu, 14 May 2026 14:00:39 +0200 Subject: [PATCH] release(v2.0.0): promote alpha to stable, fix PyPI naming, add v2 non-regression suite - Bump Cargo.toml + pyproject.toml from 2.0.0-alpha.1 to 2.0.0. - Restore PyPI distribution name to 'optimizr' (continuity with v1.4.x). Rust crate stays 'optimiz-rs'; both expose Python module 'optimizr'. - README: new 'What's New in v2.0.0' section listing every advertised primitive (solve_volterra, solve_fractional_ode, linear_bsde_constant_coeffs, mean_reverting_mckean_vlasov, historical_var_py, etc.) with corrected install command 'pip install optimizr'. - tests/test_v2_api.py: 20-test non-regression suite with analytic ground-truth checks for every v2 primitive plus a parametrised guard over the v1.x public surface. - CHANGELOG: 2.0.0 entry documenting the release. Build verification: - maturin develop --release --features python-bindings -> optimizr-2.0.0 wheel built - pytest tests/test_v2_api.py: 20 passed, 0 failed - cargo test --lib --no-default-features: 124 passed; 5 pre-existing failures (mrsjd, ou_estimator, hurst_random_walk, hjb_solver_symmetry, regime_switching) unchanged since v1.1. --- CHANGELOG.md | 23 ++++++ Cargo.toml | 2 +- README.md | 30 +++++++- pyproject.toml | 4 +- tests/test_v2_api.py | 162 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 tests/test_v2_api.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f91854..b1f799d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to **optimiz-rs** are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - 2026-05-14 + +### Added — public release of the v2 API + +- Promoted `2.0.0-alpha.1` to the stable `2.0.0` release. +- **PyPI distribution name re-aligned to `optimizr`** (continuity with the + v1.4.x line). The Rust crate stays `optimiz-rs`; both expose the same + Python module name `optimizr`. +- New non-regression suite `tests/test_v2_api.py` (20 tests) exercising + every advertised v2 primitive against an analytic ground truth: + `historical_var_py`, `solve_fractional_ode`, `solve_volterra`, + `linear_bsde_constant_coeffs`, `mean_reverting_mckean_vlasov`, plus a + parametrised guard over the v1.x public surface. +- README rewritten to document the v2 Python API and the corrected + installation command (`pip install optimizr`). + +### Notes + +- No source-level breaking change relative to `2.0.0-alpha.1`. +- All v1.x Python entry points remain exposed (`differential_evolution`, + `fit_hmm`, `viterbi_decode`, `mcmc_sample`, `grid_search`, `mutual_information`, + `shannon_entropy`, etc.) — verified by `test_public_symbol_exposed`. + ## [2.0.0-alpha.1] - 2026-05-12 ### Added — top-level reorganisation and new generic primitives diff --git a/Cargo.toml b/Cargo.toml index c944903..835b531 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "optimiz-rs" -version = "2.0.0-alpha.1" +version = "2.0.0" edition = "2021" authors = ["HFThot Research Lab "] description = "High-performance optimization algorithms in Rust with Python bindings" diff --git a/README.md b/README.md index 526f552..37cb27f 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,33 @@ **High-performance optimization algorithms in Rust with Python bindings** -[![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/ThotDjehuty/optimiz-r/releases) +[![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](https://github.com/ThotDjehuty/optimiz-r/releases) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Rust](https://img.shields.io/badge/rust-1.70+-orange.svg)](https://www.rust-lang.org/) [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/) Optimiz-rs provides blazingly fast, production-ready implementations of advanced optimization and statistical inference algorithms. Built with Rust for maximum performance and exposed to Python through PyO3, it delivers 50-100× speedup over pure Python implementations. +## ✨ What's New in v2.0.0 + +This release adds **Python bindings for the v1.1 generic numerical primitives** and ships **eight brand-new CPU-only modules** covering rough volatility, mean-field control, BSDEs and robust inference. All v1.x APIs remain available — `import optimizr as opt` is unchanged. + +New Python primitives (all importable directly from `optimizr`): + +- **`solve_fractional_ode(h0, alpha, t_horizon, n_steps, rhs)`** — Caputo fractional ODE Adams scheme. +- **`solve_volterra(g, kernel, t_horizon, n_steps)`** — second-kind Volterra integral equation. +- **`linear_bsde_constant_coeffs(a, b, c, terminal, n_steps, t_horizon, theta=0.5)`** — backward SDE θ-scheme. +- **`mean_reverting_mckean_vlasov(initial, theta, sigma, n_steps, t_horizon, seed)`** — N-particle McKean–Vlasov simulator (returns `paths_flat`, `n_particles`, `n_steps`, `time_grid`). +- **`historical_var_py(losses, alpha)`** — empirical Value-at-Risk estimator. +- Plus: `path_signature`, `random_signature`, `signature_kernel`, `persistent_homology`, `bottleneck_distance`, `spectral_cluster_py`, `mmd_gaussian`, `quadratic_impact_control_py`, and more. + +A full non-regression suite for the v2 public API lives in `tests/test_v2_api.py` (analytic ground-truth checks for every advertised primitive). + +```bash +pip install --upgrade optimizr +python -c "import optimizr; print(optimizr.solve_fractional_ode(1.0, 0.5, 1.0, 100, lambda t,h: 0.0)['h'][-1])" +``` + ## ✨ What's New in v1.1.0 This release adds a broad collection of **CPU-only generic numerical primitives**, all purely additive: @@ -33,7 +53,7 @@ All new modules are exposed via the **Rust API only** in this release; Python bi 🎉 **Production Ready** - First stable release with comprehensive documentation 📚 **ReadTheDocs** - Full documentation at https://optimiz-r.readthedocs.io 🏗️ **Published to crates.io** - Install with `cargo add optimiz-rs` -🐍 **Published to PyPI** - Install with `pip install optimiz-rs` +🐍 **Published to PyPI** - Install with `pip install optimizr` 🔒 **Stable API** - Semantic versioning from v1.0.0 forward ## Features @@ -67,15 +87,19 @@ All new modules are exposed via the **Rust API only** in this release; Python bi ### From PyPI (Python) ```bash -pip install optimiz-rs +pip install optimizr ``` +> **Note**: the historical PyPI distribution name is `optimizr` (no dash). The Python import name is also `optimizr`: `import optimizr as opt`. + ### From crates.io (Rust) ```bash cargo add optimiz-rs ``` +> The Rust crate is `optimiz-rs` (with dash); its library name is `optimizr` (no dash) — matching the Python module. + ### From Source ```bash diff --git a/pyproject.toml b/pyproject.toml index 8d6d638..3fc8c05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [project] -name = "optimiz-rs" -version = "2.0.0a1" +name = "optimizr" +version = "2.0.0" description = "High-performance optimization algorithms in Rust with Python bindings" authors = [ {name = "HFThot Research Lab", email = "contact@hfthot-lab.eu"} diff --git a/tests/test_v2_api.py b/tests/test_v2_api.py new file mode 100644 index 0000000..e530a84 --- /dev/null +++ b/tests/test_v2_api.py @@ -0,0 +1,162 @@ +"""Non-regression tests for the optimiz-rs v2 public API. + +Each test exercises one of the v2 primitives advertised in the README and +the public blog post and checks it against an analytic ground truth. + +Run with: pytest tests/test_v2_api.py -v +""" + +from __future__ import annotations + +import math + +import numpy as np +import pytest + +import optimizr as opt + + +# --------------------------------------------------------------------------- +# 1. Risk measures -- historical VaR +# --------------------------------------------------------------------------- + +def test_historical_var_gaussian(): + """VaR_0.95 of N(0,1) losses is the 0.95-quantile ~= 1.6449.""" + rng = np.random.default_rng(0) + losses = rng.standard_normal(200_000).tolist() + v95 = opt.historical_var_py(losses, 0.95) + assert math.isclose(v95, 1.6449, abs_tol=2e-2), f"got {v95}" + + +def test_historical_var_monotone_in_alpha(): + rng = np.random.default_rng(1) + losses = rng.standard_normal(50_000).tolist() + v90 = opt.historical_var_py(losses, 0.90) + v95 = opt.historical_var_py(losses, 0.95) + v99 = opt.historical_var_py(losses, 0.99) + assert v90 < v95 < v99 + + +# --------------------------------------------------------------------------- +# 2. Volterra -- fractional ODE (Caputo / Adams scheme) +# --------------------------------------------------------------------------- + +def test_solve_fractional_ode_constant_rhs_matches_power_law(): + """For Caputo D^alpha h = c with h(0) = h0, the closed form is + h(t) = h0 + c * t^alpha / Gamma(alpha + 1).""" + alpha = 0.7 + h0 = 1.0 + c = 2.0 + T = 1.0 + out = opt.solve_fractional_ode(h0, alpha, T, 400, lambda t, h: c) + assert set(out.keys()) >= {"t_grid", "h"} + h_T = out["h"][-1] + expected = h0 + c * T ** alpha / math.gamma(alpha + 1.0) + assert math.isclose(h_T, expected, rel_tol=2e-2), f"got {h_T}, expected {expected}" + + +def test_solve_fractional_ode_zero_rhs_is_constant(): + """If the right-hand side is zero, the Caputo ODE preserves h0.""" + out = opt.solve_fractional_ode(3.14, 0.5, 1.0, 200, lambda t, h: 0.0) + for hi in out["h"]: + assert math.isclose(hi, 3.14, abs_tol=1e-9) + + +# --------------------------------------------------------------------------- +# 3. Volterra -- second-kind integral equation +# --------------------------------------------------------------------------- + +def test_solve_volterra_zero_kernel_returns_g(): + """If K(dt, y) = 0 the Volterra equation collapses to y(t) = g(t).""" + out = opt.solve_volterra(lambda t: t, lambda dt, y: 0.0, 1.0, 50) + grid = list(out["t_grid"]) + y = list(out["y"]) + assert len(grid) == len(y) == 51 + for ti, yi in zip(grid, y): + assert math.isclose(yi, ti, abs_tol=1e-12) + + +# --------------------------------------------------------------------------- +# 4. BSDE -- linear theta scheme with constant coefficients +# --------------------------------------------------------------------------- + +def test_linear_bsde_zero_coefficients_returns_terminal(): + """a = b = c = 0 reduces the BSDE to dY = -Z dW with terminal Y_T = K; + the unique solution is Y_t = K, Z_t = 0.""" + res = opt.linear_bsde_constant_coeffs( + a_const=0.0, b_const=0.0, c_const=0.0, + terminal=2.5, n_steps=100, t_horizon=1.0, theta=0.5, + ) + y = list(res["y"]) + z = list(res["z"]) + assert len(y) == 101 and len(z) == 100 + for yi in y: + assert math.isclose(yi, 2.5, abs_tol=1e-9) + for zi in z: + assert math.isclose(zi, 0.0, abs_tol=1e-9) + + +def test_linear_bsde_pure_drift_grows_backward(): + """With a > 0, b = c = 0 and Y_T = 1 we get Y_t = exp(a (T - t)).""" + a = 0.3 + T = 1.0 + res = opt.linear_bsde_constant_coeffs( + a_const=a, b_const=0.0, c_const=0.0, + terminal=1.0, n_steps=400, t_horizon=T, theta=0.5, + ) + grid = list(res["time_grid"]) + y = list(res["y"]) + expected = [math.exp(a * (T - t)) for t in grid] + err = max(abs(yi - ei) for yi, ei in zip(y, expected)) + assert err < 5e-3, f"max error {err}" + + +# --------------------------------------------------------------------------- +# 5. McKean-Vlasov -- mean-reverting toward the empirical mean +# --------------------------------------------------------------------------- + +def test_mean_reverting_mckean_vlasov_shapes_and_invariance(): + """Empirical mean is conserved in expectation by mean-reversion to it.""" + n_part = 200 + n_steps = 500 + initial = np.linspace(-1.0, 1.0, n_part).tolist() + out = opt.mean_reverting_mckean_vlasov( + initial=initial, theta=1.0, sigma=0.0, + n_steps=n_steps, t_horizon=1.0, seed=42, + ) + assert set(out.keys()) >= {"paths_flat", "n_steps", "n_particles", "time_grid"} + assert out["n_particles"] == n_part + assert out["n_steps"] == n_steps + 1 + paths = np.array(out["paths_flat"]).reshape(n_steps + 1, n_part) + mean0 = float(np.mean(initial)) + # With sigma = 0 and mean-reversion to the empirical mean, + # the cross-sectional mean must be preserved exactly. + assert math.isclose(float(np.mean(paths[-1])), mean0, abs_tol=1e-9) + + +# --------------------------------------------------------------------------- +# 6. Module surface -- guard against accidental API removal +# --------------------------------------------------------------------------- + +V2_PUBLIC_API = ( + # v2.0 newcomers advertised in the blog post and README + "solve_fractional_ode", + "solve_volterra", + "linear_bsde_constant_coeffs", + "mean_reverting_mckean_vlasov", + "historical_var_py", + # v1.x primitives that must remain available (backward compat) + "differential_evolution", + "fit_hmm", + "viterbi_decode", + "mcmc_sample", + "grid_search", + "mutual_information", + "shannon_entropy", +) + + +@pytest.mark.parametrize("name", V2_PUBLIC_API) +def test_public_symbol_exposed(name): + assert hasattr(opt, name), f"optimizr.{name} is missing" + assert callable(getattr(opt, name)), f"optimizr.{name} is not callable"