pub struct NumericParams {Show 14 fields
pub bk: BunchKaufmanParams,
pub scaling: ScalingStrategy,
pub small_leaf: SmallLeafBatch,
pub profiler: Option<Arc<Mutex<Profiler>>>,
pub parallel_telemetry: Option<Arc<AtomicLockStats>>,
pub fma: bool,
pub allow_delayed_pivots: bool,
pub cascade_break_ratio: Option<f64>,
pub cascade_break_eps: Option<f64>,
pub min_parallel_flops: Option<u64>,
pub sqd_mode: bool,
pub static_pivot_threshold: Option<f64>,
pub warn_partial_singular: bool,
pub pattern_reused_hint: bool,
}Expand description
Numeric-phase parameters bundle.
Groups the dense Bunch-Kaufman pivot configuration with the
global symmetric scaling strategy. Both are numeric-time
choices — they depend on the matrix values, not the sparsity
pattern. Keeping them together at the numeric entry point
(rather than splitting bk into the BK call and scaling
into the symbolic call) lets the symbolic factorization stay
value-agnostic and therefore reusable across multiple numeric
factorizations of structurally identical KKTs (the IPM use
case). See dev/research/pounce-integration-interface.md and
dev/plans/scaling-in-numeric.md (β refactor).
Fields§
§bk: BunchKaufmanParamsDense BK kernel parameters.
scaling: ScalingStrategyGlobal symmetric scaling strategy applied at the start of numeric factorization.
small_leaf: SmallLeafBatchPhase 2.9 small-leaf-subtree batching gate. Default Off
preserves the reference per-supernode driver. When On the
driver processes SymbolicFactorization::small_leaf_groups
via factor_one_small_leaf instead of the generic
factor_one_supernode, skipping the per-leaf
build_row_indices call. See
dev/plans/phase-2.9-small-leaf-subtree.md.
profiler: Option<Arc<Mutex<Profiler>>>Phase 2.10 per-supernode profiler. When Some, the sequential
driver records per-supernode timings, plus prologue/epilogue
costs, into the shared Profiler. When None (default), no
timing work runs — zero overhead in production. See
dev/plans/phase-2.10-supernode-profiler.md.
parallel_telemetry: Option<Arc<AtomicLockStats>>Optional lock-contention telemetry for the rayon-parallel
multifrontal driver. When Some, the driver records the
wait+hold time spent on the shared contrib_blocks and
node_factors_out mutexes, plus a task count. When None
(default) the driver performs a single Option-is_none check
per lock acquire — branch-predicted away in production.
Diagnostic only; the values reported are not part of any
correctness contract. See dev/sessions/2026-05-12-01.md
“Next Session Should” for the cont-201 investigation that
motivates this hook.
fma: boolOpt-in FMA dispatch on the dense trailing-update / panel-update
kernels. Default false preserves the cross-arch bit-exactness
invariant of the production *_nofma kernels (one rounding per
mul plus one per sub); when true, the dense factor /
block_ldlt32 paths dispatch to the FMA siblings
(schur_panel_minus_fma_strided*, axpy_minus_unroll4,
axpy2_minus_unroll4) which fuse the multiply-accumulate into
one mul_add per step (single rounding, ~2x arithmetic
throughput on aarch64 NEON and x86 V3 AVX2+FMA).
Trade-off: the FMA path is not bit-exact with the non-FMA
path; per-element drift is bounded by n_elim * ULP (see
dense::schur_kernel::fma_vs_nofma_panel_kernels_within_n_elim_ulps
for the contract). Inertia is unchanged on well-conditioned
matrices; residuals match within 64 * EPS. Opt in when
throughput matters more than cross-policy bit-identity (large
supernode workloads like Mittelmann’s pinene_3200 NLP — see
issue #8 and dev/research/fma-kernel-opt-in.md).
allow_delayed_pivots: boolWhen true (default), non-root supernodes run with
may_delay = true — pivots that fail the column-relative
threshold or the 2×2 Duff-Reid growth bound are pushed up
the elimination tree to the parent (SSIDS-style delayed
pivoting). When false, every supernode runs as if it
were the root (may_delay = false): failing pivots are
force-accepted in place via the existing
ZeroPivotAction::ForceAccept path, with iterative
refinement to recover residual.
Disabling delayed pivoting is the FERAL analogue of MA57’s
cntl[4] static-pivoting fallback. Issue #8 (Mittelmann
pinene_3200_0009) hits a delayed-pivot cascade: 118k
pivots delayed up to ~14k-column root supernodes, yielding
an 87s factor on an otherwise sub-second problem. Setting
allow_delayed_pivots = false breaks the cascade at the
cost of bounded L growth (O(1/|d|) per force-accepted
small pivot), which iterative refinement is expected to
recover.
Default true preserves the SSIDS-canonical behavior the
FERAL corpus is verified against. Flip per-call via
Solver::with_static_pivoting(true) for the issue #8 fast
path. See dev/journal/2026-05-13-03.org 22:55 entry for
the root-cause analysis.
cascade_break_ratio: Option<f64>Adaptive static-pivoting trigger. When Some(r), a non-root
supernode whose n_delayed_in / expanded_ncol >= r flips to
may_delay = false for that one supernode only, with a
locally-overridden on_zero_pivot = ForceAccept to absorb
failures in place. The rest of the etree keeps SSIDS-style
delayed pivoting. Default None (disabled).
Issue #8 motivation: on pinene_3200_0009, METIS-ND
concentrates 118k delays into three ~14k-column expanded
fronts (87s factor). The cascade-break trigger lets the
easy iterates keep their cheap delayed-pivot path while
breaking the cliff iterates at the overloaded node.
A starting value of 0.5 (“front is at least 50% delayed
columns”) catches the cascade signature without firing on
light-delay nodes — calibrate against the corpus before
promoting to default.
Symbolic-arm gate (issue #15, 2026-05-14): the trigger is
additionally guarded by symbolic.n >= CASCADE_BREAK_MIN_N,
because cascade-break savings only accumulate when some
front can grow (via delay propagation) to several thousand
columns — bounded above by n. Below the threshold the
trigger is a no-op even when armed. See
dev/research/issue-15-cascade-break-symbolic-arm.md.
cascade_break_eps: Option<f64>Per-pivot perturbation floor for cascade-break supernodes.
When cascade_break_ratio fires AND this is Some(eps), the
triggered supernode runs with
on_zero_pivot = PerturbToEps { abs_floor: eps }, replacing
each tiny pivot by sign(d) * max(|d|, eps) and counting it
by sign rather than zero. The factor then satisfies
LDL^T = A + Δ with ||Δ||_∞ <= eps per perturbed pivot —
inertia is preserved provided every nonzero eigenvalue of
A exceeds the cumulative perturbation. Default None
keeps the legacy ForceAccept semantics (unbounded Δ,
counts perturbed pivots as zero). See
dev/journal/2026-05-13-03.org 01:15 for the matrix-specific
“sweet spot” pathology that motivates bounded-Δ
perturbation.
min_parallel_flops: Option<u64>Override the minimum estimated tree-flop count at which
should_parallelize_assembly is willing to dispatch the
rayon-parallel driver. None (default) uses PAR_MIN_FLOPS.
Issue #19 follow-up: rayon spawn / cv-wait overhead is
hardware-dependent; the default const is calibrated for the
reporter’s machine. Consumers that have measured their own
break-even point can override here. Set to Some(0) to
disable the flop gate entirely (still subject to N_PAR_MIN
and the structural multi-child gate); set to Some(u64::MAX)
to force the gate to always reject (functionally equivalent
to Solver::with_parallel(false) for tree-level dispatch).
sqd_mode: boolOpt-in symmetric-quasi-definite (SQD) fast-path. When true,
the caller asserts the input KKT has Vanderbei (1995) structure
K = [[-E, A^T], [A, F]] with E, F symmetric positive
definite — the common case in IPOPT after the first inertia
correction sets δ_w, δ_c > 0, and structural in IP-PMM
(Pougkakiotis-Gondzio 2020). Under this contract every
symmetric permutation admits an LDL^T with purely
diagonal D (Vanderbei Thm 2.1), so the per-supernode
Bunch-Kaufman 1x1-vs-2x2 search can be skipped entirely.
Default false preserves the unconditional BK + delayed-pivot
path that every existing caller is verified against. Mutually
exclusive with allow_delayed_pivots = true and with
cascade_break_ratio = Some(_); the Solver::with_sqd_mode
builder enforces the invariant by clearing both fields when
sqd_mode is enabled. Contract violations at runtime surface
as FeralError::SqdContractViolated (loud failure, no silent
BK fallback) — see commit (e) of the M7 phasing.
See dev/research/sqd-fast-path.md, dev/decisions.md
2026-05-16 entry, and issue #34.
static_pivot_threshold: Option<f64>MA57-style static-pivot perturbation threshold (issue #38).
When Some(t), Solver::factor derives an absolute floor
static_pivot_floor = t * ||D·A·D||_∞ from the scaled matrix
norm (post-scaling, via scaled_matrix_infnorm +
apply_post_scaling_overrides; N2) and propagates it into
BunchKaufmanParams.static_pivot_floor for that factor call.
Every accepted 1×1 / 2×2 pivot whose magnitude (for 2×2:
smallest |eigenvalue|) is below the floor is perturbed up to
the floor and counted by sign. The factor satisfies
LDL^T = A + Δ with ||Δ||_F ≤ floor per perturbed pivot.
Default None (disabled). Recommended starting value for IPM
use: 1e-8 (matches MA57’s cntl[0] default). The C ABI
reads FERAL_STATIC_PIVOT=<float> to set this without a
rebuild.
Inertia is then reported for the perturbed A + Δ, not A —
this is the whole point: bend small-magnitude pivots so the
returned inertia matches the IPM’s expectation, cutting the
PDPerturbationHandler δ_w escalation cost. Iterative
refinement against unperturbed A (the default in Solver:: solve_many_refined) recovers solve accuracy.
See dev/research/static-pivot-perturbation-2026-05-17.md
and dev/journal/2026-05-17-01.org §16:30 / §17:10.
warn_partial_singular: boolWhen true, the numeric drivers emit a one-line stderr
warning: whenever MC64 matching leaves variables unmatched
(ScalingInfo::PartialSingular) and scaling falls back to
identity on those rows/columns. Default false.
PartialSingular is routine and benign for IPM hosts, which
factorize structurally rank-deficient KKT systems on the first
attempt of most iterations; an unconditional stderr write
floods host logs for behavior that is expected and recovered
downstream. The same information is always available
structurally via Solver::scaling_info() (and as a count via
Solver::mc64_fallback_count for the Auto-fallback case),
so the stderr line is an opt-in diagnostic breadcrumb, not a
correctness signal. Default false keeps feral quiet as a
library should be; enable it via
Solver::with_partial_singular_warning(true) or the
FERAL_WARN_PARTIAL_SINGULAR env var (C ABI). Issue #43.
pattern_reused_hint: boolIssue #56 Lever A.2: when true, the sequential and Schur numeric
drivers consult FactorWorkspace::permute_cache to skip the
CscMatrix::from_triplets rebuild inside permute_csc_values,
reusing the cached (col_ptr, row_idx, value_map) and scattering
only the values. Set by Solver::factor to pattern_reused —
the same fingerprint-equality signal that drives symbolic-cache
reuse, which guarantees the cached permute structure is still
valid. Default false keeps direct callers
(factorize_multifrontal_supernodal_with_workspace used without
Solver) on the canonical from-triplets path. NOTE: the parallel
driver (the default on the large matrices this targets) does not
engage the cache regardless of this flag — it always rebuilds via
permute_csc_values; closing that gap is the open N3 facet tracked
in dev/decisions.md.
Implementations§
Source§impl NumericParams
impl NumericParams
Sourcepub fn with_bk(bk: BunchKaufmanParams) -> Self
pub fn with_bk(bk: BunchKaufmanParams) -> Self
Construct a NumericParams from a BunchKaufmanParams,
using the default scaling strategy. Convenience for
callers that only customize BK behavior. The supplied bk
is used verbatim — no pivot_threshold override is applied,
in contrast to Default::default().
Trait Implementations§
Source§impl Clone for NumericParams
impl Clone for NumericParams
Source§fn clone(&self) -> NumericParams
fn clone(&self) -> NumericParams
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for NumericParams
impl Debug for NumericParams
Source§impl Default for NumericParams
impl Default for NumericParams
Source§fn default() -> Self
fn default() -> Self
Sparse-multifrontal default. Sets bk.pivot_threshold = 1e-8
to match MA27’s cntl[1] default, which is also Ipopt’s
ma27_pivtol default. This activates the column-relative
pivot rejection (and downstream rook rescue / delayed
pivoting) on rank-deficient KKT-augmented systems while
staying conservative enough not to reject legitimate pivots
on Identity-scaled (un-equilibrated) matrices that consumers
like ripopt feed in directly.
BunchKaufmanParams::default() (the dense entry point used
directly by dense::factor::factor) intentionally stays at
pivot_threshold = 0.0 per the 2026-04-13 dense-vs-sparse
split: dense has no delayed-pivoting / rook-rescue
infrastructure to land rejected pivots in. See
dev/decisions.md:325-344 and
dev/research/issue-2-kkt-pivot-default.md.
Why 1e-8 instead of the SSIDS/MUMPS canonical 0.01:
0.01 was validated on MC64-equilibrated matrices where
|d| >= 0.01 * col_max rejects pivots that are tiny relative
to a normalized column. ripopt’s KKT path runs Identity
scaling (it owns scaling at a higher layer to preserve the
inertia signal — see ripopt feral_direct.rs:84-91), so the
threshold fires on raw-value ratios that have not been
equilibrated. 1e-8 matches Ipopt’s reference choice for
exactly this configuration; the in-tree sparse callers that
run with MC64/InfNorm scaling continue to override
explicitly to 0.01.
Issue #2 surfaced the gap: ripopt and other consumers that
build NumericParams::default() were inheriting 0.0 (via
BunchKaufmanParams::default()), which silently disabled
every saddle-point rescue path on rank-deficient KKT-augmented
LS-init systems and caused exact-zero multipliers on
non-structurally-zero rows.
Auto Trait Implementations§
impl Freeze for NumericParams
impl RefUnwindSafe for NumericParams
impl Send for NumericParams
impl Sync for NumericParams
impl Unpin for NumericParams
impl UnsafeUnpin for NumericParams
impl UnwindSafe for NumericParams
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more