Every scientific library accumulates a quiet kind of debt: a stub that returns a plausible number instead of a real one, a GPU probe that lies instead of erroring, an FFI boundary one missing attribute away from a crash. SciRS2 0.6.1 spent this release paying that debt down.
Today we released SciRS2 0.6.1 — a hardening release that replaces five separate placeholder, simulated, or silently-wrong code paths with real implementations, fixes a process-crashing DLPack bug, and closes a symbolic-engine soundness bug that could swap operands roughly one time in ten. Nothing about the public API expanded for its own sake this cycle; almost everything here exists because something that looked like it worked actually didn’t.
No C. No Fortran. No CUDA Toolkit, no system BLAS, nothing to apt install before cargo build succeeds. SciRS2 0.6.1 compiles to a single static binary (or WASM) and runs everywhere, exactly like every release before it — this cycle just makes sure what runs is telling you the truth.
Why SciRS2 0.6.1 is a game changer
Libraries that fake it are common, and the failure mode is always the same shape:
- A GPU benchmark reports a
speedupnumber even when no GPU dispatch actually ran. - A hardware-detection probe hardcodes
cuda_available = falseinstead of checking. - A “model packager” writes plausible-looking bytes to disk instead of compiling anything.
- A tensor-exchange FFI boundary is one missing
#[repr(C)]away from undefined behavior. - An e-graph extraction routine pops its work stack in the wrong order and silently reorders operands for non-commutative operations.
SciRS2 0.6.1 ends all five of those, concretely:
- Real model-serving codegen.
scirs2-neural’sModelPackager::package()now genuinely shells out tocargo build --releaseagainst a generated temp-directory Cargo project forPackageFormat::NativeandPackageFormat::CSharedLibrary— not the placeholder byte-writing stub it replaced. - A critical DLPack fix.
to_dlpack’sBackingStorewas missing#[repr(C)]inscirs2-python, which meant the DLPack capsule’s memory layout wasn’t guaranteed — a process-crashing SIGBUS waiting to happen the moment the capsule was garbage-collected. Fixed, along with a related capsule double-consumption bug and afrom_dlpackstride-handling bug that silently produced wrong data for non-contiguous or transposed tensors. - GPU-dispatch honesty.
scirs2-datasets’sAdvancedGpuOptimizernow performs real wgpu/GpuNdarraydispatch instead of aprintln!-based mock —BenchmarkResult.gpu_time_ms/.speedupare nowOption<f64>,Nonewhen no real GPU work ran, instead of a fabricated figure. - Hardware-detection honesty.
scirs2-core’sPlatformCapabilities::detect()now does a real runtime probe of the NVIDIA driver (dlopenoflibcuda.so.1/nvcuda.dllpluscuInit/cuDeviceGetCount) instead of hardcodingcuda_availabletofalse, and detects Metal at runtime on macOS without requiring themetalfeature. - A symbolic-engine soundness fix. The e-graph saturation engine’s
reconstruct()pushed binary e-node children in reversed order but popped them in the wrong order — invisible for commutative operations likeAdd/Mul, but mathematically wrong forPow/Sub/Div. Verified fixed with 120 consecutive passing runs of the originally-failing test, a ~12,800-check multi-identity stress test, and a new permanent regression test. Fullscirs2-symbolicsuite: 947/947 default, 1058/1058 all-features.
Technical Deep Dive: honesty at every boundary
The DLPack capsule, precisely. DLPack is a C ABI — the struct layout crossing the Rust/Python boundary has to match byte-for-byte what the consuming framework expects. BackingStore held the tensor’s backing allocation alive for the lifetime of the capsule, but without #[repr(C)], Rust was free to reorder or pad its fields however the compiler preferred. The moment Python’s garbage collector called the capsule’s destructor, that layout mismatch turned into a SIGBUS. The fix is one attribute, but finding it required treating “it works in testing” as insufficient evidence — the bug only manifests once garbage collection actually reclaims the capsule, which a short-lived test process may never trigger.
ModelPackager, for real this time. The new serving module — split into mod.rs/types.rs/packager.rs/codegen.rs/tests.rs — generates an actual temp-directory Cargo project around the model being packaged and invokes cargo build --release against it, producing a genuine native binary or C-shared-library artifact. The previous implementation wrote bytes that looked like output without ever compiling anything.
Why Backends::PRIMARY, not Backends::all(). Seven crates’ wgpu adapter/device enumeration now restricts discovery to Backends::PRIMARY (Vulkan/Metal/DX12/BrowserWebGPU). The secondary GL/GLES backend can look compatible on a host with a broken or missing Vulkan loader — enumeration succeeds, a device comes back — but GL is too limited for wgpu’s mandatory indirect-dispatch validation pipeline, so that device dies on its first real dispatch. Restricting discovery up front means adapter selection fails cleanly at startup instead of handing back a device that fails later, somewhere less convenient to debug.
The e-graph pop-order bug. cas/e_graph/extract.rs’s reconstruct() walks an e-graph iteratively (no recursion on LoweredOp, per SciRS2’s usual discipline) using an explicit work stack. Binary e-node children were pushed in reversed order but popped assuming forward order — a classic stack-vs-queue mismatch. For commutative operators the swap is invisible (a+b and b+a are equal), which is exactly why it survived undetected: it only produces a wrong answer for Pow/Sub/Div, and even then only non-deterministically, since the trigger depended on HashMap iteration order. sin²(x)+cos²(x) could intermittently — roughly one run in ten — extract as 2^sin(x)+2^cos(x) instead of simplifying to 1. The fix aligns the pop order with the already-correct pattern used by the sibling pattern::instantiate.
Getting Started
Two of 0.6.1’s new APIs, both genuinely new surface rather than internal hardening:
cargo add scirs2-stats scirs2-spatial
use scirs2_spatial::distance::hamming;
use scirs2_stats::distributions::f::F;
fn main() {
// F::ppf is new in 0.6.1 — the inverse CDF, inverting the
// regularized-incomplete-beta relation via bisection (same pattern
// already used by Beta::ppf). Previously missing at both the Rust
// and Python (PyF.ppf) layers.
let f_dist = F::new(2.0f64, 10.0, 0.0, 1.0).expect("valid F distribution");
let x = f_dist.ppf(0.5).expect("ppf(0.5) succeeds");
let p_roundtrip = f_dist.cdf(x);
println!("F(2,10).ppf(0.5) = {x:.6} (roundtrip cdf = {p_roundtrip:.6})");
assert!((p_roundtrip - 0.5).abs() < 1e-6);
// hamming() is also new — proportion of differing coordinates,
// matching scipy.spatial.distance.hamming semantics exactly.
let point1 = &[1.0, 0.0, 1.0, 1.0, 0.0];
let point2 = &[1.0, 1.0, 0.0, 1.0, 0.0];
let dist = hamming(point1, point2);
println!("hamming distance = {dist}"); // 2 of 5 coordinates differ -> 0.4
assert!((dist - 0.4f64).abs() < 1e-6);
}
What’s New in 0.6.1
Added
Fdistribution gainsppf()(inverse CDF) inscirs2-stats, at both the Rust and Python layers.hamming()distance inscirs2-spatial, matching SciPy semantics, exposed to Python too.- Finance facades in
scirs2-integrate:AdvancedMonteCarloEngine,RealTimeRiskMonitor/RiskDashboard,ExoticOptionPricer(Barrier/Asian/Lookback/Digital),RiskAnalyzer/PortfolioRiskMetrics(historical and Monte Carlo VaR, Greeks). training_history()onscirs2-core’s generative-sampling models (NormalizingFlow,ScoreBasedDiffusion,EnergyBasedModel,NeuralPosteriorEstimation) andexport_data()onProductionProfiler.simd_distance_squared_euclideanonSimdUnifiedOps; opt-in real RSS memory tracking inscirs2-stats’s SciPy benchmark framework via a newmemory_trackingfeature.
Changed
- Minor breaking change:
scirs2-datasets’sBenchmarkResult.gpu_time_ms/.speedupare nowOption<f64>instead off64. - Three oversized (>2000-line) files split into module directories via SplitRS, with re-exports preserving the existing public API.
- Dependency bumps:
oxiarc-archivefamily0.3.3→0.3.6,oxisql-core/-sqlite-compat0.3.1→0.3.2,oxicuda-*0.4.0→0.5.0.
Fixed
- The critical DLPack SIGBUS, double-consumption, and stride bugs described above.
- The e-graph operand-order soundness bug described above.
scirs2-signal’swaverecreconstructed to2×the original signal length for any filter longer than Haar’s 2 taps; DWT round-trip tests now assert real perfect reconstruction.- Restored SIMD paths for
scirs2-interpolate’sweighted_sum/squared_euclidean_distance(previously always fell back to scalar behind a stale workaround). - A GEMM operand-layout bug across the
scirs2-datasets/scirs2-interpolate/scirs2-optimizeCUDA paths (mistaggedColMajorwhereoxicuda-blasrequiresRowMajor). - Two Miri-detected undefined-behavior fixes: an unaligned-reference bug in
cast_bytes_to_slice, and a missingelsebranch in a SIMD kernel that silently returned an empty result on non-AVX2 hardware. printpdfbumped0.9.1→0.11.1inscirs2-special, resolving two realcargo auditfindings including a high-severity (CVSS 7.5) stack-overflow CVE in a transitive PDF-parsing dependency.
Removed
- 14 dead/orphaned stub types deleted from
scirs2-stats(advanced_stubs.rs) — richer real implementations already existed elsewhere under the same names.
See CHANGELOG.md [0.6.1] for the complete list.
Tips
- Match on the new
Option<f64>, don’t unwrap it. If you readBenchmarkResult.gpu_time_ms/.speedupfromscirs2-datasets,Nonenow means “no real GPU dispatch ran” — treat it as a real signal, not a null you paper over withunwrap_or(0.0), or you’ll silently reintroduce the fabricated-zero problem this release just removed. cuda_available()needs no crate feature to tell the truth now.PlatformCapabilities::detect()runtime-probes the NVIDIA driver unconditionally, so downstreamDevice::cuda_if_available()-style code (as in trustformers) sees accurate hardware state without an extra feature flag — if you had a workaround for the old hardcodedfalse, it’s safe to remove.- Upgrade immediately if you touch DLPack. The
#[repr(C)]fix addresses a real crash risk in any long-running process that creates and garbage-collects DLPack capsules — this isn’t a cosmetic fix, it’s a production stability one. F::ppfcloses a real gap if you were computing F-distribution critical values or confidence intervals by hand — it mirrorsBeta::ppf’s bisection approach and is available identically from Python viaPyF.ppf.- A custom wgpu adapter-selection path? Check it against the new
Backends::PRIMARYrestriction — if you were relying on the GL/GLES fallback on a host with a broken Vulkan loader, adapter discovery now fails at startup instead of handing you a device that dies on first dispatch. - Turn on
memory_trackinginscirs2-statsif you need real peak/average RSS numbers from the SciPy benchmark framework — without the feature, it reports honest zeros rather than a fabricated figure, which is correct but not useful if you actually need the numbers.
This is the foundation
SciRS2 0.6.1 is the sovereign scientific-computing layer of the COOLJAPAN ecosystem, and a correctness release matters precisely because so much depends on that foundation telling the truth:
- NumRS2 — NumPy-compatible N-dimensional arrays in pure Rust.
- PandRS — Pandas-compatible DataFrames.
- OptiRS — advanced ML optimizers extending SciRS2.
- ToRSh — a PyTorch-compatible deep-learning framework.
- SkleaRS — a scikit-learn-compatible ML library.
- TrustformeRS — Hugging Face Transformers in pure Rust.
Every one of these inherits SciRS2’s DLPack boundary, its GPU-dispatch reporting, and its symbolic engine along with everything else — so a SIGBUS fixed here is a SIGBUS that never reaches a downstream model-serving pipeline, and a soundness fix in the e-graph is one less place a ToRSh or TrustformeRS computation could silently get the wrong answer. The numeric core still rests on OxiBLAS and OxiFFT; verification on OxiZ; the new NVIDIA performance path on OxiCUDA; compression on OxiARC; storage on OxiSQL and OxiH5. No C. No Fortran. No exceptions in the default build.
Repository: https://github.com/cool-japan/scirs
Star the repo if a library that would rather return an honest error than a fabricated number is what you’ve been waiting for.
The era of “it probably works” is over. Pure Rust scientific computing is here — fast, safe, and honest about what it actually did.
— KitaSan at COOLJAPAN OÜ July 15, 2026