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 with np.errstate(invalid="ignore"): 28 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 29 # When BITS_PER_LOCUS is 1, all weights are 0 — that's fine, _binary_switch 30 # will produce zeros for all "on" loci. 31 32 # Parameters for the linear interpreter 33 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 34 self.linear_weights = linear_weights / linear_weights.sum() 35 36 def call(self, loci, interpreter_kind): 37 """Exposed method""" 38 39 # shape is (n_individuals, ?, bits_per_locus) 40 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 41 assert loci.shape[2] == self.BITS_PER_LOCUS 42 43 method = { 44 "const1": self._const1, 45 "single_bit": self._single_bit, 46 "threshold": self._threshold, 47 "linear": self._linear, 48 "binary": self._binary, 49 "switch": self._switch, 50 "binary_switch": self._binary_switch, 51 "uniform": self._uniform, 52 "exp": self._exp, 53 "binary_exp": self._binary_exp, 54 }[interpreter_kind] 55 interpretome = method(loci) 56 return interpretome 57 58 @staticmethod 59 def _const1(loci): 60 return np.ones((len(loci), 1)) 61 62 @staticmethod 63 def _single_bit(loci): 64 return loci[:, :, 0] 65 66 def _threshold(self, loci): 67 """Penna interpreter 68 Cares only about the first bit of the locus. 69 """ 70 return (~loci[:, :, 0]).cumsum(-1) < self.THRESHOLD 71 72 def _linear(self, loci): 73 return np.matmul(loci, self.linear_weights) 74 75 def _binary(self, loci): 76 """Interpret locus as a binary number and normalize. 77 78 High resolution (can produce 2^bits_per_locus different numbers). 79 Position-dependent. 80 """ 81 82 return np.matmul(loci, self.binary_weights) 83 84 @staticmethod 85 def _switch(loci): 86 """Return 0 if all bits are 0; 1 if all bits are 1; 0 or 1 randomly otherwise. 87 88 Low resolution (can produce 2 different numbers). 89 Position-independent. 90 """ 91 sums = loci.mean(2) 92 rand_values = variables.rng.random(loci.shape[:-1]) < 0.5 93 return np.select([sums == 0, (sums > 0) & (sums < 1), sums == 1], [0, rand_values, 1]) 94 95 def _binary_switch(self, loci): 96 """Interpret first n-1 bits as a binary number if the last bit is 1. 97 98 High resolution (can produce 2^(bits_per_locus-1) different numbers). 99 Position-dependent. 100 """ 101 where_on = loci[:, :, -1] == 1 # Loci which are turned on 102 values = np.zeros(loci.shape[:-1], dtype=np.float32) # Initialize output array with zeros 103 values[where_on] = loci[where_on].dot( 104 self.binary_switch_weights 105 ) # If the locus is turned on, make the value in the output array be the binary value 106 return values 107 108 def _uniform(self, loci): 109 """Return normalized sum of bits. 110 111 Medium resolution (can produce bits_per_locus+1 different numbers). 112 Position-independent. 113 """ 114 return loci.sum(-1) / loci.shape[-1] 115 116 def _exp(self, loci): 117 """Return base^total_number_of_zeros. 118 119 Medium resolution (can produce bits_per_locus+1 different numbers). 120 Suitable for generating very small numbers. 121 Position-independent. 122 """ 123 return self.exp_base ** np.sum(1 - loci, axis=2) 124 125 def _binary_exp(self, loci): 126 """Return base^binary_value_of_locus 127 128 High resolution (can produce 2^bits_per_locus different numbers). 129 Suitable for generating very small numbers. 130 Position-dependent. 131 """ 132 binary = self._binary(loci) 133 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 with np.errstate(invalid="ignore"): 29 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 30 # When BITS_PER_LOCUS is 1, all weights are 0 — that's fine, _binary_switch 31 # will produce zeros for all "on" loci. 32 33 # Parameters for the linear interpreter 34 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 35 self.linear_weights = linear_weights / linear_weights.sum() 36 37 def call(self, loci, interpreter_kind): 38 """Exposed method""" 39 40 # shape is (n_individuals, ?, bits_per_locus) 41 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 42 assert loci.shape[2] == self.BITS_PER_LOCUS 43 44 method = { 45 "const1": self._const1, 46 "single_bit": self._single_bit, 47 "threshold": self._threshold, 48 "linear": self._linear, 49 "binary": self._binary, 50 "switch": self._switch, 51 "binary_switch": self._binary_switch, 52 "uniform": self._uniform, 53 "exp": self._exp, 54 "binary_exp": self._binary_exp, 55 }[interpreter_kind] 56 interpretome = method(loci) 57 return interpretome 58 59 @staticmethod 60 def _const1(loci): 61 return np.ones((len(loci), 1)) 62 63 @staticmethod 64 def _single_bit(loci): 65 return loci[:, :, 0] 66 67 def _threshold(self, loci): 68 """Penna interpreter 69 Cares only about the first bit of the locus. 70 """ 71 return (~loci[:, :, 0]).cumsum(-1) < self.THRESHOLD 72 73 def _linear(self, loci): 74 return np.matmul(loci, self.linear_weights) 75 76 def _binary(self, loci): 77 """Interpret locus as a binary number and normalize. 78 79 High resolution (can produce 2^bits_per_locus different numbers). 80 Position-dependent. 81 """ 82 83 return np.matmul(loci, self.binary_weights) 84 85 @staticmethod 86 def _switch(loci): 87 """Return 0 if all bits are 0; 1 if all bits are 1; 0 or 1 randomly otherwise. 88 89 Low resolution (can produce 2 different numbers). 90 Position-independent. 91 """ 92 sums = loci.mean(2) 93 rand_values = variables.rng.random(loci.shape[:-1]) < 0.5 94 return np.select([sums == 0, (sums > 0) & (sums < 1), sums == 1], [0, rand_values, 1]) 95 96 def _binary_switch(self, loci): 97 """Interpret first n-1 bits as a binary number if the last bit is 1. 98 99 High resolution (can produce 2^(bits_per_locus-1) different numbers). 100 Position-dependent. 101 """ 102 where_on = loci[:, :, -1] == 1 # Loci which are turned on 103 values = np.zeros(loci.shape[:-1], dtype=np.float32) # Initialize output array with zeros 104 values[where_on] = loci[where_on].dot( 105 self.binary_switch_weights 106 ) # If the locus is turned on, make the value in the output array be the binary value 107 return values 108 109 def _uniform(self, loci): 110 """Return normalized sum of bits. 111 112 Medium resolution (can produce bits_per_locus+1 different numbers). 113 Position-independent. 114 """ 115 return loci.sum(-1) / loci.shape[-1] 116 117 def _exp(self, loci): 118 """Return base^total_number_of_zeros. 119 120 Medium resolution (can produce bits_per_locus+1 different numbers). 121 Suitable for generating very small numbers. 122 Position-independent. 123 """ 124 return self.exp_base ** np.sum(1 - loci, axis=2) 125 126 def _binary_exp(self, loci): 127 """Return base^binary_value_of_locus 128 129 High resolution (can produce 2^bits_per_locus different numbers). 130 Suitable for generating very small numbers. 131 Position-dependent. 132 """ 133 binary = self._binary(loci) 134 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 with np.errstate(invalid="ignore"): 29 self.binary_switch_weights = binary_switch_weights / binary_switch_weights.sum() 30 # When BITS_PER_LOCUS is 1, all weights are 0 — that's fine, _binary_switch 31 # will produce zeros for all "on" loci. 32 33 # Parameters for the linear interpreter 34 linear_weights = np.arange(self.BITS_PER_LOCUS)[::-1] + 1 35 self.linear_weights = linear_weights / linear_weights.sum()
def
call(self, loci, interpreter_kind):
37 def call(self, loci, interpreter_kind): 38 """Exposed method""" 39 40 # shape is (n_individuals, ?, bits_per_locus) 41 assert loci.shape[0] > 0, f"loci.shape[0] is {loci.shape[0]}" 42 assert loci.shape[2] == self.BITS_PER_LOCUS 43 44 method = { 45 "const1": self._const1, 46 "single_bit": self._single_bit, 47 "threshold": self._threshold, 48 "linear": self._linear, 49 "binary": self._binary, 50 "switch": self._switch, 51 "binary_switch": self._binary_switch, 52 "uniform": self._uniform, 53 "exp": self._exp, 54 "binary_exp": self._binary_exp, 55 }[interpreter_kind] 56 interpretome = method(loci) 57 return interpretome
Exposed method