Skip to content

math_spec.typesetting.walk

The walk: resolved AST → typeset lines. Written once, for every format.

Everything here is a decision about the math — where a bracket changes the reading, which dimension a reduction binds, that a mask belongs on the ∀ rather than in the equation, that a translation shows at the leaf it re-indexes. None of it is about syntax, so none is duplicated per format.

AlignedComparison = ParameterComparison | ArithmeticComparison | CountComparison | DimensionComparison | DimensionPosition | RelationComparison | RelationPairComparison module-attribute #

PositionForm = Literal['plain', 'grouped', 'from_end'] module-attribute #

TranslationPolicy = Literal['plain', 'wrap', 'edge'] module-attribute #

Noticed(policies=set(), grouped=False, positions=set(), numeric_coordinates=set()) dataclass #

What the equations printed that the legend has to explain.

grouped = False class-attribute instance-attribute #

numeric_coordinates = field(default_factory=set) class-attribute instance-attribute #

policies = field(default_factory=set) class-attribute instance-attribute #

positions = field(default_factory=set) class-attribute instance-attribute #

Walk(schema, symbols, fmt, *, inline_expressions=False) #

Walks a validated schema, emitting :class:Lines in one format.

:meth:equations prints every section and returns what it :class:Noticed; the legend methods take that record, so they can only describe symbols the equations printed.

Source code in src/math_spec/typesetting/walk.py
def __init__(
    self,
    schema: Spec,
    symbols: Symbols,
    fmt: Format,
    *,
    inline_expressions: bool = False,
) -> None:
    self.schema = schema
    self.symbols = symbols
    self.format = fmt
    #: Substitute each plain named expression where it is used, rather than
    #: printing its symbol there and its definition once.
    self.inline_expressions = inline_expressions
    self.noticed = Noticed()
    #: The dims a named expression is read over: a cased one declares them,
    #: a plain one's fall out of its body.
    self.frames: dict[str, list[str]] = {name: self._frame_of(name) for name in schema.expressions}

format = fmt instance-attribute #

frames = {name: self._frame_of(name) for name in schema.expressions} instance-attribute #

inline_expressions = inline_expressions instance-attribute #

noticed = Noticed() instance-attribute #

schema = schema instance-attribute #

symbols = symbols instance-attribute #

convention_notes() #

What the two faces mean, with the model's own symbols.

Only where the model has both, and quoting only derived symbols: a table is the author's to write, so a symbol it supplies is not one this note governs.

Source code in src/math_spec/typesetting/walk.py
def convention_notes(self) -> list[str]:
    """What the two faces mean, with the model's own symbols.

    Only where the model has both, and quoting only derived symbols: a
    table is the author's to write, so a symbol it supplies is not one this
    note governs.
    """
    derived = [
        next((n for n in names if n not in self.symbols.overridden), None)
        for names in (self.schema.parameters, self.schema.variables)
    ]
    if not all(derived):
        return []
    given, chosen = (self.format.math(self.symbols.name[n]) for n in derived if n is not None)
    return [
        f'Upright is what the model is given {self.format.dash} a parameter such as {given}, a coordinate '
        f'map, a label {self.format.dash} and italic is what the solver chooses, such as {chosen}. '
        f'An index is italic too, being what a quantifier chooses, and a set is script.'
    ]

definition(name) #

The line defining one named expression, symbol = body over its frame.

Source code in src/math_spec/typesetting/walk.py
def definition(self, name: str) -> Line:
    """The line defining one named expression, ``symbol = body`` over its frame."""
    node = self.schema.resolved.expressions[name]
    frame = self.frames[name]
    ctx = self._context(frame)
    body = (
        self.format.cases(self._arms(node, ctx))
        if isinstance(node, CasesNode)
        else self._expression(node.body, ctx)
    )
    return Line(
        label=name,
        left=ctx.indexed(self.symbols.name[name], frame),
        right=f'{self._op("equal")} {body}',
        condition=self._quantifier(frame, ''),
    )

equations() #

Every titled section of equations, and what printing them noticed for the legend.

Source code in src/math_spec/typesetting/walk.py
def equations(self) -> tuple[list[tuple[str, list[Line]]], Noticed]:
    """Every titled section of equations, and what printing them noticed for the legend."""
    sections = [
        ('Objective', self._objective()),
        ('Subject to', self._constraints()),
        ('Definitions', self._definitions()),
        ('Variable domains', self._variables()),
        ('Assumptions', self._assumptions()),
    ]
    return sections, self.noticed

glossaries(noticed) #

Source code in src/math_spec/typesetting/walk.py
def glossaries(self, noticed: Noticed) -> list[Glossary]:
    fmt = self.format
    sets = [
        self._entry(
            self.symbols.set[d],
            f'index {fmt.math(self.symbols.index[d])} {fmt.dash} {fmt.mono(d)}{self._coords(d, noticed)}',
            block.description,
        )
        for d, block in self.schema.dimensions.items()
    ]
    parameters = [
        self._entry(self.symbols.name[p], f'{fmt.mono(p)}{self._over(list(block.dims))}', block.description)
        for p, block in self.schema.parameters.items()
    ]
    variables = [
        self._entry(self.symbols.name[v], f'{fmt.mono(v)}{self._over(list(block.dims))}', block.description)
        for v, block in self.schema.variables.items()
    ]
    definitions = [
        self._entry(self.symbols.name[e], f'{fmt.mono(e)}{self._over(self.frames[e])}', block.description)
        for e, block in self.schema.expressions.items()
        if e in self._defined()
    ]
    groups = (
        Glossary('Sets', sets),
        Glossary('Parameters', parameters),
        Glossary('Variables', variables),
        Glossary('Definitions', definitions),
    )
    return [group for group in groups if group.entries]

line(name) #

The one line name prints as: a named expression, a constraint, an assumption, a curve, or a variable's domain.

name is one of the five; :func:~math_spec.typesetting.typeset_declaration refuses the rest, and a name declared as two of them. An assumption is looked up where the document prints it from, so a condition a curve's method states is a line a reader can ask for before the curve is written out.

Source code in src/math_spec/typesetting/walk.py
def line(self, name: str) -> Line:
    """The one line *name* prints as: a named expression, a constraint, an assumption, a curve, or a variable's domain.

    *name* is one of the five; :func:`~math_spec.typesetting.typeset_declaration`
    refuses the rest, and a name declared as two of them. An assumption is
    looked up where the document prints it from, so a condition a curve's
    method states is a line a reader can ask for before the curve is
    written out.
    """
    if name in self.schema.expressions:
        return self.definition(name)
    if name in self.schema.constraints:
        return self._constraint(name)
    if name in self.schema.resolved.assumptions:
        return self._assumption(name)
    if name in self.schema.piecewise:
        return self._piecewise(name)
    return self._variable(name)

position_notes(noticed) #

A sentence for each positional symbol the model printed; the first says which of pos(t) and t is the position.

Source code in src/math_spec/typesetting/walk.py
def position_notes(self, noticed: Noticed) -> list[str]:
    """A sentence for each positional symbol the model printed; the first says which of ``pos(t)`` and ``t`` is the position."""
    notes = []
    if noticed.positions:
        index = self.format.math('t')
        place = self.format.math(self.format.apply(self._op('position'), 't'))
        dash = self.format.dash
        notes.append(
            f"{place} denotes where index {index} sits along its dimension's own order {dash} the order "
            f'{self.format.mono("shift")} steps along, not the order labels sort in {dash} counted from '
            f'{self.format.math("0")}. The index itself stays the coordinate, so {index} compares against '
            f'labels and {place} against positions.'
        )
    if 'grouped' in noticed.positions:
        applied = self.format.apply(self.format.upright('relation'), 't')
        grouped = self.format.math(self.format.apply(self.format.subscript(self._op('position'), [applied]), 't'))
        group = self.format.math(self.format.subscript(self.format.script('T'), [applied]))
        notes.append(
            f'{grouped} counts within the group a relation puts {self.format.math("t")} in: the subscript names '
            f'the map, {group} is the group it lands in, and that group has a first position of its own.'
        )
    if 'from_end' in noticed.positions:
        size = self.format.cardinality(self.format.script('T'))
        last = self.format.math(f'{size} {self._op("minus")} {self._number(1)}')
        notes.append(
            f'{self.format.math(size)} denotes the size of the set being counted along, and a position '
            f'counted from the end prints against it {self.format.dash} {last} is the last position, one '
            f'less than the size because the first is {self.format.math("0")}.'
        )
    return notes

sides(node, ctx) #

One comparison as its two sides, the relation symbol leading the right.

Split so that a line whose whole predicate is one comparison aligns on the relation, as a constraint does.

Source code in src/math_spec/typesetting/walk.py
def sides(self, node: AlignedComparison, ctx: _Context) -> tuple[str, str]:
    """One comparison as its two sides, the relation symbol leading the right.

    Split so that a line whose whole predicate is one comparison aligns on
    the relation, as a constraint does.
    """
    if isinstance(node, ParameterComparison):
        left, right = ctx.indexed(self.symbols.name[node.name], list(node.dims)), self._literal(node.value)
    elif isinstance(node, ArithmeticComparison):
        left, right = self._expression(node.left, ctx), self._expression(node.right, ctx)
    elif isinstance(node, DimensionComparison):
        if isinstance(node.value, int | float):
            self.noticed.numeric_coordinates.add(node.name)
        left, right = ctx.subscript(node.name), self._literal(node.value)
    elif isinstance(node, DimensionPosition):
        grouping = (
            None
            if node.partition is None
            else self._tuple([self._value_read(node.partition.name, c, ctx) for c in node.partition.group])
        )
        left = self._position(ctx.subscript(node.name), grouping)
        right = self._ordinal(node.name, node.position, grouping)
    elif isinstance(node, RelationComparison):
        left, right = self._value_read(node.name, node.column, ctx), self._literal(node.value)
    elif isinstance(node, RelationPairComparison):
        left = self._value_read(node.name, node.column, ctx)
        right = self._value_read(node.other, node.other_column, ctx)
    elif isinstance(node, CountComparison):
        index, inner = ctx.reducing(node.over)
        counted = self.format.set_of(
            self._membership(node.over, index), self._predicate(node.predicate.root, inner)
        )
        left, right = self.format.cardinality(counted), self._number(node.value)
    else:
        assert_never(node)
    return left, f'{self._op(_PREDICATES[node.op])} {right}'

translation_notes(noticed) #

A sentence for each translation symbol the model printed; plain t-k needs none.

Source code in src/math_spec/typesetting/walk.py
def translation_notes(self, noticed: Noticed) -> list[str]:
    """A sentence for each translation symbol the model printed; plain ``t-k`` needs none."""
    notes = []
    if 'wrap' in noticed.policies:
        cyclic = self.format.math(f't {self._op("cyclic_minus")} k')
        notes.append(
            f'{cyclic} denotes cyclic translation: index {self.format.math("t-k")} taken modulo the size of '
            f'the dimension ({self.format.mono("roll")}). Plain {self.format.math("t-k")} '
            f'({self.format.mono("shift")}) has no wraparound {self.format.dash} terms translated past '
            f'the edge are simply absent.'
        )
    if 'edge' in noticed.policies:
        filled = self.format.math(f't {self.format.subscript(self._op("edge_minus"), ["v"])} k')
        notes.append(
            f'{filled} denotes translation with {self.format.math("v")} standing where index '
            f'{self.format.math("t-k")} leaves the dimension ({self.format.mono("shift(edge=v)")}), so the row '
            f'at that boundary is built and carries {self.format.math("v")} rather than being dropped.'
        )
    if noticed.grouped:
        applied = self.format.apply(self.format.upright('relation'), 't')
        counted = self.format.math(f't {self.format.superscript(self._op("cyclic_minus"), applied)} k')
        note = (
            f'{counted} denotes a translation counted inside the group a relation puts {self.format.math("t")} '
            f'in ({self.format.mono("shift(by=relation)")}), so a term never crosses out of its own group.'
        )
        if 'edge' in noticed.policies:
            both = self.format.superscript(self.format.subscript(self._op('edge_minus'), ['v']), applied)
            note += (
                f' The two modifiers take different slots {self.format.dash} the group above, the fill '
                f'below {self.format.dash} so {self.format.math(f"t {both} k")} is both at once.'
            )
        notes.append(note)
    return notes