TOMK / src /python /tom_world03 /model.py
c0mbatduckzz's picture
Publish WQK 0.6 formal specification, use cases and verified release (part 2)
767a051 verified
Raw History Blame Contribute Delete
9.94 kB
"""Typed 0.3 world records: affine trajectories, supports, compatibility, relations."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Sequence
from .canonical import require_hash, verify_hash
from .expression import validate_expression
from .interval import ClosedInterval
from .rational import Q
@dataclass(frozen=True, slots=True)
class AffineField:
initial: Q
rate: Q
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "AffineField":
return cls(Q.from_value(record.get("initial", 0)), Q.from_value(record.get("rate", 0)))
@dataclass(frozen=True, slots=True)
class AffineTrajectory:
id: str
domain: ClosedInterval
fields: Mapping[str, AffineField]
source_program: str | None = None
content_hash: str | None = None
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "AffineTrajectory":
require_hash(record, label="trajectory")
if record.get("kind") != "affine-rational-trajectory":
raise ValueError("trajectory kind must be affine-rational-trajectory")
fields_record = record.get("fields")
if not isinstance(fields_record, Mapping) or not fields_record:
raise ValueError("trajectory requires a nonempty fields object")
fields = {str(name): AffineField.from_record(value) for name, value in fields_record.items()}
return cls(
id=str(record["id"]),
domain=ClosedInterval.from_value(record["domain"]),
fields=fields,
source_program=None if record.get("source_program") is None else str(record["source_program"]),
content_hash=str(record["content_hash"]),
)
def require_time(self, time: Q) -> None:
if not self.domain.contains(time):
raise ValueError(f"time {time} is outside trajectory domain {self.domain}")
def state_at(self, time: Q) -> dict[str, Q]:
time = Q.from_value(time)
self.require_time(time)
return {name: field.initial + field.rate * time for name, field in self.fields.items()}
def field_rate(self, name: str) -> Q:
try:
return self.fields[name].rate
except KeyError as exc:
raise ValueError(f"trajectory has no field {name}") from exc
def field_affine(self, name: str) -> tuple[Q, Q]:
try:
field = self.fields[name]
except KeyError as exc:
raise ValueError(f"trajectory has no field {name}") from exc
return field.rate, field.initial
def field_interval(self, name: str, time: ClosedInterval) -> ClosedInterval:
intersection = self.domain.intersection(time)
if intersection is None or intersection != time:
raise ValueError("requested interval is outside trajectory domain")
field = self.fields.get(name)
if field is None:
raise ValueError(f"trajectory has no field {name}")
return ClosedInterval.hull([
field.initial + field.rate * time.lower,
field.initial + field.rate * time.upper,
])
def state_interval(self, time: ClosedInterval) -> dict[str, ClosedInterval]:
return {name: self.field_interval(name, time) for name in self.fields}
@dataclass(frozen=True, slots=True)
class Support:
id: str
bounds: Mapping[str, ClosedInterval]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "Support":
require_hash(record, label="support")
if record.get("kind") != "interval-support":
raise ValueError("support kind must be interval-support")
bounds_record = record.get("bounds", {})
if not isinstance(bounds_record, Mapping):
raise ValueError("support bounds must be an object")
return cls(str(record["id"]), {
str(name): ClosedInterval.from_value(value)
for name, value in bounds_record.items()
}, str(record["content_hash"]))
def accepts_point(self, state: Mapping[str, Q]) -> bool:
return all(name in state and bound.contains(state[name]) for name, bound in self.bounds.items())
def contains_interval(self, state: Mapping[str, ClosedInterval]) -> bool:
return all(name in state and state[name].subset_of(bound) for name, bound in self.bounds.items())
@dataclass(frozen=True, slots=True)
class Compatibility:
id: str
equals: Mapping[str, Q]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "Compatibility":
require_hash(record, label="compatibility")
if record.get("kind") != "field-equality-compatibility":
raise ValueError("compatibility kind must be field-equality-compatibility")
equals = record.get("equals", {})
if not isinstance(equals, Mapping):
raise ValueError("compatibility equals must be an object")
return cls(str(record["id"]), {str(k): Q.from_value(v) for k, v in equals.items()},
str(record["content_hash"]))
def accepts(self, state: Mapping[str, Q]) -> bool:
return all(state.get(name) == expected for name, expected in self.equals.items())
@dataclass(frozen=True, slots=True)
class TransitionOp:
field: str
mode: str
value: Q
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "TransitionOp":
field = str(record.get("field", ""))
mode = str(record.get("mode", ""))
if not field:
raise ValueError("transition operation requires field")
if mode not in {"set", "add", "xor"}:
raise ValueError(f"unsupported transition mode {mode}")
value = Q.from_value(record.get("value", 0))
if mode == "xor" and not value.is_integer():
raise ValueError("xor transition value must be an integer")
return cls(field, mode, value)
def to_record(self) -> dict[str, Any]:
return {"field": self.field, "mode": self.mode, "value": self.value.to_record()}
@dataclass(frozen=True, slots=True)
class Relation:
id: str
priority: int
expression: Mapping[str, Any]
support_id: str
compatibility_id: str
active_time: ClosedInterval
event_id: str
transition: tuple[TransitionOp, ...]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "Relation":
require_hash(record, label="relation")
if record.get("kind") != "continuous-zero-relation":
raise ValueError("relation kind must be continuous-zero-relation")
expression = record.get("expression")
if not isinstance(expression, Mapping):
raise ValueError("relation expression must be an object")
validate_expression(expression)
transition_record = record.get("transition", [])
if not isinstance(transition_record, list):
raise ValueError("relation transition must be an array")
return cls(
id=str(record["id"]),
priority=int(record.get("priority", 0)),
expression=expression,
support_id=str(record["support_id"]),
compatibility_id=str(record["compatibility_id"]),
active_time=ClosedInterval.from_value(record["active_time"]),
event_id=str(record["event_id"]),
transition=tuple(TransitionOp.from_record(item) for item in transition_record),
content_hash=str(record["content_hash"]),
)
@dataclass(frozen=True, slots=True)
class IntervalWorld:
schema: str
seed_sha256: str
trajectory: AffineTrajectory
supports: Mapping[str, Support]
compatibilities: Mapping[str, Compatibility]
relations: tuple[Relation, ...]
solver: Mapping[str, Any]
content_hash: str
@classmethod
def from_record(cls, record: Mapping[str, Any]) -> "IntervalWorld":
require_hash(record, label="interval world")
if record.get("schema") != "TOM-WORLD-INTERVAL-EVENTS-0.3":
raise ValueError("unsupported interval world schema")
trajectory = AffineTrajectory.from_record(record["trajectory"])
supports_list = record.get("supports", [])
compat_list = record.get("compatibilities", [])
relations_list = record.get("relations", [])
if not all(isinstance(x, list) for x in (supports_list, compat_list, relations_list)):
raise ValueError("supports, compatibilities, and relations must be arrays")
supports = {obj.id: obj for obj in map(Support.from_record, supports_list)}
compatibilities = {obj.id: obj for obj in map(Compatibility.from_record, compat_list)}
relations = tuple(map(Relation.from_record, relations_list))
if len(supports) != len(supports_list):
raise ValueError("duplicate support id")
if len(compatibilities) != len(compat_list):
raise ValueError("duplicate compatibility id")
if len({r.id for r in relations}) != len(relations):
raise ValueError("duplicate relation id")
for relation in relations:
if relation.support_id not in supports:
raise ValueError(f"relation {relation.id} references unknown support")
if relation.compatibility_id not in compatibilities:
raise ValueError(f"relation {relation.id} references unknown compatibility")
solver = record.get("solver", {})
if not isinstance(solver, Mapping):
raise ValueError("solver profile must be an object")
return cls(
schema=str(record["schema"]),
seed_sha256=str(record["seed_sha256"]),
trajectory=trajectory,
supports=supports,
compatibilities=compatibilities,
relations=relations,
solver=dict(solver),
content_hash=str(record["content_hash"]),
)