perf: use single-step sparse matrix slicing in MatrixAccessor#618
perf: use single-step sparse matrix slicing in MatrixAccessor#618MaykThewessen wants to merge 3 commits intoPyPSA:masterfrom
Conversation
Replace double-slicing pattern A[clabels][:, vlabels] with single-step A[np.ix_(clabels, vlabels)] in MatrixAccessor.A and MatrixAccessor.Q. The double-slice creates an intermediate sparse matrix (selecting rows first, then columns), which allocates temporary storage proportional to the full matrix. np.ix_() performs both row and column selection in a single operation, avoiding the intermediate allocation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers the code paths optimised by these PRs: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add for label string concatenation - PyPSA#618 sparse matrix slicing in MatrixAccessor.A - PyPSA#619 numpy solution unpacking Reproduces benchmark results on PyPSA SciGrid-DE (24–500 snapshots) and a synthetic model. Supports JSON output and --compare mode for cross-branch comparison. Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json --label "after" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Added Reproduce with: The |
9a82ea3 to
7e6c586
Compare
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
a3cd22c to
0a79b2a
Compare
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 path python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
874dbe0 to
0a79b2a
Compare
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds benchmark/scripts/benchmark_matrix_gen.py covering all four performance code paths: - PyPSA#616 cached_property on MatrixAccessor (flat_vars / flat_cons) - PyPSA#617 np.char.add label string concatenation - PyPSA#618 single-step sparse matrix slicing - PyPSA#619 numpy dense-array solution unpacking Reproduce with: python benchmark/scripts/benchmark_matrix_gen.py -o results.json python benchmark/scripts/benchmark_matrix_gen.py --include-solve # PR PyPSA#619 python benchmark/scripts/benchmark_matrix_gen.py --compare before.json after.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
for more information, see https://pre-commit.ci
|
@MaykThewessen Your benchmark doesn't really isolate the influence of you change. With contiguous [0..N] labels (no gaps, no permutation), A[rows][:, cols] and A[np.ix_(rows, cols)] do the same work. The double-slice creates one intermediate, but for an identity index on a CSC matrix, both paths are equally fast. If you provide more concise evidence of this actually speeding up i can reopen the PR of course |
Benchmark Results: master vs PR #618Tested on actual linopy implementation using PyPSA SciGrid-DE. Each phase calls the real Setup: Python 3.14.3, numpy 2.4.3, Apple M-series (arm64), macOS, 5 repeats (best-of). Matrix Generation
End-to-End Solve (HiGHS direct)
Summary: The single-step sparse slicing shows 1.1–1.5x improvement on matrix generation at medium sizes, and is the only PR that shows measurable end-to-end solve improvement (1.06–1.12x). The A_matrix phase benefit flattens at 500 snapshots where the sparse matrix itself dominates. Benchmark methodology
|
Summary
Replace the double-slicing pattern in
MatrixAccessor.AandMatrixAccessor.Qwith single-stepnp.ix_()indexing.Before:
After:
Motivation
The double-slice
A[rows][:, cols]creates an intermediate sparse matrix after the first slice, then slices again.np.ix_()expresses the row+column selection as a single operation, avoiding the intermediate allocation. For large constraint matrices (~1.38M rows × ~593K cols), this reduces memory churn.Context
See #198 (comment) — item 4 in the priority list.
Test plan
test_matrices.py— all 4 tests pass (shape validation, masked models, duplicated variables, float coefficients)test_io.py::test_to_highspy— passestest_optimization.pyhighs-direct — 24/25 pass (one pre-existing failure)🤖 Generated with Claude Code