Metadata-Version: 2.4
Name: rslab
Version: 0.27.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Requires-Dist: numpy>=1.21
Requires-Dist: scipy>=1.7
Summary: Pure-Rust sparse direct solver (symmetric LDLᵀ + unsymmetric LU) and preconditioner, with NumPy/SciPy bindings.
Keywords: sparse,solver,linear-algebra,ldlt,lu,preconditioner,pardiso,scipy
Author: Milan Rother
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/milanofthe/rslab
Project-URL: Repository, https://github.com/milanofthe/rslab

# rslab (Python bindings)

NumPy/SciPy bindings for [RSLAB](https://github.com/milanofthe/rslab), a
pure-Rust sparse direct solver and preconditioner: complex/real symmetric LDLᵀ
(Bunch-Kaufman), unsymmetric LU, and a KLU-style path for circuit-shaped
matrices. A thin wrapper, all numeric work happens in Rust.

## Install

```bash
pip install rslab
```

## Usage

```python
import numpy as np
import scipy.sparse as sp
import rslab

# Symmetric system (real or complex; the dtype selects the path).
A = sp.random(5000, 5000, density=1e-3, format="csc") + sp.eye(5000) * 10
A = A + A.T
b = np.random.rand(5000)

# One-shot solve.
x = rslab.spsolve(A, b)

# Factor once, solve many right-hand sides.
f = rslab.ldlt(A)
x1 = f.solve(b)
X = f.solve_many(np.random.rand(5000, 8))   # n x nrhs

print(f.n, f.factor_nnz, f.inertia, f.dtype)
```

Complex-symmetric matrices (EM/FEM, PARDISO `mtype 6`) work identically:

```python
A = A.astype(np.complex128); A.data += 1j * 0.3 * A.data.real
x = rslab.ldlt(A).solve(np.ones(A.shape[0], dtype=np.complex128))
```

Unsymmetric matrices use the LU path:

```python
f = rslab.lu(A_general)
x = f.solve(b)
```

Circuit-shaped matrices (MNA / SPICE-class: very sparse, unsymmetric,
near-triangularizable) use the KLU path, bit-deterministic, with a
numeric-only `refactor` for fixed-pattern sweeps:

```python
f = rslab.klu(A_circuit)
x = f.solve(b)
A_circuit.data *= 1.5            # frequency sweep: same pattern, new values
f.refactor(A_circuit.data)       # no symbolic work, no pivot search
x2 = f.solve(b)
y = f.solve_transpose(b)         # A.T @ y = b on the same factors (adjoint)
```

`solve_transpose` is the plain transpose; for the conjugate-transpose adjoint
use `f.solve_transpose(b.conj()).conj()`.

### Preconditioner mode

Never-fail static pivoting plus iterative refinement for hard/indefinite
systems:

```python
f = rslab.ldlt(A, preconditioner=1e-4)
x = f.solve(b, refine=20)        # refine against the original A
```

## Configuration (keyword arguments)

By default `ldlt`, `lu` and `spsolve` use RSLAB's deterministic heuristic
pick, the adaptive ordering plus an exact nested-dissection bakeoff on large
systems (adopted only on a clear predicted win with no fill/memory
regression). A one-time `rslab.install_diagnose()` measures this machine's
throughput and speedup curve and caches it; afterwards the default also picks
its worker count from the calibration (until then the conservative capped
default applies). Keyword arguments override the pick:

| kwarg            | default          | meaning                                                        |
|------------------|------------------|----------------------------------------------------------------|
| `threads`        | `None` (auto)    | `None` = calibrated/structural per-matrix pick; int = fixed (`0` = all) |
| `preconditioner` | `None`           | static-pivot floor (e.g. `1e-4`); never-fail, refine to solve  |
| `drop_tol`       | `None`           | incomplete-factor threshold (preconditioner)                   |
| `method`         | `"left_looking"` | `"left_looking"` or `"multifrontal"`                           |
| `memory`         | `"low"`          | `"low"` or `"eager"` factor emit strategy                      |
| `force_accept`   | `False`          | accept tiny pivots in exact mode instead of failing            |

`klu` accepts:

| kwarg         | default | meaning                                                          |
|---------------|---------|------------------------------------------------------------------|
| `pivot_tol`   | `1e-3`  | diagonal-preference threshold; `1.0` = plain partial pivoting    |
| `row_scaling` | `True`  | divide each row by its max-magnitude entry before factoring      |
| `btf`         | `True`  | permute to block upper triangular form first (keep it on)       |
| `parallel`    | `None`  | per-block parallel factor/refactor over the BTF blocks; `None` = auto gate (≥4 blocks, ≥8000 nnz, no dominant block), `True`/`False` force on/off; bit-identical result in every mode |

Supported dtypes: `float64`, `float32`, `complex128`, `complex64`.

## API

Everything ships in the flat `rslab` namespace; full parameter documentation
lives in the docstrings (`help(rslab.klu)` etc.).

**Functions**

| function | meaning |
|----------|---------|
| `spsolve(A, b, **kw)` | one-shot factor-and-solve; detects symmetry and picks the LDLᵀ or LU path |
| `ldlt(A, **kw) -> Ldlt` | factor a real/complex **symmetric** matrix (Bunch-Kaufman LDLᵀ) |
| `lu(A, **kw) -> Lu` | factor a general unsymmetric matrix (supernodal multifrontal LU) |
| `klu(A, **kw) -> Klu` | factor a circuit-shaped matrix (BTF + per-block Gilbert-Peierls LU) |
| `install_diagnose()` | one-time machine calibration; caches the measured thread-speedup curve |

**Factor handles** — factor once, then:

| method / attribute | `Ldlt` | `Lu` | `Klu` | meaning |
|--------------------|:-:|:-:|:-:|---------|
| `solve(b, refine=0)` | ✓ | ✓ | ✓ | solve one RHS, optional iterative-refinement steps against the original `A` |
| `solve_many(B)` | ✓ | ✓ | ✓ | solve `n × nrhs` RHS in one batched pass |
| `solve_transpose(b)` | – | – | ✓ | solve `Aᵀ y = b` on the same factors (plain transpose, not conjugate) |
| `refactor(data)` | – | – | ✓ | numeric-only re-factorization for new values on the **same** pattern (no symbolic work, no pivot search) |
| `gmres(b, tol=1e-8, maxit=400, restart=None, x0=None, recycle=None)` | ✓ | ✓ | ✓ | GMRES with this factor as preconditioner |
| `gmres_block(B, tol=1e-8, maxit=400, restart=None, x0=None)` | ✓ | ✓ | ✓ | block GMRES for multiple RHS |
| `recycle(k)` | ✓ | ✓ | ✓ | a `Recycle` workspace holding up to `k` deflation vectors across `gmres` calls |
| `n`, `factor_nnz`, `n_perturbed`, `dtype` | ✓ | ✓ | ✓ | dimension, stored factor entries, perturbed pivots (always `0` for `Klu`), NumPy dtype name |
| `inertia` | ✓ | – | – | `(n_pos, n_neg, n_zero)` eigenvalue counts from LDLᵀ |
| `n_blocks` | – | – | ✓ | number of BTF diagonal blocks |

**`Recycle`** — deflation-subspace carrier for sweeps: attributes `k`,
`active`, `dtype`; `clear()` resets it.

## License

MIT.

