Migrating to v3
Every breaking change between v2.6 and v3.0, grouped by crate, with the one-line replacement for each.
v3 is an API-stabilisation release: no new pricing layer, no renamed crates. The changes below are the ones that stop a v2.6 program from compiling, in the order you are most likely to hit them. Each entry gives the replacement; the design behind it is on design philosophy.
Every process carries its sampling backend as a type parameter
Gbm<T, S> became Gbm<T, S, B = Cpu> — on all 131 processes. The default
means existing constructors and type names keep working; what changes is
struct literals and generic bounds.
- Struct literals name the handle:
backend: Cpuwhere 2.x hadbackend: std::marker::PhantomData(or build through the constructor). - Generic code over a process spells the third parameter:
fn f<T, S, B: HostBackend>(p: &Gbm<T, S, B>).HostBackend,FgnBackendandEulerBackendare the three capability traits a bound can name; see GPU support for which process accepts which. - Device sampling is
.on::<MetalNative>()(orCudaNative,CubeCl,Accelerate) on the process, or.on_device(handle)with an explicitCudaNative { ordinal, batch_budget }-style handle; thebackendfield holds that handle as a value (backend: Cpuin a struct literal). - Fallible sampling is on the trait:
ProcessExt::try_sample()/try_sample_par(m)returnResult<_, DeviceError>for every process (alwaysOkon the host); the inherenttry_sample_parofGbm/Ou/Cir/Fgnmoved there,try_sample_matrixstays inherent.
PricerExt is gone; pricers are models
The bundled-market-data trait (calculate_call_put(), calculate_price(),
implied_volatility()) is removed. Every pricer that implemented it is now
a model: the struct holds model parameters only and the query travels as
arguments through ModelPricer:
// v2.6
let p = HestonPricer::builder().s(100.0).k(100.0).r(0.03).tau(0.5)
.v0(0.04).theta(0.04).kappa(2.0).sigma(0.5).rho(-0.7).build();
let (call, put) = p.calculate_call_put();
// v3
let p = HestonPricer::new(0.04, 0.04, 2.0, 0.5, -0.7);
let call = p.price_call(100.0, 100.0, 0.03, 0.0, 0.5);
let put = p.price_put(100.0, 100.0, 0.03, 0.0, 0.5);The affected types and what each lost: HestonPricer, Merton1976Pricer,
SabrPricer (absorbs the old SabrModel), HestonStochCorrPricer (absorbs
HscmModel), KirkSpreadPricer, FiniteDifferencePricer (and gained a
dividend yield in the PDE drift), GbmMalliavinPricer, SnellEnvelopePricer,
BjerksundStensland2002Pricer. Their …Builder types are removed with
them; implied_volatility, char_func, forward(), sigma() and
price_detailed take the (s, k, r, q, tau) query. TimeExt is untouched
and stays on the two instruments. Put Greeks on HestonPricer are new: the
greeks(…, OptionType::Put) call differentiates the put, where v2 always
differentiated the call.
Calibrators
- Every calibrator has a public
regularization: Option<Regularization>field; a struct literal must set it (Nonereproduces v2). SabrCalibrator::newasserts that strikes and vols have the same length, and the shifted caplet calibrator returnsf64::MAXresiduals instead ofNaNfor an out-of-domain trial point, so a Levenberg–Marquardt step no longer stalls on a non-finite loss.
Copulas
Clayton::partial_derivativereturns the conditional distribution (the h-function), as the other Archimedean copulas do. v2 returned zeros for every input because of an invertedndarraypredicate.BB1andBB7are new variants ofCopulaType; an exhaustivematchon that enum needs the two arms.
Growing enums are #[non_exhaustive]
73 enums that will keep gaining variants (CopulaType, EulerSpec,
HazardInterpolation, the scheme and family selectors, …) carry
#[non_exhaustive]. Downstream match statements need a _ => arm.
Closed sets — OptionType, OptionStyle, Moneyness, the day-count
conventions — stay exhaustive.
Parameter asserts name the parameter and its value
Constructors that rejected a bad parameter with a bare assert! now panic
with "sigma must be positive, got -0.2". If a test pinned the old
message through #[should_panic(expected = …)], update the string. The
three-way convention (invalid parameter → panic; not computable → documented
NaN; data-dependent failure → Result) is in
design philosophy §6.
Sampling results that change for a given seed
RoughBergomiuses the Bennedsen–Lunde–Pakkanen hybrid scheme (κ = 1) instead of the exact covariance Cholesky path; paths for a fixed seed differ from v2, distributions agree.Fbm/Fgnon a device backend draw their own stream; host and device paths agree in distribution, not bit for bit.
Prelude and hub
MultivariateExtjoined the prelude (the linear-algebra stack moved to pure-Rustfaer, so the feature-gate reason for excluding it died).Instrument,InstrumentExt,PricingEngine,PricingResultandGreeksExtleft the prelude; import them fromstochastic_rs::traits::*.- The
openblas/nalgebralinear-algebra features are gone with thefaermove; no feature is needed for the multivariate copulas.
Python
- The wheel is CPU-only.
metal,cuda-native,cubecl-cuda,cubecl-wgpu,accelerateandaiarematurinbuild features; a class or function behind one of them raisesValueErrorwith a rebuild hint when it is called from a build that lacks it. device=on the device-capable classes is the device entry point for the Euler engine.- The AI classes (
HestonNn,RBergomiNn,OneFactorNn,calibrate_surrogate) are behind theaifeature.
What is not a breaking change
Widening a process's bound from HostBackend to EulerBackend (a process
gaining a device kernel), a new CopulaType or EulerSpec variant, a new
prelude item, and a new pricer or calibrator: all of these are additive
and arrive in minor releases.
Benchmarks
Criterion bench numbers — FGN CPU vs CUDA, distribution sampling speedups, and the all-backends matrix (CPU / cubecl / Metal / Accelerate).
Contributing
How to contribute to stochastic-rs — coding conventions, the SKILL system that automates per-feature recipes, and the per-PR docs/tests/bench rule.