rustebra 0.4.0

A hybrid no_std/alloc linear algebra crate for Rust, scaling from embedded targets to dynamic Krylov subspace solvers.
Documentation
# Sparse Operations

Sparse matrices support scaling, matrix-vector and matrix-matrix products against a dense
buffer, sparse-sparse addition, sparse-sparse multiplication, and pruning near-zero
entries. Each operation is a free function taking the sparse matrix by value or reference,
rather than a method — and each is provided in both a CSR and a CSC variant.

Scaling every stored value by a constant:

```rust
{{#include ../../../examples/sparse/scale.rs}}
```

Multiplying a sparse matrix by a dense vector:

```rust
{{#include ../../../examples/sparse/matvec.rs}}
```

Multiplying a sparse matrix by a dense matrix:

```rust
{{#include ../../../examples/sparse/matmat.rs}}
```

Adding two sparse matrices of the same shape:

```rust
{{#include ../../../examples/sparse/add.rs}}
```

Sparse-sparse matrix multiplication (`spmm_csr`), which multiplies two `CsrMatrix`
operands and returns a sorted result:

```rust
{{#include ../../../examples/sparse/spmm.rs}}
```

Pruning entries within a tolerance of zero:

```rust
{{#include ../../../examples/sparse/prune.rs}}
```

Sparse matrices also implement `SparseLinearOp`, an abstraction for "apply this matrix to
a dense vector, writing into a caller-supplied buffer" — the interface Krylov solvers are
written against so they work with any sparse format:

```rust
use rustebra::sparse::{CsrMatrix, SparseLinearOp};

let eye = CsrMatrix::new(2, 2, vec![0, 1, 2], vec![0, 1], vec![1.0_f64, 1.0]).unwrap();
let mut y = [0.0; 2];
eye.apply(&[3.0, 5.0], &mut y).unwrap();
assert_eq!(y, [3.0, 5.0]);
```

## Gotchas

- Every operation here returns `Result` (e.g. `DimensionMismatch` for shape-incompatible
  operands) rather than panicking — sparse matrices carry their shape as runtime fields,
  so there's no type-level guarantee two operands are compatible the way there is for
  `StaticMatrix`.
- `apply` (from `SparseLinearOp`) writes into a caller-supplied output buffer instead of
  allocating a new one, so solver loops built on top of it can reuse the same buffer across
  iterations without allocating per call.
- `validate_csr`/`validate_csc` (in `rustebra::sparse`) check the canonical format
  invariants — including that no stored value is an *explicit* zero — separately from
  construction. `CsrMatrix::new`/`CscMatrix::new` accept explicit zeros; only
  `validate_csr`/`validate_csc` flag them.