aegis_sim.submodels.genetics.composite.architecture

 1import numpy as np
 2from aegis_sim import constants
 3from aegis_sim import variables
 4
 5from aegis_sim.submodels.genetics.composite.interpreter import Interpreter
 6from aegis_sim import parameterization
 7from aegis_sim.submodels.genetics import ploider
 8
 9
10class CompositeArchitecture:
11    """
12
13    GUI
14    - when pleiotropy is not needed;
15    - it is quick, easy to analyze, delivers a diversity of phenotypes
16    - every trait (surv repr muta neut) can be evolvable or not
17    - if not evolvable, the value is set by !!!
18    - if evolvable, it can be agespecific or age-independent
19    - probability of a trait at each age is determined by a BITS_PER_LOCUS adjacent bits forming a "locus" / gene
20    - the method by which these loci are converted into a phenotypic value is the Interpreter type
21
22    """
23
24    def __init__(self, BITS_PER_LOCUS, AGE_LIMIT, THRESHOLD):
25        self.BITS_PER_LOCUS = BITS_PER_LOCUS
26        self.n_loci = sum(trait.length for trait in parameterization.traits.values())
27        self.length = self.n_loci * BITS_PER_LOCUS
28        self.AGE_LIMIT = AGE_LIMIT
29
30        self.evolvable = [trait for trait in parameterization.traits.values() if trait.evolvable]
31
32        self.interpreter = Interpreter(
33            self.BITS_PER_LOCUS,
34            THRESHOLD,
35        )
36
37        # Fixed seed=0 so all populations (including hybridizing ones with different RANDOM_SEEDs)
38        # share an identical physical genome layout; locus_permutation[i] = physical position of logical locus i
39        self.locus_permutation = np.random.default_rng(0).permutation(self.n_loci)
40
41        # Per-locus dominance coefficient h, indexed by *physical* locus position
42        # (because diploid_to_haploid operates on the physical layout, before reorder).
43        # Built from each trait's G_<trait>_dominance value (default 0.5 = codominant).
44        self.dominance_per_locus = np.full(self.n_loci, 0.5, dtype=np.float32)
45        for trait in parameterization.traits.values():
46            if trait.length == 0:
47                continue
48            phys_pos = self.locus_permutation[trait.start:trait.end]
49            self.dominance_per_locus[phys_pos] = np.float32(trait.dominance)
50
51    def get_number_of_bits(self):
52        return ploider.ploider.y * self.n_loci * self.BITS_PER_LOCUS
53
54    def get_shape(self):
55        return (ploider.ploider.y, self.n_loci, self.BITS_PER_LOCUS)
56
57    def init_genome_array(self, popsize):
58        # TODO enable agespecific False
59        array = variables.rng.random(size=(popsize, *self.get_shape()))
60
61        for trait in parameterization.traits.values():
62            phys_pos = self.locus_permutation[trait.start:trait.end]
63            array[:, :, phys_pos, :] = array[:, :, phys_pos, :] < trait.initgeno
64
65        return array
66
67    def compute(self, genomes):
68
69        if genomes.shape[1] == 1:  # Do not calculate mean if genomes are haploid
70            genomes = genomes[:, 0]
71        else:
72            genomes = ploider.ploider.diploid_to_haploid(genomes, dominance_per_locus=self.dominance_per_locus)
73
74        # Reorder from physical storage order to logical (trait×age) order
75        genomes = genomes[:, self.locus_permutation, :]
76
77        interpretome = np.zeros(shape=(genomes.shape[0], genomes.shape[1]), dtype=np.float32)
78        for trait in parameterization.traits.values():
79            loci = genomes[:, trait.slice]  # fetch
80            probs = self.interpreter.call(loci, trait.interpreter)  # interpret
81            # Map the [0, 1] interpreter output onto the trait's [lo, hi] phenotypic range.
82            # This is what makes G_<trait>_lo / G_<trait>_hi do anything — without this scaling
83            # the lo/hi parameters are parsed but discarded. Defaults: G_surv_lo=0.7, G_surv_hi=1.0
84            # (surv never goes to 0); G_repr_lo=0, G_repr_hi=0.5; others lo=0, hi=1.
85            probs = trait.lo + (trait.hi - trait.lo) * probs
86            interpretome[:, trait.slice] += probs  # add back
87
88        return interpretome
89
90    # def diffuse(self, probs):
91    #     window_size = parametermanager.parameters.DIFFUSION_FACTOR * 2 + 1
92    #     p = np.empty(shape=(probs.shape[0], probs.shape[1] + window_size - 1))
93    #     p[:, :window_size] = np.repeat(probs[:, 0], window_size).reshape(-1, window_size)
94    #     p[:, window_size - 1 :] = probs[:]
95    #     diffusome = np.convolve(p[0], np.ones(window_size) / window_size, mode="valid")
96
97    def get_map(self):
98        pass
class CompositeArchitecture:
11class CompositeArchitecture:
12    """
13
14    GUI
15    - when pleiotropy is not needed;
16    - it is quick, easy to analyze, delivers a diversity of phenotypes
17    - every trait (surv repr muta neut) can be evolvable or not
18    - if not evolvable, the value is set by !!!
19    - if evolvable, it can be agespecific or age-independent
20    - probability of a trait at each age is determined by a BITS_PER_LOCUS adjacent bits forming a "locus" / gene
21    - the method by which these loci are converted into a phenotypic value is the Interpreter type
22
23    """
24
25    def __init__(self, BITS_PER_LOCUS, AGE_LIMIT, THRESHOLD):
26        self.BITS_PER_LOCUS = BITS_PER_LOCUS
27        self.n_loci = sum(trait.length for trait in parameterization.traits.values())
28        self.length = self.n_loci * BITS_PER_LOCUS
29        self.AGE_LIMIT = AGE_LIMIT
30
31        self.evolvable = [trait for trait in parameterization.traits.values() if trait.evolvable]
32
33        self.interpreter = Interpreter(
34            self.BITS_PER_LOCUS,
35            THRESHOLD,
36        )
37
38        # Fixed seed=0 so all populations (including hybridizing ones with different RANDOM_SEEDs)
39        # share an identical physical genome layout; locus_permutation[i] = physical position of logical locus i
40        self.locus_permutation = np.random.default_rng(0).permutation(self.n_loci)
41
42        # Per-locus dominance coefficient h, indexed by *physical* locus position
43        # (because diploid_to_haploid operates on the physical layout, before reorder).
44        # Built from each trait's G_<trait>_dominance value (default 0.5 = codominant).
45        self.dominance_per_locus = np.full(self.n_loci, 0.5, dtype=np.float32)
46        for trait in parameterization.traits.values():
47            if trait.length == 0:
48                continue
49            phys_pos = self.locus_permutation[trait.start:trait.end]
50            self.dominance_per_locus[phys_pos] = np.float32(trait.dominance)
51
52    def get_number_of_bits(self):
53        return ploider.ploider.y * self.n_loci * self.BITS_PER_LOCUS
54
55    def get_shape(self):
56        return (ploider.ploider.y, self.n_loci, self.BITS_PER_LOCUS)
57
58    def init_genome_array(self, popsize):
59        # TODO enable agespecific False
60        array = variables.rng.random(size=(popsize, *self.get_shape()))
61
62        for trait in parameterization.traits.values():
63            phys_pos = self.locus_permutation[trait.start:trait.end]
64            array[:, :, phys_pos, :] = array[:, :, phys_pos, :] < trait.initgeno
65
66        return array
67
68    def compute(self, genomes):
69
70        if genomes.shape[1] == 1:  # Do not calculate mean if genomes are haploid
71            genomes = genomes[:, 0]
72        else:
73            genomes = ploider.ploider.diploid_to_haploid(genomes, dominance_per_locus=self.dominance_per_locus)
74
75        # Reorder from physical storage order to logical (trait×age) order
76        genomes = genomes[:, self.locus_permutation, :]
77
78        interpretome = np.zeros(shape=(genomes.shape[0], genomes.shape[1]), dtype=np.float32)
79        for trait in parameterization.traits.values():
80            loci = genomes[:, trait.slice]  # fetch
81            probs = self.interpreter.call(loci, trait.interpreter)  # interpret
82            # Map the [0, 1] interpreter output onto the trait's [lo, hi] phenotypic range.
83            # This is what makes G_<trait>_lo / G_<trait>_hi do anything — without this scaling
84            # the lo/hi parameters are parsed but discarded. Defaults: G_surv_lo=0.7, G_surv_hi=1.0
85            # (surv never goes to 0); G_repr_lo=0, G_repr_hi=0.5; others lo=0, hi=1.
86            probs = trait.lo + (trait.hi - trait.lo) * probs
87            interpretome[:, trait.slice] += probs  # add back
88
89        return interpretome
90
91    # def diffuse(self, probs):
92    #     window_size = parametermanager.parameters.DIFFUSION_FACTOR * 2 + 1
93    #     p = np.empty(shape=(probs.shape[0], probs.shape[1] + window_size - 1))
94    #     p[:, :window_size] = np.repeat(probs[:, 0], window_size).reshape(-1, window_size)
95    #     p[:, window_size - 1 :] = probs[:]
96    #     diffusome = np.convolve(p[0], np.ones(window_size) / window_size, mode="valid")
97
98    def get_map(self):
99        pass

GUI

  • when pleiotropy is not needed;
  • it is quick, easy to analyze, delivers a diversity of phenotypes
  • every trait (surv repr muta neut) can be evolvable or not
  • if not evolvable, the value is set by !!!
  • if evolvable, it can be agespecific or age-independent
  • probability of a trait at each age is determined by a BITS_PER_LOCUS adjacent bits forming a "locus" / gene
  • the method by which these loci are converted into a phenotypic value is the Interpreter type
CompositeArchitecture(BITS_PER_LOCUS, AGE_LIMIT, THRESHOLD)
25    def __init__(self, BITS_PER_LOCUS, AGE_LIMIT, THRESHOLD):
26        self.BITS_PER_LOCUS = BITS_PER_LOCUS
27        self.n_loci = sum(trait.length for trait in parameterization.traits.values())
28        self.length = self.n_loci * BITS_PER_LOCUS
29        self.AGE_LIMIT = AGE_LIMIT
30
31        self.evolvable = [trait for trait in parameterization.traits.values() if trait.evolvable]
32
33        self.interpreter = Interpreter(
34            self.BITS_PER_LOCUS,
35            THRESHOLD,
36        )
37
38        # Fixed seed=0 so all populations (including hybridizing ones with different RANDOM_SEEDs)
39        # share an identical physical genome layout; locus_permutation[i] = physical position of logical locus i
40        self.locus_permutation = np.random.default_rng(0).permutation(self.n_loci)
41
42        # Per-locus dominance coefficient h, indexed by *physical* locus position
43        # (because diploid_to_haploid operates on the physical layout, before reorder).
44        # Built from each trait's G_<trait>_dominance value (default 0.5 = codominant).
45        self.dominance_per_locus = np.full(self.n_loci, 0.5, dtype=np.float32)
46        for trait in parameterization.traits.values():
47            if trait.length == 0:
48                continue
49            phys_pos = self.locus_permutation[trait.start:trait.end]
50            self.dominance_per_locus[phys_pos] = np.float32(trait.dominance)
BITS_PER_LOCUS
n_loci
length
AGE_LIMIT
evolvable
interpreter
locus_permutation
dominance_per_locus
def get_number_of_bits(self):
52    def get_number_of_bits(self):
53        return ploider.ploider.y * self.n_loci * self.BITS_PER_LOCUS
def get_shape(self):
55    def get_shape(self):
56        return (ploider.ploider.y, self.n_loci, self.BITS_PER_LOCUS)
def init_genome_array(self, popsize):
58    def init_genome_array(self, popsize):
59        # TODO enable agespecific False
60        array = variables.rng.random(size=(popsize, *self.get_shape()))
61
62        for trait in parameterization.traits.values():
63            phys_pos = self.locus_permutation[trait.start:trait.end]
64            array[:, :, phys_pos, :] = array[:, :, phys_pos, :] < trait.initgeno
65
66        return array
def compute(self, genomes):
68    def compute(self, genomes):
69
70        if genomes.shape[1] == 1:  # Do not calculate mean if genomes are haploid
71            genomes = genomes[:, 0]
72        else:
73            genomes = ploider.ploider.diploid_to_haploid(genomes, dominance_per_locus=self.dominance_per_locus)
74
75        # Reorder from physical storage order to logical (trait×age) order
76        genomes = genomes[:, self.locus_permutation, :]
77
78        interpretome = np.zeros(shape=(genomes.shape[0], genomes.shape[1]), dtype=np.float32)
79        for trait in parameterization.traits.values():
80            loci = genomes[:, trait.slice]  # fetch
81            probs = self.interpreter.call(loci, trait.interpreter)  # interpret
82            # Map the [0, 1] interpreter output onto the trait's [lo, hi] phenotypic range.
83            # This is what makes G_<trait>_lo / G_<trait>_hi do anything — without this scaling
84            # the lo/hi parameters are parsed but discarded. Defaults: G_surv_lo=0.7, G_surv_hi=1.0
85            # (surv never goes to 0); G_repr_lo=0, G_repr_hi=0.5; others lo=0, hi=1.
86            probs = trait.lo + (trait.hi - trait.lo) * probs
87            interpretome[:, trait.slice] += probs  # add back
88
89        return interpretome
def get_map(self):
98    def get_map(self):
99        pass