Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 5
groups:
# One PR per month for routine bumps; anything that needs thought will fail CI
# on its own rather than being buried in a batch of five.
minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: CI

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
# Not `--all-targets`: that pulls in the bench targets, and criterion's
# smoke-run of them in a debug build takes minutes.
- name: Test
run: cargo test --lib --tests --examples
# `las2` is off by default, so the default run never compiles that module.
- name: Test (all features)
run: cargo test --all-features --lib --tests --examples
- name: Doctests
run: cargo test --all-features --doc
- name: Build benches
run: cargo build --all-features --benches
- name: Clippy
run: cargo clippy --all-features --all-targets -- -D warnings
- name: Format
run: cargo fmt --check

# `rust-version` in Cargo.toml is a promise; without this job it is only a comment.
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.88.0
# Dev-dependencies routinely need a newer compiler than the library does, so the
# promise is checked against the library and its public API, not the test suite.
- name: Build at the declared MSRV
run: cargo build --all-features

# Plain cargo-audit rather than rustsec/audit-check: that action reports through the
# Checks API, which needs `checks: write` and is unavailable to fork PRs entirely. It
# degrades to printing the report and does not reliably fail the job.
audit:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit --locked
# Exits non-zero on vulnerabilities. Unsoundness warnings are printed but do not
# fail the build — they are usually in transitive crates we cannot bump.
- name: Audit
run: cargo audit
7 changes: 2 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
name: Publish to crates.io

on:
push:
branches:
- master
tags:
- 'v*'
release:
types: [published]

jobs:
publish:
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/target

# Editor state
.idea
.idea/
.idea/
.vscode/
*.swp

# macOS
.DS_Store
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Changelog

## 2.0.0

A rewrite: different sparse backend, new solvers, and a handful of 1.x results that were
just wrong.

### Breaking

- `sprs` replaces `nalgebra-sparse`. `SvdMat<T, I = u32, Iptr = u64>` is `sprs::CsMatI`,
generic over both index types — 12 bytes per non-zero for `f64` instead of 16.
`nalgebra` is now dev-only.
- `u` is always `m × d` (numpy layout). 1.x returned `d × m` from Lanczos and `m × d`
from randomized.
- `SMat` is split into `SparseMat` (shape, matvec) and `SparseMatDense` (block products,
means, centering).
- `MaskedCSRMatrix` is now `MaskedCsMat`, and masks rows as well as columns.
- Solvers take config builders (`IrlbaConfig`, `RandomizedConfig`) instead of positional
arguments.
- `randomized` returns `single_svdlib::Result`, not `anyhow::Result`.
- `svd` and `svd_dim` use IRLBA, not LAS2.
- LAS2 is deprecated and behind the `las2` feature, off by default.
- IRLBA errors instead of returning an unconverged result. `allow_unconverged()` opts out.
- MSRV 1.88.

### Added

- `irlba` — restarted Lanczos bidiagonalization. The default solver.
- `dense::tsqr` — tall-skinny QR.
- `dense::small_svd` — one-sided Jacobi, replacing a dependency that lost accuracy on
badly scaled columns.
- Block Krylov sketching in `randomized`.
- `explained_variance`, `explained_variance_ratio`, `total_variance` and `scores` on
`SvdRec`.
- `MaskedCsMat::to_sparse` — copy a view into an owned sparse submatrix.
- `SvdRec::converged` and `max_residual`.
- `Diagnostics::matvecs`.
- Property tests, an edge-case suite, and benchmarks.

### Fixed

All checked against the published 1.0.9.

- `randomized_svd` panicked for every built-in matrix type — four of five `SMat` methods
were `todo!()`.
- Mean centering computed the product of the sums instead of the sum of the products.
- `seed: None` meant seed 0, so unseeded sketches were all identical.
- Masked matrices used the unmasked matrix for small inputs.
- NaN or infinity in the input could hang forever.
- All-zero matrices reported a non-zero singular value.
- Rank-deficient matrices could fail on an absolute breakdown test.
- `min(rows, cols) == 1` was rejected instead of solved directly.
- The centered Frobenius norm cancelled on columns with a large offset — 51% off at an
`f32` offset of 1000. Now summed per entry.

### Known issues

LAS2 is still wrong: 18–100% error against LAPACK. One cause is fixed (`imtqlb` hoisted
its shift out of the loop), at least one remains in `ritvec`. Inherited from 1.x, not the
port. Its accuracy tests are `#[ignore]`d. Hence deprecated and gated.
Loading
Loading