Repository navigation
feat(program): each expression node states the dimensions it is read over - #899
Conversation
…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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…terms do not carry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…over Closes #897. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Documentation build overview
38 files changed ·
|
… it is built Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…in, listed with every other Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…t breaks it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…er, rather than its frame Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…xed over, rather than its frame Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…frame # Conflicts: # CHANGELOG.md
… feat/node-frames
brynpickering
left a comment
There was a problem hiding this comment.
Uncontroversial IMO. No objections, just one point of clarification that I couldn't quite understand from the docs entry / source
| `ExpressionReference` has the dimensions of its body. The order of an entry's | ||
| own `dims` can be different: a named expression keeps the order its `dims:` | ||
| declares. |
There was a problem hiding this comment.
ok, so the node will always have dims ordered as they are defined in dimensions even if that differs from the order of dimensions defined in a named expression's dims?
There was a problem hiding this comment.
Yes. This leads to deterministic dimension ordering. And a canonical yaml should replicate it.
Might be worth a follow up issue to verify this is properly done already
There was a problem hiding this comment.
Actually, this can't really work predictably with composition. I revert my option: We should simply sort by name, like all other entries too.
See other PR.
…es, whatever order its entry declares Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
# Conflicts: # CHANGELOG.md # src/mathspec/_lowering.py # tests/test_dimensions.py
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
`_frame_of` keeps #899's body dims and sorts a declared `dims:` with #899's `sorted_dims`. Every program dims this branch sorts goes through `sorted_dims` too, so the rule has one home. A relation's key dims keep `tuple(sorted(...))`, because a key may name one dimension twice and the refusal of a bare relation reads the repeat. The reading guide says an entry's dims are sorted, as a node's are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNjZXCjH1FpCvxUvDGjWNw
Note
The following content was generated by AI.
Every expression node in the program has
dims, sorted by name. Each node is checked against its operator's rule when it is built, anddims_ofis gone. A load lists every dimension fault it finds. Closes #897.What changes
program.pytakedimsas a required keyword field. It is part of equality, so two trees are equal only with the same dims on every node.dimsfrom its children'sdims.sorted_dimsin_dimensions.pysorts them by name, which is the rule fix(language): two files that declare their dimensions in different orders load to the same program #911 set for the dims the loader derives.Sumremoves the dims its plain axes name. AJointhat opens axes has the dims of the sum that closes them, so an axis is never a dim (Each expression node of the program states the dimensions it is read over #897, decision 1).Joinusesjoined(), the rule thatjoin_dimsalso returns for a condition.Caseshas the dims of its values and itswhen:conditions.ExpressionReferencehas its body's dims (Each expression node of the program states the dimensions it is read over #897, decision 2, which fix(language)!: a named expression whosedims:gives a dimension its expression is not indexed over is refused at load #898 made true).check_nodeholds aSum, aJoin, aTranslateor aWindowSumto its operator's rule on the dims its operand states. The resolver calls it on each such node it builds, and appends a refusal to its errors, as the condition resolver already does for conditions. A tree is checked once, bottom up. Before, a subtree was checked again under eachat(...)and baresum(...), and once more at the end.dims_ofis removed.check_speckeeps the entry rules and reads.dims: a named expression'sdims:, rule 5, the objective, bounds,where:. The condition resolver,_piecewiseand the resolver'satand baresumread.dimstoo.Namespacerecords each refusal a dim rule writes.Namespace.errorraises oneDimensionErrorthat lists the refusals when every fault is a dim rule's, and oneSchemaErrorwhen any other fault is among them. A refusal that only points at an entry's own ("named expression 'x' does not load") leaves the class to that entry's fault._lowering._frame_ofreads the body'sdims. The typesetter rebuilds anAddor aMultiplyfor printing with the node's owndims.dimensions:or an entry'sdims:writes. The error reference says which class a load with several faults raises. TheExpression,Joinand_dimensionsdocstrings state the rest.What a file author sees
sum(x, over=d)wherexis not indexed overd, a join oraton a column the operand lacks, or ashiftalong a dim the operand lacksDimensionErrorthat lists every oneSchemaErrorthat lists every oneDimensionErrorthat names the first such entry, as beforeBefore, the first operator fault stopped the load as a
DimensionError. Three cases wereSchemaErrorinstead: a fault under a baresum(...), a fault under anat(...), and a join fault in awhere:condition.sum(sum(p, over=bus))was aSchemaError, andsum(sum(p, over=bus), over=generator)aDimensionError. Now both are aDimensionError. The message text is the same.Verification
Gates on
677fcba, which mergesmainwith #911:pixi run lintpasses.pixi run testpasses: 3055 passed, 1 skipped.pixi run docs-buildpasses.compile-texwas not run, because the container cannot download the TeX bundle. CI runs it.The check (#897).
test_every_expression_node_states_its_dims_sorted_by_nameloads 76 specs: every file inexamples/, both sides of eachtests/expand/pair, and the golden model. It checks each spec as loaded and afterspec.expand(). It walks every expression node the program holds, and comparesdimswith the dims that the test derives from the spec by the language's rules, without readingdims. #911'stest_the_order_of_dimensions_reaches_no_value_of_the_programnow covers the nodes as well: on the merge alone, before node dims were sorted, it failed for 45 specs.The class.
test_every_fault_resolution_meets_is_listed_under_the_class_of_what_is_wrongloads a file with two operator faults (aDimensionErrorthat lists both), the same file with a name nothing declares (aSchemaErrorthat lists all three), and a file with a conditionat(...)through a dim it lacks (aDimensionError).Mutation table. Each change was made 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. Row 1 is on677fcba. Rows 2 to 7 are on5372843, rows 8 to 11 oncc86700, and rows 12 to 15 on152a73a. Some tests had other names then. Later commits renamed them and changed only the words.test_the_composed_sum_prints_its_terms_by_name,test_every_python_step_prints_what_the_page_showstest_a_join_opens_an_axis_for_the_column_it_drops_and_the_sum_over_it_closes_itandtest_dim_inferenceexamples/pypsa.yamlno longer loads: 3 modules stop at collectiontest_terms.pyandtest_library_example.pywhen:examples/pypsa.yamlno longer loads: 3 modules stop at collectiondims:is not checked as its entry (since removed: the body is checked as it is built)test_a_plain_entry_that_breaks_a_dim_rule_is_refused_at_load_under_its_own_namecheck_nodetest_an_ill_dimensioned_expression_is_rejected,test_a_rule_decided_without_data,test_two_joined_columns_do_not_share_a_dimension,test_a_plain_entry_that_breaks_a_dim_rule_is_refused_at_load_under_its_own_nametest_two_joined_columns_do_not_share_a_dimensiontest_an_ill_dimensioned_expression_is_rejectedSchemaErrortest_an_ill_dimensioned_expression_is_rejectedtest_a_plain_entry_that_breaks_a_dim_rule_is_refused_at_load_under_its_own_name[an-entry-a-constraint-reads]test_every_fault_resolution_meets_is_listed_under_the_class_of_what_is_wrong[a-condition-read-through-a-dim-it-lacks]test_two_joined_columns_do_not_share_a_dimensionCoverage moved.
test_lowering.py,test_expansion.pyandtest_piecewise.pynow states itsdims, so these tests also assert the dims.test_an_ill_dimensioned_expression_is_rejected, the fault under a baresum()expects aDimensionError, as the same fault under a sum over a named dim does.SHAPES_MODELintest_lowering.pydeclaresleadover no dimension. The test resolvedshift(p, along=g, offset=lead)withleadoverg, which the dim rules refuse. It passed only because it never ran them.expression_of,comparison_ofandwhere_ofraise the errorNamespace.errorpicks, as a load does.Not done
dimensions:. The order follows fix(language): two files that declare their dimensions in different orders load to the same program #911 instead.dims:writes, as onmain. feat(language)!: every entry is indexed by its dimensions sorted by name, whatever order the file writes them in #914 sorts those too. The two PRs conflict in_lowering._frame_of,CHANGELOG.mdand the end oftests/test_dimensions.py. Whichever merges second takesmainand resolves them.sum()of an operand with no dims stays aSchemaError("its operand has no dimensions to sum over"), as onmain. It is a refusal of the call, not of a dim rule.dimsneeds a mathspec release with this field. It is a separate PR in specsolve.🤖 Generated with Claude Code
https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6