Skip to main content

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_batchedbatched 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_interopscipy.sparse round-trip vs spsolve.
  • 05_lu_and_introspection — the LU basis engine (ftran/btran, product-form updates, P A Q = L U), factor access (L/D reconstruction, 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

feral_solver-0.17.0.tar.gz (998.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

feral_solver-0.17.0-cp310-abi3-win_amd64.whl (878.9 kB view details)

Uploaded CPython 3.10+Windows x86-64

feral_solver-0.17.0-cp310-abi3-manylinux_2_28_aarch64.whl (889.2 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

feral_solver-0.17.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (974.9 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

feral_solver-0.17.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (1.7 MB view details)

Uploaded CPython 3.10+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file feral_solver-0.17.0.tar.gz.

File metadata

  • Download URL: feral_solver-0.17.0.tar.gz
  • Upload date:
  • Size: 998.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for feral_solver-0.17.0.tar.gz
Algorithm Hash digest
SHA256 e2827c38985f523537c8787f48de53eed5403294f1c4e9654a5f6a1b7c3cf7ff
MD5 ba09089fbc95f16f31af6e76b1fee509
BLAKE2b-256 f2b7e4c15051f7d224ae590dcce272a4f7de32d461a9715b8c59712fc8527375

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.17.0.tar.gz:

Publisher: python-wheels.yml on jkitchin/feral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file feral_solver-0.17.0-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for feral_solver-0.17.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7aba59a8cdaf29c6c35a1a45f0e4bc178b8b7cad47af00f2f2546bece2cbf746
MD5 31e3d5f578f81ff7fbeb63c2a1f6baef
BLAKE2b-256 0f442896b2c0e21624c1b2def5e1ce3d66c4fcddd51b74512c570aa08f8a5b6e

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.17.0-cp310-abi3-win_amd64.whl:

Publisher: python-wheels.yml on jkitchin/feral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file feral_solver-0.17.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for feral_solver-0.17.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 f05156df831f32a54b8de7daa4a2b007e77979f9b1d2e4cd75b21fa2ff7c0395
MD5 95374a145605e765db7178d84f2b119a
BLAKE2b-256 5b28dcab1614dc8b622a57df36a850cce78d218b242b2a06a13f88f4db090284

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.17.0-cp310-abi3-manylinux_2_28_aarch64.whl:

Publisher: python-wheels.yml on jkitchin/feral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file feral_solver-0.17.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for feral_solver-0.17.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7a22933d7b13adfcb924ed07a4276a4f586b98209896c162e03d84193a90af5d
MD5 3449e56ff9090ce3c56bf55ea0f503a3
BLAKE2b-256 06ceb894919b459289c1a2a1969e4335b3579a659ac08861d9204a616a7b538e

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.17.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: python-wheels.yml on jkitchin/feral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file feral_solver-0.17.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for feral_solver-0.17.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 dd9138d4c4fdeb33f1670c6cd08ed59e8969f5c8c21bb36fbe5d2704584eb858
MD5 a0806c740edc49b06823b5a7eb94fdc2
BLAKE2b-256 1917c3af272147c64ca57d8f566e23a44a51149eda85581769f57a06a73b9537

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.17.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: python-wheels.yml on jkitchin/feral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.17.0 This release

5 files

0.16.0

5 files

0.15.1

5 files

0.15.0

5 files

0.14.0

5 files

0.13.0

5 files

0.12.0

5 files

0.11.3

5 files

0.11.2

5 files

0.11.1

5 files

0.11.0

5 files

0.10.0

5 files

0.9.0

5 files

0.8.0

5 files

0.7.0

5 files

0.6.0

5 files

0.5.0

5 files

0.4.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page