Skip to main content

NumericParams

Struct NumericParams 

Source
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: BunchKaufmanParams

Dense BK kernel parameters.

§scaling: ScalingStrategy

Global symmetric scaling strategy applied at the start of numeric factorization.

§small_leaf: SmallLeafBatch

Phase 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: bool

Opt-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: bool

When 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: bool

Opt-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: bool

When 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: bool

Issue #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

Source

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

Source§

fn clone(&self) -> NumericParams

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for NumericParams

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for NumericParams

Source§

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.