Adaptation to different optima with multivariate distribution of effect sizes#

This example uses a multivariate Gaussian distribution of effect sizes to simulate correlated mutational effects on a single trait in two demes. See Different effect sizes of mutations in different demes for background.

Two demes with N diploids each evolve for 10N generations around an optimum trait value of zero. Then, the optimum changes to 1 in deme 0 and -1 in deme 1.

The covariance matrix of effect sizes defines a strong positive correlation between effect sizes in each deme. Thus, after the optimum shift, adaptive mutations in deme 0 are deleterious in deme 1 and vice-versa.

The simulation runs for 200 generations past the optimum shift. At that point, the mean trait value and mean fitness for each deme are printed to the screen.

Note

If you want to model mutations having the same effect size in all demes, then setting up the sregions and gvalue as done in Gaussian stabilizing selection with an optimum shift will do the trick. You’ll just need to modify the demography in that example to include multiple demes.

import sys

import numpy as np

import fwdpy11

N = int(sys.argv[1])
rho = float(sys.argv[2])

yaml = f"""
time_units: generations
demes:
  - name: alpha
    epochs:
      - start_size: {N}
  - name: beta
    epochs:
      - start_size: {N}
migrations:
  - demes: [alpha, beta]
    rate: 1e-2
"""

demography = fwdpy11.ForwardDemesGraph.from_demes(
    yaml, burnin=10 * N + 200, burnin_is_exact=True
)

moving_optimum_deme_0 = fwdpy11.GaussianStabilizingSelection.single_trait(
    optima=[
        fwdpy11.Optimum(when=0, optimum=0.0, VS=1.0),
        fwdpy11.Optimum(when=10 * N, optimum=1.0, VS=1.0),
    ],
)
moving_optimum_deme_1 = fwdpy11.GaussianStabilizingSelection.single_trait(
    optima=[
        fwdpy11.Optimum(when=0, optimum=0.0, VS=1.0),
        fwdpy11.Optimum(when=10 * N, optimum=-1.0, VS=1.0),
    ],
)

# The covariance matrix for effect sizes.
# The marginal Gaussians will have mean zero and sd = 0.1
# The deviates will be highly positively correlated.
sd = 0.1
covariance_matrix = np.array([0.999] * 4).reshape((2, 2))
np.fill_diagonal(covariance_matrix, 1)
covariance_matrix *= np.power(sd, 2)

pdict = {
    "nregions": [],
    "sregions": [
        # Multivariate Gaussian distribution of effect sizes
        fwdpy11.mvDES(
            fwdpy11.MultivariateGaussianEffects(
                beg=0, end=1, weight=1, h=1, cov_matrix=covariance_matrix
            ),
            # Means of zero for each marginal Gaussian
            np.zeros(2),
        )
    ],
    "recregions": [fwdpy11.PoissonInterval(0, 1, rho / (4 * N))],
    "rates": (0, 1e-3, None),
    "demography": demography,
    "simlen": 10 * N + 200,  # 200 gens past optimum shift
    # Specify one gvalue object per deme
    "gvalue": [
        fwdpy11.Additive(
            ndemes=2,  # Number of demes
            scaling=2,  # 0, 0+sh, 0+2s for AA, Aa, and aa, respectively
            # Mapping of trait (genetic) value to fitness
            gvalue_to_fitness=moving_optimum_deme_0,
        ),
        fwdpy11.Additive(ndemes=2, scaling=2, gvalue_to_fitness=moving_optimum_deme_1),
    ],
    "prune_selected": False,
}

params = fwdpy11.ModelParams(**pdict)
pop = fwdpy11.DiploidPopulation([N, N], 1.0)
rng = fwdpy11.GSLrng(1010)
fwdpy11.evolvets(rng, pop, params, 100)
assert pop.generation == 10 * N + 200
md = np.array(pop.diploid_metadata, copy=False)

for deme in np.unique(md["deme"]):
    mean_genetic_value = md["g"][np.where(md["deme"] == deme)].mean()
    mean_fitness = md["w"][np.where(md["deme"] == deme)].mean()
    print(f"{deme} {mean_genetic_value} {mean_fitness}")