Heuristic:Avhz RustQuant Discretization Scheme Selection
| Knowledge Sources | |
|---|---|
| Domains | Stochastic_Processes, Monte_Carlo |
| Last Updated | 2026-02-07 19:00 GMT |
Overview
Choose between Euler-Maruyama, Milstein, and Strang Splitting discretization schemes based on required accuracy, diffusion complexity, and computational budget.
Description
RustQuant implements three SDE discretization schemes via the `StochasticScheme` enum: Euler-Maruyama (simplest, first-order), Milstein (includes diffusion derivative correction term), and Strang Splitting (operator splitting for separating drift and diffusion). The choice impacts accuracy, stability, and computational cost. Milstein uses a finite difference approximation with step size `1e-5` to estimate the diffusion derivative, while Strang Splitting evaluates diffusion at a half-step drift-advanced point.
Usage
Apply this heuristic when configuring a `StochasticProcessConfig`. Choose the scheme based on the process characteristics and required accuracy for the simulation.
The Insight (Rule of Thumb)
- Euler-Maruyama: Use as the default for most processes. Simplest, O(dt) strong convergence. Suitable when the diffusion coefficient is well-behaved and many paths compensate for discretization error.
- Milstein: Use when higher accuracy is needed per path, especially for processes with state-dependent volatility (e.g., CEV, CIR). Adds a second-order correction term. Costs roughly 3x more per step due to two extra diffusion evaluations for the numerical derivative.
- Strang Splitting: Use for highly nonlinear processes where drift and diffusion operators benefit from separate treatment. Evaluates diffusion at a half-step drift-advanced point, improving stability for stiff systems.
- Trade-off: More sophisticated schemes reduce per-path error but cost more per step. For European option pricing where only terminal values matter, Euler-Maruyama with more paths may outperform Milstein with fewer paths at the same total cost.
Reasoning
The Milstein scheme adds the correction term:
`0.5 * sigma(x) * sigma'(x) * (dW^2 - dt)`
where `sigma'(x)` is approximated via central finite differences with step `h = 1e-5`. This step size balances truncation error O(h^2) against floating-point rounding error O(epsilon/h) and is near-optimal for `f64` precision. The correction vanishes for additive noise (constant diffusion), making Milstein equivalent to Euler-Maruyama in that case.
Strang Splitting computes: half-drift, full-diffusion, half-drift. This symmetric splitting achieves second-order accuracy for the deterministic component while maintaining first-order accuracy for the stochastic component.
Code Evidence
Milstein finite difference derivative from `simulation.rs:93-99`:
+ 0.5
* (stochastic_process.diffusion(path[t], times_ref[t])
* ((stochastic_process.diffusion(path[t] + 1e-5, times_ref[t])
- stochastic_process.diffusion(path[t] - 1e-5, times_ref[t]))
/ 2.0
* 1e-5)
* ((dw * dw) - dt))
Strang Splitting half-step evaluation from `simulation.rs:119-129`:
path.push(
path[t]
+ 0.5 * stochastic_process.drift(path[t], times_ref[t]) * dt
+ stochastic_process.diffusion(
path[t] + 0.5 * stochastic_process.drift(path[t], times[t]) * dt,
times[t] + 0.5 * dt,
) * dw
+ 0.5 * stochastic_process.drift(path[t], times_ref[t]) * dt
);
Noise scaling from `simulation.rs:38`:
let diffusion_scale: f64 = dt.sqrt();