Skip to content

feat(program): a model says whether an axis may be built a window at a time, and what each coordinate needs from its neighbours - #374

Merged
FBumann merged 7 commits into
mainfrom
feat/separability-a-driver-can-act-on
Sep 2, 2026
Merged

FBumann merged 7 commits into
mainfrom
feat/separability-a-driver-can-act-on

Conversation

@FBumann

@FBumann FBumann commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "Can you just do a stacked PR fixing those?" — the three points lpspec's driver raised against #248, then "Yes!" to naming each coupling's remedy and answering the scenario sweep too.
Then, reviewing the lpspec side (lpspec#1514): "I thought the changes in mathspec would simplify lpspec and reduce code here." — and "Lets do it!" to the driver handing over values rather than holding the rule; then "Remove them!" to behind and independent, which nothing reads.

Note

The following content was generated by AI.

Supersedes #248 and #370. #248 branched before alpha.66 moved the where nodes into program.py, and GitHub refuses to retarget a stacked PR, so this is #248's feature rebased onto alpha.67 in one commit — a mask's leaves now come off Mask.atoms rather than a walk of its own — plus the five changes below. Same shape and placement as #248: one dataclass and one attribute beside Footprint.

Three changes read from the consumer that will call it (lpspec.strategy.EachWindow):

  1. halo becomes ahead, and what a row reads behind is not reported. A translation reads ahead for a negative offset (t - offset), which a window must overlap to cover. A positive offset and a trailing window read behind, and a window starts where the driver puts it: what its first rows meet there is the edge policy, the opening state a rolling horizon seeds. abs(reach) could not tell the two apart, and only one of them is the model's question.
  2. Reasons are typed by what a driver can do about them, as three mappings rather than one. coupled (a sum in a constraint, a grouping, a wrap, a set, an at()) no window satisfies. undecided lists each read whose values set the reach — a named offset, a partition, an at() — as a Reach of the declaration, the parameter or lookup, and what it stands as; the same shape as coupled refused PyPSA's link delay, which rolls routinely. restarts names a position() a window restarts, which a rolling horizon seeding its opening state means. windowable is false while anything is coupled or undecided; a restart does not count against it.
  3. The claim is narrowed to what the walk certifies: every row builds complete inside some window. It cannot say the windowed answer is the whole-horizon one — a store carried over one row windows cleanly and a rolling solve of it is still a different answer — and the docstrings and reading.md said it could.

One more, from a follow-up question ("can we say what would make it windowable?"):

  1. Each coupled entry names the change that would lift it, after a dash: a horizon total becomes a rolling sum_back(within=n), a wrap an opening-state seed at position(t) == 0, a grouping is windowed along the dimension it groups into, a set needs a window holding it whole. Named and not applied — every one changes what the model means, and math-spec's Advice already takes that line. at() moves from coupled to undecided, naming the lookup whose values set its reach.
    Two more, from the lpspec review:

  2. The verdict folds a parameter's values in itself. lpspec's first driver parsed undecided's joined string back apart, re-walked the tree for the Translate whose offset it named, and applied its own sign rule — and that copy had already drifted (it skipped variable bounds). Now undecided is structured and Separability.resolved(least) takes each named parameter's least value and returns the same verdict with ahead widened, so the rule that a negative offset reads ahead, and a positive one reads behind and asks nothing, has one home. A reach a lookup decides — a partition, a coordinate — is not a value, so it stays undecided and the driver refuses or resolves it; a name nothing waits on is a KeyError rather than a silent zero.

  3. behind and independent are gone. No driver read either: lpspec's window cannot overlap backwards, and its scenario sweep slices a column the spec never declares, so the model never sees that axis. A named sum_back width is therefore not undecided either — it reads behind only. They come back the day a driver asks, knowing then what shape it wants.

Verified

After merging main (alpha.70) and change 6: pixi run lint (lefthook: prettier, ruff, typos), pixi run typecheck (main's three pre-existing yaml stub errors in this env and nothing new), pytest: 1049 passed — the twenty of #248 as reworked here less the four independent cases, plus five for resolved: both signs of an offset, a folded value widening a static reach, a static reach and a lookup's surviving the fold, and the refused name. reading.md gains one executed line (the remedy) and the page test's claim count follows. Not run: mkdocs build --strict, compile-tex.

Coverage moved: the six coupling cases split into two structural (coupled), three data-decided (undecided, asserting the name), and one restart (asserting it stays windowable); a negative-offset case, the at() case, four independent cases and the remedy wording are new.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FPBsP3UM6esCBREVmjMEhZ

FBumann and others added 2 commits August 31, 2026 20:25
…at a time, before a driver cuts one

A rolling horizon or a myopic pathway asks one thing of a model before it
starts: would windowing change the answer? separability reports the overlap two
windows need and, where none would do, names each declaration and the construct
tying the axis together. A reduction means opposite things by position — in a
constraint a sum ties every window to every other, in the objective it is
additively separable.

Squashed from the four commits of claude/separable-along and rebased onto main
alone, dropping the closed #247 serialisation base it was stacked on.

Co-Authored-By: Claude <noreply@anthropic.com>
…a time, and what each coordinate needs from its neighbours

One dataclass and one attribute beside the Footprint the program already
answers: program.separability[dim] says how far a row reads behind and
ahead along the axis, which declarations tie it together and the modelling
change that would lift each, which reaches only data can decide and the
parameter or lookup that decides them, and which masks count a position a
window would restart. windowable and independent are the two verdicts a
rolling-horizon driver and a scenario sweep ask before cutting a slice.

Carries #248 rebased onto alpha.67, where a mask is a Mask and its leaves
come off Mask.atoms rather than a walk of its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPBsP3UM6esCBREVmjMEhZ
FBumann and others added 3 commits September 2, 2026 11:22
… verdict folds a parameter's values in

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEyTfEbZzdTFavr43bRgr
…and nothing reads it for a scenario sweep

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEyTfEbZzdTFavr43bRgr
@FBumann
FBumann merged commit 1e029b4 into main Sep 2, 2026
6 checks passed
@FBumann
FBumann deleted the feat/separability-a-driver-can-act-on branch September 9, 2026 06:45
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.

1 participant