Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ it releases that version ([RELEASING.md](https://github.com/energy-models/mathsp

## Upcoming version

- refactor(program)!: the node a use of a named expression stands as is `NamedExpression`, beside `NamedMask`, and `Named` is gone ([#822](https://github.com/energy-models/mathspec/pull/822))
- docs(pypsa): the pypsa spec names its repeated row conditions as masks, and its topic files read them under `given: masks:` ([#825](https://github.com/energy-models/mathspec/pull/825))
- feat(language): a where predicate is named once under `masks:`, and another file reads it under `given: masks:` ([#821](https://github.com/energy-models/mathspec/pull/821))
- feat(language)!: a construct that reads the order of a dimension needs the dimension declared ordered ([#793](https://github.com/energy-models/mathspec/pull/793))
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ node's operands, and `where_children()` walks a predicate's. `walk()` yields
every node under an expression, parents first. `walk_regions()` yields each node
with the `cases:` regions it stands inside, outermost first.

A `Named` stands where an `expressions:` entry is used. Its `body` is the
A `NamedExpression` stands where an `expressions:` entry is used. Its `body` is the
entry's expression, the same object that `program.expressions[name].expression`
holds, and its value is the body's value. `children()` steps into the body, so
a walk reads through it.
Expand Down
2 changes: 1 addition & 1 deletion src/mathspec/_expression_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def _name(self, node: NameNode) -> Expression | None:

A named expression arrives as the one node [`Namespace.named`][]
built for it; the cast is the one place a
[`Named`][mathspec.program.Named] enters a tree typed as a program's,
[`NamedExpression`][mathspec.program.NamedExpression] enters a tree typed as a program's,
which lowering makes true.
"""
if node.name in self.formals:
Expand Down
6 changes: 3 additions & 3 deletions src/mathspec/boundedness.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
Expression,
Join,
Multiply,
Named,
NamedExpression,
Negate,
Parameter,
Power,
Expand Down Expand Up @@ -121,7 +121,7 @@ def _coefficient_sign(node: Expression) -> Sign:
"""
if isinstance(node, Negate):
return _flip(_coefficient_sign(node.operand))
if isinstance(node, Named):
if isinstance(node, NamedExpression):
return _coefficient_sign(node.body)
if isinstance(node, Constant) and node.value != 0:
return '+' if node.value > 0 else '-'
Expand Down Expand Up @@ -162,7 +162,7 @@ def _record_signs(node: Expression, sign: Sign, signs: dict[str, Sign]) -> None:
_record_signs(node.base, None, signs)
_record_signs(node.exponent, None, signs)
return
if isinstance(node, Sum | Join | Translate | WindowSum | Cases | Named):
if isinstance(node, Sum | Join | Translate | WindowSum | Cases | NamedExpression):
for child in children(node):
_record_signs(child, sign, signs)
return
Expand Down
6 changes: 3 additions & 3 deletions src/mathspec/dimensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
JoinedPredicate,
Mask,
Multiply,
Named,
NamedExpression,
Negate,
Parameter,
ParameterComparison,
Expand Down Expand Up @@ -75,7 +75,7 @@ def dims_of(node: Expression, schema: Spec, context: str) -> frozenset[str]:
if isinstance(node, Dual):
return frozenset({**schema.constraints, **schema.given.constraints}[node.constraint].dims)

if isinstance(node, Named):
if isinstance(node, NamedExpression):
return _named_dims(node, schema, context)

if isinstance(node, Cases):
Expand All @@ -96,7 +96,7 @@ def dims_of(node: Expression, schema: Spec, context: str) -> frozenset[str]:
assert_never(node)


def _named_dims(node: Named, schema: Spec, context: str) -> frozenset[str]:
def _named_dims(node: NamedExpression, schema: Spec, context: str) -> frozenset[str]:
"""An entry's declared frame where it has one — a narrower arm or body broadcasts along the rest — else its body's."""
declared = schema.expressions[node.name].dims
if declared is not None:
Expand Down
18 changes: 9 additions & 9 deletions src/mathspec/lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
Link,
Mask,
MaskDeclaration,
Named,
NamedExpression,
ObjectiveDeclaration,
Parameter,
ParameterDeclaration,
Expand Down Expand Up @@ -121,7 +121,7 @@ def lower(schema: Spec) -> Program:
)
resolve_expression(body_ast, ns, context, errors, formals=formals)

entries: dict[str, Named] = {}
entries: dict[str, NamedExpression] = {}
for ename in schema.expressions:
node, refusals = ns.named_entry(ename)
errors.extend(refusals)
Expand Down Expand Up @@ -196,7 +196,7 @@ def lower(schema: Spec) -> Program:
roots.append(objective.expression)
roots.extend(link for links in curves.values() for link in links)
roots.extend(terms)
in_math = frozenset(node.name for node in walk(*roots) if isinstance(node, Named))
in_math = frozenset(node.name for node in walk(*roots) if isinstance(node, NamedExpression))

piecewise = {}
for pname, links in curves.items():
Expand Down Expand Up @@ -269,7 +269,7 @@ def lower(schema: Spec) -> Program:
return program


def _terms(entries: Iterable[str], schema: Spec, ns: Namespace, errors: list[str]) -> list[Named]:
def _terms(entries: Iterable[str], schema: Spec, ns: Namespace, errors: list[str]) -> list[NamedExpression]:
"""Every entry with ``adds_to:`` that loads as a term, with a refusal in *errors* for each other.

A term that reads its own sum, directly or through the sums the file's
Expand All @@ -282,7 +282,7 @@ def _terms(entries: Iterable[str], schema: Spec, ns: Namespace, errors: list[str
if (target := schema.expressions[name].adds_to) is not None
and (entry := _term(name, target, schema, ns, errors)) is not None
}
sums: dict[str, list[Named]] = {}
sums: dict[str, list[NamedExpression]] = {}
for target, entry in resolved.values():
sums.setdefault(target, []).append(entry)
terms = []
Expand All @@ -299,7 +299,7 @@ def _terms(entries: Iterable[str], schema: Spec, ns: Namespace, errors: list[str
return terms


def _term(name: str, target: str, schema: Spec, ns: Namespace, errors: list[str]) -> Named | None:
def _term(name: str, target: str, schema: Spec, ns: Namespace, errors: list[str]) -> NamedExpression | None:
"""Named expression *name* as the term it writes into a given expression of its file, or ``None``.

The given entry is what makes a misspelt target a refusal in the file that
Expand All @@ -325,11 +325,11 @@ def _term(name: str, target: str, schema: Spec, ns: Namespace, errors: list[str]
entry = resolve_expression_text(name, ns, context, errors, ceiling=2)
if entry is None:
return None
assert isinstance(entry, Named), 'a term is a name, and a name resolves to the entry it names'
assert isinstance(entry, NamedExpression), 'a term is a name, and a name resolves to the entry it names'
return entry


def _loop(target: str, entry: Named, sums: Mapping[str, list[Named]]) -> list[str] | None:
def _loop(target: str, entry: NamedExpression, sums: Mapping[str, list[NamedExpression]]) -> list[str] | None:
"""The sums *entry* reads *target* through, by the terms in *sums*, or ``None`` where it does not read it.

``[]`` is a term that reads its own sum.
Expand All @@ -347,7 +347,7 @@ def _loop(target: str, entry: Named, sums: Mapping[str, list[Named]]) -> list[st
return None


def _frame_of(name: str, entry: Named, schema: Spec) -> tuple[str, ...]:
def _frame_of(name: str, entry: NamedExpression, schema: Spec) -> tuple[str, ...]:
"""The dims an entry is read over: the ``dims:`` it declares, as written, else the body's in declaration order."""
declared = schema.expressions[name].dims
if declared is not None:
Expand Down
12 changes: 6 additions & 6 deletions src/mathspec/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
'Mask',
'MaskDeclaration',
'Multiply',
'Named',
'NamedExpression',
'NamedMask',
'Negate',
'Not',
Expand Down Expand Up @@ -396,7 +396,7 @@ class Cases:


@dataclass(frozen=True)
class Named:
class NamedExpression:
"""A use of an ``expressions:`` entry, standing where its name was written, with the entry's body under it.

Its value is its body's: a consumer building rows steps through it, as
Expand Down Expand Up @@ -435,13 +435,13 @@ class Named:
| Translate
| WindowSum
| Cases
| Named
| NamedExpression
)


def children(expression: Expression) -> tuple[Expression, ...]:
"""The sub-expressions of *expression* — what every walk recurses through."""
if isinstance(expression, Named):
if isinstance(expression, NamedExpression):
return (expression.body,)
if isinstance(expression, Negate):
return (expression.operand,)
Expand Down Expand Up @@ -1039,7 +1039,7 @@ class Program:
#: each and refuses with [`assumption_message`][].
assumptions: Mapping[str, Assumption] = Sealed({})
#: Declared ``expressions:``, each saying whether the math reads it. None
#: builds a row of its own — one the math reads stands as a [`Named`][]
#: builds a row of its own — one the math reads stands as a [`NamedExpression`][]
#: where it is read — but all are lowered with the program, so a file whose
#: named expression is outside the language is refused by every verb that
#: reads the file rather than only by the one that reads the expression.
Expand Down Expand Up @@ -1416,7 +1416,7 @@ class NamedMask:
Its truth is its body's: a consumer steps through it, as
[`where_children`][] does. It is kept as a node rather than written in so
the typesetter can print the symbol where the name stood and define it
once, as [`Named`][] does for an expression.
once, as [`NamedExpression`][] does for an expression.
"""

name: str
Expand Down
14 changes: 7 additions & 7 deletions src/mathspec/resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
Cases,
Expression,
Mask,
Named,
NamedExpression,
NamedMask,
Predicate,
Region,
Expand Down Expand Up @@ -128,15 +128,15 @@ def __init__(self, schema: Spec) -> None:
}
#: named expression -> its resolved node, or ``None``, and its refusals;
#: filled the first time anything reads the name.
self._named: dict[str, tuple[Named | None, tuple[str, ...]]] = {}
self._named: dict[str, tuple[NamedExpression | None, tuple[str, ...]]] = {}
#: The named expressions and masks waiting to be resolved, the one
#: asked for first — each above the entries it reads, so it is a cycle's chain.
self._loading: list[str] = []
#: mask -> its resolved node, or ``None``, and its refusals; filled
#: the first time anything reads the name.
self._masks: dict[str, tuple[NamedMask | None, tuple[str, ...]]] = {}

def named(self, name: str, context: str) -> Named:
def named(self, name: str, context: str) -> NamedExpression:
"""The ``expressions:`` entry *name* as the node that stands where its name is written.

Resolved under the entry's own context the first time it is asked
Expand Down Expand Up @@ -187,7 +187,7 @@ def mask_entry(self, name: str) -> tuple[NamedMask | None, tuple[str, ...]]:
self._masks[name] = (node, tuple(errors))
return self._masks[name]

def named_entry(self, name: str) -> tuple[Named | None, tuple[str, ...]]:
def named_entry(self, name: str) -> tuple[NamedExpression | None, tuple[str, ...]]:
"""The ``expressions:`` entry *name* resolved, or ``None``, with every refusal it earned.

The entries it reads are resolved before it, walked from a stack that
Expand Down Expand Up @@ -475,7 +475,7 @@ def _over_the_ceiling(node: Expression, context: str, errors: list[str], *, ceil
return False


def _named(name: str, block: ExpressionBlock, ns: Namespace, errors: list[str]) -> Named | None:
def _named(name: str, block: ExpressionBlock, ns: Namespace, errors: list[str]) -> NamedExpression | None:
"""One ``expressions:`` entry as the node every use of it holds, or ``None`` once anything in it failed.

A cased entry's arms are checked one by one, so every fault is collected
Expand All @@ -488,7 +488,7 @@ def _named(name: str, block: ExpressionBlock, ns: Namespace, errors: list[str])
if not block.cases:
assert block.expression is not None
body = resolve_expression_text(block.expression, ns, context, errors, ceiling=None)
return None if body is None else Named(name, body)
return None if body is None else NamedExpression(name, body)

found = len(errors)
regions: list[Region] = []
Expand All @@ -511,7 +511,7 @@ def _named(name: str, block: ExpressionBlock, ns: Namespace, errors: list[str])
if len(errors) > found:
return None
left_over = Region(remainder(region.when for region in regions), fallback)
return Named(name, Cases((*regions, left_over)))
return NamedExpression(name, Cases((*regions, left_over)))


def _mask(name: str, block: MaskBlock, ns: Namespace, errors: list[str]) -> NamedMask | None:
Expand Down
6 changes: 3 additions & 3 deletions src/mathspec/typesetting/walk.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
JoinedPredicate,
Mask,
Multiply,
Named,
NamedExpression,
NamedMask,
Negate,
Not,
Expand Down Expand Up @@ -319,7 +319,7 @@ def _expression(self, node: Expression, ctx: _Context, *, need: int = 0) -> str:

def _arithmetic(self, node: Expression, ctx: _Context) -> tuple[str, int]:
"""Render *node*, returning the text and the precedence it binds at."""
if isinstance(node, Named):
if isinstance(node, NamedExpression):
if self.inline_expressions and not isinstance(node.body, Cases):
return self._arithmetic(node.body, ctx)
return ctx.indexed(self.symbols.name[node.name], self._frame_of(node.name)), _ATOM
Expand Down Expand Up @@ -409,7 +409,7 @@ def _substituted(self, node: Expression) -> Expression:

[`_binary`][] folds the sign of the result, so a substituted term prints as its body written out.
"""
while self.inline_expressions and isinstance(node, Named) and not isinstance(node.body, Cases):
while self.inline_expressions and isinstance(node, NamedExpression) and not isinstance(node.body, Cases):
node = node.body
return node

Expand Down
2 changes: 1 addition & 1 deletion tests/test_boundedness.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_a_variable_the_objective_drives_unopposed_is_named_with_its_side(patch,
)
def test_a_named_constant_coefficient_carries_its_sign(objective, side):
"""A coefficient written as an ``expressions:`` entry reaches the pass as a
``Named`` node over its constant. The sign was read off the node alone, so a
``NamedExpression`` node over its constant. The sign was read off the node alone, so a
named ``2`` claimed nothing and the unbounded variable went unnamed."""
notes = _notes(
**{
Expand Down
2 changes: 1 addition & 1 deletion tests/test_degree.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def test_calls_dual_finds_a_dual_wherever_it_stands(text, found):
def test_calls_dual_finds_a_dual_inside_a_cased_arm():
"""`calls_dual` recurses through a region of a `Cases`, not only the top node.

The reference resolves to the `Named` node carrying the block, so this also
The reference resolves to the `NamedExpression` node carrying the block, so this also
guards that the walk steps through it into the region values, reaching a
dual a non-recursive check — one that only inspected the node it was
handed — would miss.
Expand Down
12 changes: 6 additions & 6 deletions tests/test_expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from mathspec import to_spec
from mathspec.errors import LanguageError
from mathspec.expansion import parse_and_expand
from mathspec.program import Axis, Multiply, Named, Parameter, Sum, Translate, Variable
from mathspec.program import Axis, Multiply, NamedExpression, Parameter, Sum, Translate, Variable
from mathspec.resolution import Namespace
from tests.fixtures import DISPATCH_MODEL, SMALL_MODEL, comparison_of, expression_of, schema_of

Expand All @@ -36,7 +36,7 @@ def _resolved(text, ns):

def _bodies(resolved):
"""*resolved* with every named expression's body standing bare where its name was."""
if isinstance(resolved, Named):
if isinstance(resolved, NamedExpression):
return _bodies(resolved.body)
if isinstance(resolved, tuple):
return tuple(_bodies(part) for part in resolved)
Expand Down Expand Up @@ -132,7 +132,7 @@ def _bodies(resolved):
)
def test_a_call_expands_to_core_ast(expressions, macros, call, want):
"""The math a call expands to is what `want` spells; a named expression's
body arrives under the `Named` node carrying its name, which `_bodies`
body arrives under the `NamedExpression` node carrying its name, which `_bodies`
inlines, as lowering does."""
ns = Namespace(schema(expressions=expressions, macros=macros))
assert _bodies(_resolved(call, ns)) == _resolved(want, ns)
Expand All @@ -141,9 +141,9 @@ def test_a_call_expands_to_core_ast(expressions, macros, call, want):
def test_a_named_expression_arrives_under_the_node_carrying_its_name():
ns = Namespace(schema(expressions={'gen_cost': 'p * cost'}))
resolved = expression_of('sum(gen_cost, over=generator)', ns, 'e')
assert resolved == Sum(Named('gen_cost', Multiply(Variable('p'), Parameter('cost'))), (Axis('generator'),)), (
'the body is inlined resolved and the name kept, for the typesetter to define it once'
)
assert resolved == Sum(
NamedExpression('gen_cost', Multiply(Variable('p'), Parameter('cost'))), (Axis('generator'),)
), 'the body is inlined resolved and the name kept, for the typesetter to define it once'
assert isinstance(resolved, Sum)
assert resolved.operand is expression_of('gen_cost', ns, 'another use'), 'every use reads the one node'

Expand Down
6 changes: 3 additions & 3 deletions tests/typesetting/test_golden.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import pytest

from mathspec.operators import BUILTIN_NAMES
from mathspec.program import Dual, Expression, Join, Named, Predicate, Sum, Translate, WindowSum
from mathspec.program import Dual, Expression, Join, NamedExpression, Predicate, Sum, Translate, WindowSum
from mathspec.typesetting import FORMATS, legend, to_latex, typeset, walk
from mathspec.typesetting.format import OPERATOR_NAMES
from mathspec.validation import to_spec
Expand Down Expand Up @@ -166,7 +166,7 @@ def test_the_golden_model_carries_every_node_kind_the_walk_renders():
`coverage` installed, and its failure names the construct rather than a line.
"""
kinds = {type(node).__name__ for tree in _rendered_trees() for node in _nodes(tree)} - CARRIERS
declared = {node.__name__ for node in (*get_args(Predicate), *get_args(Expression), Named)}
declared = {node.__name__ for node in (*get_args(Predicate), *get_args(Expression), NamedExpression)}
assert kinds == declared, (
f'tests/typesetting/golden/model.yaml reaches {sorted(kinds - declared)} and misses '
f'{sorted(declared - kinds)}. Every node the walk renders needs a case here, '
Expand All @@ -191,7 +191,7 @@ def test_the_golden_model_calls_every_operator_in_the_language():


#: What the fixture cannot reach, by module and the source text of the line.
#: A bare ``Cases`` stands under the ``Named`` node resolution builds for its
#: A bare ``Cases`` stands under the ``NamedExpression`` node resolution builds for its
#: entry and nowhere else, so the arm that would print one in place is the
#: type's closure rather than a case. The absent objective is the arm a
#: *different* model takes — a file declares at most one — and
Expand Down
Loading
Loading