Add LP format reader; accept .lp wherever .mps is accepted#1120
Conversation
|
/ok to test 870a37f |
| if (lower == "such" && name_equals_ci(t2, "that")) return true; | ||
| if (lower == "st" || lower == "s.t.") return true; | ||
| if (lower == "lazy" && name_equals_ci(t2, "constraints")) return true; | ||
| if (lower == "user" && name_equals_ci(t2, "cuts")) return true; |
There was a problem hiding this comment.
We should not support "user cuts"
There was a problem hiding this comment.
They're not supported; added a test to confirm. Recognizing the section here allows us to print a more helpful error message, I believe.
|
/ok to test cf03235 |
|
/ok to test 003ecff |
| // Purely linear term inside the brackets — permitted as long as the | ||
| // surrounding /2 convention is respected (the linear term is scaled | ||
| // the same way as the quadratic ones). | ||
| out_linear.push_back({i1, sign * coeff}); |
There was a problem hiding this comment.
I was skimming this to see if there was anything of interest for JuMP's reader. We don't support linear terms inside [].
There was a problem hiding this comment.
Yeah sounds like there's no reason for us to allow this.
| // Require the "/ 2" suffix after a quadratic objective expression. | ||
| // Without it there is no ambiguity-free way to tell whether the user | ||
| // meant /2 and forgot vs. intended bare coefficients, so we enforce the | ||
| // stricter form. |
There was a problem hiding this comment.
Multiple solvers treat the ]/2 as optional in the objective. I don't remember if I just copied them, or if there were some common instances where this happened.
There was a problem hiding this comment.
Agreed multiple solvers treat ]/2 as optional in the objective, but it seems scary to me. CPLEX says it's required, IIRC. I'd rather disallow this unless there's a good reason otherwise.
There was a problem hiding this comment.
CPLEX says it's required
Oh, they all say it's required. But the files that they can actually read depart quite a lot from the written spec.
|
|
||
| // A negative upper bound requires an explicitly stated lower bound, | ||
| // otherwise the default lower of 0 would collide with the upper and make | ||
| // the variable silently infeasible. Flag this at parse time. |
There was a problem hiding this comment.
Hmm. I think if the upper bound is negative, we assume that the lower bound is -Inf. But I see now that Gurobi flags the model as infeasible.
b277e5e to
98f9626
Compare
|
/ok to test 98f9626 |
cuOptReadProblem, cuopt_cli, the Python ParseLp() wrapper, and the self-hosted client now dispatch on the input filename: a case-insensitive ".lp" suffix routes to a new LP parser; everything else (including .mps, .mps.gz, .mps.bz2, and extensionless inputs) continues to use parse_mps. The LP parser supports LP, MIP, and QP problems in the conventional LP dialect used by most commercial solvers (not the lpsolve variant). SOS, PWL, semi-continuous, user-cut, and general-constraint sections raise a ValidationError rather than silently mis-parsing. Quadratic-constraint support (QCMATRIX) remains MPS-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Miles Lubin <mlubin@nvidia.com>
|
/ok to test 92ffa8f |
What
A new LP-format parser sits alongside the existing MPS parser. Every
cuOpt entry point that takes an input file (the
cuopt_cliCLI,cuOptReadProblemin the C API, the PythonParseLp/ParseMpswrappers, and the self-hosted client) now accepts LP files via a
case-insensitive extension dispatcher:
.lp,.lp.gz,.lp.bz2.mps,.mps.gz,.mps.bz2,.qps,.qps.gz,.qps.bz2The unified entry point is
parse_problem<i_t, f_t>(path)incuopt/linear_programming/io/parser.hpp.parse_lpandparse_lp_from_string(string-input counterpart, mirroringparse_mps_from_string) are exposed alongside their MPS siblings.LP format scope
The parser accepts the conventional LP dialect implemented by most
commercial optimization solvers (not the lpsolve variant):
Semi-Continuoussection; eachlisted variable requires a finite upper bound.
<=only. Quadratic terms appearinside
[ ... ]. The convention differs between objective andconstraint:
/ 2(LP file uses the0.5 x^T Q xconvention)./ 2(coefficientsat face value,
x^T Q x).SOS constraints, piecewise-linear objectives, general constraints, and
user cuts raise a clear
ValidationError.Compressed inputs (
.lp.gz,.lp.bz2) are handled by the samedlopen-based zlib / libbz2 path that already serves
.mps.gz/.mps.bz2; the helper was extracted tocpp/src/io/file_to_string.{hpp,cpp}.Notable cleanups along the way
cython_mps_parser.{hpp,cpp}→cython_parser.{hpp,cpp}(now exposesboth
call_parse_mpsandcall_parse_lp).mps_parser_test.cpp→parser_test.cpp(consolidated; covers bothparsers and the dispatcher).
CUOPT_MPS_FILE_ERRORsubstring checkgeneric-ified from "MPS file" → "input file".
Tests
parser_test.cppcovering MPS, LP,semi-continuous, quadratic objective, quadratic constraint, the
extension dispatcher, and unsupported-section rejection. Compressed
LP fixtures (
good-mps-1.lp.{gz,bz2}) exercise the shareddecompression path.
c_api.read_lp_file_by_extensionconfirmscuOptReadProblemdispatches to the LP parser by extension.test_parse_lp_basic,test_parse_lp_rejects_unsupported_section,test_parse_lp_and_parse_mps_agree_on_trivial_problem.Docs
User-facing docs (
cuopt-cli,cuopt-c/lp-qp-milp) state the supportedextensions and link to the API reference. The full LP-format spec
(scope, conventions, rejections) lives in the
parse_lp/parse_lp_from_stringdocstrings inparser.hppand theParseLpdocstring in
parser.py.Size
44 files changed (20 added, 21 modified, 1 deleted, 2 renamed); ~5.6k
insertions / ~1.7k deletions, the bulk being the new LP parser
(
lp_parser.cpp, ~1.1k lines) and consolidated tests (parser_test.cpp).