Thanks for contributing to solvr. This guide covers the architecture conventions and quality gates.
- A recent stable Rust toolchain.
- A clean working tree before you open a pull request.
Useful contributions:
- Missing algorithms: methods that SciPy, scikit-learn, or scikit-image have and solvr lacks, or new methods in an existing module.
- Bug fixes, numerical-accuracy improvements, and additional backend coverage.
Open an issue before you write a non-trivial algorithm. Describe the method, its reference, and the crate it belongs in. Small, self-contained fixes can go straight to a pull request.
Place work by what it is:
| Crate | Scope |
|---|---|
| numr | Foundational primitives: tensor ops, dtypes, the Runtime/backend abstraction and new backends, FFT, core linear algebra (matmul, LU/QR/SVD/eigen, solve), special functions, basic descriptive statistics. |
| solvr (this crate) | Scientific and solving algorithms built from numr primitives: optimization, ODE/DAE/BVP/PDE, interpolation, advanced statistics (distributions, tests, regression), signal processing, spatial, clustering, graphs, morphology, matrix-equation solvers. |
| boostr | AI/ML building blocks: attention, positional encodings, mixture-of-experts, quantization, neural-network layers, training and inference machinery. |
Quick test:
- A low-level primitive (tensor op, FFT, linear-algebra factorization, special function) or a new backend: numr.
- A domain solver a scientist or engineer reaches for: solvr.
- Makes sense only for neural networks or LLMs: boostr.
When in doubt, propose the work in an issue. A primitive that several higher layers reuse belongs in numr. Do not duplicate it in solvr.
solvr is backend-agnostic. The same algorithm runs on CPU, CUDA, and WebGPU through numr's Runtime abstraction. Write each algorithm once, generically. Each backend is a thin delegation.
- Be generic over
R: Runtime. Operate onTensor<R>, never on&[f64],Vec<f64>, or struct fields of those types. - Build computation from numr operations, not scalar
forloops. numr uses SIMD on CPU and kernels on GPU. Scalar loops are slower and not portable. - Support
F32andF64. Respect backend dtype limits, such as WebGPU being F32-only. Return a clear error instead of degrading silently. - If numr lacks a primitive you need, add it to numr. Do not work around it with a host-side loop.
Each module places the algorithm in exactly one location:
src/<module>/
├── mod.rs # ONLY `pub mod` + `pub use`
├── traits/ # trait definitions + option/result types
├── impl_generic/ # the algorithm: `fn <algo>_impl<R, C>(...)`
├── cpu/ # `impl Trait for CpuClient` — delegates to *_impl
├── cuda/ # `impl Trait for CudaClient` — delegates to *_impl
└── wgpu/ # `impl Trait for WgpuClient` — delegates to *_impl
- One algorithm is one file. Use the same file name under
traits/,impl_generic/,cpu/,cuda/, andwgpu/. mod.rscontains onlypub modandpub use. It holds no logic, traits, or types.- Backend files are thin. Each implements its trait by calling the generic
*_implfunction and nothing else. - Add files for a new algorithm. Do not expand existing files.
Host/device transfers cost far more than the computation. Inside algorithms, never call tensor.to_vec() or Tensor::from_slice(...). Two transfers are allowed:
- At the public API boundary, for user input and returned output.
- A single scalar pulled to the host for a convergence or control-flow check.
Keep state in Tensor<R>. Keep loops on the device with numr ops.
cargo build # CPU (default)
cargo build --features cuda # CUDA (requires a CUDA 12.8+ or 13.x toolchain)
cargo build --features wgpu # WebGPU
cargo build --features sparse # sparse-tensor-backed modulesThe graph and pde features enable sparse. Both features are default features.
- Put unit tests in the same file as the code, in
#[cfg(test)] mod tests. - Test numerical correctness against an analytic or reference result, not only that the call returns
Ok. - A backend-specific test skips gracefully when no device is available.
- Run the suite on each backend you can. CUDA and WebGPU paths run the same generic code and catch backend-specific bugs.
cargo test --release # CPU
cargo test --release --features cuda,sparse
cargo test --release --features wgpuRun these before you submit. CI runs clippy with -D warnings, so treat a warning as an error.
cargo fmt --all -- --check
cargo clippy --all-targets --features f16,sparse,graph,pde -- -D warnings
cargo test --releaseIf you touch a GPU backend, also run clippy with --features cuda and --features wgpu.
- Keep each pull request focused and scoped.
- Keep the module structure and
impl_genericpattern. - Include tests for behavioral changes. Check numerical parity across backends where it applies.
- Update docs when public APIs or features change.
- Avoid
.unwrap()in library code. Return a typed error with context.
Use Conventional Commits with a clear, imperative summary:
feat(integrate): add automatic Jacobian sparsity detection for BDF and Radau
fix(spatial): correct single-vector rotation to apply R instead of R^T