aegis_sim.submodels.genetics.composite.interpreter
Interpreter of genomic loci
Transforms bool array into an array of numbers. These numbers can be loosely understood as gene activity.
1"""Interpreter of genomic loci 2 3Transforms bool array into an array of numbers. 4These numbers can be loosely understood as gene activity. 5""" 6 7import numpy as np 8from aegis_sim import variables 9 10 11class Interpreter: 12 def __init__(self, BITS_PER_LOCUS, THRESHOLD): 13 14 self.BITS_PER_LOCUS = BITS_PER_LOCUS 15 self.THRESHOLD = THRESHOLD 16 17 self.exp_base = 0.5 # Important for _exp 18 self.binary_exp_base = 0.98 # Important for _binary_exp 19 20 # Parameters for the binary interpreter 21 binary_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 22 self.binary_weights = binary_weights / binary_weights.sum() 23 24 # Parameters for the binary switch interpreter 25 binary_switch_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 26 binary_switch_weights[-1] = 0 # Switch bit does not add to locus value 27 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 28 # e.g. when BITS_PER_LOCUS is 4, binary_switch_weights are [4/7, 2/7, 1/7, 0] 29 30 # Parameters for the linear interpreter 31 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 32 self.linear_weights = linear_weights / linear_weights.sum() 33 34 def call(self, loci, interpreter_kind): 35 """Exposed method""" 36 37 # shape is (n_individuals, ?, bits_per_locus) 38 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 39 assert loci.shape[2] == self.BITS_PER_LOCUS 40 41 method = { 42 "const1": self._const1, 43 "single_bit": self._single_bit, 44 "threshold": self._threshold, 45 "linear": self._linear, 46 "binary": self._binary, 47 "switch": self._switch, 48 "binary_switch": self._binary_switch, 49 "uniform": self._uniform, 50 "exp": self._exp, 51 "binary_exp": self._binary_exp, 52 }[interpreter_kind] 53 interpretome = method(loci) 54 return interpretome 55 56 @staticmethod 57 def _const1(loci): 58 return np.ones((len(loci), 1)) 59 60 @staticmethod 61 def _single_bit(loci): 62 return loci[:, :, 0] 63 64 def _threshold(self, loci): 65 """Penna interpreter 66 Cares only about the first bit of the locus. 67 """ 68 return (~loci[:, :, 0]).cumsum(-1) < self.THRESHOLD 69 70 def _linear(self, loci): 71 return np.matmul(loci, self.linear_weights) 72 73 def _binary(self, loci): 74 """Interpret locus as a binary number and normalize. 75 76 High resolution (can produce 2^bits_per_locus different numbers). 77 Position-dependent. 78 """ 79 80 return np.matmul(loci, self.binary_weights) 81 82 @staticmethod 83 def _switch(loci): 84 """Return 0 if all bits are 0; 1 if all bits are 1; 0 or 1 randomly otherwise. 85 86 Low resolution (can produce 2 different numbers). 87 Position-independent. 88 """ 89 sums = loci.mean(2) 90 rand_values = variables.rng.random(loci.shape[:-1]) < 0.5 91 return np.select([sums == 0, (sums > 0) & (sums < 1), sums == 1], [0, rand_values, 1]) 92 93 def _binary_switch(self, loci): 94 """Interpret first n-1 bits as a binary number if the last bit is 1. 95 96 High resolution (can produce 2^(bits_per_locus-1) different numbers). 97 Position-dependent. 98 """ 99 where_on = loci[:, :, -1] == 1 # Loci which are turned on 100 values = np.zeros(loci.shape[:-1], dtype=np.float32) # Initialize output array with zeros 101 values[where_on] = loci[where_on].dot( 102 self.binary_switch_weights 103 ) # If the locus is turned on, make the value in the output array be the binary value 104 return values 105 106 def _uniform(loci): 107 """Return normalized sum of bits. 108 109 Medium resolution (can produce bits_per_locus+1 different numbers). 110 Position-independent. 111 """ 112 return loci.sum(-1) / loci.shape[-1] 113 114 def _exp(self, loci): 115 """Return base^total_number_of_zeros. 116 117 Medium resolution (can produce bits_per_locus+1 different numbers). 118 Suitable for generating very small numbers. 119 Position-independent. 120 """ 121 return self.exp_base ** np.sum(1 - loci, axis=2) 122 123 def _binary_exp(self, loci): 124 """Return base^binary_value_of_locus 125 126 High resolution (can produce 2^bits_per_locus different numbers). 127 Suitable for generating very small numbers. 128 Position-dependent. 129 """ 130 binary = self._binary(loci) 131 return self.binary_exp_base**binary
class
Interpreter:
12class Interpreter: 13 def __init__(self, BITS_PER_LOCUS, THRESHOLD): 14 15 self.BITS_PER_LOCUS = BITS_PER_LOCUS 16 self.THRESHOLD = THRESHOLD 17 18 self.exp_base = 0.5 # Important for _exp 19 self.binary_exp_base = 0.98 # Important for _binary_exp 20 21 # Parameters for the binary interpreter 22 binary_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 23 self.binary_weights = binary_weights / binary_weights.sum() 24 25 # Parameters for the binary switch interpreter 26 binary_switch_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 27 binary_switch_weights[-1] = 0 # Switch bit does not add to locus value 28 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 29 # e.g. when BITS_PER_LOCUS is 4, binary_switch_weights are [4/7, 2/7, 1/7, 0] 30 31 # Parameters for the linear interpreter 32 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 33 self.linear_weights = linear_weights / linear_weights.sum() 34 35 def call(self, loci, interpreter_kind): 36 """Exposed method""" 37 38 # shape is (n_individuals, ?, bits_per_locus) 39 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 40 assert loci.shape[2] == self.BITS_PER_LOCUS 41 42 method = { 43 "const1": self._const1, 44 "single_bit": self._single_bit, 45 "threshold": self._threshold, 46 "linear": self._linear, 47 "binary": self._binary, 48 "switch": self._switch, 49 "binary_switch": self._binary_switch, 50 "uniform": self._uniform, 51 "exp": self._exp, 52 "binary_exp": self._binary_exp, 53 }[interpreter_kind] 54 interpretome = method(loci) 55 return interpretome 56 57 @staticmethod 58 def _const1(loci): 59 return np.ones((len(loci), 1)) 60 61 @staticmethod 62 def _single_bit(loci): 63 return loci[:, :, 0] 64 65 def _threshold(self, loci): 66 """Penna interpreter 67 Cares only about the first bit of the locus. 68 """ 69 return (~loci[:, :, 0]).cumsum(-1) < self.THRESHOLD 70 71 def _linear(self, loci): 72 return np.matmul(loci, self.linear_weights) 73 74 def _binary(self, loci): 75 """Interpret locus as a binary number and normalize. 76 77 High resolution (can produce 2^bits_per_locus different numbers). 78 Position-dependent. 79 """ 80 81 return np.matmul(loci, self.binary_weights) 82 83 @staticmethod 84 def _switch(loci): 85 """Return 0 if all bits are 0; 1 if all bits are 1; 0 or 1 randomly otherwise. 86 87 Low resolution (can produce 2 different numbers). 88 Position-independent. 89 """ 90 sums = loci.mean(2) 91 rand_values = variables.rng.random(loci.shape[:-1]) < 0.5 92 return np.select([sums == 0, (sums > 0) & (sums < 1), sums == 1], [0, rand_values, 1]) 93 94 def _binary_switch(self, loci): 95 """Interpret first n-1 bits as a binary number if the last bit is 1. 96 97 High resolution (can produce 2^(bits_per_locus-1) different numbers). 98 Position-dependent. 99 """ 100 where_on = loci[:, :, -1] == 1 # Loci which are turned on 101 values = np.zeros(loci.shape[:-1], dtype=np.float32) # Initialize output array with zeros 102 values[where_on] = loci[where_on].dot( 103 self.binary_switch_weights 104 ) # If the locus is turned on, make the value in the output array be the binary value 105 return values 106 107 def _uniform(loci): 108 """Return normalized sum of bits. 109 110 Medium resolution (can produce bits_per_locus+1 different numbers). 111 Position-independent. 112 """ 113 return loci.sum(-1) / loci.shape[-1] 114 115 def _exp(self, loci): 116 """Return base^total_number_of_zeros. 117 118 Medium resolution (can produce bits_per_locus+1 different numbers). 119 Suitable for generating very small numbers. 120 Position-independent. 121 """ 122 return self.exp_base ** np.sum(1 - loci, axis=2) 123 124 def _binary_exp(self, loci): 125 """Return base^binary_value_of_locus 126 127 High resolution (can produce 2^bits_per_locus different numbers). 128 Suitable for generating very small numbers. 129 Position-dependent. 130 """ 131 binary = self._binary(loci) 132 return self.binary_exp_base**binary
Interpreter(BITS_PER_LOCUS, THRESHOLD)
13 def __init__(self, BITS_PER_LOCUS, THRESHOLD): 14 15 self.BITS_PER_LOCUS = BITS_PER_LOCUS 16 self.THRESHOLD = THRESHOLD 17 18 self.exp_base = 0.5 # Important for _exp 19 self.binary_exp_base = 0.98 # Important for _binary_exp 20 21 # Parameters for the binary interpreter 22 binary_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 23 self.binary_weights = binary_weights / binary_weights.sum() 24 25 # Parameters for the binary switch interpreter 26 binary_switch_weights = 2 ** np.arange(self.BITS_PER_LOCUS)[::-1] 27 binary_switch_weights[-1] = 0 # Switch bit does not add to locus value 28 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 29 # e.g. when BITS_PER_LOCUS is 4, binary_switch_weights are [4/7, 2/7, 1/7, 0] 30 31 # Parameters for the linear interpreter 32 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 33 self.linear_weights = linear_weights / linear_weights.sum()
def
call(self, loci, interpreter_kind):
35 def call(self, loci, interpreter_kind): 36 """Exposed method""" 37 38 # shape is (n_individuals, ?, bits_per_locus) 39 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 40 assert loci.shape[2] == self.BITS_PER_LOCUS 41 42 method = { 43 "const1": self._const1, 44 "single_bit": self._single_bit, 45 "threshold": self._threshold, 46 "linear": self._linear, 47 "binary": self._binary, 48 "switch": self._switch, 49 "binary_switch": self._binary_switch, 50 "uniform": self._uniform, 51 "exp": self._exp, 52 "binary_exp": self._binary_exp, 53 }[interpreter_kind] 54 interpretome = method(loci) 55 return interpretome
Exposed method