Skip to main content

Sparse symmetric indefinite direct solver with certified inertia, in pure Rust.

Project description

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.

Project details


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.11.0.tar.gz (814.8 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.11.0-cp310-abi3-win_amd64.whl (780.3 kB view details)

Uploaded CPython 3.10+Windows x86-64

feral_solver-0.11.0-cp310-abi3-manylinux_2_28_aarch64.whl (792.1 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

feral_solver-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (880.4 kB view details)

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

feral_solver-0.11.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (1.6 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.11.0.tar.gz.

File metadata

  • Download URL: feral_solver-0.11.0.tar.gz
  • Upload date:
  • Size: 814.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for feral_solver-0.11.0.tar.gz
Algorithm Hash digest
SHA256 af877a798d2c1d75954da06723f06523d7bc95a6b74172b1e2bf97206b8a579f
MD5 014317ec38a027bd8f7ed69988c8c284
BLAKE2b-256 61777839644c19220533aa4e993fb74b1115b80851dd6f47049476b99b6ec57f

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.11.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.11.0-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for feral_solver-0.11.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 82a4206b4fa5ca604b047875cc2bb4fc19f4db7b8402d0ea5796d58bc6af4b81
MD5 f716983470cab9f741e32d95f0842e98
BLAKE2b-256 a353baf6392e6d0d92f4f77adfea59012c1d4dbab48f1a8fbc0f1d1b8d50cab8

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.11.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.11.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for feral_solver-0.11.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 fe8831e907d07b18ac003d5fa9d4df64d3e1b2d93f2bc4275230d7b481b612ab
MD5 9c0b1aef85d51038ff9bf951727e1054
BLAKE2b-256 0fd4c5ac7180e60c349293b7c538f9091f8222291c0f72f032332a399fdf042f

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.11.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.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for feral_solver-0.11.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 4d81d4a9d512da2cf0f0792484ddb953eaaf9718c92c81f970dd497c92a3743b
MD5 e325e2dcced1048971685e8195948226
BLAKE2b-256 2b8da75e32c1d560f34f656382f7e9c882272271f32210021627ccf182f56749

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.11.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.11.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.11.0-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 119eedcd969504e396538332aa1ec0eae4e7b0edbce1d67be16ed3d2f06aeaec
MD5 8a24fd43fa84d4769ed62316b2fa21f8
BLAKE2b-256 16342c572483e39fbc25b9e1690c95f79dd6f91089465d43d2060e863a4bb69e

See more details on using hashes here.

Provenance

The following attestation bundles were made for feral_solver-0.11.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.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page