Download src/sr_harness_engine/expression.py from YuMeow/SRHarness: direct link, hf CLI and curl.
- Browser
- Download file 15.8 kB
-
https://huggingface.co/spaces/YuMeow/SRHarness/resolve/main/src/sr_harness_engine/expression.py
- Command line
-
hf download hf://spaces/YuMeow/SRHarness/src/sr_harness_engine/expression.py
-
curl -L -o expression.py https://huggingface.co/spaces/YuMeow/SRHarness/resolve/main/src/sr_harness_engine/expression.py
15.8 kB
| """Expression nodes for the SRHarness symbolic engine.""" | |
| from __future__ import annotations | |
| from copy import deepcopy | |
| from dataclasses import dataclass, field | |
| from collections.abc import Iterator | |
| from typing import TYPE_CHECKING, Any, Mapping | |
| if TYPE_CHECKING: | |
| from .optimize import FitResult | |
| class Expression: | |
| """Base class of every symbolic expression node.""" | |
| def __add__(self, other: Any) -> Expression: | |
| return Binary("+", self, as_expression(other)) | |
| def __radd__(self, other: Any) -> Expression: | |
| return Binary("+", as_expression(other), self) | |
| def __sub__(self, other: Any) -> Expression: | |
| return Binary("-", self, as_expression(other)) | |
| def __rsub__(self, other: Any) -> Expression: | |
| return Binary("-", as_expression(other), self) | |
| def __mul__(self, other: Any) -> Expression: | |
| return Binary("*", self, as_expression(other)) | |
| def __rmul__(self, other: Any) -> Expression: | |
| return Binary("*", as_expression(other), self) | |
| def __truediv__(self, other: Any) -> Expression: | |
| return Binary("/", self, as_expression(other)) | |
| def __rtruediv__(self, other: Any) -> Expression: | |
| return Binary("/", as_expression(other), self) | |
| def __pow__(self, other: Any) -> Expression: | |
| return Binary("**", self, as_expression(other)) | |
| def __rpow__(self, other: Any) -> Expression: | |
| return Binary("**", as_expression(other), self) | |
| def __neg__(self) -> Expression: | |
| return Unary("-", self) | |
| def __getitem__(self, index: Index | tuple[Index, ...] | str) -> Expression: | |
| if isinstance(index, tuple): | |
| indices = tuple(as_index(item) for item in index) | |
| else: | |
| indices = (as_index(index),) | |
| return Indexed(self, indices) | |
| def evaluate( | |
| self, | |
| values: Mapping[str, Any] | None = None, | |
| *, | |
| parameters: Mapping[str, Any] | None = None, | |
| time: Any = None, | |
| delay_resolver: Any = None, | |
| num_nodes: int | None = None, | |
| ) -> Any: | |
| """Evaluate this expression with NumPy values. | |
| Args: | |
| values: Values keyed by symbol name, or a context exposing ``data`` | |
| and optional ``num_nodes`` metadata. | |
| parameters: Fitted parameter values keyed by parameter name. | |
| time: Optional sample times. | |
| delay_resolver: Optional callback that resolves delayed values. | |
| num_nodes: Explicit node count for indexed expressions. | |
| Returns: | |
| The evaluated scalar or array. | |
| """ | |
| from .context import resolve_context | |
| from .evaluation import evaluate | |
| values, _, num_nodes = resolve_context(values, num_nodes=num_nodes) | |
| return evaluate( | |
| self, | |
| values=values, | |
| parameters=parameters, | |
| time=time, | |
| delay_resolver=delay_resolver, | |
| num_nodes=num_nodes, | |
| ) | |
| eval = evaluate | |
| def operands(self) -> tuple[Expression, ...]: | |
| """Child expressions, exposed as an immutable tuple. | |
| Returns: | |
| The direct child nodes in expression order. | |
| """ | |
| from .tree import children | |
| return children(self) | |
| def iter_preorder(self) -> Iterator[Expression]: | |
| """Yield this node followed by its descendants. | |
| Yields: | |
| Expression nodes in preorder. | |
| """ | |
| from .tree import iter_preorder | |
| yield from iter_preorder(self) | |
| def iter_postorder(self) -> Iterator[Expression]: | |
| """Yield descendants followed by this node. | |
| Yields: | |
| Expression nodes in postorder. | |
| """ | |
| from .tree import iter_postorder | |
| yield from iter_postorder(self) | |
| def copy(self) -> Expression: | |
| """Return an independent copy. | |
| Returns: | |
| A deep copy of this expression. | |
| """ | |
| return deepcopy(self) | |
| def replace(self, old: Expression, new: Expression) -> Expression: | |
| """Return a tree in which the exact *old* node is replaced by *new*. | |
| Args: | |
| old: Existing expression node to replace. | |
| new: Replacement expression node. | |
| Returns: | |
| A copied expression tree with matching nodes replaced. | |
| """ | |
| from .tree import replace | |
| return replace(self, old, new) | |
| def to_str( | |
| self, | |
| *, | |
| latex: bool = False, | |
| number_format: str = "", | |
| ) -> str: | |
| """Render the expression as plain text or LaTeX. | |
| Args: | |
| latex: Whether to render LaTeX notation. | |
| number_format: Format specification for numeric literals. | |
| Returns: | |
| The rendered expression. | |
| """ | |
| from .render import render | |
| return render(self, latex=latex, number_format=number_format) | |
| def to_tree(self, *, number_format: str = "") -> str: | |
| """Render a compact preorder tree for diagnostics. | |
| Args: | |
| number_format: Format specification for numeric literals. | |
| Returns: | |
| A multiline representation of the expression tree. | |
| """ | |
| from .tree import children | |
| lines = [self.to_str(number_format=number_format)] | |
| def visit(node: Expression, prefix: str, connector: str) -> None: | |
| lines.append(f"{prefix}{connector}{node.to_str(number_format=number_format)}") | |
| values = children(node) | |
| continuation = " " if connector == "└ " else "│ " | |
| for index, child in enumerate(values): | |
| last = index == len(values) - 1 | |
| visit(child, prefix + continuation, "└ " if last else "├ ") | |
| values = children(self) | |
| for index, child in enumerate(values): | |
| visit(child, "", "└ " if index == len(values) - 1 else "├ ") | |
| return "\n".join(lines) | |
| def __len__(self) -> int: | |
| return sum(1 for _ in self.iter_preorder()) | |
| def fit( | |
| self, | |
| values: Mapping[str, Any], | |
| target: Any = None, | |
| *, | |
| initial: Mapping[str, Any] | None = None, | |
| method: str = "BFGS", | |
| options: Mapping[str, Any] | None = None, | |
| num_nodes: int | None = None, | |
| ) -> FitResult: | |
| """Fit named and grouped parameters against a target array. | |
| Args: | |
| values: Values keyed by symbol name, or a context exposing ``data``, | |
| ``target``, and optional ``num_nodes`` metadata. | |
| target: Target name or target values. Omit when supplied by context. | |
| initial: Optional initial parameter values. | |
| method: Optimization method name. | |
| options: Optional optimizer settings. | |
| num_nodes: Explicit node count for indexed expressions. | |
| Returns: | |
| The fitted expression, parameter values, predictions, and loss. | |
| """ | |
| from .context import resolve_context | |
| from .optimize import fit | |
| values, target, num_nodes = resolve_context(values, target, num_nodes) | |
| if target is None: | |
| raise ValueError("A target array or a context target is required for fitting.") | |
| return fit( | |
| self, values, target, initial=initial, method=method, | |
| options=options, num_nodes=num_nodes, | |
| ) | |
| def fold_constants(self) -> Expression: | |
| """Return a copy with closed numerical subexpressions evaluated. | |
| Returns: | |
| A simplified expression with closed numeric branches folded. | |
| """ | |
| from .analysis import fold_constants | |
| return fold_constants(self) | |
| def count_parameters( | |
| self, | |
| values: Mapping[str, Any] | None = None, | |
| *, | |
| parameters: Mapping[str, Any] | None = None, | |
| ) -> int: | |
| """Count independent fitted values represented by this expression. | |
| Args: | |
| values: Values keyed by symbol name. | |
| parameters: Fitted parameter values keyed by parameter name. | |
| Returns: | |
| The number of independent scalar parameter values. | |
| """ | |
| from .analysis import count_parameters | |
| return count_parameters(self, values, parameters=parameters) | |
| def __str__(self) -> str: | |
| from .render import render | |
| return render(self) | |
| class Number(Expression): | |
| """Fixed numeric literal.""" | |
| value: int | float | |
| class Symbol(Expression): | |
| """Named input symbol with an optional bound value.""" | |
| name: str | |
| value: Any = field(default=None, compare=False, repr=False) | |
| class Parameter(Expression): | |
| """Named scalar parameter optimized during fitting.""" | |
| name: str | |
| value: float | None = field(default=None, compare=False) | |
| class GroupedParameter(Expression): | |
| """Parameter with one fitted value per category.""" | |
| by: Expression | |
| name: str | None = None | |
| value: Mapping[Any, float] | None = field(default=None, compare=False) | |
| default: float | None = field(default=None, compare=False) | |
| class Index: | |
| """Symbolic relation index.""" | |
| name: str | |
| def __str__(self) -> str: | |
| return self.name | |
| class Unary(Expression): | |
| """Unary expression node.""" | |
| operator: str | |
| operand: Expression | |
| class Binary(Expression): | |
| """Binary expression node.""" | |
| operator: str | |
| left: Expression | |
| right: Expression | |
| class Function(Expression): | |
| """Named function-call expression node.""" | |
| name: str | |
| arguments: tuple[Expression, ...] | |
| class Indexed(Expression): | |
| """Expression annotated with symbolic indices.""" | |
| base: Expression | |
| indices: tuple[Index, ...] | |
| class Reduction(Expression): | |
| """Sum reduction with an optional relation binder.""" | |
| indices: tuple[Index, ...] | |
| operand: Expression | |
| relation: Expression | None = None | |
| class Gather(Expression): | |
| """Weighted collection of a structural expression on relation entries.""" | |
| relation: Expression | |
| operand: Expression | |
| class Aggregate(Expression): | |
| """Convenience aggregation node lowered to indexed syntax.""" | |
| relation: Expression | |
| operand: Expression | |
| class RelationLift(Expression): | |
| """Convenience source or target projection used inside an aggregation.""" | |
| role: str | |
| operand: Expression | |
| relation: Expression | None = None | |
| def as_expression(value: Any) -> Expression: | |
| """Convert a numeric literal or expression into an expression node. | |
| Args: | |
| value: Existing expression or numeric literal. | |
| Returns: | |
| The corresponding expression node. | |
| """ | |
| if isinstance(value, Expression): | |
| return value | |
| if isinstance(value, bool) or not isinstance(value, (int, float)): | |
| raise TypeError(f"Expected an expression or number, got {type(value).__name__}.") | |
| return Number(value) | |
| Variable = Symbol | |
| def as_index(value: Index | str) -> Index: | |
| """Convert an index name into an index node. | |
| Args: | |
| value: Existing index or valid Python identifier. | |
| Returns: | |
| The corresponding symbolic index. | |
| """ | |
| if isinstance(value, Index): | |
| return value | |
| if isinstance(value, str) and value.isidentifier(): | |
| return Index(value) | |
| raise TypeError(f"Expected an index name, got {value!r}.") | |
| def param(name: str, value: float | None = None) -> Parameter: | |
| """Create a named scalar parameter. | |
| Args: | |
| name: Parameter name shared by all matching occurrences. | |
| value: Optional initial or fixed value. | |
| Returns: | |
| A symbolic scalar parameter. | |
| """ | |
| return Parameter(name, value) | |
| def grouped_param( | |
| by: Expression, | |
| *, | |
| name: str | None = None, | |
| value: Mapping[Any, float] | None = None, | |
| default: float | None = None, | |
| ) -> GroupedParameter: | |
| """Create a parameter with one fitted value per category. | |
| Args: | |
| by: Symbol or expression containing category labels. | |
| name: Optional parameter-map name. | |
| value: Optional initial values keyed by category. | |
| default: Value used for categories absent from ``value``. | |
| Returns: | |
| A category-dependent parameter expression. | |
| """ | |
| return GroupedParameter(by, name=name, value=value, default=default) | |
| def function(name: str, *arguments: Any) -> Function: | |
| """Create a supported symbolic function call. | |
| Args: | |
| name: Function name recognized by the evaluator. | |
| *arguments: Function operands. | |
| Returns: | |
| A symbolic function node. | |
| """ | |
| return Function(name, tuple(as_expression(argument) for argument in arguments)) | |
| def reduction( | |
| indices: Index | tuple[Index, ...], | |
| operand: Any, | |
| relation: Any = None, | |
| ) -> Reduction: | |
| """Create an indexed sum reduction. | |
| Args: | |
| indices: Symbolic indices. | |
| operand: Expression being reduced or transformed. | |
| relation: Relation expression that binds symbolic indices. | |
| Returns: | |
| A symbolic reduction node. | |
| """ | |
| if not isinstance(indices, tuple): | |
| indices = (indices,) | |
| return Reduction( | |
| tuple(as_index(index) for index in indices), | |
| as_expression(operand), | |
| None if relation is None else as_expression(relation), | |
| ) | |
| def gather(relation: Any, operand: Any) -> Gather: | |
| """Collect structural values at the nonzero entries of a relation. | |
| Args: | |
| relation: Indexed relation or relation-valued expression. | |
| operand: Structural expression evaluated at relation coordinates. | |
| Returns: | |
| A relation-aligned symbolic field. | |
| """ | |
| return Gather(as_expression(relation), as_expression(operand)) | |
| def aggr(relation: Any, operand: Any = None) -> Expression: | |
| """Build and lower target-wise graph aggregation syntax. | |
| Args: | |
| relation: Edge relation, or a product containing it when ``operand`` is omitted. | |
| operand: Message expression to aggregate by target node. | |
| Returns: | |
| The equivalent canonical indexed reduction. | |
| """ | |
| from .desugar import desugar | |
| from .parser import _split_aggregation | |
| if operand is None: | |
| relation, operand = _split_aggregation(as_expression(relation)) | |
| return desugar(Aggregate(as_expression(relation), as_expression(operand))) | |
| def targ(*arguments: Any) -> RelationLift: | |
| """Project node values onto relation targets inside ``aggr``. | |
| Args: | |
| *arguments: Either ``value`` or ``relation, value``. | |
| Returns: | |
| A target projection used by aggregation desugaring. | |
| """ | |
| if len(arguments) == 1: | |
| return RelationLift("target", as_expression(arguments[0])) | |
| if len(arguments) == 2: | |
| return RelationLift("target", as_expression(arguments[1]), as_expression(arguments[0])) | |
| raise TypeError("targ expects targ(value) or targ(relation, value).") | |
| def sour(*arguments: Any) -> RelationLift: | |
| """Project node values onto relation sources inside ``aggr``. | |
| Args: | |
| *arguments: Either ``value`` or ``relation, value``. | |
| Returns: | |
| A source projection used by aggregation desugaring. | |
| """ | |
| if len(arguments) == 1: | |
| return RelationLift("source", as_expression(arguments[0])) | |
| if len(arguments) == 2: | |
| return RelationLift("source", as_expression(arguments[1]), as_expression(arguments[0])) | |
| raise TypeError("sour expects sour(value) or sour(relation, value).") | |