Skip to content

A datetime dimension cannot appear in a where: the grammar has no date literal #300

Description

@FBumann

Note

The following content was generated by AI.

dtype: datetime works end to end — it declares, binds date-typed data, builds
and solves. The one thing it cannot do is appear in a where, because the
grammar's value production has no date literal. So a datetime dimension is
usable exactly as long as nothing about the model is conditional on time.

What works, and where the wall is

Verified against a three-day horizon with load bound from a polars frame of
datetime.date values:

dimensions:
  snapshot: {dtype: datetime, values: [2024-01-01, 2024-01-02, 2024-01-03]}
declare, bind, build, solve — no where solves, objective 60.0
where: "snapshot > 2024-01-01" SchemaError: Failed to parse where string
where: "snapshot > '2024-01-01'" SchemaError: Failed to parse where string
where: "snapshot != 2024-01-01" SchemaError: Failed to parse where string

SPEC §6 gives value ::= NUMBER | NAME_OR_STRING. A bare 2024-01-01 fails on
the -; the quoted form fails because a quoted string is not a value either.
There is no third spelling.

Why it matters more than the corpus suggests

Every one of the 16 shipped models declares snapshot: {dtype: int}, so nothing
in the repo exercises this. That is not because integer snapshots are natural —
PyPSA's own snapshots are timestamps, and the ports convert them to 0, 1, 2 …
to get in the door.

The constructs it removes are the ones the ladder was built to demonstrate. With
a datetime snapshot you cannot write ramp limits (where: "snapshot > 0"),
non-cyclic storage seeding (where: "snapshot == 0"), or unit commitment's
boundary rows — rungs 2, 3 and the MILP entry. Nine where clauses across four
ported models are unwritable on the dtype the domain actually uses.

The workaround, and what it costs

A bool parameter carrying the mask works, and solves correctly (objective
50.0, first snapshot dropped):

parameters:
  after_first: {dims: [snapshot], dtype: bool}
constraints:
  bal:
    foreach: [snapshot]
    where: "after_first"

This is defensible under SPEC §11 — computing a mask is data prep. What it costs
is that "the first snapshot" stops being a statement in the model and becomes a
column that has to be regenerated whenever the horizon moves. Two files must
agree about which row is first, and nothing checks that they do.

Options

  1. A date literal in the where grammar. Extends value with an ISO-8601
    form. Interacts with SPEC §1's deliberate decision to keep YAML 1.1's implicit
    timestamp resolution — the where string is a quoted scalar, so the literal
    inside it is never touched by that, but the two need to agree on what a date
    means.
  2. Coordinate helpers instead — Track 1 item 3, index(dim, i) with negative
    i from the end. This is the better answer for the dominant case: where: "snapshot == index(snapshot, 0)" says "the first snapshot" on any dtype and
    survives the horizon moving, which a hardcoded literal does not. It does not
    cover comparing against a specific date, which is a real thing to want
    (a policy start year, a maintenance window).
  3. Refuse dtype: datetime at declaration until one of the above lands, so
    the language stops accepting a dimension it cannot filter. Worse for anyone
    using it successfully today without a where, and there is no way to know how
    many that is.

Options 1 and 2 are complementary rather than alternatives; 3 is a stopgap that
trades a loud failure late for a loud failure early.

Related: _yaml.py already flags datetime as "accepted and does not yet
implement"
, pointing at the dtype guard from #65 (closed). That note is about
label coercion; this is the separate half.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:languageSchema, grammar, AST, macros, primitivesbugFails loudly: exception or obviously wrong result

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions