You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
discopt depends on the pounce-solver wheel, which ships pounce-nl — "AMPL .nl reader, reverse-mode AD tape, and TNLP evaluator" — exposing read_nl, parse_nl_text and build_nl_problem at the Python top level. discopt separately maintains crates/discopt-core/src/nl_parser.rs (3,527 lines) for the same file format.
Two implementations of the same format spec live in the same dependency closure, in two projects owned by the same author. Format-level bugs, dialect quirks and opcode coverage have to be fixed twice.
Important qualification — they are NOT drop-in duplicates
This was my first reading and it is wrong; recording the correction so nobody acts on it.
structure: McCormick envelopes, FBBT, term classification, convexity detection, presolve
You cannot build a relaxation from an evaluable TNLP. There is no NlProblem → ModelRepr lowering and there cannot be one — the structure is gone by then. So replacing nl_parser.rs with pounce-nl is not an option, and any plan that assumes it is should stop here.
discopt's parser additionally handles two things NlProblem does not surface:
complementarity (type-5) rows — parse_nl_with_complementarity recovers pairs from the r segment (nl_parser.rs:738-750), consumed by the MPEC path;
suffixes (nl_parser.rs:1800).
What is actually shared
Only the bottom layer: the .nldecoder — header parsing, segment dispatch, opcode decoding, the .col/.row sidecar convention. Above that, one side builds an arena and the other an evaluable tape. That is a legitimate divergence, not redundancy.
So the question this issue exists to answer is narrow: is the shared decoding layer large enough, and stable enough, to be worth factoring into one crate with two builders on top?
Why this is an audit, not a port
The payoff is maintenance-only — no performance, no capability. Against that:
nl_parser.rs is battle-tested against the ~4,800-instance MINLPLib snapshot and the 66-instance in-repo corpus. Churning it risks the corpus for no user-visible gain.
Cross-crate edges have a measured cost in this repo. Cargo.toml records feral 0.11.2 → 0.11.3 silently regressing set-covering from ~3 s to a >30 s timeout on a patch bump (an LU refactor going quadratic→cubic in row count), now guarded by test_setcover_lp_regression.py. Every new edge also inherits the bound-neutrality bump protocol (the 49/49 bit-identical panel).
A plausible and acceptable outcome of this issue is "keep both, document why" — which is still worth having written down, because the overlap will otherwise be rediscovered.
Tasks
Measure the actual shared surface: how much of nl_parser.rs is format decoding vs ModelRepr construction? (A line-level split, not an estimate.)
Compare opcode/segment coverage against pounce-nl — does either handle cases the other refuses? Complementarity and suffixes are known discopt-only; check the reverse direction too (AMPLFUNC external-function resolution is a known pounce-nl feature).
Decide and record: factor a shared decoder crate, or keep both with the rationale documented in docs/dev/.
If factoring: the corpus is the gate — every one of the 66 in-repo instances must parse to a byte-identical ModelRepr, and complementarity/suffix handling must be preserved, before anything is deleted.
Broader context: this came out of asking whether discopt's Rust components (notably lp/simplex/, 11,384 lines of warm-started dual simplex, which POUNCE lacks) belong in the POUNCE crate family. That is a larger question and is deliberately not in scope here.
The finding
discopt depends on the
pounce-solverwheel, which shipspounce-nl— "AMPL.nlreader, reverse-mode AD tape, and TNLP evaluator" — exposingread_nl,parse_nl_textandbuild_nl_problemat the Python top level. discopt separately maintainscrates/discopt-core/src/nl_parser.rs(3,527 lines) for the same file format.Two implementations of the same format spec live in the same dependency closure, in two projects owned by the same author. Format-level bugs, dialect quirks and opcode coverage have to be fixed twice.
Important qualification — they are NOT drop-in duplicates
This was my first reading and it is wrong; recording the correction so nobody acts on it.
pounce.read_nlNlProblem—objective,gradient,constraints,jacobian(_structure),hessian(_structure),x_l/x_u,x0, namesdiscoptparse_nl_fileModelRepr— the expression arenaYou cannot build a relaxation from an evaluable TNLP. There is no
NlProblem → ModelReprlowering and there cannot be one — the structure is gone by then. So replacingnl_parser.rswithpounce-nlis not an option, and any plan that assumes it is should stop here.discopt's parser additionally handles two things
NlProblemdoes not surface:parse_nl_with_complementarityrecovers pairs from thersegment (nl_parser.rs:738-750), consumed by the MPEC path;nl_parser.rs:1800).What is actually shared
Only the bottom layer: the
.nldecoder — header parsing, segment dispatch, opcode decoding, the.col/.rowsidecar convention. Above that, one side builds an arena and the other an evaluable tape. That is a legitimate divergence, not redundancy.So the question this issue exists to answer is narrow: is the shared decoding layer large enough, and stable enough, to be worth factoring into one crate with two builders on top?
Why this is an audit, not a port
The payoff is maintenance-only — no performance, no capability. Against that:
nl_parser.rsis battle-tested against the ~4,800-instance MINLPLib snapshot and the 66-instance in-repo corpus. Churning it risks the corpus for no user-visible gain.Cargo.tomlrecordsferal0.11.2 → 0.11.3 silently regressing set-covering from ~3 s to a >30 s timeout on a patch bump (an LU refactor going quadratic→cubic in row count), now guarded bytest_setcover_lp_regression.py. Every new edge also inherits the bound-neutrality bump protocol (the 49/49 bit-identical panel).A plausible and acceptable outcome of this issue is "keep both, document why" — which is still worth having written down, because the overlap will otherwise be rediscovered.
Tasks
nl_parser.rsis format decoding vsModelReprconstruction? (A line-level split, not an estimate.)pounce-nl— does either handle cases the other refuses? Complementarity and suffixes are known discopt-only; check the reverse direction too (AMPLFUNCexternal-function resolution is a knownpounce-nlfeature).docs/dev/.ModelRepr, and complementarity/suffix handling must be preserved, before anything is deleted.Related
discopt-solvebinary (.nl → certificate, zero Python) + apples-to-apples SCIP comparison #1212 (standalonediscopt-solvebinary + SCIP comparison) — that issue should not block on this one. It needsModelRepr, which only discopt's parser produces today. Worth noting there thatpounce-clialready solves.nlfiles, so the two CLIs are a second, separate overlap to think about.lp/simplex/, 11,384 lines of warm-started dual simplex, which POUNCE lacks) belong in the POUNCE crate family. That is a larger question and is deliberately not in scope here.