aegis_sim.recording.selectionrecorder

 1import logging
 2import pathlib
 3
 4from .recorder import Recorder
 5from aegis_sim import variables, submodels
 6from aegis_sim.parameterization import parametermanager
 7
 8
 9HEADER = "step,n_alive,n_alleles,n_carriers,allele_freq\n"
10
11
12class SelectionRecorder(Recorder):
13    """Track the allele frequency at the injection locus over time.
14
15    Active only when ALLELE_INJECTION_STEP > 0. Records start at step 1
16    (so the baseline before injection is visible) and continue through
17    the rest of the simulation.
18
19    Output: /selection/selection.csv with columns
20        step,n_alive,n_alleles,n_carriers,allele_freq
21    where n_alleles = 2 * n_alive (diploid count of alleles at the locus),
22    n_carriers = count of chromatids carrying ALLELE_INJECTION_ALLELE at
23    the injection locus, allele_freq = n_carriers / n_alleles.
24    """
25
26    def __init__(self, odir: pathlib.Path):
27        self.odir = odir / "selection"
28        self.init_odir()
29        self._file_path = self.odir / "selection.csv"
30        self._file = None
31        # Cached physical locus + bit, populated on first write.
32        self._physical_locus = None
33        self._bit_in_locus = None
34        self._tracked_allele = None
35
36    def _resolve_locus(self):
37        if self._physical_locus is not None:
38            return True
39        from aegis_sim import parameterization
40
41        trait_name = parametermanager.parameters.ALLELE_INJECTION_TRAIT
42        age = int(parametermanager.parameters.ALLELE_INJECTION_AGE)
43        bit_in_locus = int(parametermanager.parameters.ALLELE_INJECTION_BIT)
44        allele = bool(int(parametermanager.parameters.ALLELE_INJECTION_ALLELE))
45
46        trait = parameterization.traits.get(trait_name)
47        if trait is None or trait.length == 0:
48            return False
49        if trait.agespecific is True:
50            if not (0 <= age < trait.length):
51                return False
52            logical_locus = trait.start + age
53        else:
54            logical_locus = trait.start
55
56        self._physical_locus = int(submodels.architect.architecture.locus_permutation[logical_locus])
57        self._bit_in_locus = bit_in_locus
58        self._tracked_allele = allele
59        return True
60
61    def _ensure_open(self):
62        if self._file is not None:
63            return
64        mode = "a" if self._file_path.exists() and self._file_path.stat().st_size > 0 else "w"
65        self._file = open(self._file_path, mode)
66        if mode == "w":
67            self._file.write(HEADER)
68
69    def write(self, population):
70        if parametermanager.parameters.ALLELE_INJECTION_STEP <= 0:
71            return
72        if not self._resolve_locus():
73            return
74        n = len(population)
75        if n == 0:
76            return
77
78        bits = population.genomes.array[:, :, self._physical_locus, self._bit_in_locus]
79        # bits shape: (n, 2)
80        n_alleles = bits.size  # n * 2
81        n_carriers = int((bits == self._tracked_allele).sum())
82        freq = n_carriers / n_alleles if n_alleles else 0.0
83
84        self._ensure_open()
85        self._file.write(f"{variables.steps},{n},{n_alleles},{n_carriers},{freq:.6f}\n")
86        self._file.flush()
class SelectionRecorder(aegis_sim.recording.recorder.Recorder):
13class SelectionRecorder(Recorder):
14    """Track the allele frequency at the injection locus over time.
15
16    Active only when ALLELE_INJECTION_STEP > 0. Records start at step 1
17    (so the baseline before injection is visible) and continue through
18    the rest of the simulation.
19
20    Output: /selection/selection.csv with columns
21        step,n_alive,n_alleles,n_carriers,allele_freq
22    where n_alleles = 2 * n_alive (diploid count of alleles at the locus),
23    n_carriers = count of chromatids carrying ALLELE_INJECTION_ALLELE at
24    the injection locus, allele_freq = n_carriers / n_alleles.
25    """
26
27    def __init__(self, odir: pathlib.Path):
28        self.odir = odir / "selection"
29        self.init_odir()
30        self._file_path = self.odir / "selection.csv"
31        self._file = None
32        # Cached physical locus + bit, populated on first write.
33        self._physical_locus = None
34        self._bit_in_locus = None
35        self._tracked_allele = None
36
37    def _resolve_locus(self):
38        if self._physical_locus is not None:
39            return True
40        from aegis_sim import parameterization
41
42        trait_name = parametermanager.parameters.ALLELE_INJECTION_TRAIT
43        age = int(parametermanager.parameters.ALLELE_INJECTION_AGE)
44        bit_in_locus = int(parametermanager.parameters.ALLELE_INJECTION_BIT)
45        allele = bool(int(parametermanager.parameters.ALLELE_INJECTION_ALLELE))
46
47        trait = parameterization.traits.get(trait_name)
48        if trait is None or trait.length == 0:
49            return False
50        if trait.agespecific is True:
51            if not (0 <= age < trait.length):
52                return False
53            logical_locus = trait.start + age
54        else:
55            logical_locus = trait.start
56
57        self._physical_locus = int(submodels.architect.architecture.locus_permutation[logical_locus])
58        self._bit_in_locus = bit_in_locus
59        self._tracked_allele = allele
60        return True
61
62    def _ensure_open(self):
63        if self._file is not None:
64            return
65        mode = "a" if self._file_path.exists() and self._file_path.stat().st_size > 0 else "w"
66        self._file = open(self._file_path, mode)
67        if mode == "w":
68            self._file.write(HEADER)
69
70    def write(self, population):
71        if parametermanager.parameters.ALLELE_INJECTION_STEP <= 0:
72            return
73        if not self._resolve_locus():
74            return
75        n = len(population)
76        if n == 0:
77            return
78
79        bits = population.genomes.array[:, :, self._physical_locus, self._bit_in_locus]
80        # bits shape: (n, 2)
81        n_alleles = bits.size  # n * 2
82        n_carriers = int((bits == self._tracked_allele).sum())
83        freq = n_carriers / n_alleles if n_alleles else 0.0
84
85        self._ensure_open()
86        self._file.write(f"{variables.steps},{n},{n_alleles},{n_carriers},{freq:.6f}\n")
87        self._file.flush()

Track the allele frequency at the injection locus over time.

Active only when ALLELE_INJECTION_STEP > 0. Records start at step 1 (so the baseline before injection is visible) and continue through the rest of the simulation.

Output: /selection/selection.csv with columns step,n_alive,n_alleles,n_carriers,allele_freq where n_alleles = 2 * n_alive (diploid count of alleles at the locus), n_carriers = count of chromatids carrying ALLELE_INJECTION_ALLELE at the injection locus, allele_freq = n_carriers / n_alleles.

SelectionRecorder(odir: pathlib.Path)
27    def __init__(self, odir: pathlib.Path):
28        self.odir = odir / "selection"
29        self.init_odir()
30        self._file_path = self.odir / "selection.csv"
31        self._file = None
32        # Cached physical locus + bit, populated on first write.
33        self._physical_locus = None
34        self._bit_in_locus = None
35        self._tracked_allele = None
odir
def write(self, population):
70    def write(self, population):
71        if parametermanager.parameters.ALLELE_INJECTION_STEP <= 0:
72            return
73        if not self._resolve_locus():
74            return
75        n = len(population)
76        if n == 0:
77            return
78
79        bits = population.genomes.array[:, :, self._physical_locus, self._bit_in_locus]
80        # bits shape: (n, 2)
81        n_alleles = bits.size  # n * 2
82        n_carriers = int((bits == self._tracked_allele).sum())
83        freq = n_carriers / n_alleles if n_alleles else 0.0
84
85        self._ensure_open()
86        self._file.write(f"{variables.steps},{n},{n_alleles},{n_carriers},{freq:.6f}\n")
87        self._file.flush()