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()
BITS_PER_LOCUS
THRESHOLD
exp_base
binary_exp_base
binary_weights
binary_switch_weights
linear_weights
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