discopt.solvers.nlp_native#

Native POUNCE NLP solve — route node NLPs through POUNCE’s own AD.

The production node-NLP path drives POUNCE (a pure-Rust Ipopt port) through a Python callback proxy (_IpoptCallbacks → JAX NLPEvaluator): POUNCE asks for f/∇f/g/J/H once per IPM iteration and each request crosses the Rust→ Python→JAX boundary. POUNCE already parses .nl files into its own AD-backed problem (pounce.read_nl → NlProblem), so for a model that originated from (or can be emitted to) an .nl we can hand POUNCE the whole problem once and let it differentiate natively — zero JAX in the node loop.

Why the orderings line up (validated by tests/test_native_nlp_equivalence):

  • Variables — from_nl builds model._variables in .nl column order, and the JAX evaluator’s flat x is that same declaration order (dag_compiler._compute_var_offset). So for .nl-originated models the evaluator order is the .nl order: objective/gradient/Hessian agree to ~1e-15 under the identity map. A model emitted via to_nl is reordered into canonical AMPL order (nonlinear-first); for that case we recover the exact permutation from the writer and apply it on the way in/out.

  • Constraints — the .nl canonicalizes bodies (constant terms moved to the bound side, rows reordered) so its constraint representation differs from the evaluator’s. This is benign: POUNCE owns the constraints end-to-end from the .nl (same feasible region), and discopt only consumes the solution vector (variable-ordered) and the objective. The node solve overrides variable bounds only; constraint bounds are static and come from the .nl.

Objective sense: POUNCE always minimizes internally — base.objective, base.gradient and the solved obj_val are all reported in minimization sense even when base.minimize is False (a maximize .nl). The evaluator/B&B also work in minimization sense (the evaluator negates a maximize objective), so the two line up directly with no sign correction. base.minimize is metadata only.

Every base is validated numerically (objective + gradient at a probe point) before use; on any mismatch (or when POUNCE/AD is unavailable) the caller falls back to the JAX path, so correctness never depends on an unverified assumption.

Attributes#

Classes#

NativeNlpBase

A POUNCE-native NLP problem aligned to a discopt evaluator's variables.

Functions#

build_native_base(→ Optional[NativeNlpBase])

Construct a POUNCE-native problem aligned to evaluator, or None.

get_native_base(→ Optional[NativeNlpBase])

Return a cached native base for evaluator's model, building once.

solve_node_native(→ object)

Solve one node NLP natively; return an NLPResult (minimization sense).

Module Contents#

discopt.solvers.nlp_native.logger#
class discopt.solvers.nlp_native.NativeNlpBase#

A POUNCE-native NLP problem aligned to a discopt evaluator’s variables.

base is the parsed NlProblem (POUNCE’s own AD). perm maps a .nl column index to the evaluator’s flat variable index — perm[j] is the evaluator index of .nl column j — so x_nl = x_eval[perm] and x_eval[perm] = x_nl. perm is None means the identity map (the common from_nl case). No objective-sign correction is needed: POUNCE reports everything in minimization sense (see module docstring); base.minimize is metadata only.

base: pounce.NlProblem#
n: int#
perm: numpy.ndarray | None#
source: str#
to_nl_order(x_eval: numpy.ndarray) → numpy.ndarray#

Permute an evaluator-ordered vector into .nl column order.

to_eval_order(x_nl: numpy.ndarray) → numpy.ndarray#

Permute a .nl-ordered vector back into evaluator order.

variant(node_lb, node_ub, x0)#

Build a per-node NlProblem with tightened variable bounds.

Shares the parsed DAG / AD tapes with base (cheap); only the bound / start vectors are replaced. Inputs are in evaluator order; they are permuted into .nl order for POUNCE.

cleanup() → None#
discopt.solvers.nlp_native.build_native_base(evaluator, *, validate: bool = True) → NativeNlpBase | None#

Construct a POUNCE-native problem aligned to evaluator, or None.

Prefers the model’s source .nl (identity variable order). Falls back to emitting the model via to_nl (canonical reorder → recovered permutation). Returns None when POUNCE is unavailable, the model cannot be expressed as an .nl, or numeric validation fails — the caller then uses the JAX path.

discopt.solvers.nlp_native.get_native_base(evaluator) → NativeNlpBase | None#

Return a cached native base for evaluator’s model, building once.

Cached on the model and invalidated by the same structural fingerprint the evaluator cache uses, so a reformulated/edited model rebuilds its base. False is cached for a model that cannot use the native path, so we do not repeatedly re-attempt (and re-emit) a failing model.

The cache is shared across threads, which POUNCE supports as of pounce#477; the first base built in a process verifies that (see _CROSS_THREAD_OK) and the native path is disabled outright against a POUNCE that does not.

discopt.solvers.nlp_native.solve_node_native(nb: NativeNlpBase, x0, node_lb, node_ub, options) → object#

Solve one node NLP natively; return an NLPResult (minimization sense).