aegis_sim.submodels.genetics.modifying.gpm_decoder

  1import functools
  2import numpy as np
  3
  4
  5from aegis_sim.parameterization import parametermanager
  6
  7
  8def stan_age(age):
  9    return age / parametermanager.parameters.AGE_LIMIT
 10
 11
 12class GPM_decoder:
 13    """Converter of simple user input into a genotype-phenotype map (as a list or a matrix).
 14
 15    --- Example input ---
 16
 17    PHENOMAP:
 18        "AP1, 7": # name of block, number of sites
 19            - ["surv", "agespec", 0.1] # trait, age-dependency, scale
 20            - ["repr", "agespec", -0.1]
 21        "MA1, 13":
 22            - ["surv", "agespec", -0.1]
 23
 24    # Make sure you set:
 25    GENARCH_TYPE: modifying
 26    BITS_PER_LOCUS: 1
 27
 28    # Make sure your genome size is big enough for all sites set up
 29    MODIF_GENOME_SIZE: 2000
 30
 31    ---
 32    """
 33
 34    def __init__(self, config_PHENOMAP):
 35        self.blocks = {}
 36        self.n = 0
 37
 38        for namen, quartets in config_PHENOMAP.items():
 39            # YML dict cannot take tuple as key, so it is fused as f'{name}, {n}'
 40            name, n = namen.split(",")
 41            n = int(n)
 42            self.__add_genblock(name, n)
 43            for trait, agefunc, funcparam in quartets:
 44                self.__add_encoding(name, trait, agefunc, funcparam)
 45
 46    def get_total_phenolist(self):
 47        phenolist = []
 48        for block in self.blocks.values():
 49            phenolist.extend(block.get_phenolist())
 50        return phenolist
 51
 52    def __len__(self):
 53        return self.n
 54
 55    # PRIVATE
 56    def __add_genblock(self, name, n):
 57        assert name not in self.blocks.keys(), f"Block with name {name} already exists."
 58
 59        genblock = Genblock(name=name, n=n, position=self.n)
 60        self.blocks[name] = genblock
 61        self.n += genblock.n
 62
 63    def __add_encoding(self, name, trait, agefunc, funcparam):
 64        self.blocks[name].add_encoding(trait, agefunc, funcparam)
 65
 66
 67class Genblock:
 68    def __init__(self, name, n, position):
 69        self.name = name
 70        self.n = n
 71
 72        self.position = position
 73
 74        self.encodings = []
 75
 76        self.phenolist = []
 77
 78    def add_encoding(self, trait, agefunc, funcparam):
 79        encoding = {
 80            "trait": trait,
 81            "agefunc": agefunc,
 82            "funcparam": funcparam,
 83        }
 84        self.encodings.append(encoding)
 85        self.__decode(encoding=encoding)
 86
 87    def get_phenolist(self):
 88        for index, trait, age, magnitude in self.phenolist:
 89            yield index + self.position, trait, age, magnitude
 90
 91    def __decode(self, encoding):
 92
 93        def add_to_phenolist(index, trait, age, magnitude):
 94            self.phenolist.append([index, trait, age, magnitude])
 95
 96        for i in range(self.n):
 97            func = Genblock.__resolve_function(encoding["agefunc"], encoding["funcparam"])
 98            if encoding["agefunc"] == "agespec":
 99                age = np.random.randint(0, parametermanager.parameters.AGE_LIMIT)
100                magnitude = func(age)
101                add_to_phenolist(i, encoding["trait"], age, magnitude)
102            else:
103                for age in range(parametermanager.parameters.AGE_LIMIT):
104                    magnitude = func(age)
105                    add_to_phenolist(i, encoding["trait"], age, magnitude)
106
107    @staticmethod
108    def __resolve_function(func, params):
109
110        scale = params
111
112        # PARAMETER DISTRIBUTIONS
113        def _beta_symmetrical(a=3, b=3):
114            """
115            a=3, b=3 -> bell-shape
116            a=1, b=3 -> decay (highest around 0, decreases with distance from 0)
117            a=1, b=1 -> flat
118            """
119            return np.random.beta(a, b) * 2 - 1
120
121        def _beta_onesided(a, b):
122            return np.random.beta(a, b)
123
124        # PHENOTYPE(AGE) functions
125        # These functions are used to compute phenotypic effects for every age
126        # They are going to be partial-ed with beta-shaped parameters (above)
127        # And then they take in age as a variable
128
129        def acc(intercept, slope, acc, age):
130            x = stan_age(age)
131            y = intercept + slope * x + acc * x**3
132            return scale * y
133
134        def exp(exp, age):
135            x = stan_age(age)
136            sign = [-1, 1][exp > 0]
137            y = sign * ((x + 1) ** (abs(exp) * 5) - 1)
138            return scale * y
139
140        def hump(amplitude, x0, width, age):
141            x = stan_age(age)
142            midpoint = 0.5
143            y = amplitude * np.exp(-((x - midpoint - x0) ** 2) / (0.02 + (width * 0.2) ** 2))
144            return scale * y
145
146        def sigm(slope, shift, age):
147            x = stan_age(age)
148            k = slope * 50
149            midpoint = 0.5
150            y = 1 / (1 + np.exp((-1) * k * (x - midpoint - shift / 2)))
151            return scale * y - scale / 2
152
153        # Assembling it together
154        if func == "agespec":
155            return functools.partial(acc, _beta_onesided(a=1, b=3), 0, 0)
156        elif func == "lin":
157            # linearly changing with age
158            return functools.partial(acc, _beta_symmetrical() / 2, _beta_symmetrical(), 0)
159        elif func == "acc":
160            # exponentially changing with age
161            return functools.partial(acc, _beta_symmetrical(), 0, _beta_symmetrical())
162        elif func == "exp":
163            # exponentially changing with age
164            return functools.partial(exp, _beta_symmetrical())
165        elif func == "hump":
166            # not age-specific but with an age-hump
167            return functools.partial(hump, _beta_symmetrical(), _beta_symmetrical(1, 1), _beta_symmetrical())
168        elif func == "sigm":
169            # sigmoidal across age
170            return functools.partial(sigm, _beta_symmetrical(), _beta_symmetrical(1, 1))
171        else:
172            raise Exception(f"{func} not allowed.")
def stan_age(age):
 9def stan_age(age):
10    return age / parametermanager.parameters.AGE_LIMIT
class GPM_decoder:
13class GPM_decoder:
14    """Converter of simple user input into a genotype-phenotype map (as a list or a matrix).
15
16    --- Example input ---
17
18    PHENOMAP:
19        "AP1, 7": # name of block, number of sites
20            - ["surv", "agespec", 0.1] # trait, age-dependency, scale
21            - ["repr", "agespec", -0.1]
22        "MA1, 13":
23            - ["surv", "agespec", -0.1]
24
25    # Make sure you set:
26    GENARCH_TYPE: modifying
27    BITS_PER_LOCUS: 1
28
29    # Make sure your genome size is big enough for all sites set up
30    MODIF_GENOME_SIZE: 2000
31
32    ---
33    """
34
35    def __init__(self, config_PHENOMAP):
36        self.blocks = {}
37        self.n = 0
38
39        for namen, quartets in config_PHENOMAP.items():
40            # YML dict cannot take tuple as key, so it is fused as f'{name}, {n}'
41            name, n = namen.split(",")
42            n = int(n)
43            self.__add_genblock(name, n)
44            for trait, agefunc, funcparam in quartets:
45                self.__add_encoding(name, trait, agefunc, funcparam)
46
47    def get_total_phenolist(self):
48        phenolist = []
49        for block in self.blocks.values():
50            phenolist.extend(block.get_phenolist())
51        return phenolist
52
53    def __len__(self):
54        return self.n
55
56    # PRIVATE
57    def __add_genblock(self, name, n):
58        assert name not in self.blocks.keys(), f"Block with name {name} already exists."
59
60        genblock = Genblock(name=name, n=n, position=self.n)
61        self.blocks[name] = genblock
62        self.n += genblock.n
63
64    def __add_encoding(self, name, trait, agefunc, funcparam):
65        self.blocks[name].add_encoding(trait, agefunc, funcparam)

Converter of simple user input into a genotype-phenotype map (as a list or a matrix).

--- Example input ---

PHENOMAP: "AP1, 7": # name of block, number of sites - ["surv", "agespec", 0.1] # trait, age-dependency, scale - ["repr", "agespec", -0.1] "MA1, 13": - ["surv", "agespec", -0.1]

Make sure you set:

GENARCH_TYPE: modifying BITS_PER_LOCUS: 1

Make sure your genome size is big enough for all sites set up

MODIF_GENOME_SIZE: 2000


GPM_decoder(config_PHENOMAP)
35    def __init__(self, config_PHENOMAP):
36        self.blocks = {}
37        self.n = 0
38
39        for namen, quartets in config_PHENOMAP.items():
40            # YML dict cannot take tuple as key, so it is fused as f'{name}, {n}'
41            name, n = namen.split(",")
42            n = int(n)
43            self.__add_genblock(name, n)
44            for trait, agefunc, funcparam in quartets:
45                self.__add_encoding(name, trait, agefunc, funcparam)
blocks
n
def get_total_phenolist(self):
47    def get_total_phenolist(self):
48        phenolist = []
49        for block in self.blocks.values():
50            phenolist.extend(block.get_phenolist())
51        return phenolist
class Genblock:
 68class Genblock:
 69    def __init__(self, name, n, position):
 70        self.name = name
 71        self.n = n
 72
 73        self.position = position
 74
 75        self.encodings = []
 76
 77        self.phenolist = []
 78
 79    def add_encoding(self, trait, agefunc, funcparam):
 80        encoding = {
 81            "trait": trait,
 82            "agefunc": agefunc,
 83            "funcparam": funcparam,
 84        }
 85        self.encodings.append(encoding)
 86        self.__decode(encoding=encoding)
 87
 88    def get_phenolist(self):
 89        for index, trait, age, magnitude in self.phenolist:
 90            yield index + self.position, trait, age, magnitude
 91
 92    def __decode(self, encoding):
 93
 94        def add_to_phenolist(index, trait, age, magnitude):
 95            self.phenolist.append([index, trait, age, magnitude])
 96
 97        for i in range(self.n):
 98            func = Genblock.__resolve_function(encoding["agefunc"], encoding["funcparam"])
 99            if encoding["agefunc"] == "agespec":
100                age = np.random.randint(0, parametermanager.parameters.AGE_LIMIT)
101                magnitude = func(age)
102                add_to_phenolist(i, encoding["trait"], age, magnitude)
103            else:
104                for age in range(parametermanager.parameters.AGE_LIMIT):
105                    magnitude = func(age)
106                    add_to_phenolist(i, encoding["trait"], age, magnitude)
107
108    @staticmethod
109    def __resolve_function(func, params):
110
111        scale = params
112
113        # PARAMETER DISTRIBUTIONS
114        def _beta_symmetrical(a=3, b=3):
115            """
116            a=3, b=3 -> bell-shape
117            a=1, b=3 -> decay (highest around 0, decreases with distance from 0)
118            a=1, b=1 -> flat
119            """
120            return np.random.beta(a, b) * 2 - 1
121
122        def _beta_onesided(a, b):
123            return np.random.beta(a, b)
124
125        # PHENOTYPE(AGE) functions
126        # These functions are used to compute phenotypic effects for every age
127        # They are going to be partial-ed with beta-shaped parameters (above)
128        # And then they take in age as a variable
129
130        def acc(intercept, slope, acc, age):
131            x = stan_age(age)
132            y = intercept + slope * x + acc * x**3
133            return scale * y
134
135        def exp(exp, age):
136            x = stan_age(age)
137            sign = [-1, 1][exp > 0]
138            y = sign * ((x + 1) ** (abs(exp) * 5) - 1)
139            return scale * y
140
141        def hump(amplitude, x0, width, age):
142            x = stan_age(age)
143            midpoint = 0.5
144            y = amplitude * np.exp(-((x - midpoint - x0) ** 2) / (0.02 + (width * 0.2) ** 2))
145            return scale * y
146
147        def sigm(slope, shift, age):
148            x = stan_age(age)
149            k = slope * 50
150            midpoint = 0.5
151            y = 1 / (1 + np.exp((-1) * k * (x - midpoint - shift / 2)))
152            return scale * y - scale / 2
153
154        # Assembling it together
155        if func == "agespec":
156            return functools.partial(acc, _beta_onesided(a=1, b=3), 0, 0)
157        elif func == "lin":
158            # linearly changing with age
159            return functools.partial(acc, _beta_symmetrical() / 2, _beta_symmetrical(), 0)
160        elif func == "acc":
161            # exponentially changing with age
162            return functools.partial(acc, _beta_symmetrical(), 0, _beta_symmetrical())
163        elif func == "exp":
164            # exponentially changing with age
165            return functools.partial(exp, _beta_symmetrical())
166        elif func == "hump":
167            # not age-specific but with an age-hump
168            return functools.partial(hump, _beta_symmetrical(), _beta_symmetrical(1, 1), _beta_symmetrical())
169        elif func == "sigm":
170            # sigmoidal across age
171            return functools.partial(sigm, _beta_symmetrical(), _beta_symmetrical(1, 1))
172        else:
173            raise Exception(f"{func} not allowed.")
Genblock(name, n, position)
69    def __init__(self, name, n, position):
70        self.name = name
71        self.n = n
72
73        self.position = position
74
75        self.encodings = []
76
77        self.phenolist = []
name
n
position
encodings
phenolist
def add_encoding(self, trait, agefunc, funcparam):
79    def add_encoding(self, trait, agefunc, funcparam):
80        encoding = {
81            "trait": trait,
82            "agefunc": agefunc,
83            "funcparam": funcparam,
84        }
85        self.encodings.append(encoding)
86        self.__decode(encoding=encoding)
def get_phenolist(self):
88    def get_phenolist(self):
89        for index, trait, age, magnitude in self.phenolist:
90            yield index + self.position, trait, age, magnitude