Skip to content

fix: let a window stop at each group's edge, as its checks already assumed - #59

Closed
FBumann wants to merge 2 commits into
feat/sum-forwardfrom
fix/window-partition
Closed

FBumann wants to merge 2 commits into
feat/sum-forwardfrom
fix/window-partition

Conversation

@FBumann

@FBumann FBumann commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #57. Stacked on #56 — base is feat/sum-forward, so review that first.

What was wrong

by= was half-landed. dimensions.py partition-checked sum_back, and operators.py's docstring said by= worked on it — but the Builtin had no lookup_kwargs, so call_shape_error refused the call before either could run. Checked, documented, unreachable.

What was missing

Only the render. Resolution is generic (it reads lookup_kwargs), so the signature change alone makes by= arrive as a LookupNode and the existing dimension checks light up. What had to be built is the group on the operator inside the domain, where shift carries it on a leaf subscript instead:

$$\sum_{h' \in \mathcal{H} ,:, 0 \le h \ominus^{\mathrm{day_of}(h)} h' < 3} \mathit{started}_{u,h'} \le \mathit{on}_{u,h}$$

The superscript takes the bare index, not the subscript in force — the group is a property of the row being written, and a window over a translated operand still asks which group that row is in. That matches _Context.subscript, which does the same for shift.

Why it's needed

Representative periods. A dozen typical days stand in for a year, each weighted, their hours numbered consecutively — but that numbering is storage order, not a timeline. The last hour of one representative day does not precede the first of the next in any physical sense.

An unpartitioned window sums straight across that seam. It builds, it solves, and the commitment it returns couples two independent samples through a boundary that doesn't exist. Nothing warns, because the axis is ordered and the language has no other way to learn the order isn't time.

Not sayable another way, so this is Capability rather than ergonomics under #30: the partition is a condition inside the summation's domain, relating the bound index to the row's. A where masks which rows get built, not how far one reaches — excluding rows near a boundary would drop the constraints entirely rather than shorten the windows.

Notes for review

  • The -^{group} typography is pre-existing. A bare partitioned window renders 0 \le h -^{\mathrm{day\_of}(h)} h' < 3, a plain minus carrying a superscript. shift already does exactly this — x_{h -^{\mathrm{day\_of}(h)} 1} — so the windows now follow the established convention rather than inventing one. If that convention is worth revisiting, it should be revisited for both.
  • test_the_golden_model_reaches_every_line_of_the_walk caught the missing golden coverage before I did; seasonal_window is there for it.
  • Two new probes (sum_back_partitioned, sum_forward_partitioned), two doc-table rows, and a prose section leading with the representative-periods case.
  • 438 passing, lint clean.

🤖 Generated with Claude Code

FBumann and others added 2 commits August 24, 2026 20:29
`sum_back` reaches back along an axis; nothing reached forward. With a
literal width that is a composition and belongs in `macros:` --- but the
composition is not the same operator turned around:

- `shift` past the end drops the rows it vacates, or fills them with a
  number. A window running off the axis is short, not empty, and the row
  being written is always inside its own window, so none is ever lost.
- A width that is a parameter has no forward spelling at all. It would
  need `offset=-(p-1)`, a negative named offset, which the language
  refuses so a translation's direction is never something the data
  decides. Per-entity forward windows --- a minimum down time, a notice
  period --- were unsayable.

So it is one operator pointed two ways, and it lands as one branch: the
width, the edge policy, the summation and the pullback are all shared,
and only which side of the difference carries the prime tells them
apart. `0 <= t - t' < n` becomes `0 <= t' - t < n`, and nothing else in
the equation moves --- which the golden diff says out loud, being pure
addition with no rendered line disturbed.

No new node type: an operator is a `FunctionCallNode` and its name, so
the public surface that grows is `BUILTIN_NAMES`, by one string.

Refs #49

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sumed

`dimensions.py` partition-checked the windows and `operators.py` said `by=`
worked on them, but neither `Builtin` carried `lookup_kwargs`, so
`call_shape_error` refused the call before either could speak. Half a
feature, checked but unreachable.

The half that was missing is the render. Resolution is generic --- it
reads `lookup_kwargs` --- so the signature alone makes `by=` arrive as a
`LookupNode`; what had to be built is the group on the operator inside
the domain, where `shift` carries it on a leaf subscript instead. It is
the bare index there, not the subscript in force: the group is a
property of the row being written, and a window over a translated
operand still asks which group *that row* is in.

What asks for it is representative periods. A dozen typical days stand
in for a year and their hours are numbered consecutively, but that
numbering is storage order, not a timeline --- so an unpartitioned
window sums straight across the seam between two samples and returns a
schedule coupling days that never touch. Nothing could warn about it,
the axis being ordered and the language having no other way to learn the
order is not time. Not sayable another way either: the partition is a
condition inside the summation's domain, and a `where` masks which rows
are built rather than how far one reaches.

Closes #57

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@read-the-docs-community

Copy link
Copy Markdown

@FBumann
FBumann marked this pull request as ready for review August 25, 2026 05:02
@FBumann
FBumann requested a review from brynpickering as a code owner August 25, 2026 05:02
FBumann added a commit that referenced this pull request Aug 25, 2026
…ordinate

Two of the three rules #62 collected, and the first is why it was filed.
`shift(offset=-lead)` loaded clean and then raised a bare
`AssertionError` out of the typesetter, because `_amount` matches a unary
minus and asserts the operand is a number. That assert was reading a
rule the language stated and never enforced --- and it is the rule the
whole treatment of a named offset rests on: `walk.py` renders one as
always-backward *because* a negated one is refused. It is refused now,
at load, in a sentence, so the assert is a precondition rather than a
hope. Its docstring said `by=` throughout, which was the kwarg's name
two renames ago and is now a different kwarg on the same operator.

The second rule is the dim half of the same idea: an amount is read at
the coordinate its operator walks, so a parameter varying over a dim
that coordinate does not carry has nowhere to be read from. It rendered
as a bare symbol, which on the page says "one number" about a column ---
the same way an unchecked width did before #58, and wrong in the same
direction.

`by=` is what makes such a parameter readable again, one amount per
group, so the rule admits the dims a partition maps into. That half was
`shift`'s alone until #59 gave the windows a partition too; both get it
here, and a width per representative day now says what it means.

Refs #62

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FBumann added a commit that referenced this pull request Aug 25, 2026
`sum_forward(<expr>, over=<dim>, within=<n|parameter>[, edge='wrap'][, by=<lookup>])`
sums the next n positions along an axis, where `sum_back` sums the last n.
The two are one operator with a direction, so the typesetter renders them
from one branch — `_WINDOWS` decides only which side of the lag operator
the written index falls on.

Also carries the `sum_forward` half of #59: `by=` partitions a leading
window exactly as it does a trailing one, so a window stops at its group's
edge rather than reaching across it. The `sum_back` half of that change
landed separately, since it fixed a rule main already documented.

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

FBumann commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Split in two and closing as superseded — the change turned out to be two independent ones wearing a single commit.

  • fix: let sum_back stop at each group's edge, as its checks already assumed #65 takes the sum_back half, straight to main. That half was not waiting on sum_forward at all: the BUILTINS comment already said by= partitions the axis for "shift and sum_back", and _dims_call already ran every partition check for both names. Only the builtin never declared the kwarg, so the rule two other places described was unreachable. The rendering there is identical to this PR's for seasonal_window, in all three formats.
  • feat: sum_forward, the window sum_back already was pointed the other way #56 takes the sum_forward half — the builtin, sum_forward_partitioned.yaml, the generator row, and the comment reword to "the two windows" — so that operator arrives complete instead of landing and then getting a by= bolt-on.

Nothing from this branch is dropped.


Generated by Claude Code

@FBumann FBumann closed this Aug 25, 2026
FBumann added a commit that referenced this pull request Aug 25, 2026
…ordinate (#63)

Two of the three rules #62 collected, and the first is why it was filed.
`shift(offset=-lead)` loaded clean and then raised a bare
`AssertionError` out of the typesetter, because `_amount` matches a unary
minus and asserts the operand is a number. That assert was reading a
rule the language stated and never enforced --- and it is the rule the
whole treatment of a named offset rests on: `walk.py` renders one as
always-backward *because* a negated one is refused. It is refused now,
at load, in a sentence, so the assert is a precondition rather than a
hope. Its docstring said `by=` throughout, which was the kwarg's name
two renames ago and is now a different kwarg on the same operator.

The second rule is the dim half of the same idea: an amount is read at
the coordinate its operator walks, so a parameter varying over a dim
that coordinate does not carry has nowhere to be read from. It rendered
as a bare symbol, which on the page says "one number" about a column ---
the same way an unchecked width did before #58, and wrong in the same
direction.

`by=` is what makes such a parameter readable again, one amount per
group, so the rule admits the dims a partition maps into. That half was
`shift`'s alone until #59 gave the windows a partition too; both get it
here, and a width per representative day now says what it means.

Refs #62

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@FBumann
FBumann deleted the fix/window-partition 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.

sum_back/sum_forward partition-checking is unreachable, and the docstring claims by= works

1 participant