aegis_sim.recording.latticerecorder
Lattice spatial snapshot recorder.
Writes a per-individual CSV snapshot of the lattice state at the configured rate. Columns: step, q, r, age, sex, lineage_id, ancestry_fraction.
runs/lattice_animate.py consumes the snapshot files in a sim's
/lattice/ directory and produces a PNG montage (selected steps) or an
animated GIF.
No-op when LATTICE_MODE is False or LATTICE_RECORD_RATE <= 0.
1"""Lattice spatial snapshot recorder. 2 3Writes a per-individual CSV snapshot of the lattice state at the configured 4rate. Columns: step, q, r, age, sex, lineage_id, ancestry_fraction. 5 6`runs/lattice_animate.py` consumes the snapshot files in a sim's 7`/lattice/` directory and produces a PNG montage (selected steps) or an 8animated GIF. 9 10No-op when LATTICE_MODE is False or LATTICE_RECORD_RATE <= 0. 11""" 12 13import logging 14import pathlib 15 16import numpy as np 17 18from .recorder import Recorder 19from aegis_sim import variables 20from aegis_sim.parameterization import parametermanager 21from aegis_sim.utilities.funcs import skip 22 23 24HEADER = "step,q,r,age,sex,lineage_id,ancestry_fraction\n" 25 26 27class LatticeRecorder(Recorder): 28 """Per-step CSV snapshots of every individual's lattice position + 29 a handful of attributes useful for color-coding the animation.""" 30 31 def __init__(self, odir: pathlib.Path): 32 self.odir = odir / "lattice" 33 self.init_odir() 34 35 def write(self, population): 36 """ 37 # OUTPUT SPECIFICATION 38 path: /lattice/step{step}.csv 39 filetype: csv 40 category: log 41 description: Per-individual lattice positions at a single step. One row per living individual: step, q, r, age, sex, lineage_id, ancestry_fraction. Sex and ancestry-related columns are -1 when their respective tracking is disabled. 42 trait granularity: individual 43 time granularity: snapshot 44 frequency parameter: LATTICE_RECORD_RATE 45 structure: CSV. 46 """ 47 if not parametermanager.parameters.LATTICE_MODE: 48 return 49 if parametermanager.parameters.LATTICE_RECORD_RATE <= 0: 50 return 51 if population.positions is None or len(population) == 0: 52 return 53 54 step = variables.steps 55 should_skip = skip("LATTICE_RECORD_RATE") 56 is_first_step = step == 1 57 is_last_step = step == parametermanager.parameters.STEPS_PER_SIMULATION 58 if not (is_first_step or not should_skip or is_last_step): 59 return 60 61 n = len(population) 62 positions = population.positions # (n, 2) 63 ages = population.ages # (n,) 64 # sexes: existing AEGIS conventions vary; we just dump the raw int values 65 sexes = population.sexes if population.sexes is not None else np.full(n, -1, dtype=np.int32) 66 lineage_id = ( 67 population.lineage_id if population.lineage_id is not None 68 else np.full(n, -1, dtype=np.int64) 69 ) 70 if population.ancestry is not None: 71 ancestry = population.ancestry 72 # ancestry is bool, shape (n, ploidy, n_loci, bits_per_locus). Per-individual 73 # fraction = mean(True bits) across each individual's whole genome. 74 frac = ancestry.reshape(n, -1).mean(axis=1) 75 else: 76 frac = np.full(n, -1.0, dtype=np.float32) 77 78 path = self.odir / f"step{step}.csv" 79 with open(path, "w") as fh: 80 fh.write(HEADER) 81 for i in range(n): 82 fh.write( 83 f"{step},{int(positions[i, 0])},{int(positions[i, 1])}," 84 f"{int(ages[i])},{int(sexes[i])},{int(lineage_id[i])},{float(frac[i]):.4f}\n" 85 ) 86 logging.debug(f"lattice snapshot recorded at step {step}.")
28class LatticeRecorder(Recorder): 29 """Per-step CSV snapshots of every individual's lattice position + 30 a handful of attributes useful for color-coding the animation.""" 31 32 def __init__(self, odir: pathlib.Path): 33 self.odir = odir / "lattice" 34 self.init_odir() 35 36 def write(self, population): 37 """ 38 # OUTPUT SPECIFICATION 39 path: /lattice/step{step}.csv 40 filetype: csv 41 category: log 42 description: Per-individual lattice positions at a single step. One row per living individual: step, q, r, age, sex, lineage_id, ancestry_fraction. Sex and ancestry-related columns are -1 when their respective tracking is disabled. 43 trait granularity: individual 44 time granularity: snapshot 45 frequency parameter: LATTICE_RECORD_RATE 46 structure: CSV. 47 """ 48 if not parametermanager.parameters.LATTICE_MODE: 49 return 50 if parametermanager.parameters.LATTICE_RECORD_RATE <= 0: 51 return 52 if population.positions is None or len(population) == 0: 53 return 54 55 step = variables.steps 56 should_skip = skip("LATTICE_RECORD_RATE") 57 is_first_step = step == 1 58 is_last_step = step == parametermanager.parameters.STEPS_PER_SIMULATION 59 if not (is_first_step or not should_skip or is_last_step): 60 return 61 62 n = len(population) 63 positions = population.positions # (n, 2) 64 ages = population.ages # (n,) 65 # sexes: existing AEGIS conventions vary; we just dump the raw int values 66 sexes = population.sexes if population.sexes is not None else np.full(n, -1, dtype=np.int32) 67 lineage_id = ( 68 population.lineage_id if population.lineage_id is not None 69 else np.full(n, -1, dtype=np.int64) 70 ) 71 if population.ancestry is not None: 72 ancestry = population.ancestry 73 # ancestry is bool, shape (n, ploidy, n_loci, bits_per_locus). Per-individual 74 # fraction = mean(True bits) across each individual's whole genome. 75 frac = ancestry.reshape(n, -1).mean(axis=1) 76 else: 77 frac = np.full(n, -1.0, dtype=np.float32) 78 79 path = self.odir / f"step{step}.csv" 80 with open(path, "w") as fh: 81 fh.write(HEADER) 82 for i in range(n): 83 fh.write( 84 f"{step},{int(positions[i, 0])},{int(positions[i, 1])}," 85 f"{int(ages[i])},{int(sexes[i])},{int(lineage_id[i])},{float(frac[i]):.4f}\n" 86 ) 87 logging.debug(f"lattice snapshot recorded at step {step}.")
Per-step CSV snapshots of every individual's lattice position + a handful of attributes useful for color-coding the animation.
36 def write(self, population): 37 """ 38 # OUTPUT SPECIFICATION 39 path: /lattice/step{step}.csv 40 filetype: csv 41 category: log 42 description: Per-individual lattice positions at a single step. One row per living individual: step, q, r, age, sex, lineage_id, ancestry_fraction. Sex and ancestry-related columns are -1 when their respective tracking is disabled. 43 trait granularity: individual 44 time granularity: snapshot 45 frequency parameter: LATTICE_RECORD_RATE 46 structure: CSV. 47 """ 48 if not parametermanager.parameters.LATTICE_MODE: 49 return 50 if parametermanager.parameters.LATTICE_RECORD_RATE <= 0: 51 return 52 if population.positions is None or len(population) == 0: 53 return 54 55 step = variables.steps 56 should_skip = skip("LATTICE_RECORD_RATE") 57 is_first_step = step == 1 58 is_last_step = step == parametermanager.parameters.STEPS_PER_SIMULATION 59 if not (is_first_step or not should_skip or is_last_step): 60 return 61 62 n = len(population) 63 positions = population.positions # (n, 2) 64 ages = population.ages # (n,) 65 # sexes: existing AEGIS conventions vary; we just dump the raw int values 66 sexes = population.sexes if population.sexes is not None else np.full(n, -1, dtype=np.int32) 67 lineage_id = ( 68 population.lineage_id if population.lineage_id is not None 69 else np.full(n, -1, dtype=np.int64) 70 ) 71 if population.ancestry is not None: 72 ancestry = population.ancestry 73 # ancestry is bool, shape (n, ploidy, n_loci, bits_per_locus). Per-individual 74 # fraction = mean(True bits) across each individual's whole genome. 75 frac = ancestry.reshape(n, -1).mean(axis=1) 76 else: 77 frac = np.full(n, -1.0, dtype=np.float32) 78 79 path = self.odir / f"step{step}.csv" 80 with open(path, "w") as fh: 81 fh.write(HEADER) 82 for i in range(n): 83 fh.write( 84 f"{step},{int(positions[i, 0])},{int(positions[i, 1])}," 85 f"{int(ages[i])},{int(sexes[i])},{int(lineage_id[i])},{float(frac[i]):.4f}\n" 86 ) 87 logging.debug(f"lattice snapshot recorded at step {step}.")
OUTPUT SPECIFICATION
path: /lattice/step{step}.csv filetype: csv category: log description: Per-individual lattice positions at a single step. One row per living individual: step, q, r, age, sex, lineage_id, ancestry_fraction. Sex and ancestry-related columns are -1 when their respective tracking is disabled. trait granularity: individual time granularity: snapshot frequency parameter: LATTICE_RECORD_RATE structure: CSV.