Skip to content

index(dim, i): name the first and last coordinate instead of hardcoding the label #301

Description

@FBumann

Note

The following content was generated by AI.

ROADMAP Track 1 item 3coordinate helpers in where, index(dim, i)
with negative i from the end — has no issue, though the table marks it
"blocks 4". Filing it because two other issues now point at it and because
the workaround it replaces fails silently.

constraints:
  ramp:
    foreach: [snapshot, generator]
    where: "snapshot > index(snapshot, 0)"      # every snapshot but the first

1. The workaround fails silently

Today the boundary is written as a literal — where: "snapshot > 0" — which is
correct only while the instance is 0-based. Same YAML, horizon relabelled from
[0, 1, 2] to [1, 2, 3], ramp limit of 20 against a price of [50, 1, -1]:

index ramp rows built p objective
[0, 1, 2] 2 [100, 100, 0] 5100.0
[1, 2, 3] 3 [100, 100, 80] 5020.0

On the 1-based index snapshot > 0 is true everywhere, so the row roll wraps
onto — precisely the row the clause exists to exclude — comes back, and the ramp
limit quietly becomes cyclic. status == ok in both runs. No warning, different
answer.

Worth separating from its sibling: the seeding form (where: "snapshot == 0")
fails loudly on a shifted index — with no row to seed it the recurrence is
infeasible, status=warning and the objective is nan. It is the > 0 form
that is silent, and it is the more common one: five of the nine boundary clauses
in examples/ports/ are > 0.

Nine clauses across four ported models currently rest on the instance happening
to start at zero:

pypsa_ramp.yaml            1
pypsa_cyclic_storage.yaml  1
pypsa_storage.yaml         3
pypsa_unit_commitment.yaml 4

2. It blocks item 4, which is the ledger's open row

ROADMAP item 4 (window sum, sum_next_n) is marked needs 3. Item 4 is what
min_up_time needs — the row
docs/ports.md
records as the thing a port could not say. So this is the first of two steps
toward the corpus's one acknowledged expressiveness gap.

3. It is the better half of #300

#300 (a datetime dimension
cannot appear in a where) lists this as its option 2, and for the dominant
case it is the stronger answer. index(snapshot, 0) says the first snapshot
on any dtype and survives the horizon moving; a date literal only helps when
comparing against a specific known date, and still hardcodes it. The two are
complementary — a policy start year wants the literal, a boundary condition
wants this — but the boundary condition is what the corpus actually writes nine
times.

Design notes

Locality is coordinate-space. The helper reads the frame's own coordinate
column and never touches bound data, so it should not need a new plan node —
it resolves to a literal at load time or to a rank over the coordinate index.

Ordering is a real precondition. index(dim, i) is positional, and SPEC §8
step 4 says a dimension derived from parameter tables gets sorted distinct
values rather than a declared order. So index means something well-defined
only against a declared or source-supplied index, and it should say so — or
refuse on a derived dimension, which is the fail-loud reading. This is the same
caveat roll/shift already carry, and it becomes more load-bearing here.

The grammar needs widening. SPEC §6 gives atom ::= NAME | NAME COMPARATOR value with value ::= NUMBER | NAME_OR_STRING. A call on the right-hand side
is not currently expressible, so this is a where-grammar change and not only a
new helper.

Open: whether index() is confined to where (as ROADMAP's wording
suggests) or is also legal in expressions, where it would overlap with item 1's
at(x, over=dim, index=value).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:languageSchema, grammar, AST, macros, primitivesfeatureNew capabilityroadmapDecided yes: on the plan. Applied on resolution, replacing 'decision'taxedNew closed-vocabulary construct: needs both an oracle and a streaming implementation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions