sparse-ir-rs
SpM-lab / sparse-ir-rs
Rust implementation of the intermediate representation (IR), the discrete Lehmann representation (DLR) and sparse sampling for imaginary-time Green's functions
Overview
Rust implementation of intermediate representation (IR) for imaginary-time propagators
Typical use cases
- Represent imaginary-time Green's functions compactly with few coefficients
- Speed up many-body calculations with sparse sampling
Target users
Fields
README
SparseIR Rust Workspace
Rust implementation of the intermediate representation (IR), the discrete Lehmann representation (DLR, built with or without an IR basis), ESPRIT/MiniPole, and sparse sampling for imaginary-time Green's functions. All of these are in the 0.12 release on crates.io.
Most end users should start from the ecosystem documentation / tutorials and use the full-featured Python/Julia libraries; this workspace focuses on Rust crates and low-level bindings.
Documentation
Start here for Rust: Rust User Guide — browser-readable tutorials with figures, runnable examples, and conventions.
| Resource | Description |
|---|---|
| Rust Tutorials | Sparse sampling, IR/DLR transformations, MiniPole, and applied examples |
| MiniPole | A few complex poles from Matsubara data (ESPRIT); entry points and contour choice |
| Current Rust API | API documentation built from main |
| Ecosystem Documentation | Theory and usage across languages |
| IR, DLR and MiniPole: history and comparison | How the three representations are related and when to use which |
| Python/Julia Tutorials | Interactive tutorials with Jupyter notebooks |
| Rust API: sparse-ir (docs.rs) | Core Rust crate API documentation |
| Rust API: sparse-ir-capi (docs.rs) | C-API Rust crate API documentation |
The Rust guide is written against the 0.12 release on crates.io, which docs.rs
documents. The guide's "Current Rust API" link is built from main and may run
ahead of the release.
Tutorial sources and local browsing
- Book contents: Markdown chapters and figures in
docs/book/. - Runnable Rust examples: the source of the included code and CSV data.
- Plotting scripts: render figures from those CSVs.
With mdBook 0.5.2 installed, browse locally:
mdbook serve docs/book --open
To reproduce the MiniPole figures from the repository root:
cargo run --manifest-path docs/tutorial-code/Cargo.toml --profile ci --locked --bin minipole
uv run --project docs/plotting python docs/plotting/minipole_plot.py
Quick start (Rust)
Add the crate from crates.io (requires Rust 1.96 or newer):
[dependencies] sparse-ir = "0.13.0"The installation guide covers the optional
system-blasfeature and the Git dependency onmain; thesparse-irREADME has fit/evaluate and DLR examples.From a checkout, run the round-trip example (DLR/IR/sampling):
cargo run --example roundtrip --release
What’s in this workspace
Rust users typically depend on the crates below. Users of other languages typically use the bindings (via the C API).
Rust crates
sparse-ir— Rust implementation of the IR basis, DLR, MiniPole and sampling (README, docs.rs). It re-exports the crates it is made of; all of them are published on crates.io and can also be used on their own:sparse-ir-core— statistics, errors, GEMM, fitters, theBasistrait and sparse samplingsparse-ir-dlr— the discrete Lehmann representation (no IR basis needed)sparse-ir-minipole— ESPRIT and minimal pole representationssparse-ir-basis— kernels, the singular value expansion and the IR basis
sparse-ir-capi— Rust crate providing a C-compatible API (shared library + C header) (README, docs.rs)
Bindings (other languages)
| Language | Component | Notes |
|---|---|---|
| C/C++ | sparse-ir-capi |
C-compatible shared library (README, docs.rs) |
| Fortran | fortran/ |
Bindings via C-API (README) |
| Python | pylibsparseir |
Thin wrapper for the C-API (not user-facing) (README) |
Full-featured wrappers (external)
Note: the name sparse-ir is used both for the Rust crate (sparse-ir) and the full-featured Python project below.
| Project | Language | Notes |
|---|---|---|
| sparse-ir (Python) | Python | Full-featured Python library (recommended for most users) |
| SparseIR.jl (Julia) | Julia | Full-featured Julia implementation |
Examples
Rust examples
| Example | Description |
|---|---|
sparse-ir/examples/roundtrip.rs |
Complete DLR/IR/sampling cycle with round-trip tests |
sparse-ir/README.md |
IR fit/evaluate and DLR quick starts, compiled as doctests |
Fortran examples
| Example | Description |
|---|---|
fortran/examples/second_order_perturbation_fort.f90 |
Second-order perturbation theory |
C/C++ integration tests
| Test | Description |
|---|---|
cxx_tests/cinterface_core.cxx |
Core C-API tests |
cxx_tests/cinterface_integration.cxx |
DLR/IR round-trip tests |
References
- sparse-ir: optimal compression and sparse sampling of many-body propagators
Markus Wallerberger, Samuel Badr, Shintaro Hoshino, Fumiya Kakizawa, Takashi Koretsune, Yuki Nagai, Kosuke Nogaki, Takuya Nomoto, Hitoshi Mori, Junya Otsuki, Soshun Ozaki, Rihito Sakurai, Constanze Vogel, Niklas Witt, Kazuyoshi Yoshimi, Hiroshi Shinaoka
arXiv:2206.11762 | SoftwareX 21, 101266 (2023)
License
This workspace is dual-licensed under the terms of the MIT license and the Apache License (Version 2.0).
- You may use the code in this repository under the terms of either license, at your option:
Some components incorporate third-party code:
- the
col_piv_qrmodule in thesparse-ir-basiscrate is based on nalgebra (Apache-2.0); seesparse-ir/README.mdandLICENSE-APACHE; - the ESPRIT and MiniPole code in the
sparse-ir-minipolecrate is a port of MiniPole (MIT); seesparse-ir-minipole/LICENSE-THIRD-PARTY.
Development
Project structure
sparse-ir-rs/
├── sparse-ir/ # Rust library facade (crates.io: sparse-ir): re-exports
│ ├── examples/ # Rust examples
│ └── tests/ # Integration tests
├── sparse-ir-core/ # Statistics, errors, GEMM, fitters, Basis trait, sampling
├── sparse-ir-dlr/ # Discrete Lehmann representation
├── sparse-ir-minipole/ # ESPRIT and minimal pole representations
├── sparse-ir-basis/ # Kernels, SVE, IR basis
├── sparse-ir-capi/ # C-compatible API (shared library)
├── python/ # Python thin wrapper (pylibsparseir)
│ ├── pylibsparseir/ # ctypes bindings to C-API
│ └── tests/ # Python tests
├── fortran/ # Fortran bindings via C-API
│ ├── src/ # Fortran modules
│ ├── examples/ # Example programs
│ └── test/ # Test programs
├── cxx_tests/ # C/C++ integration tests
├── capi_benchmark/ # C-API benchmarks
├── notebook/ # Technical notes (algorithms, design)
├── agent-skills/ # Repo-local agent skills (Rust usage, releases)
├── bump_version_downstream.md # Redirect to the shared sparse-ir-release skill
└── docs/
├── book/ # Browser-readable Rust guide (mdBook), including figures
├── tutorial-code/ # Executable examples, CSV outputs and numerical checks
├── plotting/ # CSV-to-figure scripts
└── dev/ # Internal plans and development records (not user documentation)
Build
From the workspace root:
cargo build # build all crates in debug mode
cargo build --release # optimized build
CPU baseline: the fused multiply-add is enabled
The builds ask for the fused multiply-add instruction. The double-double arithmetic uses f64::mul_add, and without the instruction that is a call to the software implementation in libm, which dominates the cost of the SVE: on an EPYC 7713P, compute_sve(LogisticKernel(1e6), 1e-10) takes 16 s instead of 50 s.
It does not change any result: the fused operation is correctly rounded whether it is an instruction or the libm routine, and the SVE output is bit-identical (same singular values to the last bit at Λ = 1e4 and 1e5).
FMA is mandatory in the aarch64 base instruction set and present on every x86-64 CPU since Intel Haswell (2013) and AMD Piledriver (2012), so only x86-64 asks for it, in .cargo/config.toml.
For a machine without it — pre-2013 x86-64, the Intel Pentium/Celeron parts of the Haswell..Skylake generation, Atom-class chips, QEMU's default qemu64 CPU model, or x86-64 emulation — build with it disabled:
RUSTFLAGS="-C target-feature=-fma" cargo build --release
An explicitly set RUSTFLAGS replaces the flags in .cargo/config.toml rather than adding to them, so the same mechanism also tunes a build for the local CPU:
RUSTFLAGS="-C target-cpu=native" cargo build --release
The default build uses the pure-Rust faer backend for matrix–matrix products.
Faer is reasonably fast, but usually considerably slower than an optimized BLAS implementation.
To enable system BLAS (LP64) for the Rust sparse-ir crate at compile time, use:
cargo build -p sparse-ir --features system-blas # forwards to sparse-ir-core/system-blas
With system-blas, the default GEMM backend becomes BLAS at compile time. Regardless of the feature, arbitrary BLAS function pointers (LP64/ILP64) can be injected at runtime via the C API or the internal GEMM dispatcher.
Test
Rust tests
cargo test --all-targets --release # recommended for speed
cargo test --workspace --exclude sparse-ir-capi --doc --release # doctests (not included in --all-targets)
C++ integration tests
Tests C-API with different BLAS configurations (default, OpenBLAS LP64, OpenBLAS ILP64):
cd cxx_tests && ./run_with_rust_capi.sh
Fortran tests
cd fortran && ./test_with_rust_capi.sh
Python tests
cd python && uv sync --locked && uv run pytest tests/ -v
See .github/workflows/ for CI configurations.
Benchmarks
C API benchmarks
cd capi_benchmark && ./run_with_rust_capi.sh
Version management
Agent-facing guidance for release work lives in AGENTS.md. Repo-local skills for version suggestion and release execution live under agent-skills/.
Version consistency check
Check version consistency across the workspace:
python3 check_version.py
This script reads the canonical version from [workspace.package] in Cargo.toml and
- fails if the Python bindings version (
python/pyproject.toml) doesn't match; - fails if a
sparse-ir/sparse-ir-capidependency snippet in a README (README.mdor*/README.md, e.g. the install snippets insparse-ir/README.md) pins a different version (placeholders such asX.Y.Zare ignored); - warns if the Julia version (
julia/build_tarballs.jl) doesn't match, because it is updated only after the crates are published.
Releasing a new version
The release process is done in two stages because Julia bindings depend on the published crates.io version. In the commands below, replace X.Y.Z with the version being released.
Stage 1: Rust + Python version bump
Update the version in
Cargo.toml:[workspace.package] version = "X.Y.Z" # Update this [workspace.dependencies] sparse-ir = { version = "X.Y.Z", path = "sparse-ir" } sparse-ir-core = { version = "X.Y.Z", path = "sparse-ir-core" } sparse-ir-dlr = { version = "X.Y.Z", path = "sparse-ir-dlr" } sparse-ir-minipole = { version = "X.Y.Z", path = "sparse-ir-minipole" } sparse-ir-basis = { version = "X.Y.Z", path = "sparse-ir-basis" }Update the Python bindings version in
python/pyproject.toml:[project] version = "X.Y.Z" # Update thisUpdate the install snippets in
sparse-ir/README.md(sparse-ir = "X.Y.Z"andsparse-ir = { version = "X.Y.Z", features = ["system-blas"] }) and the quick start of the rootREADME.md, and the snippets indocs/book/src/getting-started/installation.md. The crate README is packaged with the crate and rendered on crates.io, socheck_version.pyfails until they all match. Also update the prose that names the release the docs are written against ("the 0.N release") inREADME.md,sparse-ir/README.md, the four sub-crate READMEs andinstallation.md;check_version.pydoes not check it.Regenerate the lock files:
cargo update -w,(cd python && uv lock), and(cd docs/tutorial-code && cargo update -p sparse-ir)(check that only the workspace crates moved).Verify version consistency and test publishing (dry run):
python3 check_version.py cargo publish -p sparse-ir-core --dry-runThe library crates depend on each other (
sparse-ir-core<-sparse-ir-dlr<-sparse-ir-minipole,sparse-ir-basis<-sparse-ir<-sparse-ir-capi), so the registry verification of each one only succeeds after the version it depends on is visible on crates.io; onlysparse-ir-corecan be dry-run first. The manual release workflow publishes them in that order and waits for each one.Create a PR for the version bump:
git checkout -b release/vX.Y.Z git add Cargo.toml Cargo.lock python/pyproject.toml python/uv.lock README.md sparse-ir*/README.md \ docs/book/src/getting-started/installation.md docs/tutorial-code/Cargo.lock git commit -m "chore: bump version to X.Y.Z" git push origin release/vX.Y.ZThen create a PR on GitHub and get it reviewed.
After the PR is merged, start the manual Rust release workflow:
gh workflow run manual-release.yml \ -f release_ref=main \ -f expected_version=X.Y.Z \ -f confirm_publish=trueWatch the workflow:
RUN_ID=$(gh run list --workflow manual-release.yml --limit 1 --json databaseId --jq '.[0].databaseId') gh run watch "$RUN_ID"The workflow publishes
sparse-ir-core,sparse-ir-dlr,sparse-ir-minipole,sparse-ir-basis,sparse-ir, andsparse-ir-capiin dependency order, waiting for registry visibility before proceeding. Only then does it pushvX.Y.Z.The manual Rust release workflow updates the
libsparseirYggdrasil branch from the new release tag. After that workflow succeeds, dispatch the standalone PyPI workflow from the release tag. The upload job is defined directly in this workflow because PyPI Trusted Publishing does not support reusable workflow jobs:RUN_ID=$(gh run list --workflow manual-release.yml --limit 1 --json databaseId --jq '.[0].databaseId') gh run watch "$RUN_ID" gh workflow run PublishPyPI.yml --ref vX.Y.Z PYPI_RUN_ID=$(gh run list --workflow PublishPyPI.yml --limit 1 --json databaseId --jq '.[0].databaseId') gh run watch "$PYPI_RUN_ID" curl -fsSL "https://pypi.org/pypi/pylibsparseir/X.Y.Z/json" >/dev/nullDispatch the conda workflow from the release tag as well. Like
PublishPyPI.yml, it does not start on its own, because the release workflow pushes the tag withGITHUB_TOKEN:gh workflow run publish_conda.yml --ref vX.Y.Z CONDA_RUN_ID=$(gh run list --workflow publish_conda.yml --limit 1 --json databaseId --jq '.[0].databaseId') gh run watch "$CONDA_RUN_ID" curl -fsS https://api.anaconda.org/package/spm-lab/pylibsparseir \ | jq -r --arg v X.Y.Z '[.files[] | select(.version == $v)] | length' # > 0For the full cross-repository release (Yggdrasil,
SparseIR.jl,sparse-ir), follow the sharedsparse-ir-releaseskill.If the Python publish needs to be retried after the tag already exists, rerun the same workflow from the release tag:
gh workflow run PublishPyPI.yml --ref vX.Y.ZIf the Yggdrasil update leg needs to be retried independently after the tag already exists, rerun
.github/workflows/publish-libsparseir.ymlmanually:gh workflow run publish-libsparseir.ymlRequirements:
- GitHub Actions secret
CRATES_IO_TOKENmust be configured. - The workflow file is
.github/workflows/manual-release.yml. - Julia and BinaryBuilder follow-up work still happen after crates.io publication.
- GitHub Actions secret
Stage 2: Julia version bump (after crates.io publication)
After the new version is published to crates.io and available:
Update
julia/build_tarballs.jlusing the update script:julia julia/update_build_tarballs.jl vX.Y.ZThis script automatically:
- Updates the version number
- Fetches and updates the commit hash for the tag
- Updates the comment with the tag reference
Verify version consistency:
python3 check_version.pyCreate a PR for the Julia version bump:
git checkout -b update-julia-vX.Y.Z git add julia/build_tarballs.jl git commit -m "chore: bump Julia bindings version to X.Y.Z" git push origin update-julia-vX.Y.ZThen create a PR on GitHub and get it reviewed.
After the PR is merged, follow the Julia package release process (Yggdrasil PR, etc.)
Source: https://github.com/SpM-lab/sparse-ir-rs README
Richness
Richness = how many of docs / examples / tests are in place, shown as 0–3 stars (1 star = 1 item; the checklist below is the breakdown). Software listed by hand (without repository data) is not assessed and is left out when filtering by a richness of 1 or more.
- Yes: README
- Yes: LICENSE
- Yes: docs
- No: examples
- Yes: tests
- No: CITATION
- No: release
Information
- Primary language
- Rust
- Languages
- Rust, C++, Fortran, Python, Julia, C, Shell, CMake, Makefile, Batchfile
- License
- MIT
- Provider
- github
- Documentation
- https://spm-lab.github.io/sparse-ir-rs/