Skip to content

feat(language): a divisor or a power over parameters may add, so a discount factor is written where it is used - #757

Merged
FBumann merged 4 commits into
mainfrom
claude/rebase-pr-648-s0zjwf
Sep 29, 2026
Merged

FBumann merged 4 commits into
mainfrom
claude/rebase-pr-648-s0zjwf

Conversation

@FBumann

@FBumann FBumann commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: Rebase #648
Open a new PR if needed.

Note

The following content was generated by AI.

#648 on current main. It replaces #648, whose branch could only be rebased with a force-push. x / (a + b) and (1 + rate) ** years load, and a variable there is still refused. Closes #277.

What moved in the rebase, gates, what was not done

The change is #648's, unchanged in substance. The method, typeset output and "deliberately not done" list in #648's body still hold.

What the rebase had to change.

History. One commit replays #648's net diff onto 0efbf59. Two merge commits bring in what main gained after that. There is no force-push.

Gates. pixi is not available in this container. The tools ran from a uv venv at the versions pixi.toml pins, on the last merge commit.

  • ruff check, ruff format --check: pass.
  • pyrefly check: 0 errors.
  • pytest -n auto: 2518 passed, 52 skipped.
  • prettier --check on CHANGELOG.md, docs/ and examples/: clean.
  • Before the last merge, with main's degree.py restored: 16 failed, 4 errors.
  • Not run: lefthook (typos, reuse, taplo, zizmor), docs-build and compile-tex. CI runs them.

Not done. #648 stays open. Close it when this one replaces it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C7TEPiMeF7aowcvTX6CRRh
https://claude.ai/code/session_019C9UKzzVdzrcoK3sPrnean

…scount factor is written where it is used

`x / (a + b)` and `(1 + rate) ** years` load. The degree check refused a
sum anywhere under a divisor, a base or an exponent. The program already
carries the tree, and the typesetter prints it in all three formats.

The PyPSA stochastic example reads `alpha` and divides by `1 - alpha`
in CVaR-def, instead of binding the inverted tail as data.

expressions.md: n 92, avg 15.4, median 14, over25 11 (was n 91, same).

Closes #277

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7TEPiMeF7aowcvTX6CRRh
The typesetting test's helper is called varied on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7TEPiMeF7aowcvTX6CRRh
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7TEPiMeF7aowcvTX6CRRh
@FBumann FBumann added the area: arithmetic Adding, subtracting, multiplying etc of variables, parameters etc. label Sep 28, 2026 — with Claude
…s0zjwf

main split pypsa_stochastic into the pypsa spec and its topic files
(#736, #620). The CVaR divisor change moves to examples/pypsa.yaml and
its symbols; the topic files and gallery pages are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C9UKzzVdzrcoK3sPrnean
@FBumann
FBumann merged commit c86ab1f into main Sep 29, 2026
7 checks passed
FBumann pushed a commit that referenced this pull request Sep 29, 2026
Brings in #757, #765, #778 and #779, which the next commits use to drop
two of the port's workarounds. CHANGELOG.md keeps both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
FBumann pushed a commit that referenced this pull request Sep 29, 2026
…multi-dimension sums as Calliope does

#757 admits a sum of parameters under ** and as a divisor, so
storage_retention and cost_annuity_factor are gone: the storage files read
(1 - storage_loss) ** …, and the depreciation rate states Calliope's annuity
factor. #778 and #779 admit sum(over=[a, b]), so the 21 nested sums are
one call each. port.md marks gaps 1 and 8 closed and keeps the numbering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
FBumann added a commit to fluxopt/specsolve that referenced this pull request Oct 2, 2026
…ver parameters may add and a spec may be composed from several files (#1800)

> **Prompt:** update specsolve to the latest mathspec release

> [!NOTE]
> The following content was generated by AI.

specsolve now requires mathspec 0.2.1. A divisor, base or exponent that
adds over parameters now builds. A spec fragment is refused by name
instead of raising `KeyError`. The PyPSA parity corpus stays pinned at
v0.2.0.

<details><summary>What changed, and why</summary>

**Main is red against 0.2.1 today.** The suite job solves its
environment fresh, so it already resolves mathspec 0.2.1. 0.2.1 admits
`x / (1 - cost)` and `(1 + rate) ** years` (energy-models/mathspec#757).
The compiler then stopped at `AssertionError: a divisor that adds is
refused at load`. `test_degree_parity[a-divisor-that-adds]` failed for
the same reason.

- **Compiler.** `_added_up` now adds a variable-free operand up to one
value per coordinate at a build too. A read already did this. How a
missing parameter row reads at a build:
- **Divisor:** `spreads`. The divisor is null where any parameter under
it has no row, so the existing check refuses it and names the parameter.
This matches the linopy lane and mathspec's rule that a divisor has no
fill value.
- **Power base or exponent:** `zero`. This is how a missing row reads
everywhere outside a divisor. The linopy lane agrees.
  - **Reads:** unchanged.
- **Fragments.** `lowered` refuses a program whose `given:` is not
empty. It names what the program reads and points to `mathspec.merge`.
Before, `check` only warned, and a build raised `KeyError`.
- **Pins.** The `[project.dependencies]` floor, the floors environment
and `uv.lock` move to 0.2.1. The lock diff touches mathspec only.
- **Parity corpus.** `pypsa-parity.yml` took its corpus tag from the
floor. The 0.2.1 corpus has 58 rungs instead of 16. Its `pypsa.yaml`
declares relations that `prep.py` does not supply (`snapshot_period`,
`Link_carrier`, `Generator_maintenance_cover`), so every rung came back
UNATTACHED. The job now checks out the corpus at `v0.2.0` and still
installs mathspec at the floor. With mathspec 0.2.1 installed, all 16
rungs MATCH and `differential/pypsa` regenerates byte for byte. Moving
the corpus is a separate `chore` PR, with the rungs it adds.

**Coverage moved.** `test_degree_parity[a-divisor-that-adds]` and
`test_power[an-operand-that-adds]` asserted the old refusal, so I
removed them. The compound operands are now covered in two places:
- `test_power`: three both-lane cases, the hand-computed discount factor
written as `(1 + rate) ** period`, and a missing row under a power.
- `test_arithmetic_laws`: a compound divisor on both lanes, and a sparse
compound divisor refused with the same sentence on both lanes.

`tests/test_composition.py` is new. A spec merged from the three files
of mathspec's composition how-to solves to the hand-computed 40. Three
fragments are each refused by name.

</details>

<details><summary>Verification</summary>

- I wrote the six compound-operand tests first and watched them fail on
the old tree, at the compiler's assertion.
- `pixi run check`: lint, format, pyrefly, suite (4541 passed, 522
skipped, 1 xfailed).
- `pixi run -e floors test-floors` on mathspec 0.2.1: 2838 passed.
- `pixi run docs-test`: 70 passed.
- `python -m tools.changelog check` and the `entry` check against this
title both pass.
- `parity.py` with the workflow's own command, against the v0.2.0 corpus
with mathspec 0.2.1: every rung MATCH, no diff under `differential/`.
- **Not run:** `pixi run test-bench` refused because the machine was
loaded. The diff does not touch `bench/`. `docs-build` was not run.

Mutation table, full suite each time. The first row comes from
`tools/mutate.py`. The other three were taken by hand: clean tree,
restore with `git checkout --`, `__pycache__` dropped on both sides.

| mutation | result |
|---|---|
| the fragment guard (`lanes.py:120-127`) deleted | **caught** |
| divisor `spreads` → `zero` | **caught** —
`test_a_sparse_divisor_that_adds_is_named_and_refused_on_both_lanes` |
| power `zero` → `spreads` | **caught** —
`test_a_missing_row_under_a_power_that_adds_reads_as_zero_on_both_lanes`
|
| the old "a build skips adding up" early return restored | **caught** —
7 tests |

</details>

<details><summary>Not done, and departures</summary>

- **Declared frame (energy-models/mathspec#741):** a named expression
may now declare a frame wider than its body. `Result.evaluate('cap')`
returns values over the body's dimensions, not the declared frame. A
build that reads it is correct. I left this unchanged.
- **Ladder catch-up:** moving the parity corpus to v0.2.1, with prep for
the new relations and the 42 new rungs, is not in this PR.
- **Worktree:** I worked on the session's assigned branch, not in a
fresh `../wt/<topic>` worktree.

</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01XSFapw8E3nibM7VFUCScU3

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XSFapw8E3nibM7VFUCScU3)_

---------

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

area: arithmetic Adding, subtracting, multiplying etc of variables, parameters etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(1 + rate) ** period is refused, so a discount factor ships as a prepared column

2 participants