Source code for src.core.problem
"""Task instance schema from proposal.md; independent of legacy interfaces."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal
import sympy as sp
VariableRole = Literal['target', 'input', 'internal', 'auxiliary']
MECHANISM_ROLES = (
'governing/dynamical laws',
'conservation/balance relations',
'constitutive/component relations',
'kinematic/geometric constraints',
'auxiliary/regime constraints',
)
[docs]
@dataclass
class VariableSpec:
name: str
description: str
unit: str | None
role: VariableRole
sampling: dict[str, float | str] | None = None
[docs]
@dataclass
class MechanismItem:
formula_str: str
formula: sp.Expr # LHS - RHS
role: str
description: str
[docs]
@dataclass
class MechanismProbe:
probe: str
description: str
answer: str
[docs]
@dataclass
class Task:
task_name: str
task_description: str
mutation: str
mechanism_model: list[MechanismItem]
phenomenal_model: str
variables: list[VariableSpec]
mechanism_probes: list[MechanismProbe]
solution: dict[str, sp.Expr] = field(default_factory=dict)
[docs]
def by_role(self, *roles: str) -> list[VariableSpec]:
return [v for v in self.variables if v.role in roles]
@property
def target(self) -> VariableSpec:
targets = self.by_role('target')
if len(targets) != 1:
raise ValueError('Exactly one target variable is required.')
return targets[0]
@property
def observed(self) -> list[VariableSpec]:
# This order is also the row order in every exported NPY array.
return [self.target, *self.by_role('input', 'auxiliary')]