Time travel#
Recording, rewinding, branching and diffing runs. How to use them is in Time travel and run diff.
Module |
Contents |
|---|---|
|
|
|
the in-memory and on-disk checkpoint rings |
|
|
|
|
|
|
Time travel: rewind a run, replay it with changes, branch it, diff two runs.
Recorder checkpoints a running model into a ring held in memory or on
disk; Recorder.at(step) returns a model at any recorded step, bit-identical
to a straight run; Recorder.branch returns several futures from one state;
diff finds the first step, agent and attribute where two runs diverge.
Timeline is the small interface tools such as a timeline scrubber use:
anything with steps, span and at. Recorder implements it.
Record a run as a ring of checkpoints and rewind it to any step.
A Recorder checkpoints the model when recording starts and then every
every time units, holding the checkpoints in a ring
(modelly.timetravel.ring). at(step) restores the nearest checkpoint
at or before step and replays forward to it. Checkpoints and replay are
the 1.2 checkpoints (modelly.Model.save()), so the model returned is the
one a straight run reaches at that step, bit for bit.
- reseed_model(model: Model, seed: np.random.SeedSequence) None[source]#
Reseed both generators of
modelin place fromseed.model.rnggets a bit generator of its own class seeded withseedandmodel.randomthe stdlib seedModelderives from a seed sequence, so spaces and columns holding the generators follow.
- class Recorder(model: Model, every: float = 10, *, memory_budget: int = 268435456, directory: str | Path | None = None, compress: bool = False)[source]#
Checkpoint a model as it runs, to rewind, replay and branch it.
Use it as a context manager, or call
start()andstop():with Recorder(model, every=10) as rec: model.run_for(1000) past = rec.at(312)
- Parameters:
model – The model to record.
every – Time units between checkpoints; one is also taken at start.
memory_budget – Bytes the checkpoints may take in all, in memory or, with
directory, on disk. The oldest are evicted beyond it, except the first and the newest.directory – Write the checkpoints to this directory instead of holding them in memory.
compress – Gzip each checkpoint (smaller, slower).
Prepare to record
model; recording starts withstart().- checkpoint() None[source]#
Checkpoint the model now, after changing it from outside the run.
Replay reruns the model’s own dynamics only: a change made between two
run_forcalls, such as setting an agent attribute, reachesatfor the steps after the next checkpoint. Call this right after such a change so that every later step includes it.
- at(step: float) Model[source]#
A new model in the state of the recorded run at
step.The nearest checkpoint at or before
stepis restored and run on tostep. The model is independent of the recorded one and of the checkpoints, and its scenario can be changed (see the time travel guide for what that means).- Raises:
ValueError – If
stepis outsidespan.
- branch(step: float, n: int, *, reseed: bool = False) list[Model][source]#
nindependent models, all in the state of the run atstep.Without
reseedevery branch continues the same random streams, so identical runs stay identical until you change one. Withreseed, branchiis reseeded fromscenario.seed_sequence.spawn(n)[i]: the child seedsScenario.spawn_replications(n)uses.
Checkpoint rings: the recorded checkpoints of a run, within a byte budget.
A ring maps a step (the model’s time when the checkpoint was taken) to the
bytes of a checkpoint file. When the ring holds more bytes than its budget it
drops its oldest checkpoints, except the first one, so a recorded run can
always be rewound to where recording started.
MemoryRing keeps the bytes in memory. DiskRing writes each
checkpoint to its own file through a temporary file and os.replace, which
is atomic on Windows, macOS and Linux: a file in the ring is always whole.
- class MemoryRing(budget: int)[source]#
Checkpoints held in memory as bytes.
Hold checkpoints up to
budgetbytes of memory.
- class DiskRing(directory: str | Path, budget: int)[source]#
Checkpoints held as files
step-<time>.ckptin one directory.Each file is an ordinary checkpoint that
Model.loadreads. Files left when the ring is dropped stay on disk; evicted ones are deleted.Hold checkpoints up to
budgetbytes of disk indirectory.
Find where two runs diverge: first in their collected data, then in state.
diff takes two runs given as models, recorders, DataCollector objects
or model-variable DataFrames (get_model_vars_dataframe()):
collected data (both runs have a collector or a DataFrame): the first collection step at which a model reporter differs, or, from collectors, the first agent and reporter whose agent record differs;
state (both runs are models or recorders): the first model scalar, agent or agent attribute that differs (see
modelly.timetravel.snapshot). Two recorders are compared at their common checkpoints, then replayed one time unit at a time from the last matching one, so the state difference is at the first step where the runs diverge; two models are compared as they are.
- data_difference(a: Model | Recorder | DataCollector | DataFrame, b: Model | Recorder | DataCollector | DataFrame) Difference | None[source]#
The first difference in the data two runs collected.
- state_difference(a: Model | Recorder | DataCollector | DataFrame, b: Model | Recorder | DataCollector | DataFrame) Difference | None[source]#
The first difference in the state of two runs.
- diff(run_a: Model | Recorder | DataCollector | DataFrame, run_b: Model | Recorder | DataCollector | DataFrame) RunDiff[source]#
Where two runs first diverge.
- Parameters:
run_a – A model, a
Recorder, aDataCollectoror a DataFrame of model variables.run_b – The run to compare it with, of any of the same kinds.
- Returns:
str()of it is a readable report, and itsdataandstatefields say step, agent and attribute.- Return type:
A
RunDiff
The result of modelly.timetravel.diff(): where two runs diverge.
- class Difference(step: float, name: str, a: Any, b: Any, agent: int | None = None, agent_class: str | None = None)[source]#
One value that differs between two runs.
- Variables:
step (float) – The model time at which the value differs.
name (str) – The reporter, model attribute or agent attribute that differs.
a (Any) – Its value in the first run.
b (Any) – Its value in the second run.
agent (int | None) – The
unique_idof the agent, or None for a model value.agent_class (str | None) – The class name of the agent, when known.
- class RunDiff(data: Difference | None = None, state: Difference | None = None)[source]#
Where two runs first diverge, in their collected data and their state.
- Variables:
data (modelly.timetravel.report.Difference | None) – The first difference in the collected data (model reporters, then agent reporters), or None.
state (modelly.timetravel.report.Difference | None) – 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.
Stable snapshots of model and agent state, and their first difference.
An agent’s snapshot is its unique_id, class name and the values that make
up its state: its Column values, the scalars in its __dict__ (names
starting with _ excluded) and where it is (cell coordinate or
position). Short numpy vectors and tuples of scalars count as scalars;
lists, sets, other agents and objects do not. The model’s snapshot adds its own
scalars and a digest of each random generator’s state.
- agent_state(agent: Agent) dict[str, Any][source]#
The state of
agent: columns,__dict__scalars and place.
- class ModelState(model: dict[str, Any], agents: dict[int, tuple[str, dict[str, Any]]], generators: dict[str, str])[source]#
A model’s snapshot: its scalars, its agents and its generators.
- model_state(model: Model) ModelState[source]#
The snapshot of
modeland every agent it holds.
- first_value_difference(a: dict[str, Any], b: dict[str, Any], step: float, **who: Any) Difference | None[source]#
The first name, in
a’s order thenb’s, whose values differ.
- first_state_difference(a: ModelState, b: ModelState, step: float) Difference | None[source]#
The first difference: model scalars, then agents, then generators.