Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
The network reads Bus_injection under `given:`, and each component adds its term with `adds_to:`. merge takes a list, a new component joins the list, and a merged spec takes no further term. Every quoted message is from a run of the page's files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
7ac2212 to
2592e16
Compare
…no file marks a sum as open A term was named from the sum's side: `term:` on a `given: expressions:` entry, and the file that owned the sum declared it `empty: true`. Now the term names the sum: `adds_to:` on the named expression names a given expression of the same file. The given entry is the read, and `adds_to:` the write. The loader checks the target, the frame and self-reference in the one file. merge writes the sum as the body one fragment defines, if any, plus every term, so no file declares a sum open and a later merge adds more terms. Terms that only their own files read are refused with the near miss: some fragment has to define the name, read it and add nothing, or use it in its math. `empty: true`, `given: ... term:`, the empty-sum line and the ellipsis go. Docs: declarations.md n 197, several-files.md and named.md measured in the PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
A name one fragment defines takes no term, so a body means what its file says. merge defines a read name as its terms by name over the readers' frame, and refuses a term on a defined name, a merged sum included, with both fragments named. A file with a part of its own, such as a slack, adds it as a term of its own reading. The definer's body, its brackets, the cased-definition refusal and the definer clause of the misspelling check go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
The network reads Bus_injection under `given:`, and each component adds its term with `adds_to:`. A new component joins the list, and a merged spec takes no further term. Every quoted message is from a run of the page's files against this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
… term The misspelling check counted a reported expression as a use in the math, so a contributor that reported its misspelt sum passed it. `adds_to:` is now dropped from every composed expression, not only where a sum was written, and `merge` documents the two refusals. The PyPSA splitter keeps a folded term body, and the gallery index lists a hub once per fragment and names the sum that has no described reader instead of a bare KeyError. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcGptSPeUeVFHwf7Nbpjc
b5a24a3 to
7978d43
Compare
The conflict in composition.py: main (#768) reads each fragment's given: block with exclude_unset, so a default does not claim a value against the introducer, and strips `term:` from it. This branch removes `term:`, so the merge keeps the exclude_unset read and drops the stripping helper. #767's test case for an `empty: true` sum goes, since this branch removes empty sums. Its case for a given expression covers the open sum here, and failed on this branch before the merge with KeyError: 'e'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
Brings in #778, #779 and #751. CHANGELOG.md keeps both sides; nothing else conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
…write it into `adds_to:` naming an expression of the same file was refused as a name the file does not read under `given:`, ending in "Declared: nothing." It now says the file defines the name, and that a term is written into that body, or the name is read and its body added as a term. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
#780 refuses a file whose terms read each other's sums. Under `adds_to:` one file may add several terms to one sum, so `_terms` groups the terms by the sum they add to, and `_loop` follows each term's reads through those groups. The direct self-read is the loop of length one, and keeps its message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
`_term_block` nested a flow mapping under `expression:`, and kept only the head line of a plain body that runs on. It now rereads such a block as YAML and writes it as a mapping. The fragments it writes from examples/pypsa.yaml do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
…t it read wrong - A one-line patch on a named expression replaces the body and keeps `adds_to:`, `dims:` and `description:`. - A name one fragment alone reads is refused as a misspelling, even where that fragment uses it in its math. - A term on a name a sibling declares as a variable, a parameter or a constraint names that kind. - Readers that write a sum's dims in different orders are refused, so the order of the list does not reach the canonical text. - `_uses` reads through `variables_of`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
|
I like that. Thank you! Two minor points. First, I don't think a having a key with an underscore in it is super elegant like "adds_to". is there an alternative without it? - hard to think of one... |
|
|
@FabianHofmann I think there aren't any viable alternatives.
|
|
i see, what about
? |
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
|
okay, I would vote for |
|
@FabianHofmann Cool. |
A term's adds_to: may name an expression another fragment defines with one expression:. The merged body is that body followed by every term, joined with a plain +, so a merged spec takes more terms in a later merge and two merges print the same sum as one. A definition written as cases: takes no term. A term that reads its own sum through a name another fragment defines is refused at merge, both fragments named, rather than by the composed load, which named the loop only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JiNwuVCdEkSLKNxiCNaNAv
Four splits: the last file later, the reader alone first, three steps, and a body a file defines. Each asserts the written body, since the canonical form reads a bracketed sum the same. A step that closes a sum no other file reads is refused, and the Terms section says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JiNwuVCdEkSLKNxiCNaNAv
…ames The Terms section, the named-expression page, the merge table and the docstrings said an entry "is a term of" a given expression, which reads as a kind of declaration. A term is one part of a sum, so each now says what adds_to: does with the expression: it adds it as a term to the sum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JiNwuVCdEkSLKNxiCNaNAv
Note
The following content was generated by AI.
What this changes
A named expression adds itself to a sum with
adds_to:. The target must be agiven: expressions:entry of the same file. The given entry is the read, andadds_to:is the write.mergewrites the terms after the body another file defines, or as the whole body where no file defines one. A merged spec takes more terms in a later merge.empty: trueandgiven: … term:are removed.The syntax
A file that uses the sum reads it under
given:. The network readsinjectionbecause its balance uses it:A file that contributes also reads the sum. It writes its share as an ordinary named expression and names the sum with
adds_to::Alone, each file loads and prints. A contributor reads
injectionas a column over its frame.checknotes the term:The legend names the term:
injectionoverconsumptiontoMerged,
merge(['network.yaml', 'fleet.yaml', 'load.yaml'])definesinjectionasgeneration + consumption, over the readers' frame. Each term stays a named expression, and itsadds_to:is dropped. Nothing is left undergiven:, so the result is fully defined:A body another file defines takes terms too. If
network.yamldefinesinjection: {expression: slack}instead of reading it, the merge givesslack + generation + consumption. The body keeps itsdims:and its description.Incrementally,
merge([merge(['network.yaml', 'fleet.yaml']), 'load.yaml'])givesgeneration + consumption. The terms join with a plain+, so the written body is the one-list merge's, and so is the canonical form. A merge defines every sum it has terms for, so the step that first merges the terms of a sum also merges a file that reads it.merge([merge(['fleet.yaml', 'load.yaml']), 'network.yaml'])is refused, as "Terms nothing else reads" below.Before and after
mainexpressions: {injection: {dims: […], empty: true}}given: {expressions: {injection: {dims: […]}}}, or one file defines it with a bodygiven: {expressions: {injection: {dims: […], term: generation}}}expressions: {generation: {expression: …, adds_to: injection}}(body) + term, the body in bracketsbody + term, so a sum merged in steps is written as one merged in one listAllowed
Each result is the output of the fixtures above on this branch's head.
injection=generation + consumptionmerge([load, fleet, network])givesconsumption + generation. The canonical form of both orders is the same textinjection: slackslack + generation + consumption, with the definer'sdims:and descriptionshed: {expression: slack, adds_to: injection}innetwork.yamlshed + generation + consumption. The balance uses the sum, so the network counts as a readerinjection <= 10), beside another file that reads itconsumptionandloss)generation + consumption + lossdemandover[bus]only)snapshot, and the sum keeps the readers' framecases:withotherwise:given:for a host model to provide, as any given expression doesoverride(load, [{'expressions': {'consumption': '-2 * demand'}}]){'expression': '-2 * demand', 'adds_to': 'injection'}. The one-line form replaces the body,expression:orcases:, and keeps the other fieldsRefused when the one file loads
Each file is checked alone, so a mistake is named in the file that made it.
adds_to:names something the file does not read undergiven: expressions:. This covers a typo:adds_to:names an expression the same file defines. A file writes its own body in one place:empty: trueused to mean. The message names the new form:Refused at merge
cases:. A term follows one body:x -> injection -> t -> x, but no fragment:… reads 'injection' back through the sum 'withdrawal', 'inflow' of '#3', ….given:entry, the suggested fix is drawn from the names the fragments read, never from term names:… Did you mean 'injection'?Why
The old form had two problems:
given:entry. Sogiven:, which reads, also carried a write.empty: true.Now
given:only reads,adds_to:writes, and no file marks a sum as open: every named expression with one body takes terms from other files.Method, gate output, what goes, guards
This branch is on
mainafter #761, #762, #766 and #780, with latermainmerged in. #766's fix, that a term keeps its definition line when expressions are inlined, is written againstadds_to::Walk.defined()keeps an entry whoseadds_tois set. #780's loop check is written againstadds_to:in the merge commit:_termsgroups a file's terms by the sum they add to, and_loopfollows each term's reads through those groups.What goes.
ExpressionBlock.empty,_check_emptyandempty_sums.GivenExpressionBlock.term, andGivenDeclaration.termand.empty.symbol = ⋯definition line andFormat.ellipsis.walk.composition.py:_readings,_landedand_summand._read_elsewhere,_uses,_undeclared,_frame,_acyclicand_pathreplace them.Terms on a defined body (
5e51689). An earlier head refused a term on any name a fragment defined, so a merged spec took no further term. On request,_summednow starts from the definer's entry and appends the terms._undeclaredrefuses only acases:definition and the other kinds._read_elsewhereand_frameapply only where no fragment defines the sum._acyclicfollows each fragment's reads, and each sum's terms, from every term back to its sum. Coverage moved:test_a_term_on_a_name_a_fragment_defines_is_refusedis gone. Its file-definition and merged-sum cases are nowtest_a_term_follows_the_body_a_fragment_defines, and its cased case istest_a_term_on_a_cased_definition_is_refused.Merges in steps (
09cb6f2).test_a_sum_merged_in_steps_is_the_sum_merged_in_onetakes four splits and asserts the written body of both sides, then their canonical text. The written body is the assertion that pins the plain+: the canonical form reads(a + b) + casa + b + c, so it matches with or without brackets.test_a_step_where_no_other_file_reads_the_sum_is_refusedpins the refusal of a step with no reader.PyPSA.
tools/pypsa_split.pywritesadds_to:on each of the 40 terms. The home fragments (network, power_flow, settings) read their sums undergiven:, with the description. All 24 fragments load alone and merge to the one file's canonical form. The gallery pages are regenerated.First review. Six findings. Five are fixed here; the sixth was #766.
_useswalks only in-math expressions, so reporting a misspelled sum does not pass the misspelling check.adds_to:is stripped from every composed expression.merge()'sRaises:names both refusals.tools/pypsa_split.pykeeps the body of a term written asName: >-.tools/gallery.pybuilds the index withsplit_index(specs).Second review. Ten findings.
mainin fix(language): a file whose terms read each other's sums is refused at load #780, carried here in the merge commit.d9d0367.adds_to::_bodyreads it as a new body over the entry._undeclarednames the kind._framerefuses readers that order the frame apart._term_blocknested a flow mapping and dropped a continued plain body: it rereads such a block as YAML. The fragments it writes do not change._usesreads throughvariables_of.where:mask. It does not reproduce, since a mask that reads a given expression is refused at load ("may test parameters and dimension coordinates only")._useswalks an in-math body twice. The cost is small and the code is shorter as it is.Guards. Each check was deleted, and the suite run. For the second review's checks, each test below failed on the tree before its fix. The four rows for
5e51689were taken by hand on that head, each against the full suite.given:test_what_a_term_may_not_be_is_refused_at_load[a-mistyped-target][a-target-this-file-defines]. Before the dedicated check, this case got the "does not read undergiven:" message ending in "Declared: injection.", and the test failed on it[a-term-reading-the-sum][two-terms-reading-each-other-s-sum][a-term-wider-than-the-entry]test_a_term_follows_the_body_a_fragment_defines[a-definition-a-file-writes],[a-sum-a-merge-wrote],test_the_definer_keeps_its_description, the stepped-merge test,test_a_reader_that_states_less_than_the_definer_is_refused+(body bracketed instead)test_a_term_follows_the_body_a_fragment_defines[…](both), the stepped-merge test, by its written-body assertiontest_a_term_on_a_cased_definition_is_refused_acyclic)test_a_term_that_reads_its_sum_through_another_fragment_is_refused[through-a-definition],[through-another-sum]. Without it the composed load refuses with a message that names no fragmenttest_a_sum_a_sibling_declares_as_another_kind_names_that_kindtest_readers_that_order_the_frame_apart_are_refusedtest_leaving_out_the_reader_of_a_sum_no_model_goes_without_is_refused[the-bus-balance]test_terms_only_their_own_files_read_are_refused[a-misspelt-given-entry-the-same-file-caps]test_a_fragment_that_reads_the_sum_for_more_than_adding_lets_the_terms_land[a-contributor-whose-own-constraint-reads-it]test_terms_only_their_own_files_read_are_refused[a-misspelt-given-entry-the-same-file-reports]adds_to:test_a_patch_changes_a_term_s_body_and_keeps_what_it_adds_to[one-line]test_a_term_block_carries_its_body_in_every_source_form[folded],[flow-mapping],[one-line-continued]test_the_split_index_names_a_hub_once_per_fragment_and_needs_a_described_readertest_inlining_keeps_the_definition_of_a_termDocs.
declarations.md"Terms" (anchor#terms): a body another file defines, a later merge and the reader its first step needs, thecases:refusal, the cross-file loop with its message, and the rules that hold only where no file defines the sum.howto/compose.md: a merged spec takes a further term; the table row foradds_to:; a one-line patch replaces the body and keeps the other fields.merge,GivenExpressionBlockandExpressionBlock, and the regenerated schema.named.md,reading.md,typeset.md, the tutorialseveral-files.md, and the PyPSA page intros.Gates.
09cb6f2:pixi run lintclean;pixi run test2611 passed.5e51689:pixi run ciran.docs-buildstopped on one warning, an unresolvedpathlib.Pathautoref, because the session's proxy blockeddocs.python.org/3/objects.inv.compile-texcould not download the tectonic bundle. Both are left to CI, and no file underexamples/changed.d9d03671:pixi run lintclean;pixi run test2590 passed.Deliberately not done. One file may add two terms to one sum. Both are summed, and the gallery index lists both. A term may not add to a body its own file defines: one file has one place for its body. The objectives of a merge in steps still nest their brackets,
((a) + (b)) + (c); the canonical text is the same, and a plain join changes every merged objective, so it is a PR of its own. The first commit's body quotes a wrong sentence count (n 197); the measured figures are in the docs commit.🤖 Generated with Claude Code
https://claude.ai/code/session_01AApRDPxpZcALYLrFMXEF7z
https://claude.ai/code/session_01AzXL2w5xdUm8FsYMNWf1KW
https://claude.ai/code/session_01JiNwuVCdEkSLKNxiCNaNAv