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)
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