# KLU Matrix

`klumatrix.h`/`klumatrix.cpp` wrap the KLU sparse LU library (part of SuiteSparse) into the simulator's circuit-sized linear solve: given the circuit's sparsity pattern (built by device binding, see [Data structure](circuit.md)), `KluMatrixCore` builds the compressed-column structure, factors it, and solves `Ax=b` or `A^T x=b`. It is the sparse counterpart to [Dense Linear Algebra](densematrix.md), used wherever a solve is over the full circuit Jacobian rather than a small fixed-size block, and it is the base that [KLU Block-Sparse Matrix](klubsmatrix.md) builds on for per-harmonic/per-instance block Jacobians.

## `SparsityMap`

`SparsityMap` collects the set of nonzero `(row, column)` positions of a matrix before it is built, as an `unordered_map` from `MatrixEntryPosition` (`std::pair<EquationIndex,UnknownIndex>`) to an `Entry{ index, flags }`. Positions are 1-based; index 0 is reserved for the reference node (ground), matching the per-unknown indexing convention used throughout the simulator (see [Namespaces and Types](common.md)).

- **`insert(e, u, f)`** adds a position (or, if already present, ORs in the new `EntryFlags`) and reports whether a new entry was created. `EntryFlags` (`Resistive`/`Reactive`) record which kind of device contribution touches an entry - transient/AC cores use this to tell resistive-only from reactive-carrying entries without a second pass over devices.
- **`enumerate()`** sorts the collected positions column-major (by unknown, then by equation - the order KLU's compressed-column format requires) and assigns each entry a dense 0-based `index` in that order, and caches the sorted order in `positions()`. This must run after all devices have registered their positions and before `KluMatrixCore::rebuild()` consumes the map.
- **`find()`** looks up an entry (used by `MatrixAccess` implementations at bind time), `size()` gives the nonzero count, `clear()` resets it, `dump()` prints it for debugging.

`SparsityMap` is non-copyable/non-movable - it is built once per circuit topology and shared (by reference) with every matrix that has the same sparsity, real or complex, single or block.

## `MatrixAccess<IndexType>`

An abstract interface, independent of the concrete value type (`double` vs `Complex`) and of whether the matrix is atomic (one scalar per entry) or block-sparse, that lets `Device::bind()` resolve a `MatrixEntryPosition` to a storage location without knowing which matrix implementation it is talking to:

- `valueArray()`/`cxValueArray()` return the raw nonzero-value array (whichever of the two is not the matrix's element type returns `nullptr`).
- `valueIndex(mep, blockMep)` returns the dense array index for an entry (and whether it exists).
- `valuePtr()`/`cxValuePtr()` return a pointer to a component directly; for a position not present in the sparsity map they return a pointer to a scratch "bucket" element instead of `nullptr`, so device load code can write to a slot unconditionally without a found-check on every store (a write that lands nowhere real, not a fault).

`blockMep` (block element position) is only meaningful for block-sparse matrices; atomic matrices ignore it. This interface is what lets analysis code stay matrix-type-agnostic while binding instances to matrix storage - see `Device::bind()` for the producer side.

## `KluMatrixCore<IndexType, ValueType>`

The template that does the actual KLU work. `IndexType` is `int32_t` or `int64_t` (KLU's two index widths, selected via `std::conditional` for the `klu_common`/`klu_symbolic`/`klu_numeric` types and dispatched at every KLU call with `if constexpr`); `ValueType` is `double` or `Complex` (real vs. complex KLU entry points, `klu_*` vs `klu_z_*`/`klu_zl_*`). Both axes are `static_assert`-enforced in the constructor. The project typedefs the practically used instantiations: `KluRealMatrixCore`/`KluComplexMatrixCore` (both with `MatrixEntryIndex` = `int32_t`).

### Storage

- `AP`/`AI`/`Ax` are KLU's compressed-column arrays: `AP` (size `AN+1`) gives each column's starting offset into `AI`/`Ax`, `AI` holds 0-based row indices, `Ax` holds the values (as raw `double` or as `Complex`, reinterpreted to `double*` when calling the complex KLU entry points, which expect interleaved real/imaginary doubles).
- `symbolic`/`numeric` are KLU's opaque analysis and factorization objects; `common` is KLU's per-call options/status structure.
- `smap` is a non-owning pointer to the `SparsityMap` the matrix was last built from; `valid()`/`isBuilt()` test `symbolic`/`smap` respectively, `isFactored()` tests `numeric`.
- `bucket_` is the scratch element `elementPtr()` returns a pointer to when a requested position is not in the sparsity map (see `MatrixAccess` above).

### Building

**`rebuild(SparsityMap& m, EquationIndex n)`** consumes an already-`enumerate()`d sparsity map and:
1. Walks `m.positions()` (sorted column-major) to fill `AP`/`AI`, skipping any position with a zero row or column (the ground reference), and closing empty columns' `AP` entries as it goes.
2. Resizes `Ax` and zeroes it.
3. Calls `klu_defaults()`/`klu_l_defaults()` then `klu_analyze()`/`klu_l_analyze()` to get a fresh symbolic factorization (fill-reducing ordering) for the new pattern.

`rebuild()` must be called again whenever the sparsity pattern changes (e.g. after elaboration adds/removes devices); it discards any previous `symbolic`/`numeric` via `deleteKluObjects()` first. It returns `false` (and records `Error::Defaults`/`Error::Analysis`) if KLU itself fails, which normally means the pattern is structurally singular.

### Factorization and solving

- **`factor()`** calls `klu_factor()`/`klu_z_factor()` (and frees any prior `numeric` first). Failure - `!numeric`, `KLU_SINGULAR` status, or a numerical rank not equal to `AN` - is reported via the `bool` return, records `Error::Factorization`, `errorIndex` (`singularColumn()`) and `errorRank_` (`numericalRank()`), and discards the half-built `numeric`.
- **`refactor()`** reuses the existing `symbolic` (and pivot structure) via `klu_refactor()`, which is cheaper than a full `factor()` when the sparsity pattern is unchanged and only the values changed (a common case: nonlinear iterations, transient time steps). If there is no prior `numeric` it falls through to `factor()` instead of failing.
- **`solve(b)`**/`tsolve(b)` solve `Ax=b`/`A^T x=b` in place for a single right-hand side; `solveBlock(B, nrhs)`/`tsolveBlock(B, nrhs)` do the same for `nrhs` right-hand sides packed column-major with leading dimension `AN`, useful for e.g. solving several sensitivity or Krylov right-hand sides against one factorization at once.
- **`rgrowth()`**/`rcond()` expose KLU's reciprocal pivot growth and (cheap) reciprocal condition number estimates, used for numerical-quality diagnostics rather than the solve path itself.

**Accounting**: `factor()`/`refactor()`/`solve()`/`tsolve()`/`solveBlock()`/`tsolveBlock()` all bump counters and wall-clock timers on the `Accounting` object set via `setAccounting()` (see [Accounting](acct.md)), split by real vs. complex (`factor`/`cxfactor`, etc.) - this is how the `stats` control command reports factorization/solve counts and time.

### Other operations

- **`product()`/`tproduct()`** compute `Ax`/`A^T x` directly from the compressed-column arrays (a plain double loop over `AP`/`AI`/`Ax`, not a KLU call) - both a raw-pointer overload and a `VectorView` overload (letting the vectors be, e.g., matrix columns or history-buffer slots without copying).
- **`residual()`** computes `Ax-b` by calling `product()` then subtracting.
- **`isFinite()`** (matrix-wide or vector overload) scans for `inf`/`nan`, used to catch a diverged Newton-Raphson step before it reaches the solver; `rowMaxNorm()` gives each row's max absolute entry, e.g. for scaling-based convergence checks.
- **`nonzeroOffset(row, col)`** binary-searches a column's row indices for a given row - the mechanism behind `dumpSparsity()`'s dense-pattern printout and `dump()`'s full value dump.
- **`zero()`** clears `Ax` (optionally only the real or imaginary `Component`) and clears the error state, ready for devices to reload entries.

### Error reporting

Every fallible method sets `lastError` (an `Error` enum) and, where relevant, `errorIndex`/`errorRank_`/`errorNan`, instead of throwing. **`formatError(s, resolver)`** turns the last error into a human-readable message written into a `Status` (see [Status](status.md)): with a `NameResolver` supplied it names the offending row/column by circuit node name (`Circuit` implements `NameResolver` over its node table) instead of a bare 1-based index. `formatError()` is explicitly documented as not cheap (it builds strings), so callers only invoke it once a solve has actually failed, not on every call.

## `KluAtomicMatrix<IndexType, ValueType>`

`KluAtomicMatrix` adds the `MatrixAccess` interface on top of `KluMatrixCore` for the common case of one scalar entry per matrix position (as opposed to the dense-block-per-position case in [KLU Block-Sparse Matrix](klubsmatrix.md)). Its four `MatrixAccess` overrides ignore `blockMep` and simply forward to `KluMatrixCore`'s `data()`/`elementPtr()`/`smap->find()`. `KluRealMatrix`/`KluComplexMatrix` are the project typedefs analyses actually instantiate for a plain circuit Jacobian; `KluMatrixCore` itself is documented separately because it is also the base class `KluBlockSparseMatrixCore` derives from, and deriving directly from `KluMatrixCore` (bypassing `KluAtomicMatrix`) is how a new matrix flavor should be built.

## Index and value types

`MatrixEntryIndex` (`int32_t`, from `common.h`) is the index type used throughout the project's KLU instantiations - the `int64_t` (`klu_l_*`) code paths exist in the template for large-problem support but are not the type any project typedef currently uses. `EquationIndex`/`UnknownIndex` (both `uint32_t`) index the row/column position space before it is narrowed to `MatrixEntryIndex` for KLU's own arrays.
