Skip to content

fix(typeset): a substituted term prints its leading minus as a subtraction, and the PyPSA split has a page per fragment - #740

Merged
FBumann merged 17 commits into
docs/pypsa-fragmentsfrom
claude/vibrant-clarke-y58rek
Sep 28, 2026
Merged

FBumann merged 17 commits into
docs/pypsa-fragmentsfrom
claude/vibrant-clarke-y58rek

Conversation

@FBumann

@FBumann FBumann commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "Can we improve/simplify the pypsa proof? Does it read well? Can it be looked at nivcely in the docs?" — then "Do it as another stacked PR", and, on the empty sum: "I thought only one of the files will hold the empty sum at the end? The others either contribute or just read" — "Do it".

Note

The following content was generated by AI.

examples/pypsa.yaml writes each of its nine hubs as a sum of named terms with a declared frame, so check is one canonical comparison and the splitter loses its row algebra. In the split, one fragment declares each sum as an expression with a frame and no body, the others contribute or read. The split gets an index and a page per fragment under "PyPSA in 24 files". core.yaml is settings.yaml.

Stacked on #736. It carries #742 by merge commit until #742 lands in feat/composition and #736 takes the base; the empty sum is #742's rule.

What this changes

  • The one file names its terms, and frames its sums. Bus_nodal_balance reads Bus_injection == 0, Bus_injection is Generator_injection + … + Transformer_injection over [scenario, snapshot, bus], and each term is a named expression, forty in all, for the nine hubs. The canonical form of the file is unchanged apart from the nine frames, which feat(language): a named expression may declare the frame it is read over #741 admits and which are what the terms are held to.
  • One owner per sum. network declares Bus_injection, power_flow declares Cycle_angle_sum, and settings declares the seven totals, each with its frame and description and no body. Every other fragment either adds a term through its given: entry or reads the name under given:. Leaving an owner out while a contributor is in is refused with "no fragment declares it".
  • The tool is 415 lines, from 578. Model reads the hubs and their terms off the file instead of computing them; the rewrite machinery, same_rows, _inlined, _row, _written and _share are gone. check loads each fragment, merges them, and compares canonical forms with the one file. split is the bridge until you decide which side is authored.
  • settings.yaml, not core.yaml: the weightings, the risk preference, the flags every topic reads, and the seven totals whose own readers a model may leave out.
  • Pages. docs/examples/pypsa/index.md says what the split is and carries two generated tables: each sum with its frame, the fragment that declares it and the terms by the fragment that adds each; and each fragment with what it declares, reads and adds to. One generated page per fragment shows the file and its math in the one file's symbols; the three owner pages print each sum they declare as symbol = ⋯ under Definitions, as feat(language): a sum other files add terms to is declared as an expression with empty: true #742 renders an empty sum. All 25 sit under "Proofs of concept" as "PyPSA in 24 files", and tools/gallery.py renders them like the library pages, so tests/test_docs.py holds them current.
  • The one-file page keeps its balance rows. A row that reads a hub, hub == 0 over a sum of named expressions, prints with the sum substituted in, as the file wrote it before each term had a name. The terms and the hubs then print once each as definitions.
  • The typesetter fix. Substituting a named term that opens with a minus printed a + -b, and one that is itself a sum printed a + -b + c. Walk._substituted unwraps a plain named expression before the sign folding, and a right-hand sum is re-associated to the left, so both print as written out: a - b and a - b + c. This is what makes the balance rows read as before. The golden model gains net, a plain expression whose body is -spill + slack, and netted, which adds it on the right of a plus, so the line-coverage guard reaches both new lines; the notation page shows them under "Named expressions".

Why

The proof kept two sources of truth in lockstep with a generator and a row comparison, because the one file wrote each balance inline while the fragments wrote it as named terms. Naming the terms in the one file removes the difference, and gives every component's contribution a name there too. With #742, the sum's regular block lives in a fragment somebody wrote, which is what makes the split readable: every given: entry in it points at a block in one other file. Nothing showed the split in the docs; the library pages already had the shape to copy.

Method, gates, what was not done

The rewrite. A one-off script took the old tool's Model.data, which already held every hub as a sum and every term's body, and edited pypsa.yaml in place: the two rows' expressions, the seven hub expressions with their terms inserted before them, and the two balance hubs with their terms at the end of expressions:. canonical_yaml(to_spec(new file)) == canonical_yaml(to_spec(Model.data)) held before the tool was touched. Terms that fit on a line are written as name: body. The frames were added to the nine hub blocks in a second pass, so the composed sums, which keep their owner's frame, and the one file have one canonical form.

Guards. test_a_substituted_term_that_opens_with_a_minus_prints_as_a_subtraction fails without either half of the typesetter fix, and test_the_golden_model_reaches_every_line_of_the_walk failed on CI until the golden model carried the case (it skips where coverage is not installed, which is why it did not fail here first). test_the_fragments_merge_to_the_one_file is the check; test_every_sum_is_declared_empty_with_its_description_in_one_fragment pins one owner per sum; test_leaving_out_the_owner_of_a_sum_no_model_goes_without_is_refused pins the refusal. test_the_one_file_with_its_hubs_as_sums_states_the_rows_of_pypsa_yaml is gone with the row comparison it tested.

Gates. Pixi cannot be installed in this container. In a Python 3.12 uv virtualenv with the runtime pins of pixi.toml plus coverage, on the head that carries #742's ellipsis rendering:

pytest tests -n 8                         2501 passed, 45 skipped
ruff check . / ruff format --check .      clean
pyrefly check                             0 errors (9 suppressed)
prettier --check docs CHANGELOG.md examples mkdocs.yml   clean
python -m tools.gallery --check           34 page(s) current
python -m tools.notation --check          current
python -m tools.schema                    no diff
python -m tools.pypsa_split check examples/pypsa   24/24 load alone, one canonical form

Not run: docs-build, compile-tex, the PyPSA reference solves, typos, reuse lint, zizmor, taplo. The skips beyond #736's count are the docs-feature tests, which the bare environment skips.

Not done.

  • The terms carry no descriptions. Their names say what they are; prose would be forty sentences to write by hand, and the split copies whatever the one file says.
  • The per-fragment pages carry one sentence of prose each. The library pages have more, and these can grow the same way.
  • Which of the one file and the fragments is authored is still open. The generator stays until it is decided.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

FBumann and others added 3 commits September 25, 2026 18:30
…and a diagram shows what mathspec leaves to engines and other tools (#730)

* docs: a page shows where it sits in the navigation, and the theme's tooltips are styled

Turn on the `navigation.path` breadcrumbs and `content.tooltips`.

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

* docs: the site follows the reader's light or dark setting, in one sky colour

The palette toggle has three states: system, light, dark. A new visitor gets
the scheme the operating system asks for, not dark.

Primary and accent are one sky hue. The primary (sky-700 on white, sky-400 on
slate) rests on links and the primary button; the accent (one shade brighter)
answers hover and focus. Links now hold 4.5:1 on white, where sky-600 held
4.1:1. The hand-written link colours and the second, dark-only target flash
go: both read the palette now.

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

* docs: add the changelog line

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

* docs: code examples are readable in light and dark mode, in the theme's own colours

Drop the Monokai code colours: in light mode strings, functions and
variables fell below 4.5:1, and in dark mode comments were 2.8:1. The
`modern` variant's own highlight colours replace them.

Drop two rules the `modern` variant already carries: the scroll on wide
display math and the scroll on wide tables. At 390 px on the notation
reference, 41 of 110 equations are wider than the column, and all 41 stay
reachable without the rule.

The Python API indentation line takes the theme's hairline colour instead
of a fixed light grey that glared in dark mode.

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

* docs: add the changelog line

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

* docs: a diagram shows what mathspec does and what an engine or another tool does with a spec

The README's engines section, which the landing page includes, draws the
spec going into mathspec, where it is checked and printed as math, and
out to an engine (specsolve, linopy) that takes the data and returns the
answers, or to any other tool through the Program API. The boxes outside
mathspec are dashed, and the prose says so. The site gets the `mermaid`
fence; GitHub renders the same block.

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

* docs: code keeps its Monokai colours, each at 4.5:1 contrast or more

The Monokai block comes back, with the six colours that fell short
shifted along their own hue until they reach 4.5:1 on the code
background: in light mode strings #998800 -> #7f7100, functions and
decorators #4d8d04 -> #457e04, variables #d35400 -> #c14d00; in dark mode
comments #75715e -> #9b9682, keywords and operators #f92672 -> #fb5d95.
The lowest is now 4.65:1. The comment on the dark code background no
longer names a page lightness the modern variant does not use.

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

* docs: the changelog line names what the PR now covers

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

* docs: the diagram of engines and other tools follows the example and its math

The engines paragraph and its diagram move below the example, under
"Engines and other tools", in the README and on the landing page. The
diagram's solid boxes are checking and printing, which the reader has
now seen done.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ction, and the PyPSA split has a page per fragment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB
…og line to #740

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB
claude and others added 9 commits September 25, 2026 21:52
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB
…ant-clarke-y58rek

# Conflicts:
#	CHANGELOG.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB
…e carries the one description written for it (#743)

FBumann commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Prompt: should we then combine 736 and 740 into one PR? — "Yes"

Note

The following content was generated by AI.

This PR is closed. Its PyPSA work is merged into #736 as 1cbdeec, with the owner blocks written empty: true. Its typesetter fix landed separately as #759.


Generated by Claude Code

FBumann added a commit that referenced this pull request Sep 28, 2026
… it, each component adding its share of a sum by name (#736)

* docs(pypsa): a single file covers the standard, stochastic and multi-period classes

Fold pypsa_stochastic and pypsa_multi_period into examples/pypsa.yaml; retire
the sibling pages, symbols and example files. Add the rung 14 and rung 15
sections to the gallery page, and guard the plain-run collapse to the standard
model with a frozen shape fixture.

* docs(pypsa): the spec covers the process and transformer components

Add the Process component (a generalized multi-port converter, superset of
Link) and the full-depth Transformer (a passive branch like Line, with tap
ratio and a fixed phase shift in the KVL cycle) to examples/pypsa.yaml, with
symbols, the regenerated gallery, rung 17 and rung 18 reference networks and
oracles, and the collapse guard extended to the new standard names.

* docs(pypsa): the spec covers quadratic marginal cost

* docs(pypsa): the spec covers transmission losses

* docs(pypsa): the spec covers the secant loss mode

* docs(pypsa): the secant loss mode carries a solved reference

rung_19 records the secant-mode triangle solved through the pinned pypsa,
objective 10840.93, the same 150 rows as the tangent rung. The two loss-cut
blocks now stand for both their tangent and secant PyPSA names, matched by a
gallery helper that reads every backticked name before the dash.

* docs(pypsa): the spec covers the optimised transformer phase shift

A phase-shifting transformer's angle becomes a per-snapshot decision where
phase_shift_min < phase_shift_max, bounded by them and entering the KVL cycle
sum in place of the fixed constant; a plain run keeps the shift fixed and
collapses byte-for-byte. rung_20 records the phase-shifter triangle solved
through the pinned pypsa, objective 16455.0.

* docs(pypsa): the carrier growth limit binds every extendable component

* docs(pypsa): the spec covers transformer transmission losses

A transformer carries its own loss under transmission_losses, as a line does:
the loss counted against its rating, its cap, its tangent or secant cuts and half
of it at either bus. A plain run collapses byte-for-byte. rung_22 and rung_23
record the transformer triangle in both modes, objectives 10643.48 and 10822.00.

* docs(pypsa): a committable unit serving its brought-in down time stays off

Generator_com_status_must_stay_down mirrors the must-stay-up block over a
data-prep mask, status fixed to zero; PyPSA names the row
Generator-com-status-min_down_time_must_stay_up. rung_24 records a cheap unit
held off for two snapshots, objective 9007.5.

* docs(pypsa): a committable link carries the generator's unit commitment

Link gains the status, start-up and shut-down variables, the committable,
big-M and modular p bounds, transitions, up and down times, both must-stay
rules, the committed ramp rows, n_mod and its costs, mirroring Generator.
rung_25 records five committable links, objective 14013.0.

* docs(pypsa): a committable process carries the generator's unit commitment

Process gains the same unit commitment blocks as Link, over its internal
power. rung_26 restates rung 25's links as processes drawing a quarter more
than they deliver, objective 15956.125.

* docs(pypsa): a ramp row follows pypsa for a modular committed build and a start-up ramp alone

A committable extendable modular unit takes the ordinary ramp rows against
one module, not the big-M rows. A start-up or shut-down ramp alone builds
the row, and a missing limit reads as the full build. rung_27 (45469.5) and
rung_28 (83283.0) record both for Generator, Link and Process.

* docs(pypsa): storage cycles or reopens per investment period, and a ramp restarts at a period start

* docs(pypsa): a security-constrained run limits every branch flow after any one listed outage

* docs(pypsa): an mga flag caps the system cost at a budget and minimises weighted builds

* docs(pypsa): mga leaves the standard spec until it can land as a patch file

Reverts 5109686. The commit is kept on docs/pypsa-mga and waits for #571.

* docs(pypsa): a storage built in a later period opens at its first active snapshot

* docs(pypsa): a unit may be scheduled off for maintenance

* docs(pypsa): the spec states what maintenance and late-opening storage assume of the data

* docs(pypsa): a growth limit counts no transformer, as pypsa does

* docs(pypsa): a global constraint may count one investment period, weighted by its years

* docs(pypsa): a process, a storage unit and a store may carry a quadratic marginal cost

* docs(pypsa): a store's power and a storage unit's dispatch and charging may each be pinned to a schedule

* docs(pypsa): a link's and a process's delay applies within each investment period

* docs(pypsa): a negative relative growth adds nothing to a carrier's growth limit, as pypsa clips it at zero

* docs(pypsa): a tech capacity expansion limit on a network with several scenarios is refused, as pypsa does

* docs(pypsa): the carrier growth rung says a transformer counts in no carrier

* docs(pypsa): a global constraint takes its own constant and sense in each scenario

* docs(pypsa): the stochastic rung says which component data spans a scenario

* docs(pypsa): component data spans a scenario wherever pypsa reads it per scenario (#688)

* docs(pypsa): component data spans a scenario wherever pypsa reads it per scenario

* docs(pypsa): two rungs show operating and first-stage data that differ by scenario

* docs(pypsa): a component's sign turns its term in the bus balance around

* docs(pypsa): the linearized commitment file keeps a unit down, ramps at the full build where a limit is missing, and schedules maintenance

* docs(pypsa): a ramp limit may change over time and lift at a snapshot

* docs(pypsa): a unit that came in running ramps from its p_init into the first snapshot

* docs(pypsa): the relaxed commitment file ramps every generator per snapshot and from its p_init, tightens at the full build and signs its balance

* docs(pypsa): a start and a stop cost what they cost, with no snapshot or period weight

* docs(pypsa): a growth limit binds only under multi_investment_periods

* docs(pypsa): a load that is not active draws nothing from its bus

* docs(pypsa): a security-constrained run dissipates no transmission loss

* docs: the changelog lists the pypsa spec that covers every model class and component

* docs(pypsa): an efficiency, a rate or a phase shift may change from snapshot to snapshot

* docs: the changelog lists efficiencies per snapshot

* docs(pypsa): a growth limit counts an asset only in the first period it stands in

A rung may now record a PyPSA bug: its intended objective from an oracle,
and what PyPSA 1.3.0 gives instead. Rung 51 records PyPSA/PyPSA#1938.

* docs(pypsa): a transmission cost or volume limit holds in every scenario

Rungs 52 and 53 record PyPSA/PyPSA#1939, where PyPSA 1.3.0 drops both rows.

* docs(pypsa): each scenario delays a link's and a process's flow by its own delay

Rung 54 records PyPSA/PyPSA#1941, where PyPSA 1.3.0 delivers the flow twice.

* docs(pypsa): transformer cycles, security-constrained runs, fixed builds and committable units hold in every scenario

Rungs 55 to 58 record PyPSA/PyPSA#1942 and PyPSA/PyPSA#1913, where PyPSA 1.3.0 raises.

* docs(pypsa): the first-snapshot ramp refusals link the open PyPSA question

* feat(language): two files that state the same spec write one text, and canonical --check fails a file that is not in it

Spec.to_yaml(canonical=True) writes the normal form: sections in one
order, declarations sorted by name, every expression printed from its
parsed tree with the terms of a sum and the factors of a product sorted,
one term per line. python -m mathspec canonical writes it; --check exits
1 for a file not in the form and --write rewrites it.

Squashes #530 and #718 onto main after #721, in its words: a file states
a spec. Also normalises a named expression written on one line, which the
form passed through as written.

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

* Add the changelog link for #731

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

* feat(language): a spec is composed from files that each state part of it, and patched with files that each change part of it

merge composes fragments as peers: each loads on its own, owns what it
declares, and reads what a sibling declares under given:, which now takes
parameters, variables, named expressions and row families. A given
expression's dims bound what it reads. A given: expressions: entry marked
additive: true says the name is a sum other files add terms to; each
contributor declares an ordinary named expression, merge sums them and
keeps the marked entry. override lays patches over a base, field by
field, with null as the removal. Both return a loaded Spec.

Squashes #571, #690, #728 and #729 onto #731, in #721's words. Adds the
tutorial 'A spec in several files', with a test that runs every step, and
sorts the names under given: in the canonical form.

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

* Add the changelog link for #732

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

* refactor(language): a term of a sum is an ordinary named expression, and only `merge` holds it to the rules of the sum

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Add the changelog link for #734

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* The reader's description wins, and the term checks stay at load

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* feat(language): a file adds a term to an expression another file defines, under `given:`

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Walk the loaded fragments by value

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* A term names an expression of its file, and merge adds it by name

`term:` on a `given: expressions:` entry now names a named expression the
same file declares, and no longer takes an expression written inline. The
term is then free: it takes `cases:`, a description and every rule of a
named expression, and it counts as read by the math.

`merge` adds the terms by name, without brackets around a name, and keeps
each term as a named expression of the composed spec. A refusal for terms
that land on no name names only a near miss.

The typeset term line is `name = ⋯ + term`. The tutorial, the reference,
the how-to, the golden model and the notation page follow. The #734 line
leaves the changelog; #732 carries the design.

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

* Name the term in a term collision, and suggest only a name a term can land on

Two fragments that declare one term name now hear that terms share one
namespace, not the dimension-rows advice. Terms that land on no name are
refused with a near miss among the definitions and readings only, since a
term name is no place for a term to land; `did_you_mean` takes
`listing=False` for the case where only a near miss helps. The how-to says
to name each term after its component.

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

* Cut pypsa.yaml into topic fragments whose components add named terms

`examples/pypsa/` holds the 24 fragments `tools/pypsa_split.py` writes from
#620's `examples/pypsa.yaml`. Each component's share of a sum (the bus
balance, the cycle sum, the operating cost, the carrier additions and the
five global-constraint totals) is a named expression of its own, such as
`Generator_injection`, and the `term:` of its `given:` entry names it. The
reader of each sum describes it: `network` and `power_flow` for the two
rows, `core` for the rest.

`check` merges the fragments to the canonical form of the one file with its
sums written as named terms, and compares that file with `pypsa.yaml` row by
row, every term substituted back.

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

* Add the changelog line for #736

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

* fix(language): a merged spec's descriptions do not depend on the order the fragments are passed in, and a reader's fills one its owner left out (#739)

* Choose a merged description by fragment name, and let a reader's fill a gap

A shared dimension or relation, a reading several fragments share, and a
sum built from terms took the description of whichever fragment was passed
first. Each now takes the first description in the fragments' name order, as
the objective already did, so the order of the arguments reaches no field.
The order of the declarations still follows the order passed in, which is
presentation.

A folded given declaration dropped its reader's description even where the
declaration it folds into had none; it now fills that gap, and yields to the
owner's own.

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

* Add the changelog line for #739

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

---------

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(language): a term prints as the definition it is, and one rule folds every reading (#738)

* refactor(language): a term prints as the definition it is, and merge takes the fragments in name order once

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Add the changelog link for #738

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Retitle the changelog line: the name-order rule is #739's

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(typeset): a substituted term prints its leading minus as a subtraction, and the PyPSA split has a page per fragment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Expect the substituted sum over both dimensions, and link the changelog line to #740

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* feat(language): a named expression may declare the frame it is read over

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Add the changelog link for #741

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* feat(language): a sum other files add terms to is declared as an expression with a frame and no body

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Add the changelog link for #742

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* The owner of each PyPSA sum declares it with a frame and no body

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* The golden model substitutes a signed sum into a plus

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* docs: a file restates a shared dimension as its dtype alone, and merge carries the one description written for it (#743)

* An empty sum prints as a definition with an ellipsis for its body

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* The owner pages of the PyPSA split print their sums with an ellipsis

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1F2gprjeiGPnvDaWjZwBB

* Keep main's patch case in test_composition, which #740 carried from an earlier #742

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

---------

Co-authored-by: Fabian <fab.hof@gmx.de>
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.

2 participants