Source code for modelly.timetravel.report
"""The result of :func:`modelly.timetravel.diff`: where two runs diverge."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
[docs]
@dataclass(frozen=True)
class Difference:
"""One value that differs between two runs.
Attributes:
step: The model time at which the value differs.
name: The reporter, model attribute or agent attribute that differs.
a: Its value in the first run.
b: Its value in the second run.
agent: The ``unique_id`` of the agent, or None for a model value.
agent_class: The class name of the agent, when known.
"""
step: float
name: str
a: Any
b: Any
agent: int | None = None
agent_class: str | None = None
def __str__(self) -> str:
"""One line: step, who, what, both values."""
if self.agent is None:
who = "model"
elif self.agent_class is None:
who = f"agent {self.agent}"
else:
who = f"agent {self.agent} ({self.agent_class})"
return f"step {self.step:g}, {who}, {self.name}: {self.a!r} != {self.b!r}"
[docs]
@dataclass(frozen=True)
class RunDiff:
"""Where two runs first diverge, in their collected data and their state.
Attributes:
data: The first difference in the collected data (model reporters,
then agent reporters), or None.
state: The first difference in model or agent state, or None. For two
recorders it is at the first step where the states differ; for
two models, at their current time.
"""
data: Difference | None = None
state: Difference | None = None
@property
def identical(self) -> bool:
"""Whether no difference was found."""
return self.data is None and self.state is None
@property
def step(self) -> float | None:
"""The first step at which the runs differ, or None."""
found = [d.step for d in (self.data, self.state) if d is not None]
return min(found, default=None)
[docs]
def report(self) -> str:
"""A readable report of the divergence."""
if self.identical:
return "The runs are identical."
lines = [f"The runs diverge at step {self.step:g}."]
if self.data is not None:
lines.append(f"Collected data: {self.data}")
if self.state is not None:
lines.append(f"State: {self.state}")
return "\n".join(lines)
def __str__(self) -> str:
"""The readable report."""
return self.report()