Skip to content

chore(engine): a constraint's rows are numbered when the model is frozen rather than while it is built - #2020

Closed
FBumann wants to merge 2 commits into
mainfrom
claude/rows-numbered-at-freeze
Closed

FBumann wants to merge 2 commits into
mainfrom
claude/rows-numbered-at-freeze

Conversation

@FBumann

@FBumann FBumann commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Prompt: Discuss the naming and possible refactroings this points us to

Prompt: DO it. Do it as stacked PRs

Note

The following content was generated by AI.

Each constraint builds on rows of its own from 0 and drops its rows with no terms there. Assembly.run moves every block onto the model's rows once. No build step carries a start or a running row count. No change in what is built, and no measurable change in build time.

What changed, mutation table, method

Why. Discussion of #2019 showed where its complexity came from. A constraint's rows were numbered when it was labelled, and a block could then drop rows with no terms, so every later block's numbers were provisional. #2019 made that visible as a shift step. This PR is the base #2019 is rebuilt on.

What.

  • _build_constraint returns its labelled block, rows, matrix share and quadratic share, all on its own rows. labels.frame(..., 'row', 0, ...), _dense_row(dims) and _drop_termless_rows no longer take a start.
  • run() computes each block's offset, sets self.constraints[name] with the block's frame moved lazily, and stacks rows, the matrix and the quadratic matrix through _on_model_rows.
  • _on_model_rows moves only a block whose offset is not zero, by adding to its row Series, so a model with one constraint pays nothing. A first version moved every entry through one polars query and cost 10.5 ms per build on dispatch/m.
  • Assembly.n_rows is gone; the row count is computed at freeze. Variables keep their numbering up front, because constraint terms need their final column numbers and a variable never loses a column after labelling.

Mutation table, taken by hand: tools.mutate cannot express an edited expression. Clean tree, restored with git checkout --, __pycache__ dropped before and after each run.

mutation result
the labelled block's offset (frame.with_columns(pl.col('row') + offset) → frame) caught, 6 failed
the stacked tables' offset (the shift in _on_model_rows removed) caught, 734 failed

Method. Engine.build from tidied sources, the median of 5–15 builds per process, four alternating process runs per arm. 4-vCPU cloud container. Base: main's src/ at b646d61.

model main ms this ms ratio
dispatch/3xs, dispatch/xs 15.4, 18.9 16.3, 19.5 1.06, 1.03
nodal/2xs 21.8 20.8 0.96
network/2xs 54.5 48.1 0.88
osemosys_utopia 110.8 100.0 0.90
pypsa_unit_commitment 76.4 79.9 1.05
genx_piecewise_fuel 190.7 203.0 1.06
pypsa_storage 87.7 83.0 0.95
dispatch/m, network/m 99.6, 333.9 96.5, 320.5 0.97, 0.96

Every cell is inside its runs' spread.

Gates. pixi run lint, pyrefly check on src/, ruff format --check ., pixi run test: 5593 passed.

Not done. Variables are still numbered while they are built. The batching itself is #2019, rebased onto this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_015YXphRT4nkmgh8LgdYMgCj


Generated by Claude Code

…zen rather than while it is built

Each constraint builds its labelled block, its rows and its shares on rows
of its own from 0, and drops its rows with no terms there. run() moves every
block onto the model's rows once, so no build step carries a start or a
running row count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YXphRT4nkmgh8LgdYMgCj
…an offset, by adding to its row column

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YXphRT4nkmgh8LgdYMgCj
@codspeed

codspeed Bot commented Oct 13, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 104 untouched benchmarks
⏩ 307 skipped benchmarks1


Comparing claude/rows-numbered-at-freeze (77de082) with main (b646d61)

Open in CodSpeed

Footnotes

  1. 307 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

FBumann commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Note

The following content was generated by AI.

Closed and folded into #2019. On its own this change does not remove code. assembly.py has 382 code lines here and 376 on main, not counting docstrings, blank lines and comments. With the no-terms branch also removed it would have 378. #2019 carries these commits unchanged, and its description now covers both.


Generated by Claude Code

Sign up for free to 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.

2 participants