Distributions of effect sizes#

Note

The lists of objects described here are passed to the sregions parameter when initializing instances of fwdpy11.ModelParams.

One of the main reasons to perform forward simulations is to be able to model mutations affecting individual fitness. To do so, we need to specify both mutation rates and the resulting effect sizes.

fwdpy11 works by specifying an overall mutation rate to variants affecting fitness. Given that a mutation occurs, we need to specify its “effect size”.

fwdpy11 chooses the effect size of a new mutation by first determining what region is mutated and then generating a mutation from the distribution of effect size associated with that region.

Each region is represented by instances of classes derived from the ABC fwdpy11.Sregion. Each instance is associated with a weight. These weights establish the relative probability that a mutation comes from a given region. Thus, given an overall mutation rate to non-neutral variants, instances of “sregions” are used to set up a multinomial distribution for generating new mutations.

The following sets up a model where mutations have a constant effect size (\(s=-0.01\)), dominance \(h=0.25\), and occur uniformly on the interval \([0, 1)\):

import fwdpy11

sregions = [fwdpy11.ConstantS(beg=0.0, end=1.0, weight=1.0, s=-0.01, h=0.25)]

The previous example uses argument names for clarity, and the following is equivalent, with the int values getting converted to float automatically:

sregions = [fwdpy11.ConstantS(0, 1, 1, -0.01, 0.25)]
sregions[0]
fwdpy11.ConstantS(beg=0, end=1, weight=1, s=-0.01, h=0.25, coupled=True, label=0, scaling=1.0)

Note that the constructor parameters for these classes often have default values–see the specific class documentation for details.

In some scenarios, it is useful to think about the distribution of effect sizes as scaled with respect to the population size. For example, selection coefficients may be exponentially-distributed with a mean of \(2Ns\). To do this in fwdpy11:

# ALPHA = 2Ns
MEAN_ALPHA = -10
N = 1000
sregions = [fwdpy11.ExpS(0, 1, 1, MEAN_ALPHA, scaling=2 * N)]
sregions[0]
fwdpy11.ExpS(beg=0, end=1, weight=1, mean=-10, h=1.0, coupled=True, label=0, scaling=2000)

Region weighting#

When multiple “sregion” objects are used, the default behavior is to multiply the input weight by end-beg:

sregions = [
   fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2),
   fwdpy11.ConstantS(beg=1.0, end=3.0, weight=1.0, s=-0.1),
]
sregions
[fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, h=1.0, coupled=True, label=0, scaling=1.0),
 fwdpy11.ConstantS(beg=1.0, end=3.0, weight=2.0, s=-0.1, h=1.0, coupled=True, label=0, scaling=1.0)]

Here, the input weight is interpreted to mean the weight “per site” is constant. In this example, twice as many mutations will have positions in \([1, 3)\) as from \([0, 1)\). To change the default behavior, one can prevent the coupling between input weight and region length:

sregions = [
   fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, coupled=False),
   fwdpy11.ConstantS(beg=1.0, end=3.0, weight=1.0, s=-0.1, coupled=False),
]
sregions
[fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, h=1.0, coupled=False, label=0, scaling=1.0),
 fwdpy11.ConstantS(beg=1.0, end=3.0, weight=1.0, s=-0.1, h=1.0, coupled=False, label=0, scaling=1.0)]

The absolute values of the weight parameters themselves is irrelevant. The only thing that matters is the relative values from region to region. Simulations based on the above examples would give the same results if the weight were 42 or 73.06. Therefore, we can recreate our first example with code like the following:

sregions = [
   fwdpy11.ExpS(beg=0.0, end=1.0, weight=56.0, mean=-0.2, coupled=False),
   fwdpy11.ConstantS(beg=1.0, end=3.0, weight=112.0, s=-0.1, coupled=False),
]
sregions
[fwdpy11.ExpS(beg=0.0, end=1.0, weight=56.0, mean=-0.2, h=1.0, coupled=False, label=0, scaling=1.0),
 fwdpy11.ConstantS(beg=1.0, end=3.0, weight=112.0, s=-0.1, h=1.0, coupled=False, label=0, scaling=1.0)]

In the above example, twice as many mutations occur in the second region because the weights have relative values of 2:1.

Note

Different regions are allowed to overlap, allowing the simulation of concepts like “coding regions” where the DFE are a weighted mixture from multiple distributions, etc.

Setting the dominance of new mutations#

The dominance of a new mutation is set by the h parameter during initialization:

fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, h=1.0)
fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, h=0.0)
fwdpy11.ExpS(beg=0.0, end=1.0, weight=1.0, mean=-0.2, h=0.0, coupled=True, label=0, scaling=1.0)

Built-in distributions of effect sizes#

Labelling mutations from different regions#

It may be of use to know what region a mutation came from. To do so, give a nonzero value to the label argument:

fwdpy11.ConstantS(beg=0.0, end=1.0, weight=1.0, s=1e-3, label=1)
fwdpy11.ConstantS(beg=0.0, end=1.0, weight=1.0, s=0.001, h=1.0, coupled=True, label=1, scaling=1.0)

At the end of the simulation, mutations from this region will have the label value stored in the attribute fwdpy11.Mutation.label.

The value of label must fit into a 16-bit unsigned integer, e.g., numpy.uint16. Larger values, or negative values, will result in exceptions. The following example tries to use a value one larger than the maximum allowed:

import numpy as np

fwdpy11.ConstantS(
    beg=0.0, end=1.0, weight=1.0, s=1e-3, label=np.iinfo(np.uint16).max + 1
)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[9], line 3
      1 import numpy as np
----> 3 fwdpy11.ConstantS(
      4     beg=0.0, end=1.0, weight=1.0, s=1e-3, label=np.iinfo(np.uint16).max + 1
      5 )

File <attrs generated init fwdpy11.regions.ConstantS>:12, in __init__(self, beg, end, weight, s, h, coupled, label, scaling)
     10 _inst_dict['label'] = label
     11 _inst_dict['scaling'] = scaling
---> 12 self.__attrs_post_init__()

File ~/work/fwdpy11/fwdpy11/fwdpy11/regions.py:136, in ConstantS.__attrs_post_init__(self)
    135 def __attrs_post_init__(self):
--> 136     super(ConstantS, self).__init__(
    137         self.beg,
    138         self.end,
    139         self.weight,
    140         self.s,
    141         self.h,
    142         self.coupled,
    143         self.label,
    144         self.scaling,
    145     )

TypeError: __init__(): incompatible constructor arguments. The following argument types are supported:
    1. fwdpy11._fwdpy11._ll_ConstantS(beg: float, end: float, weight: float, s: float, h: float, coupled: bool, label: int, scaling: float)
    2. fwdpy11._fwdpy11._ll_ConstantS(beg: float, end: float, weight: float, s: float, h: fwdpy11._fwdpy11.MutationDominance, coupled: bool, label: int, scaling: float)

Invoked with: 0.0, 1.0, 1.0, 0.001, 1.0, True, 65536, 1.0