Skip to content

perf: lp write speed - #562

Closed
FBumann wants to merge 11 commits into
PyPSA:masterfrom
fluxopt:perf/lp-write-speed
Closed

perf: lp write speed#562
FBumann wants to merge 11 commits into
PyPSA:masterfrom
fluxopt:perf/lp-write-speed

Conversation

@FBumann

@FBumannFBumann commented Jan 31, 2026

Copy link
Copy Markdown
Collaborator

Changes proposed in this Pull Request

Optimize the LP file writing pipeline, achieving ~40-60% speedup on m.to_file() across synthetic and realistic PyPSA models.

Benchmark results

Measured with dev-scripts/benchmark_lp_writer.py (10 iterations, warmup). Before/after run back-to-back on same machine.

basic_model (2 × N² vars, 2 × N² constraints):

NVarsConsBeforeAfterSpeedup
505,0005,00024ms10ms56%
10020,00020,00042ms18ms57%
20080,00080,000138ms51ms63%
500500,000500,000732ms251ms66%
10002,000,0002,000,0003091ms1008ms67%

knapsack_model (N binary vars, 1 constraint with N terms):

NVarsBeforeAfterSpeedup
1001005.6ms4.5ms20%
1,0001,0007.8ms5.5ms29%
10,00010,00011.8ms8.5ms28%
50,00050,00036.6ms23.6ms36%
100,000100,00063.1ms37.3ms41%

PyPSA SciGrid-DE (realistic power system, 585 buses, 1423 generators, 852 lines):

ModelVarsConsBeforeAfterSpeedup
scigrid-de (24 snapshots)59,640142,968294ms262ms11%

Per-commit impact (basic_model)

Cumulative impact measured on basic_model (N=100 → 20k vars, N=500 → 500k vars):

CommitDescriptionN=100N=500Δ N=500
BaselineOriginal code44ms722ms
ccb9cd2Replace concat+sort with join in Constraint.to_polars(), remove group_by validation in constraints_to_file()41ms613ms-15%
aab95f5Skip group_terms when _term=1, build short DataFrame with numpy instead of xarray broadcast, fast sign column via pl.lit()29ms516ms-16%
7762659Skip group_terms in LinearExpression.to_polars() when no duplicate vars (objective speedup)28ms508ms-2%
bdbb042Pre-cast rhs to String in with_columns instead of inside concat_str33ms483ms-5%
44b115fUse Polars streaming engine for concat_str + write_csv with fallback to eager36ms390ms-19%
Total36ms390ms-46%

Per-commit impact (PyPSA SciGrid-DE 240h — 596,400 vars, 1,429,680 cons)

Measured with 2 warmup iterations + 8 timed iterations on the extended SciGrid-DE model (240 snapshots).

CommitDescriptionTimeΔ cumulativeΔ vs previous
BaselineOriginal code2724ms ± 121ms
ccb9cd2Replace concat+sort with join1926ms ± 11ms-29%-29%
aab95f5Skip group_terms, numpy short DF1560ms ± 29ms-43%-19%
7762659Skip group_terms in objective1527ms ± 53ms-44%-2%
bdbb042Pre-cast rhs to String1576ms ± 21ms-42%+3% (regression)
44b115fPolars streaming engine1237ms ± 27ms-55%-21%

Note: bdbb042 shows a small +3% regression on the PyPSA model while helping the basic model. The streaming engine commit recovers and extends the gains.

Benchmark script

Run with python dev-scripts/benchmark_lp_writer.py. Tests basic_model, knapsack_model (up to 100k vars), and PyPSA SciGrid-DE.

Checklist

  • Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in doc.
  • Unit tests for new features were added (if applicable).
  • A note for the release notes doc/release_notes.rst of the upcoming release is included.
  • I consent to the release of this PR's code under the MIT license.

FBumannand others added 11 commits January 31, 2026 17:24
Replace the vertical concat + sort approach in Constraint.to_polars()
with an inner join, so every row has all columns populated. This removes
the need for the group_by validation step in constraints_to_file() and
simplifies the formatting expressions by eliminating null checks on
coeffs/vars columns.
…r short DataFrame
- Skip group_terms_polars when _term dim size is 1 (no duplicate vars)
- Build the short DataFrame (labels, rhs, sign) directly with numpy
instead of going through xarray.broadcast + to_polars
- Add sign column via pl.lit when uniform (common case), avoiding
costly numpy string array → polars conversion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…e vars
Check n_unique before running the expensive group_by+sum. When all
variable references are unique (common case for objectives), this
saves ~31ms per 320k terms.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…_str
Move the rhs float→String cast into the with_columns step so it runs
once unconditionally rather than inside a when().then() per row.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add realistic PyPSA SciGrid-DE network model and knapsack model
to the benchmark script alongside the existing basic_model.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace np.unique with faster numpy equality check for sign uniformity.
Eliminate redundant filter_nulls_polars and check_has_nulls_polars on
the short DataFrame by applying the labels mask directly during
construction.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@FBumann

Copy link
Copy Markdown
CollaboratorAuthor

Closed in favor of #564

@FBumannFBumann closed this Jan 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@FBumann