Skip to content

fix(differential): a rung's projection names its quantities in the file's order, so a tree nobody changed stops going red - #1469

Merged
FBumann merged 1 commit into
mainfrom
fix/projection-order
Aug 31, 2026
Merged

FBumann merged 1 commit into
mainfrom
fix/projection-order

Conversation

@FBumann

@FBumann FBumann commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Prompt: "pypsa parity failed" — run 33408075907, on main.

Note

The following content was generated by AI.

main is red and this fixes it. The PyPSA parity job's last step — the committed
certificate, projections and tables are what this tree produces
— failed on six rung
projections that no commit had touched. They differed only in the order of two named
expressions:

 expressions:
-  Store_energy_carried_in:
-    ...
   StorageUnit_charge_carried_in:
     ...
+  Store_energy_carried_in:
+    ...

Cause: a set decided what order to write

projection.py's walk over named expressions, added in #1466, was driven by a set:

frontier = {n for n in survived if n in mentioned}
while frontier:
    expressions |= {n: survived[n] for n in frontier}

Python seeds string hashing per process, so that set iterates differently on every run:

seed=1 -> ['Link_p_nom_effective', 'StorageUnit_charge_carried_in', 'Store_energy_carried_in', ...]
seed=2 -> ['Store_energy_carried_in', 'StorageUnit_charge_carried_in', 'Link_p_nom_effective', ...]
seed=3 -> ['Generator_previous_status', 'StorageUnit_charge_carried_in', 'Link_p_nom_effective', ...]

The projection is diff-gated, so a diff-gated artifact was being written in a random order.
It passed on #1466 by luck and failed on the very next run.

Membership sets are fine and the function keeps several — mentioned, dead, dims are
only ever queried, and every emitted collection is built by iterating the raw file and
filtering by them. What may not happen is a set choosing the order of what is written, which
is the one place this got it wrong. The walk now goes in the file's own declaration order.

Verified

  • The gate, run as CI runs it: parity exits 0 and git diff --exit-code -- differential
    is clean afterwards.
  • Determinism measured, not argued — regenerating under PYTHONHASHSEED 5, 6 and 7 gives
    one tree hash, cb2a253006b2b3b7, three times.
  • pixi run lint / format-check clean; pyrefly 0 errors.
  • pixi run test — 3465 passed, 251 skipped, 94 failed, every failure xpress (the expired
    local licence, matched by file and parameter id).
  • The six projections and the pages tools.ladder prints from them are regenerated; the
    certificate and tables are unchanged, this being an ordering fix only.

Mutation table

mutation result
the walk ordered by the file rather than by a set (projection.py) caught

test_a_projection_orders_its_names_the_same_whatever_the_hash_seed emits a projection in
four subprocesses under different PYTHONHASHSEED values and asserts one answer, then asserts
that answer is the file's order. Restoring the set-driven loop turns it red; it is green with
the fix. Subprocesses because one interpreter has one seed and cannot see the difference — the
reason the whole suite missed this.

Deliberately not done

  • The membership sets stay sets. They are queried, not iterated into output, and turning
    them into lists would cost lookups to buy nothing.
  • No other generator was audited for the same fault; if it is worth a sweep it is worth its
    own issue.

…le's order, so a tree nobody changed stops going red

The walk that collects named expressions was driven by a set, and Python seeds
string hashing per process, so the emitted YAML reordered itself between runs
and the committed projection failed its own diff gate on `main`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZbCoSM1Ah7YWg6K4Ps5sU
@FBumann
FBumann enabled auto-merge (squash) August 31, 2026 17:55
@codspeed

codspeed Bot commented Aug 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 16 untouched benchmarks
⏩ 42 skipped benchmarks1


Comparing fix/projection-order (d1f4979) with main (126f8c0)2

Open in CodSpeed

Footnotes

  1. 42 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. ↩

  2. No successful run was found on main (8e73c68) during the generation of this report, so 126f8c0 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@FBumann
FBumann merged commit 1603efa into main Aug 31, 2026
12 checks passed
@FBumann
FBumann deleted the fix/projection-order branch August 31, 2026 17:58
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.

1 participant