Skip to content

fix(language): two files that declare their dimensions in different orders load to the same program - #911

Merged
FBumann merged 3 commits into
mainfrom
feat/derived-dims-by-name
Oct 20, 2026
Merged

FBumann merged 3 commits into
mainfrom
feat/derived-dims-by-name

Conversation

@FBumann

@FBumann FBumann commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: I think making it sorty by name, stable and predictable, is better. We can add mroe complex rules later maybe...

Note

The following content was generated by AI.

What this changes

The dims that the loader derives for an entry are sorted by name. The order of dimensions: no longer reaches any value of the program. Entries that declare dims: keep the order the file writes.

The four derived orders, and what a reader sees
Derived dims Before Now
a named condition (NamedCondition.dims) in the order dimensions: declares sorted by name
a named expression that declares no dims: in the order dimensions: declares sorted by name
a piecewise curve's frame (Piecewise.frame), and so the dims of the variables and constraints its expansion writes in the order dimensions: declares, in the order the links are met sorted by name, then the breakpoint dimension
a comparison of two expressions in a condition (ExpressionComparison.dims) in the order dimensions: declares sorted by name

What a reader sees:

  • The printed indices of those entries follow, for example $\mathit{marginal\price}{b,t}$ instead of $\mathit{marginal\price}{t,b}$, and cost_curve_lam over [generator, snapshot, bp] instead of [snapshot, generator, bp].
  • One line can print two orders. A composed sum whose reader declares dims: [snapshot, bus] prints as injection_(t,b) = generator_injection_(b,t) + …, because its terms declare no dims:.
  • specsolve lays out a named condition and the expansion's variables in the new order.
  • The golden typesetter output, the generated pages, and the pypsa fragments (given: conditions: restates the condition's dims) are regenerated with their tools, not edited.
  • docs/several-files.md shows printed output and is not generated, so it is edited by hand.

Why

On main (ed6af52), the same file loaded to a different program in 23 of the 53 examples when only dimensions: was reversed. The canonical form sorts dimensions:, so it changed the program too. A consumer needs one fixed order, not the author's order. A sort by name is that order, with nothing to keep in step across files. This is also what lets merge stay as it is.

Verification

The test. test_the_order_of_dimensions_reaches_no_value_of_the_program reverses dimensions: in every example and asserts that the program is equal, before and after expand(). On ed6af52 it fails for 23 of the 53 examples. It passes with this change.

test_the_form_declares_the_same_spec now compares the dims of each constraint and variable in their order, not as a set. Its docstring said that sorting variables: could reorder the dims of a constraint the expansion writes. That is no longer true. reading.md said the same, and is corrected.

Gates on ae0e4ec:

  • pixi run lint passes.
  • pixi run test passes: 2976 passed, 1 skipped.
  • pixi run docs-build passes.
  • compile-tex was not run, because the container cannot download the TeX bundle. CI runs it.

Mutation table. Each change was made by hand and the full suite was run. The file was restored with git checkout --, __pycache__ was dropped before and after each run, and the tree came back clean each time.

Change Caught by
a named condition's dims in declaration order 33 failures: 16 cases of the new test, 15 generated pages, test_the_fragments_are_what_the_splitter_writes, test_a_named_condition_stands_where_its_name_is_written_with_its_condition_under_it
a named expression without dims: in declaration order 24 failures: 9 cases of the new test, 10 generated pages, the golden output, test_the_composed_sum_prints_its_terms_by_name, test_every_python_step_prints_what_the_page_shows
a curve frame in declaration order 18 failures: 5 cases of the new test, 5 of test_the_form_declares_the_same_spec, the golden output, test_a_curve_prints_over_the_frame_its_expansion_builds_one_per_coordinate_of
a comparison of two expressions in declaration order 7 cases of the new test

Coverage moved. test_the_emitted_foreach_follows_file_order is now test_the_emitted_frame_is_sorted_by_name_whatever_order_the_file_declares, with the same two orders of dimensions: and one expected frame.

Not done

🤖 Generated with Claude Code

https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6

…rders load to the same program

The dims the loader derives for a named condition, for a named
expression that declares no dims:, for a piecewise curve and for a
comparison of two expressions in a condition are sorted by name, rather
than read in the order dimensions: declares. Printed indices of these
entries follow, and the golden output and generated pages are
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
@read-the-docs-community

read-the-docs-community Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

@brynpickering brynpickering left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My general preference is to follow the order defined in dimensions as the user might have good reason to order it like that. However, this will mean that diffs become harder. If the user changes the order of dimensions in one version compared to the last then the diff will be riddled with changes that are immaterial (dims list reordering).

So, agreed that this is the safest approach. It will match the dimension order on dump anyway since those keys get ordered by name (right?).

Comment thread CHANGELOG.md Outdated
@FBumann

FBumann commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

My general preference is to follow the order defined in dimensions as the user might have good reason to order it like that. However, this will mean that diffs become harder. If the user changes the order of dimensions in one version compared to the last then the diff will be riddled with changes that are immaterial (dims list reordering).

So, agreed that this is the safest approach. It will match the dimension order on dump anyway since those keys get ordered by name (right?).

Yes. I also think that having a custom order would be appreciated, but making this work early with composition will be quite hard...

We might revisit this in the future.

Co-authored-by: Bryn Pickering <17178478+brynpickering@users.noreply.github.com>
@FBumann
FBumann merged commit 18207f1 into main Oct 20, 2026
5 checks passed
FBumann pushed a commit that referenced this pull request Oct 20, 2026
The order of dimensions: no longer reaches a node, which is the rule
#911 set for the dims the loader derives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
FBumann added a commit that referenced this pull request Oct 20, 2026
…over (#899)

* fix(language)!: a named expression whose dims: names a dimension its body does not carry is refused at load

Closes #896.

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

* docs(changelog): link the line to its pull request

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

* fix(composition): a sum built only from terms takes no dimension its terms do not carry

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

* chore(composition): reflow the module docstring

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

* feat(program): each expression node states the dimensions it is read over

Closes #897.

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

* docs(changelog): link the line to its pull request

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

* refactor(dimensions): each node is held to its operator's dim rule as it is built

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

* fix(dimensions): a dim fault resolution meets is a DimensionError again, listed with every other

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

* docs(errors): say which class a load with several faults raises

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

* docs(errors): a rule on a whole entry still names the first entry that breaks it

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

* docs(language): say which dimensions a named expression is indexed over, rather than its frame

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

* docs(reading): a node states its dims in the order dimensions: declares, whatever order its entry declares

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

* feat(program): a node states its dims sorted by name

The order of dimensions: no longer reaches a node, which is the rule
#911 set for the dims the loader derives.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
FBumann pushed a commit that referenced this pull request Oct 21, 2026
The big-M assumptions print their condition's indices sorted by name, as
#911 prints every derived frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
FBumann added a commit that referenced this pull request Oct 22, 2026
…ut is absent rather than zero, and a committable extendable unit is assumed to have its big-M (#912)

* docs(pypsa): a PyPSA schedule, weight or limit that the data leaves out is absent rather than zero

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

* docs(changelog): link the line to its pull request

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

* docs(pypsa): regenerate the gallery pages on the merged base

The big-M assumptions print their condition's indices sorted by name, as
#911 prints every derived frame.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

3 participants