Migrating to v3
Every breaking change between v2.6 and v3.0, grouped by crate, with the one-line replacement for each.
v3 updates the sampling and pricing APIs without renaming 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 132 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::<Metal>()(orCuda,Accelerate) on the process, orwith_backend(Cuda::new(1))with an explicit handle; thebackendfield holds that handle as a value (backend: Cpuin a struct literal). A configuration the kernels cannot carry samples on the host bit-identically, anddevice_ready()onProcessExtsays so beforehand. - CubeCL is gone. The backend was removed in 3.0.0-rc.2 because it
duplicated the native CUDA and Metal kernels and ran slower on the same
hardware. Vulkan and WebGPU targets are no longer served; use
Cudaon NVIDIA andMetalon Apple GPUs. - 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.BlackKarasinskiSwaptionCalibrator,HullWhiteSwaptionCalibrator,G2ppSwaptionCalibrator, andSabrCapletCalibratornow reportconvergedonly when the simplex objective spread falls belowsd_tolerance. An iteration limit or nonfinite simplex costs returnsfalsewith the best parameters found. This also changes the final element of their Python return tuples.
Heston SLV calibration in rc.3
calibrate_leverage takes a local-volatility Grid2D<f64>, snapshot
maturities and a ParticleMethod, and returns Result<ParticleCalibration>.
Use the result's leverage surface in place of the previously returned bare
surface. The particle estimator uses the reference kernel and bandwidth,
so recalibration can change existing numerical results.
HestonSlvCalibrator replaces calibrate_from_dupire. Supply spot, rates
and the market call grid explicitly; its method is a LeverageMethod
selecting particles or the Fokker–Planck solver. HestonSlvPricer reads
leverage at each step's start. See
Heston SLV calibration
for the current Rust and Python interfaces.
Optimizers and Rust version
Basin replaces argmin for L-BFGS and Nelder–Mead fits. L-BFGS polishing
can produce slightly different estimates because Basin skips curvature
updates that fail its numerical threshold. Basin 1.11 also replaces
levenberg-marquardt for least-squares calibration, including SVI, SSVI,
and the AI surrogate. These fits use trust-region damping, with pivoted QR
for stochastic-volatility and surrogate fits and Cholesky for the smaller
pricing objectives.
CGMYSV now optimizes in smooth bounded coordinates, as Heston already does,
to maintain convergence on weakly identified surfaces. Parameter types and
result types are unchanged.
The implementation of levenberg_marquardt::LeastSquaresProblem on the
calibrators is removed; use the Calibrator trait to run a fit.
nalgebra is no longer a direct workspace dependency; it can still appear
transitively through dependencies such as gauss-quad. The least-squares calibrators
(BSMCalibrator, SabrCalibrator, HestonCalibrator, HKDECalibrator,
DoubleHestonCalibrator, SVJCalibrator) take their c_market, s and k
quotes as ndarray::Array1<f64> instead of nalgebra::DVector<f64>, and
their From conversions, HestonCalibrator::set_residual_weights,
Regularization's row helpers and CalibrationHistory follow. A Vec<f64>
still converts with .into(), so call sites that passed prices.into() are
unchanged; DVector::from_vec(v) becomes Array1::from_vec(v) and
DVector::from_element(n, x) becomes Array1::from_elem(n, x).
Tolerance and patience settings retain their relative-tolerance and residual-evaluation-budget meanings. Evaluation counts and termination messages can differ between solvers. An exhausted budget, a failed model evaluation, or a numerical stall does not count as convergence; the fit returns the best accepted parameters.
The workspace now requires Rust 1.89 and declares it in rust-version.
Cargo's edition-2024 resolver uses this minimum when selecting dependencies,
which can favor older compatible releases during a fresh resolution.
The MSRV check covers all workspace targets with default features disabled;
optional feature combinations are checked separately on current Rust.
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.
Closed-form values that change
From rc.4, erf, erfc, norm_cdf and ndtri in
stochastic_rs_distributions::special are exact to double precision; v2.6
used Abramowitz–Stegun 7.1.26 for erf (1.5e-7 relative) and Acklam's
approximation for ndtri (1.15e-9). Closed-form prices, Greeks and implied
volatilities move in the seventh significant digit or later, so a test that
pinned one to more digits needs re-pinning. The tails no longer cancel:
norm_cdf(-10) is 7.6e-24, not 0.
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,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. - From rc.4, a panic in a constructor raises
ValueErrorand one insample/sample_parraisesRuntimeError, notpyo3_runtime.PanicException(aBaseException); code that caughtPanicExceptionaround those calls catches the ordinary exception instead.
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.
Last updated on