Conversation
Documentation build overview
49 files changed ·
|
Merging this PR will not alter performance
Comparing Footnotes
|
f40ceb7 to
ee17432
Compare
f2619fb to
8adb239
Compare
8adb239 to
c718674
Compare
c718674 to
2cb2aa2
Compare
Co-Authored-By: Claude <noreply@anthropic.com>
2cb2aa2 to
0088fa2
Compare
|
Note The following content was generated by AI. Reviewed at high effort and fixed. Four defects, all of them lane divergences — a file one lane built and the other refused, or built differently — which is the class that makes the differential tests stop being an oracle. Each was reproduced on both lanes before and after.
2 and 4 were one root cause. 1 is now refused rather than crashed. Each arm is its own frame and a quotient inverts one, so folding the arms together would be a join per arm to rebuild what the partition already guarantees. It is a 3 was the eager lane being stricter: Five regression tests added, four of them differential. Full suite 2983 passed, 323 skipped, 1 xfailed — the usual 10 gurobi/xpress-licence failures are the whole delta. Mutation table, re-takenThe guards changed, so this replaces the earlier table for the three that moved. Committed tree,
The fifth finding, not fixedThe review also flagged that Generated by Claude Code |
Note The following content was generated by AI. Not rebasable. This branch edits The design is on #711, which stays open. The branch is not deleted. |
Note
The following content was generated by AI.
Adopts energy-models/math-spec#36 —
expressions:gains acases:key: one quantity whose value varies by region, with aforeach:naming the frame the regions partition. Pinned at35d62ac.No longer stacked: #1230 merged as
3e018da, so this now targetsmaindirectly and carries one commit. It earlier absorbed the pin waypoint that was #1231, whose content #36 already contains.Still a commit pin, and still a draft: #36 is an open draft, and math-spec#33 (the partition check it stands on) is too. Only #31 has merged, released as
v0.0.0-alpha.9— which is whatmainpins today.The corpus model that wanted it
pypsa_mixed_cyclingexists because PyPSA'scyclic_state_of_chargeis a column, so one network runs both storage regimes at once. Its page already said the two regimes are "one rule and a different predecessor" — and then wrote that rule three times under complementary masks, because there was no way to say it once. Now there is:Three constraints become one. The old shape also needed a fourth idea to work: the
carriedblock's bareshiftvacates each seeded unit's first snapshot, so that row was dropped and a third block wrote it back, withdiagnostics().omissionsreporting the gap. Undercases:there is no dropped row — the arm that vacates the first position is the arm that does not claim it. Objective still 4800.0 against PyPSA's own recording.The generated page prints the definition once, and it composes with #44's upright/italic convention now on
main—\mathit{soc}is the solver's,\mathrm{cyclic}is given, andopening_levelis italic because its arms reach a variable (math-spec68ed8a8):What converting the model found
An arm's absence was reaching outside its arm. Absence propagates into a comparison and deletes the row, and the engine collects that across every term of a row at once — so the
carriedarm's vacated first snapshot deleted the seeded row that defines that very coordinate. The recurrence went unanchored with nothing to say so, and the model solved to 3200.0 instead of 4800.0._arm_presenceconfines it: the admitted set is the value exists or this arm does not claim the coordinate. It is the one place a presence widens past the edge it names, and only for an arm that has one. Found by converting a real model — every unit test written before it used arms that vacate nothing.The partition is the whole mechanism
The arms are proved disjoint and exhaustive at load, with no data bound. Both lanes lean on that, each its own way:
CompiledExpressionis already a sum of fragments, so restricting each arm's fragments to the coordinates itswhenclaims makes that sum a selection. The terminalsum(coeff)adds one contribution per coordinate because only one arm ever reaches it..where(mask, 0)— linopy fillsvarswith -1 there, so the term is genuinely dropped rather than kept at coefficient zero, and the scalar reachesconstonly. Zero rather thannan: the arms that do not apply are not absent data, and ananwould spread through the addition and delete the coordinate.A constant keeps a row outside its arm and a term does not. Constants are left-joined onto the rows and a missing one is a gap: the coverage check cannot tell an arm that does not apply from a parameter row nobody supplied, so the arm says so with an explicit zero. Terms are inner-joined and summed, where an absent row already contributes nothing — a zero there would be an explicit zero coefficient handed to the solver per non-applying arm.
Reading one back
A cased expression declares no body, so
result.expression(name)and the eagerexpression()both read it through its own reference —body_of()hands back the name, expansion builds the arms. Neither lane assembles them itself.Verified
Full suite on Python 3.12, HiGHS, on the merged
main: 2978 passed, 323 skipped, 1 xfailed.ruff check,ruff format --checkandpyrefly checkoversrc/clean.Not checked: the
gurobiandxpresssinks — neither wheel is installed here, so their 10 tests fail identically before and after and are the entire delta from a green run. The typst golden tests skip for want of the binary. Nothing was measured — the extra joins an arm costs are unquantified,_arm_presencematerialises a coordinate product where the old presence named a one-column edge, and this PR makes no performance claim either way.Mutation table
Five guards, each deleted or altered in turn against a committed tree,
__pycache__dropped either side, restored withgit checkout --, tree asserted clean afterwards. Taken before the last upstream re-pin and the rebase onto mergedmain; the guards and their line numbers are untouched by both, but the table was not re-run.tools.mutate's two, against the full suite:The other three are a changed expression rather than a deletion, which the tool does not express, so they were taken by hand with the same three precautions, against the relevant test files:
join(claimed, how='semi')dropped, so every arm applies everywhere.where(mask, 0)dropped, so arms are summed unmaskedp.presencespassed through unchangedpypsa_mixed_cyclingDeliberately not done
pypsa_mixed_cyclingis the one whose page already argued for the shape; the other ports are uniform and would gain nothing. Upstream's ownexamples/commitment.yamlwas not ported in.check_partitioncall from this package. Validation runs it upstream; a second caller here would be a second place the same claim is made.plan.Casescarries the declaredforeach, not the union of the arms — an arm narrower than the frame broadcasts, which is what a parameter with fewer dims already does.+rather thanlinopy.merge.mergetakes only linopy expressions, and an arm is routinely a plainDataArray— a constant case, or a whole constant case set — where it raisesIndexError/AttributeError. Using it would mean a type-dispatch wrapper for what its own docs call "a bit faster than summing", in the lane that is the differential oracle. Not measured, so no speed claim; the lane's existing idiom is alreadyreduce(operator.add, …).Stays a draft until math-spec#36 lands in a release — the pin goes back to a tag then, as #1230's did.