Brain-5D-Space / src /research /temporal.py
github-actions[bot]
Sync: publish Space API fix
5e0b58b
Raw History Blame Contribute Delete
4 kB
"""Multi-timescale state memory and deterministic temporal comparison."""
from __future__ import annotations
from collections import deque
from dataclasses import dataclass
from math import isfinite
from typing import Any
@dataclass(frozen=True)
class TemporalStateFrame:
"""Serializable state reference retained without rewinding the runtime."""
tick: int
state_digest: str
metrics: tuple[tuple[str, float], ...] = ()
@classmethod
def from_mapping(
cls, tick: int, state_digest: str, metrics: dict[str, float]
) -> "TemporalStateFrame":
normalized = tuple(
sorted((str(key), float(value)) for key, value in metrics.items())
)
if any(not isfinite(value) for _, value in normalized):
raise ValueError("temporal metrics must be finite")
return cls(tick=tick, state_digest=state_digest, metrics=normalized)
def metric_map(self) -> dict[str, float]:
return dict(self.metrics)
class TemporalStateMemory:
"""Keep bounded FAST/MEDIUM/SLOW references of observed state frames."""
def __init__(
self, *, horizons: dict[str, int] | None = None, capacity: int = 256
) -> None:
self.horizons = dict(horizons or {"fast": 10, "medium": 100, "slow": 1000})
if not self.horizons or any(value < 1 for value in self.horizons.values()):
raise ValueError("temporal horizons must be positive")
if capacity < 1:
raise ValueError("capacity must be positive")
self._frames: deque[TemporalStateFrame] = deque(maxlen=capacity)
def append(self, frame: TemporalStateFrame) -> None:
self._frames.append(frame)
def reference(self, current_tick: int, horizon: str) -> TemporalStateFrame | None:
if horizon not in self.horizons:
raise KeyError(horizon)
target = current_tick - self.horizons[horizon]
candidates = [frame for frame in self._frames if frame.tick <= target]
return max(candidates, key=lambda frame: frame.tick) if candidates else None
def __len__(self) -> int:
return len(self._frames)
@dataclass(frozen=True)
class TemporalComparison:
"""Deterministic difference between a current frame and a historical frame."""
horizon: str
current_tick: int
reference_tick: int | None
discrepancy: float | None
changed_metrics: tuple[str, ...]
digest_changed: bool | None
def to_dict(self) -> dict[str, Any]:
return {
"horizon": self.horizon,
"current_tick": self.current_tick,
"reference_tick": self.reference_tick,
"discrepancy": self.discrepancy,
"changed_metrics": list(self.changed_metrics),
"digest_changed": self.digest_changed,
}
class TemporalComparator:
"""Compare current measurements to a retained FAST/MEDIUM/SLOW reference."""
def compare(
self,
current: TemporalStateFrame,
reference: TemporalStateFrame | None,
*,
horizon: str,
) -> TemporalComparison:
if reference is None:
return TemporalComparison(horizon, current.tick, None, None, (), None)
current_metrics = current.metric_map()
reference_metrics = reference.metric_map()
common = sorted(set(current_metrics) & set(reference_metrics))
differences = {
name: abs(current_metrics[name] - reference_metrics[name])
for name in common
}
changed = tuple(name for name in common if differences[name] > 0.0)
discrepancy = (
sum(differences.values()) / len(differences) if differences else 0.0
)
return TemporalComparison(
horizon=horizon,
current_tick=current.tick,
reference_tick=reference.tick,
discrepancy=discrepancy,
changed_metrics=changed,
digest_changed=current.state_digest != reference.state_digest,
)