Conversation
Documentation build overview
42 files changed ·
|
019430d to
e17748c
Compare
… a generator's zone can change by period `per:` on a lookup names the dimensions the map varies along besides `over` and `into`. Every operator joins on them and passes them through: `sum(by=)`, `at(by=)`, `shift(by=)`, `position(by=)` and a `where` naming the lookup all require the operand to carry them. A `by=` list and a two-lookup comparison must share them. The map binds single-valued per `(over, *per)`, which is the consumer's check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FD5LpGRzAWdi5sKWXDdnHC
e17748c to
4081e3e
Compare
Note The following content was generated by AI. Closed: #275 is decided the other way. #437 merged as This was the second of three proposals on one base, and it lost on capability rather than on taste. From #437's own comparison, on the rows where the three differ:
What this PR argued for is kept: a map conditioned on a second dimension, and the bind check that refuses a generator on two buses where a The five models the three proposals were compared on are #453. Generated by Claude Code |
Note
The following content was generated by AI. The lpspec half is a separate PR against fluxopt/lpspec.
What this changes
Implements #161. A lookup takes
per:, the dimensions the map varies along besidesoverandinto:overis consumed,intois produced,peris joined on and passed through. Nothing changes at the call site:sum(p, by=zone_of)takesp[generator, period]to[zone, period], andat(price, by=zone_of)readsprice[zone, period]back at[generator, period].shift(by=)andposition(by=)group within eachpercoordinate, and awherenaming the lookup is read at them.Decided at load, each with its refusal and a test:
perdim is declared, and is neitherovernorinto, and named once;sum,at,shiftandsum_backcarries everyperdim, and awhereleaf naming the lookup reads them (so the frame must carry them);by=[a, b]list shares itsperas it shares itsover, since one grouping is one join;whereshare it, or no row carries both.Left to the consumer, as #161 asks: the map binds single-valued per
(over, *per), and its table is(over, *per, into).Consumer-visible.
GroupSum,At, the three lookupwherenodes andDimensionPositionNodegainper: tuple[str, ...] = ();LookupDeclarationtoo.Namespace.lookupsisname -> (over, into, per). The legend printszone_of: 𝒢 × 𝒫 → 𝒵, and every application printszone_of(g, p).The golden model's new lines, rendered
$$p_{t,g} \le \mathit{spill}{t} \cdot \mathrm{zone_cap}{\mathrm{gen_zone}(g, t)} \qquad \forall t, g : \mathrm{gen_zone}(g, t) = \text{'north'} \wedge \mathrm{pos}_{\mathrm{gen_zone}(g, t)}(g) = 0$$
The pullback subscript reads the row's own snapshot, which is the reading composite
over: [generator, snapshot]could not giveat.Where it lands
model.py:LookupBlock.per, the three declaration refusals in_lookup_targets;lookups_ofreturns the blocks so the legend can readper.program.py:peronLookupDeclaration,GroupSum,At,DimensionPositionNode,LookupComparisonNode,LookupPairComparisonNode,LookupDefinedNode;_atom_dimsreads a lookup leaf and a grouped position atpertoo.resolution.py:Namespace.per_of, the list-must-share-perrefusal, the pair-must-share-perrefusal,perstamped on every node.dimensions.py:_check_conditioned, called from the sum, at and translation rules.lowering.py: passesperthrough.typesetting/walk.py:_lookuptakes the conditioning indices under the row's context;_domainfor the legend.perfield row and a section on the dimensions page with the A lookup conditioned on a second dimension:per:, so a generator's zone may change by period #161 example and the rules; rows and sentences in operators, expressions, reading; notation regenerated.Verified
On the current head, in a Python 3.13 venv built from PyPI (pixi.sh is blocked in this environment, so the tool versions are the latest rather than the pinned ones):
pytest -q: 1223 passed, 6 skipped.ruff check .: all checks passed.ruff format --check .: 127 files already formatted.tools.schema,tools.notation,tools.spec_math,tools.home_math,tools.gallery,tests.typesetting.golden— andtests/test_docs.pypasses on the result.Not run:
pyrefly,reuse lint,typos,prettier --list-different,compile-tex, andpixi run cias a whole.mkdocs build --strictreaches its last step and then aborts on the one error the proxy causes — it cannot fetchhttps://docs.python.org/3/objects.invfor mkdocstrings.Three conflicts against main were resolved by hand:
typesetting/walk.py: main names the where-precedence levelcomparison(feat(program): a where predicate's operands are walked with where_children, as an expression's are with children #416/chore: the separability walk leaves program.py, and every table keyed by an operator is pinned to the closed set #418) where this branch still wrote the literal2; the three lookup predicates keep main's name and this branch'snode.per, ctxarguments.tests/test_lowering.py:test_a_binary_variable_lowers_to_a_vtypeistest_a_binary_variable_lowers_to_a_binary_domainon main (refactor(program): a variable's domain is called domain in the program, as the file calls it #427); the newperlowering test sits beside it under main's name.docs/reference/notation.mdandtests/typesetting/golden/markdown.outare generated, so they were regenerated.Why
A map keyed by
overalone cannot say a zone that changes by period, so the relation had to be flattened into a0/1membership parameter, which carries no cardinality claim: a generator in two zones in one period was a legal model that said what the modeller did not mean (#161's 170.0 row).per:keeps the map a map, so the slip becomes a refusal at bind, and it lands on the one lookup block #422 leaves.🤖 Generated with Claude Code
https://claude.ai/code/session_014FHQEdh9u2ppraSniF3iJd