feral-solver
Python bindings for feral, a pure-Rust sparse symmetric indefinite direct solver with certified inertia counts. Aimed at interior-point methods (the IPM in discopt is the primary consumer), but usable for any application that factors symmetric KKT-shaped systems.
Install
pip install feral-solver # plain
pip install 'feral-solver[scipy]' # with scipy.sparse adapters
uv add feral-solver # via uv
Wheels are published for CPython 3.10+ on Linux x86_64/aarch64, macOS universal2, and Windows x86_64. abi3 means one wheel per platform/arch covers all supported Python minor versions.
Quickstart
import numpy as np
import feral
A = feral.CscMatrix.from_dense(np.array([
[4.0, 1.0, 0.0],
[1.0, 3.0, 2.0],
[0.0, 2.0, 5.0],
]))
solver = feral.Solver()
status, inertia = solver.factor(A)
assert status == feral.FactorStatus.SUCCESS
print(inertia) # Inertia(n_pos=3, n_neg=0, n_zero=0)
b = np.array([1.0, 2.0, 3.0])
x = solver.solve(b)
print(np.allclose(A.symv(x), b)) # True
IPM use
The feral.ipm.KktSolver class wraps Solver with the Wächter–Biegler
2006 §3.1 perturbation-escalation loop. Symbolic analysis is cached;
across an entire Newton run solver.symbolic_call_count stays at 1.
import feral
import feral.ipm
kkt_pattern = feral.CscMatrix.from_scipy(my_kkt) # see scipy adapter
kkt = feral.ipm.KktSolver(
kkt_pattern,
expected_inertia=feral.Inertia(n_vars, n_equality_constraints),
)
for newton_iter in range(max_iter):
report = kkt.factor(values_this_iter) # auto-perturbs if needed
if report.status != feral.FactorStatus.SUCCESS:
break
dx_aff, dx_corr = kkt.solve_pair(b_aff, b_corr)
...
See examples/discopt_ipm_kkt.py for an end-to-end Newton step
against a small NLP.
Unsymmetric LU basis engine
LuFactor factors a general square matrix and solves A x = b
(ftran) / Aᵀ y = c (btran), with simplex-style product-form
updates. It auto-routes to a dense or sparse engine via the same
should_use_dense_lu heuristic the Rust core uses; pass
force_dense=True/False to override.
import numpy as np
import feral
A = np.array([[2.0, 1.0, 0.0], [0.0, 3.0, 1.0], [1.0, 0.0, 4.0]])
lu = feral.LuFactor(feral.LuMatrix.from_dense(A))
x = lu.ftran(np.array([1.0, 2.0, 3.0])) # solve A x = b
y = lu.btran(np.array([1.0, 0.0, 0.0])) # solve Aᵀ y = c
lu.update(1, np.array([0.0, 5.0, 1.0])) # replace basis column 1
# P A Q = L U : A[perm][:, qcol] == l_array() @ u_array()
A singular basis raises SingularBasisError (a FactorError); an
exhausted update budget raises NeedsRefactorError — call
lu.refactor(new_matrix).
Factor access and introspection
After Solver.factor, the assembled factor and its statistics are
available without re-solving:
s = feral.Solver(ordering="amd", profiling=True)
s.factor(A_csc)
fac = s.factors() # Factors snapshot
indptr, indices, data = fac.l_csc() # unit-lower L as CSC (factorization order)
d_diag, d_subdiag = fac.d_blocks() # block-diagonal D (2×2 where d_subdiag != 0)
L_scipy = fac.to_scipy_l() # optional scipy.sparse.csc_matrix
# Reconstruction identity (factorization order):
# L @ D @ L.T == P (S A S) Pᵀ
# with fac.perm and the per-row fac.scaling vector.
stats = s.last_factor_stats() # nnz, fill_ratio, inertia, pivot range, ...
print(s.min_pivot_magnitude, s.max_pivot_magnitude)
print(s.scaling_info.kind) # "applied" | "mc64_fallback_to_infnorm" | ...
print(s.profile_report()) # populated when profiling=True
Solver.symbolic() (and the standalone feral.analyze(A_csc, ordering=...), which runs no numeric factorization) return a
SymbolicAnalysis with the resolved ordering, perm/perm_inv,
num_supernodes, factor_nnz_estimate, col_counts, and the
elimination-tree etree_parent array (roots marked -1).
New Solver(...) keyword arguments — all optional, defaulting to the
prior behavior — expose the tuning knobs: ordering ("amd", "amf",
"metis", "scotch", "kahip", "auto", "auto_race"),
mc64_cache, profiling, partial_singular_warning, and
auto_cascade_break.
Conversion conveniences
CscMatrix.to_dense() returns the full symmetric matrix as a 2-D numpy
array; CscMatrix.from_dense(a, triangle="lower"|"upper"|"full") ingests
either triangle; CscMatrix.symmetric_pattern() returns the full
(indptr, indices) structural pattern.
Example notebooks
Runnable notebooks live in examples/notebooks/. Regenerate them from
the reviewable _build_notebooks.py generator: python _build_notebooks.py
re-executes each notebook and commits its cell outputs (the embedded
assertions double as a smoke test), or pass --no-execute for
source-only .ipynb when feral is not installed in the running
interpreter.
01_basic_factor_solve— factor, certified inertia, solve, refine, reuse.02_multi_rhs_batched— batched multi-RHS solve, motivated by a steady-state heat-conduction sweep, with a correctness check and a looped-vs-batched timing showing the per-RHS speedup (issue #57).03_kkt_saddle_inertia— indefinite KKT system with certified inertia.04_scipy_numpy_interop—scipy.sparseround-trip vsspsolve.05_lu_and_introspection— the LU basis engine (ftran/btran, product-form updates,P A Q = L U), factor access (L/Dreconstruction,feral.analyze), and introspection (knobs, factor stats, pivot range, scaling info) added in 0.11.0.
scipy.sparse interop
import scipy.sparse as sp
import feral
A_scipy = sp.csc_matrix(...)
A = feral.from_scipy(A_scipy, symmetric="full") # reads lower triangle
# ... factor, solve ...
A_back = feral.to_scipy(A) # round-trips to scipy
Building from source
Requires a stable Rust toolchain (1.75+) and Python 3.10+.
git clone https://github.com/jkitchin/feral.git
cd feral/python
pip install maturin
maturin develop --release # builds and installs into current venv
pytest tests/
License
MIT, same as the underlying Rust crate.
Metadata
Release files for feral-solver 0.19.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| feral_solver-0.19.0.tar.gz | 1.0 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| feral_solver-0.19.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| feral_solver-0.19.0-cp310-abi3-manylinux_2_28_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| feral_solver-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| feral_solver-0.19.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.10 | abi3 | macOS 10.12+ universal2 (ARM64, x86-64), macOS 11.0+ ARM64, macOS 10.12+ x86-64 | Details |
Total release size: 5.6 MB
Release files / feral_solver-0.19.0.tar.gz
| Download URL | feral_solver-0.19.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f7803b5848220dacb0f2ac7d8ffd2e95082a2db46aadd16e3cb4c56b357bdc42
|
|
BLAKE2b-256 checksum How to use checksums |
3a1d2fa1268b64646f9fb7e3445b5e90f590e2c7ec762dd35ab0e5e94f425ce2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / feral_solver-0.19.0-cp310-abi3-win_amd64.whl
| Download URL | feral_solver-0.19.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 899.8 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
397344fb50076f971830abfd12e786c1e096e2f1e0c768e691f078add87655a4
|
|
BLAKE2b-256 checksum How to use checksums |
899a6686674d12dfddd2e3f3a01558fcb662f4633fd8d881ff7ec715175ad404
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / feral_solver-0.19.0-cp310-abi3-manylinux_2_28_aarch64.whl
| Download URL | feral_solver-0.19.0-cp310-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 909.0 kB |
| Tags | CPython 3.10 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
594c2e5738b1b1253e0bbb16f0fdc71bb6feb3067e05e419ce2497b8897e2057
|
|
BLAKE2b-256 checksum How to use checksums |
93c92b72867b16cc5e3f3e2a4fc032f848037e415e9454da72e853b741243fb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / feral_solver-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | feral_solver-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 999.2 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
68cf3acb3f0295f77dbc99b536b8aca343bc62579f6d302694ce1826d737b37a
|
|
BLAKE2b-256 checksum How to use checksums |
ec013977893987c1efdd1da2fb6558c822c23976d2db689494b14e366a3c3fe2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / feral_solver-0.19.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | feral_solver-0.19.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9e3c4c501292fb52d680e59bb3c2761775f572712faa8b1e2c25e96631a79e27
|
|
BLAKE2b-256 checksum How to use checksums |
df231bff19ddae5b2a79bceb5b0bdab98ebfe43ea31e0b28f1dd0a1883f3e74f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log