Skip to content

Commit 71c1539

Browse files
committed
fix: tests and compiler error
1 parent fe1b08d commit 71c1539

3 files changed

Lines changed: 127 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CI.yml
1414

1515
# Python (helper scripts + venvs are not part of the Rust project)
1616
*.py
17+
!stochastic-rs-py/python_smoke.py
1718
venv/
1819
.venv/
1920

stochastic-rs-py/python_smoke.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
"""In-process Python smoke test for the `stochastic_rs` extension module.
3+
4+
Run with:
5+
cd stochastic-rs-py
6+
maturin develop --release # build + install editable wheel
7+
python python_smoke.py
8+
9+
The script exercises the 5 main API surfaces (distributions, stochastic
10+
processes, copulas, stats, quant) and verifies seed-determinism wherever
11+
the wrapper accepts a `seed=` keyword argument.
12+
13+
Exit code 0 = all checks pass; non-zero = first failure raises.
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import sys
19+
import traceback
20+
21+
import numpy as np
22+
import stochastic_rs as sr
23+
24+
25+
def check_distributions_seed():
26+
n1 = sr.PyNormal(0.0, 1.0, seed=42)
27+
n2 = sr.PyNormal(0.0, 1.0, seed=42)
28+
s1 = n1.sample(1024)
29+
s2 = n2.sample(1024)
30+
assert np.allclose(s1, s2), "PyNormal seed determinism failed"
31+
print("[OK] PyNormal seed determinism (1024 samples)")
32+
33+
n_unseeded = sr.PyNormal(0.0, 1.0)
34+
s3 = n_unseeded.sample(1024)
35+
assert s3.shape == (1024,)
36+
assert abs(float(np.mean(s3))) < 0.2
37+
print("[OK] PyNormal unseeded sampling (mean ~ 0)")
38+
39+
# sample_par must keep determinism on the seeded path even though the
40+
# underlying `sample_matrix` clones `self` per rayon worker.
41+
p1 = sr.PyNormal(0.0, 1.0, seed=99).sample_par(64, 1024)
42+
p2 = sr.PyNormal(0.0, 1.0, seed=99).sample_par(64, 1024)
43+
assert np.allclose(p1, p2), "PyNormal sample_par seed determinism failed"
44+
assert p1.shape == (64, 1024)
45+
print("[OK] PyNormal sample_par seed determinism (64x1024)")
46+
47+
48+
def check_stochastic_seed():
49+
g1 = sr.PyGbm(0.05, 0.2, 252, x0=100.0, t=1.0, seed=42)
50+
g2 = sr.PyGbm(0.05, 0.2, 252, x0=100.0, t=1.0, seed=42)
51+
p1 = g1.sample()
52+
p2 = g2.sample()
53+
assert np.allclose(p1, p2), "PyGbm seed determinism failed"
54+
assert p1.shape[0] == 252
55+
print("[OK] PyGbm seed determinism (T=1y, n=252)")
56+
57+
# sample_par determinism on the seeded path: the wrapper must serialize
58+
# because the default ProcessExt::sample_par would race on the shared
59+
# Deterministic atomic state.
60+
pp1 = sr.PyGbm(0.05, 0.2, 252, x0=100.0, t=1.0, seed=42).sample_par(8)
61+
pp2 = sr.PyGbm(0.05, 0.2, 252, x0=100.0, t=1.0, seed=42).sample_par(8)
62+
assert np.allclose(pp1, pp2), "PyGbm sample_par seed determinism failed"
63+
assert pp1.shape == (8, 252)
64+
print("[OK] PyGbm sample_par seed determinism (8 paths)")
65+
66+
67+
def check_copula_seed():
68+
c1 = sr.Clayton(tau=0.5)
69+
c2 = sr.Clayton(tau=0.5)
70+
c1.compute_theta()
71+
c2.compute_theta()
72+
s1 = c1.sample(1000, seed=42)
73+
s2 = c2.sample(1000, seed=42)
74+
assert np.allclose(s1, s2), "Clayton seed determinism failed"
75+
assert s1.shape == (1000, 2)
76+
print("[OK] Clayton bivariate copula seed determinism")
77+
78+
79+
def check_stats_jb():
80+
arr = np.random.default_rng(0).standard_normal(2000)
81+
jb = sr.JarqueBera(arr)
82+
stat = jb.statistic
83+
pv = jb.p_value
84+
assert stat >= 0.0
85+
assert 0.0 <= pv <= 1.0
86+
print(f"[OK] JarqueBera N(0,1)·2000 → JB={stat:.3f}, p={pv:.3f}")
87+
88+
89+
def check_quant_bsm():
90+
p = sr.BSMPricer(s=100.0, v=0.2, k=100.0, r=0.05, tau=1.0)
91+
price = p.price()
92+
assert 9.0 < price < 12.0, f"BSM ATM 1y 20% vol price out of range: {price}"
93+
print(f"[OK] BSMPricer ATM 1y σ=20%, r=5% → {price:.4f}")
94+
95+
96+
CHECKS = [
97+
("distributions", check_distributions_seed),
98+
("stochastic", check_stochastic_seed),
99+
("copulas", check_copula_seed),
100+
("stats", check_stats_jb),
101+
("quant", check_quant_bsm),
102+
]
103+
104+
105+
def main() -> int:
106+
failed = []
107+
for name, fn in CHECKS:
108+
try:
109+
fn()
110+
except Exception as exc:
111+
print(f"[FAIL] {name}: {exc}")
112+
traceback.print_exc()
113+
failed.append(name)
114+
print()
115+
if failed:
116+
print(f"smoke test FAILED ({len(failed)}/{len(CHECKS)}): {', '.join(failed)}")
117+
return 1
118+
print(f"stochastic-rs Python smoke test: ALL {len(CHECKS)} CHECKS PASSED")
119+
return 0
120+
121+
122+
if __name__ == "__main__":
123+
sys.exit(main())

stochastic-rs-quant/src/pricing/rainbow.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ impl McRainbowPricer {
297297

298298
#[cfg(test)]
299299
mod tests {
300+
#[cfg(feature = "openblas")]
301+
use ndarray::array;
302+
300303
use super::*;
301304

302305
/// Stulz: $C_{\min} + C_{\max} = C_1 + C_2$ (vanilla call sum).

0 commit comments

Comments
 (0)