newton-faer
Why another Newton solver?
Most Rust numerical computing is still young. The existing options:
- roots — great for 1D, but not systems
- argmin — focused on optimization (line searches, trust regions), not sparse Newton for general nonlinear systems
- nalgebra — has solvers, but not a sparse-aware Newton–Raphson with reusable symbolic factorizations
This crate is a thin, reusable Newton core that leans on faer for world-class sparse linear algebra, while keeping all domain logic (residual/Jacobian) outside the engine. You get:
- Separation of concerns: engine = iteration policy + linear solves; models = residual/Jacobian.
- Sparse-first: symbolic LU reused across solves when the sparsity pattern is unchanged.
- Production knobs: adaptive damping, divergence guard + backtracking, cancellation, and progress callbacks.
- Parallelism control: slam all cores for big single cases, or run many small cases in parallel with single-threaded LU (no oversubscription).
Architecture at a glance
- linalg: adapters over faer (sparse LU) and dense LU; keeps symbolic factorization cached per pattern.
- solver: Newton loop, damping policy, divergence guard, callbacks, and thread init.
Your model implements NonlinearSystem (residual + Jacobian/refresh), optionally with its own Jacobian cache.
Interface split: model ↔ engine (sparse-first)
The engine doesn’t know your math. You provide two tiny pieces:
1/ NonlinearSystem (your model)
- layout() → problem size / indexing (RowMap)
- residual(x, out) → compute 𝐹(𝑥)
- refresh_jacobian(x) → update values of a cached sparse Jacobian
- jacobian() / jacobian_mut() → hand back the cache handle
2/ JacobianCache (your storage)
- Owns the symbolic pattern (SymbolicSparseColMat) once
- Exposes a mutable values slice each iteration
- Engine calls attach() to get a SparseColMatRef for factorization
use ;
use ;
;
// usage
Parallelism
Parallelism is a policy choice: inside LU for one/few huge systems, or over cases for large batches. We auto-init Rayon once; you can override the global thread count via config.
use *;
use NewtonCfg;
// batch mode: many cases, maximize throughput
let cfg_batch = default.with_threads; // LU single-thread
let results: = contingencies
.par_iter
.map
.collect;
// big single case mode: few huge systems
let cfg_big = default.with_threads; // use all cores in LU
let mut pf = big_system_pf.clone;
let res = pf.solve_with_cfg?;
Guideline:
- Tons of scenarios? with_threads(1) + par_iter() over cases.
- One gigantic system? with_threads(0) (all cores) + sequential cases.
Adaptive Damping
Divergence guard + backtracking are enabled when adaptive=true and steered by: min_damping, max_damping, grow, shrink, divergence_ratio, ls_backtrack, ls_max_steps.
let cfg = default.with_adaptive;
Reuse LU
Keep a solver instance and reuse the symbolic factorization across solves with the same sparsity. Only the numeric phase is recomputed.
let mut lu = default;
for case in cases
Solver Progress
Stream iteration stats to your UI, support cancellation, and run the solve on a worker thread.
use ;
use ;
let = ;
let cancel = new;
let cancel_flag = cancel.clone;
let mut model = /* build system */;
spawn;
while let Ok = rx.recv
Performance notes
- Symbolic reuse is the big win in multi-scenario studies (fixed sparsity).
- Preallocated buffers and in-place solves avoid per-iteration allocations.
- Threading: pick one level of parallelism—inside LU or over cases—to avoid oversubscription.
- Ordering (AMD/ND/…) can drastically reduce fill; we expose knobs for faer’s symbolic parameters if you want to tune.
Acknowledgments
Big thanks to the faer team. newton-faer leans on faer’s fast, well-designed sparse linear algebra.