Skip to main content

Module nl_quadratic

Module nl_quadratic 

Source
Expand description

Degree-≤2 recognition over an Expr DAG.

This is the classifier’s “is this row a quadratic, and which one?” question, answered once and reused. pounce-cli’s dispatch owns the routing decision (which ProblemClass, which solver); what lives here is only the algebra, so the consumers that are not the CLI — NlTnlp’s constant-structure evaluation, the parse-time recognizer, the QcqpProblem extractor — can reach it without depending on the command-line driver. It moved out of pounce-cli/src/dispatch.rs in Q3 of the #588 series; see dev-notes/quadratic-structure-exploitation.md.

§Two properties this module is built around

It is iterative. The walk carries its own work stack rather than recursing, because the trees it is handed are not shallow: a .nl writer that emits o0 (binary +) chains for a long sum — Pyomo does — produces a left-deep Add tree one level per term. The recursive predecessor aborted the process somewhere between 4 000 and 6 000 terms on a 2 MB thread (which is what a test gets, and where the crash was first reproduced) and between 16 000 and 24 000 on the CLI’s 8 MB main thread. A stack overflow is an abort, not an error return, so the depth a recognizer survives must not depend on which thread called it.

It is worth knowing what this does not fix: nl_reader’s parser recurses too, with a fatter frame — it gives out at ~6 000 on that same 8 MB thread — so a deep .nl file still fails to load, and it fails before reaching this module. What is fixed is every path where the tree is already built (NlProblem::from_expressions, a model handed across threads) and the ceiling this module used to impose on the parser’s successor.

It never allocates per monomial. The predecessor keyed monomials on BTreeMap<Vec<usize>, f64>, so every term cost a heap allocation and every merge cloned one (entry(m.clone())). A degree-≤2 form has only three shapes of term, so Quad2 stores them in three fields with inline keys and the allocation disappears. The same change removes the O(N²) accumulation on Add chains: the old add re-scanned the whole accumulated map for zeros on every merge, which is quadratic down a left-deep chain. Zeros can only appear where a merge touched, so that is all this one looks at.

Structs§

FactoredQuadratic
A degree-2 form kept as Σ wₖ(bₖᵀx + dₖ)² + aᵀx + c — the shape the .nl writer wrote, rather than its expansion about the origin.
Quad2
A polynomial of total degree ≤ 2 in its own shape: a constant, the linear coefficients keyed by variable, and the quadratic coefficients keyed by the (i ≤ j) variable pair.
SquaredAffine
One w·(bᵀx + d)² term of a FactoredQuadratic.

Functions§

analyze_quadratic
Attempt to read an expression as a polynomial of total degree ≤ 2 and return its Hessian (constant, since the form is quadratic). None if the expression is not provably quadratic ⇒ treat as general nonlinear.
analyze_quadratic_full
Like analyze_quadratic but also returns the degree-1 (linear) coefficients and the degree-0 (constant) term of the form: (Hessian, [(var, coef), …], constant).
is_expanded_quadratic
Is this expression already a flat sum of monomials — that is, would reading it as ½xᵀHx + aᵀx + c reproduce the same additions the writer wrote, rather than algebraically expanding something it did not?
is_monomial_expr
Is this expression a single monomial — the leaf shape is_expanded_quadratic admits on its sum spine?
is_trivially_zero
True if the expression is the literal constant zero the .nl reader uses for “no nonlinear part”.
quad_form_readout
The (Hessian, linear, constant) read-out of an already-recognized form — the second half of analyze_quadratic_full, split out so a caller holding a Quad2 the parser produced (gh #588, Q5) reaches the identical numbers by the identical route. There is exactly one conversion in the crate; a second one is a second thing to keep in step.
recognize_expr
Lower an Expr to a Quad2, or None if it contains anything the recognizer cannot prove is a degree-≤2 polynomial (transcendental ops, division by a non-constant, Pow with an exponent ∉ {0, 1, 2}, products of degree > 2, external calls, comparisons, if-then-else, min/max, …). None ⇒ treat as general nonlinear.
recognize_factored_quadratic
Recognize a degree-2 body as a sum of squared affine forms plus degree-≤1 leftovers, keeping the squares factored (gh #673).

Type Aliases§

QuadForm
Full quadratic read-out: (Hessian, [(var, linear coef), …], constant). The linear and constant parts are the pieces AMPL/Pyomo fold into the nonlinear objective tree (see analyze_quadratic_full).
QuadHessian
The symmetric Hessian of a quadratic form, stored as a sparse upper- triangular (i ≤ j) map of (i, j) -> ∂²/∂xᵢ∂xⱼ. Empty means the expression is (at most) linear.