Skip to content

feat(language): a variable, a constraint and a cased expression declare their shape as dims, as a parameter does - #429

Merged
FBumann merged 1 commit into
mainfrom
feat/dims-everywhere
Sep 14, 2026
Merged

FBumann merged 1 commit into
mainfrom
feat/dims-everywhere

Conversation

@FBumann

@FBumann FBumann commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: do dims everywhere!

Prompt: "Please rebase #245 and #429" — rebased onto main at eac1930, where #422 landed.

Note

The following content was generated by AI.

Closes #425, the other way round from its title: foreach: is gone and every declaration's shape is dims:, the word a parameter, the program's three declarations and the rest of the code already used. No alias: the closed schema's own error names the valid keys.

What this changes

dims: on variables, constraints and cased expressions, in the model, the schema, every example, the golden model, the reference and the error messages that name the key. The program is untouched: ParameterDeclaration, VariableDeclaration and ConstraintDeclaration already said dims. Typeset output is unchanged, so the golden .out files did not move.

Why

One fact had two spellings on the file surface, and foreach was doing two jobs, naming a shape and reading as the ∀ the typesetter prints anyway (#425).

What the rebase onto eac1930 resolved

Four conflicts, all where #422 removed the label-space kind of lookup out from under a passage this branch had only renamed a key in:

  • dimensions.md — the dtype: label-space section this branch edited is gone from main; the one surviving foreach in the rewritten "Dimension or lookup?" paragraph took the rename.
  • tests/fixtures.py — SMALL_MODEL's tag is a parameter on main, not a label-space lookup; only the three variables entries take the rename.
  • tests/test_lowering.py — test_a_label_space_keeps_its_dtype_and_has_no_target is deleted on main, so there is nothing to rename.
  • tests/test_validation.py — one docstring line.
Gates, what was regenerated, and what was left

pixi is not installed in the session that rebased this, so the gates were run tool by tool in a plain venv at the pinned ruff==0.16.1 and pyrefly==1.2.0, on 8c809c6, clean worktree, base eac1930:

Gate Result
ruff check / ruff format --check pass, 127 files
pyrefly check 0 errors (9 suppressed)
reuse lint compliant, 203 / 203
pytest -q 1155 passed, 6 skipped — the same count origin/main gives in that venv
mkdocs build --strict pass
prettier --check on every changed page pass

docs-build had not been run on the pre-rebase branch; it passes here with the one intersphinx inventory (https://docs.python.org/3/objects.inv) removed from mkdocs.yml for the run and restored after — the proxy answers it 403, and it is the only thing that failed the gate.

  • Regenerated with their own tools and the diff read: schema/math-spec.schema.json (the key and its title), tests/typesetting/golden/*.out (no change), the gallery pages, notation.md, operators.md, docs/index.md and README.md (YAML key lines only). All were already byte-identical to what the branch carried.
  • tests/test_dimensions.py pins the reworded constraint message, which now says "not in its dims:" and "add it to dims:".
  • Left: over: on a lookup and a set names one dimension, not a shape, and stays. CHANGELOG.md keeps its history. The docs' noun for the shape is still "the frame" where it was.

Not run: compile-tex — tectonic and typst are absent here, and the LaTeX output is byte-identical. The 6 skips are those two. pixi run ci itself was not run.

Noticed, not changed: tests/test_piecewise.py still names a test test_the_emitted_foreach_follows_declaration_order, the one foreach left in the tree outside CHANGELOG.md. It is this branch's to rename, not the rebase's.

@FBumann

FBumann commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@brynpickering @FabianHofmann Opinions?

@FabianHofmann

FabianHofmann commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

I like that very much, I think dims is the natural way to go, as 1) they are dimensions defining the shape of the objects 2) we define dimensions explicitly and we pick up that wording. So I would give that a go. But I am not master of design (@brynpickering)

Note I did not take a look at the code yet and would wait until the the merge conflicts are resolved

…re their shape as dims, as a parameter does

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrrLMWgN7VSZ4Xxf27t9pz
@FBumann
FBumann force-pushed the feat/dims-everywhere branch from 8c809c6 to bc77542 Compare September 10, 2026 14:01
@brynpickering

Copy link
Copy Markdown
Contributor

@FBumann @FabianHofmann the reason for foreach was to try and be more explicit for non-linopy/xarray users, since dims is an xarray concept more than anything else. pandas doesn't have the concept of dims, but rather index. Not sure what polars/narwhals uses.

foreach is very much trying to be defining a sentence that modellers can readily understand without needing the wider programming context in mind (define A foreach [B, C], where XYZ). But it maybe isn't the right name. Can we define something else that isn't foreach and isn't dims that is the most understandable for a non-programmer audience?

@FBumann

FBumann commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

@brynpickering Do you agree that we should remove the problem of having dims and foreach interchangeably?

So we merge this pr and have dims everywhere.

Then we can decide about a final name in a follow up?

About that final word:

We have the top level concept in the yaml right now

dimensions:
  snapshot: { dtype: int }
  generator: {}

And INDEX in pandas isn't the same as dims in xarray. It's index.name = dim, index = dim.values pretty much

I think dimension/dims is the best word for it. That xarray happens to use it is not a problem I think.
But we could land on not abbreviating dims --> use dimensions everywhere...?

variables:
  p:
    dimensions: [snapshot, generator]

@brynpickering

Copy link
Copy Markdown
Contributor

@FBumann yes I agree we should use the same term everywhere. I'm fine with using dimensions everywhere for now (via this PR) and then we might want to update it later.

One caveat I'd make is that there could be a benefit to having parameters define a different name to the others since it has a different meaning. It is the maximum dimensions over which it can be defined, while for all others, it is the exact dimensions over which it will be defined. Not sure how we clarify that except through documentation or through explicit naming (max_dimensions). Can be a different issue to resolve later.

@FBumann
FBumann merged commit d8dfdb0 into main Sep 14, 2026
5 checks passed
FBumann added a commit to fluxopt/specsolve that referenced this pull request Sep 15, 2026
…re their shape as dims, as a parameter does (#1634)

> **Prompt:** Lets update lpspec to the latest mathspec release

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

Follows math-spec to `v0.0.0-alpha.88`. A variable, a constraint and a
cased
expression now declare their shape as `dims:`, the key a parameter
already
used; `foreach:` is gone from the language, so every model file in the
tree is
rewritten.

alpha.87 is the only release in the range that changes what a file may
say
(energy-models/mathspec#429). alpha.86 adds the lookups to the names a
mistyped `where` is answered with, and alpha.88 is documentation. The
lowered
`Program` this package consumes was already spelled `dims`, so no engine
logic
moved — the rename reaches the model files, the docstrings that quote
the key,
and the prose.

- `pyproject.toml` and `uv.lock` to alpha.88.
- `foreach:` → `dims:` across `examples/`, `differential/pypsa/rungs/`,
  `bench/models/`, and the inline specs under `tests/`.
- Engine internals that said "the foreach dims" now say "the frame
dims", and
`refuse_outside_foreach` is `refuse_outside_frame`: the word was the
retired
  key's, and what it describes is the declaration's frame.
- `docs/`, `README.md` and both notebooks follow. No page needed
regenerating —
  the three generators agree with the renamed models.

No alias and no hand-written message for `foreach:`; the closed schema's
own
error names the valid keys. No test asserted the old spelling as
behaviour, so
no coverage moved.

<details><summary>What was verified, and what was not</summary>

Run on this branch's head, against math-spec `5656b11`
(`v0.0.0-alpha.88`):

```
pytest -q -n auto            3960 passed, 251 skipped, 1 xfailed
ruff check .                 All checks passed!
ruff format --check .        318 files already formatted
pyrefly check                0 errors (package, and the linopy reference scripts)
pytest bench/test_harness.py 140 passed, 5 skipped   (idle box, load < 3 on 4 cores)
mkdocs build --strict        clean
python -m tools.gallery_math --check   47 pages match their models
python -m tools.ladder --check         clean
python -m tools.constructs --check     docs/examples/index.md matches the models
```

Not run: the depth-3 expression sweep, and the CodSpeed job.

`bench/` carries only model files here, so the harness run is a check
that the
renamed models still load and solve — it takes no numbers and this PR
publishes
none.

</details>

<details><summary>The one collision the sweep created</summary>

`_wide_objective_of` in `tests/test_arithmetic_laws.py` took a `foreach`
argument and built a local named `dims` for the dimensions mapping, so
renaming
the argument made the second shadow the first and the constraint
declared a
mapping where a list belongs:

```
math_spec.errors.SchemaError: constraints.c.dims: Input should be a valid list
```

The local is now `dimensions`. It was the only such clash in 205 files,
and the
suite caught it.

</details>

<details><summary>Incidental lockfile churn</summary>

`uv lock` also tightened six environment markers it had been carrying
loose —
`pycparser`, `linkify-it-py`, `ptyprocess` and memray's three. Tool
output
rather than an edit of mine, and left as the tool wrote it.

</details>

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

https://claude.ai/code/session_01C8VMbam9FtNjVAzmUYabQV

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

Co-authored-by: Claude <noreply@anthropic.com>
FBumann pushed a commit to fluxopt/specsolve that referenced this pull request Sep 15, 2026
… it is built on

The merge of main brings two renames this branch predates: `foreach:` is
`dims:` since energy-models/mathspec#429, and `Result.expression` is
`Result.evaluate` since #1627. Both reach this example, and neither is caught
by a conflict: the model file and the call site are the branch's own new
content, so git carries them across untouched and the example fails at load
and at read-back instead.

`run.out` is unchanged — the example reproduces its recorded output exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C8VMbam9FtNjVAzmUYabQV
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Three reference pages conflicted, each where main had rewritten the same
passage this branch edited. Main's prose is kept in every case and this
branch's semantics folded into it:

- absence.md: main's two-paragraph shape, carrying the coverage split and
  this branch's "the bind is refused" for the four positions where absence
  has no reading.
- declarations.md: main's field table and dtype section, plus the coverage
  row, the worked example and the four coverage paragraphs.
- dimensions.md: main's three numbered rules, whose third now says a
  coordinate with no row is how `coverage: masked` masks rather than that
  it is absence — which absence.md no longer says either. The
  partial-lookup paragraph carries the lookup example, and the lookup
  field table gains the `coverage` key the schema already took.

Two test fixtures declared `variables.s` with `foreach:`, which #429
renamed to `dims:` on main. Git flagged no conflict there; the four
coverage tests failed until they were updated.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012k5ThJdGaqKM8NYoE2XhG5
FBumann pushed a commit that referenced this pull request Sep 15, 2026
Resolves the conflict with #429's `foreach:` to `dims:` rename across the
examples, the reference pages, the tests and the golden model. Every
conflicted hunk keeps this branch's spelling (`consume=`, `window=`,
`columns:`) with `dims:` applied on top, except `docs/about/limits.md`,
which takes #468's prose on the whole-table operator. The schema, the
golden output and the four generated pages are regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fhGZgaBspo7mh9Hjd3KtT
FBumann pushed a commit that referenced this pull request Sep 15, 2026
main renamed a declaration's `foreach:` to `dims:` (#429), and this branch
renamed the example models' `p`, `p_max` and `p_min` to `dispatch`,
`capacity` and `min_output`. Every conflict is both edits on one line, so
each takes main's key with this branch's names.

The five generated pages and the golden typesetter output were re-rendered
rather than merged by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZKoWvZmUg8XxUK3nKxMZr
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.

a parameter declares its shape as dims: while every other declaration says foreach:

4 participants