Repository navigation
perf(highs): build every model with one row-wise passModel call - #1128
Conversation
Pass the cached CSR matrix to a single passModel call for LPs and MILPs alike. HiGHS stores it column-wise; converting it in Python would hold a second copy of the matrix. passModel always stores an integrality array, for which the HiGHS LP writer emits empty bin/gen/semi sections that CBC, GLPK and COPT misread. to_file(io_api="mps") now strips the empty trailing sections after HiGHS writes an .lp file, rewriting only the file tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4nx8JFY4SxxVLJEByWw8z
Merging this PR will improve performance by 25.48%1 warning
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
to_file(io_api="mps") writes through HiGHS, which picks the format from the extension, so a .lp path silently produced an LP file. Raise a ValueError on any mismatch instead and drop the LP tail cleanup, which was only needed for that case. The set_files tests now name the problem file after the io_api. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4nx8JFY4SxxVLJEByWw8z
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4nx8JFY4SxxVLJEByWw8z
Alternative to #1118.
Changes proposed in this Pull Request
Note
The following content was generated by AI.
Like #1118, this builds the HiGHS model in bulk, but it uses one row-wise
passModelcall for every model, LP or MILP. The cached CSR matrix is passed as is (no.tocsc()copy). HiGHS converts it to column-wise storage insidepassModel.passModelneeds a full-length integrality array, even for a pure LP. Because of that array, HiGHS's LP writer emits emptybin/gen/semisection headers, and CBC, GLPK and COPT misread them. This broketest_set_files[*-mps-*]in #1118. Those tests callsolve(io_api="mps", problem_fn="problem.lp").io_api="mps"makesto_filewrite through HiGHS (h.writeModel(fn)), and HiGHS picks the file format from the extension, so the "MPS" export was in fact an LP file. Withoutproblem_fn, linopy names the temp file.mps, so normal solves never hit this.#1118 avoids the array for LPs by using
addCols+addRows. This PR keepspassModeland removes the mismatch instead:to_file(and thereforesolve) raisesValueErrorwhen the file extension doesn't matchio_api:.lpfor"lp"/"lp-polars",.mpsfor"mps". Before, a mismatched name silently produced the other format.test_set_filesandtest_set_files_and_keep_filesnow name the problem file after theio_api.to_highspy(m).writeModel("x.lp")directly still gets them; HiGHS reads that file back fine, and the proper fix is upstream in the HiGHS LP writer.Row-wise vs column-wise input: passing CSC builds about 20% faster on the largest LP, but roughly doubles peak memory (rolling: 86 MB vs 40 MB growth), so this PR passes the matrix row-wise.
Results on the
test_to_solverHiGHS benchmarks, measured locally, best of ≥7 rounds:6b8cc1a). The densest LP (rolling, 4M nonzeros) is 5–9% slower than master. That's probably becausepassModelconverts to column-wise up front, whileaddRowsdefers it to solve time.Benchmark and test details
test_to_solver[highs-*], best-of-7 wall time per case, on one cloud VM, all three commits run back to back:6b8cc1a(ms)Peak RSS growth while building the HiGHS model (MB above the model already in memory, fresh process per cell):
6b8cc1aTests:
test_to_file_rejects_mismatched_extension(mps+.lp,lp+.mps,lp-polars+.mps).pytest test -q: 6389 passed, 87 skipped.ruff checkandruff formatare clean.mypy linopyshows the same error count as master in this environment.Checklist
AGENTS.md).doc.doc/release_notes.rstof the upcoming release is included.https://claude.ai/code/session_01F4nx8JFY4SxxVLJEByWw8z
Generated by Claude Code