rustebra 0.4.0

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

Compressed sparse row (CSR) and compressed sparse column (CSC) are the two primary sparse
storage formats `rustebra` operates on. Both store only the non-zero entries, using three
parallel arrays instead of a dense grid.

CSR (`CsrMatrix<T>`) stores:

- `row_ptr` — length `rows + 1`; the range `row_ptr[i]..row_ptr[i + 1]` gives the slice of
  `col_indices`/`values` belonging to row `i`.
- `col_indices` — the column index of each stored entry.
- `values` — the value of each stored entry.

CSC (`CscMatrix<T>`) is the transpose layout: a `col_ptr` of length `cols + 1` plays the
role `row_ptr` plays for CSR, and `row_indices` plays the role `col_indices` plays.

```rust
use rustebra::sparse::CsrMatrix;

// 3x3 identity: row i's single entry is at column i, value 1.0.
let eye = CsrMatrix::new(3, 3, vec![0, 1, 2, 3], vec![0, 1, 2], vec![1.0_f64, 1.0, 1.0])
    .unwrap();
assert_eq!(eye.row_ptr(), &[0, 1, 2, 3]);
```

Within a row (CSR) or column (CSC), the stored indices don't have to be sorted — but every
index must be in-bounds, and the pointer array must be non-decreasing, starting at `0` and
ending at the total non-zero count. `CsrMatrix::new`/`CscMatrix::new` validate these
invariants and return `Err` rather than constructing a malformed matrix.

## `SortedCsrMatrix` / `SortedCscMatrix`

`SortedCsrMatrix<T>` and `SortedCscMatrix<T>` wrap a `CsrMatrix`/`CscMatrix` and add the
additional guarantee that indices *within* each row/column are in ascending order. This
enables `O(log(nnz/rows))` binary-search lookup of a specific entry, and is a precondition
some algorithms (sparse triangular solves, certain preconditioners) require. Both types
implement `Deref` to their unsorted counterpart, so every read-only accessor is available
without unwrapping. Construct one directly with `SortedCsrMatrix::from_csr`/
`SortedCscMatrix::from_csc` (which sorts, paying the cost up front), or get one as the output
of any operation that produces sorted results as a side effect of its own algorithm —
`coo_to_csr`, `csr_to_csc`, `csc_to_csr`, `spmm_csr`, `add_csr`, and `add_csc`.

## Gotchas

- A CSR matrix with unsorted column indices is still a *valid* `CsrMatrix` — sortedness is
  an opt-in stronger guarantee via `SortedCsrMatrix`, not a base invariant of `CsrMatrix`
  itself.
- Storing an explicit zero is legal in `CsrMatrix`/`CscMatrix::new` (it doesn't reject
  zero-valued entries), but [`validate_csr`]../05-sparse/operations.md-style validation
  used elsewhere treats an explicit zero as a violation — don't assume every zero has been
  pruned just because construction succeeded.