FlyBrain-Lab / src /population /checkpoint.py
timfromhcs's picture
FlyBrain v4.1.0 Space build (REAL_SUBGRAPH, CPU-only, honest backend)
3d46076 verified
Raw
History Blame Contribute Delete
4.88 kB
"""Versioned population checkpoint envelope (v4.1, Phase 2).
Separates simulation state from research/provenance history while storing
both, so save -> terminate -> restore -> continue is exactly equivalent to
uninterrupted execution:
state_hash - Population.population_hash() (organisms + world)
research_hash - event history + lineages + teaching sessions
combined_hash - sha256(state_hash + research_hash)
version - FlyBrain version (src/version.py)
schema_version - this envelope format (population_checkpoint_v1)
graph_identity - canonical graph identity (REAL_SUBGRAPH / ...)
experiment_identity - experiment seed + seed bundle + graph/circuit fingerprint
rng_bundle - full SeedBundle (deterministic continuation)
world/population state - complete Population.snapshot()
event history - population EventLog + teaching sessions (dropped by the
legacy Population.restore; preserved here)
Legacy Population.snapshot()/restore() are untouched (backward compatible).
"""
import dataclasses
import hashlib
import json
from typing import Any, Dict
from src.common.events import EventLog
CHECKPOINT_SCHEMA_VERSION = "population_checkpoint_v1"
def _canon(obj: Any) -> bytes:
return json.dumps(obj, sort_keys=True, default=str).encode()
def research_hash_of(pop) -> str:
h = hashlib.sha256()
h.update(pop.events.compute_hash().encode())
h.update(_canon({"genetic": pop.genetic_lineage, "cultural": pop.cultural_lineage}))
h.update(_canon([dataclasses.asdict(s) for s in pop.teaching_sessions]))
return h.hexdigest()
def experiment_identity_of(pop) -> str:
seeds = pop.seeds
return hashlib.sha256(_canon({
"experiment_seed": pop.experiment_seed,
"seeds": {k: getattr(seeds, k) for k in
("experiment_seed", "generation_seed", "organism_seed",
"development_seed", "mutation_seed", "world_seed", "teacher_seed")},
"graph_mode": pop.graph_mode.value,
"circuit_size": pop.circuit_size,
"genome_version": pop.genome_version,
"autonomy_mode": pop.autonomy_mode,
})).hexdigest()
def save_checkpoint(pop) -> Dict[str, Any]:
from src.version import VERSION
from src.connectome.types import GraphMode as _GM
state_hash = pop.population_hash()
research_hash = research_hash_of(pop)
combined = hashlib.sha256(f"{state_hash}|{research_hash}".encode()).hexdigest()
seeds = pop.seeds
return {
"version": VERSION,
"schema_version": CHECKPOINT_SCHEMA_VERSION,
"state_hash": state_hash,
"research_hash": research_hash,
"combined_hash": combined,
"graph_identity": _GM.canonical(pop.graph_mode),
"graph_mode": pop.graph_mode.value,
"experiment_identity": experiment_identity_of(pop),
"rng_bundle": {k: getattr(seeds, k) for k in
("experiment_seed", "generation_seed", "organism_seed",
"development_seed", "mutation_seed", "world_seed", "teacher_seed")},
"population": pop.snapshot(),
"event_history": list(pop.events.events),
"teaching_sessions": [dataclasses.asdict(s) for s in pop.teaching_sessions],
}
def restore_checkpoint(payload: Dict[str, Any]):
"""Restore a population from an envelope. Verifies hashes; raises on mismatch."""
from src.common.determinism import SeedBundle
from src.connectome.types import GraphMode as _GM
from src.culture.transmission import TeachingSession
from src.population.population import Population
if payload.get("schema_version") != CHECKPOINT_SCHEMA_VERSION:
raise ValueError(f"unsupported checkpoint schema {payload.get('schema_version')!r}")
rb = payload["rng_bundle"]
seeds = SeedBundle(experiment_seed=rb["experiment_seed"],
generation_seed=rb["generation_seed"],
organism_seed=rb["organism_seed"],
development_seed=rb["development_seed"],
mutation_seed=rb["mutation_seed"],
world_seed=rb["world_seed"],
teacher_seed=rb["teacher_seed"])
pop = Population.restore(payload["population"], seeds)
# Preserve research history the legacy path drops.
pop.events = EventLog()
pop.events.events = [dict(e) for e in payload.get("event_history", [])]
pop.teaching_sessions = [TeachingSession(**s)
for s in payload.get("teaching_sessions", [])]
# Verify: restored hashes must equal the recorded ones.
if pop.population_hash() != payload["state_hash"]:
raise ValueError("checkpoint state_hash mismatch after restore")
if research_hash_of(pop) != payload["research_hash"]:
raise ValueError("checkpoint research_hash mismatch after restore")
return pop