Co-Evolution is an extension of a Mutation-Selection codon model, which considers pairs of codons.
The model adds an amino-acid correlation matrix \(\delta\), which expresses how “linked” two amino acid states are within a pair.
For example, \(\delta_{A, K} > 1\) means that \(AK\) is a favoured pair, while \(\delta_{W, Y} < 1\) means \(WY\) is disfavoured. \(\delta = 1\) signifies lack of association.
The selection coefficient is calculated for a pair of sites, assuming additive fitness.
For a substitution from codon pair \(ij\) to codon pair \(kj\) (a change at the first site; changes at the second site are symmetric):
\[ s_{ij, kj} = (\psi_k + \psi_j)\delta_{kj} - (\psi_i + \psi_j)\delta_{ij} \\ Q_{ij, kj} = 2N_e \, \mu_{ik} \, \frac{1-e ^ {-s_{ij, kj}}}{1-e^{-2 N_e s_{ij, kj}}} \]
Only single-nucleotide codon changes at a single site of the pair have non-zero instantaneous rate; simultaneous changes at both sites have rate zero (as of version 1.2.0), so compensated pairs arise as two rapid sequential substitutions.
We construct a pair landscape in which only two amino-acid configurations have high fitness: lysine–glutamate and glutamate–lysine, the classic charge-swap of an electrostatic interaction. The single-mismatch configurations (K,K and E,E) are deleterious but passable, and every other configuration is strongly deleterious. With this landscape the pair toggles between K,E and E,K through short-lived intermediates: a substitution at one site followed almost immediately by the compensating substitution at the other.
# Load phylogeny
mammals <- Phylogeny("../inst/extdata/mammals.newick")
# Flat site fitness: all structure comes from the pair coupling
f <- rep(1, 20)
Ne <- 1000
# Amino acid order is:
# A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y
K <- 9; E <- 4
# Population-scaled selection coefficients (2*Ne*s) of each pair
# configuration, relative to the compatible charge-swap pairs:
S <- matrix(-20, 20, 20) # everything else: 2Ns = -20
S[K, K] <- -8 # mismatch K,K: 2Ns = -8
S[E, E] <- -8 # mismatch E,E: 2Ns = -8
S[K, E] <- 0 # compatible: 2Ns = 0
S[E, K] <- 0
# The scaled fitness of pair (i, j) is 2*Ne*(f[i] + f[j])*delta[i, j];
# with flat f this is 4*Ne*delta, so a coefficient of S corresponds to:
delta <- 1 + S / (4 * Ne)The intermediate depth sets how tightly the two substitutions couple: the rate of escaping an intermediate grows roughly like \(e^{|2Ns|}\), while non-synonymous scaling (below) pins the overall rate of excursions. A deeper intermediate therefore gives the same number of toggles, each resolved much faster.
Non-synonymous scaling makes one unit of branch length one expected amino-acid-changing substitution per codon, so the compensatory events are delivered at a rate the tree’s branch lengths control directly.
use_genetic_code("Standard nuclear")
hky <- HasegawaKishinoYano(equilibrium = rep(0.25, 4))
co <- CoEvolution(
population_size = Ne,
mutation_rate = 1e-6,
nucleotide_model = hky,
fitness_1 = f,
fitness_2 = f,
delta = delta,
scaling_type = "non-synonymous")At equilibrium the two compatible configurations hold essentially all the mass:
eq <- as.numeric(co$equilibrium)
sum(sort(eq, decreasing = TRUE)[1:8]) # the 8 codon pairs spelling K,E / E,K## [1] 0.9996637
s <- sample_sequence(model = co, length = 30)
sim <- simulate_over_phylogeny(
phylogeny = mammals,
model = co,
sequence = s)The interaction keeps the pair under strong purifying selection, so almost every event is synonymous churn within the K and E codon families; the compensatory dynamics live in the small non-synonymous remainder:
## [1] 71618
## [1] 409
Substitutions into the two intermediates (excursion starts) are balanced by non-synonymous substitutions back into the compatible configurations (excursion ends), and no other amino-acid pair is ever reached:
##
## E,K K,K E,E K,E
## 115 105 101 88
Each excursion starts with a substitution into K,K or E,E and resolves with the next non-synonymous substitution at the same pair – either completing the toggle (landing on the opposite compatible configuration) or reverting. A small helper classifies each excursion and records how long the intermediate lasted:
count_toggles <- function(substitutions, compatible = c("K,E", "E,K")) {
toggles <- 0; reversions <- 0; waits <- c()
for (s0 in unique(substitutions$site)) {
for (nd in unique(substitutions$node)) {
g <- substitutions[substitutions$site == s0 & substitutions$node == nd, ]
if (nrow(g) < 2) next
g <- g[order(g$time), ]
for (i in seq_len(nrow(g) - 1)) {
entered <- !(g$pair_amino_acid_to[i] %in% compatible)
resolved <- g$pair_amino_acid_to[i + 1] %in% compatible
if (entered && resolved) {
waits <- c(waits, g$time[i + 1] - g$time[i])
if (g$pair_amino_acid_from[i] != g$pair_amino_acid_to[i + 1]) {
toggles <- toggles + 1
} else {
reversions <- reversions + 1
}
}
}
}
}
list(toggles = toggles, reversions = reversions, waits = waits)
}
excursions <- count_toggles(ns)
excursions$toggles## [1] 104
## [1] 99
## [1] 0.0002205953
Completed toggles and reversions occur in the expected 1:1 ratio, and the median wait in the intermediate is well under one percent of a typical branch length – the compensating substitution follows essentially immediately.
Plotting every event would bury the signal in synonymous churn, so plot only the non-synonymous events:
Each event draws one dot for the member of the pair that changed: a dot above the branch is a substitution at the first site, a dot below is a substitution at the second (green synonymous, red non-synonymous – here every plotted event is non-synonymous). The compensated toggles are the tight vertical pairs of red dots, one above and one below the branch at almost the same position: one site substitutes and the other follows within a fraction of a percent of the branch length.
The entry–exit structure is visible directly in the filtered substitution table:
| site | node | time | from | to | pair_from | pair_to | pair_amino_acid_from | pair_amino_acid_to | first_synonymous | second_synonymous | first_color | second_color |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 51 | 0.0260742 | 2496 | 2480 | AAG,GAG | AAG,AAG | K,E | K,K | TRUE | FALSE | #16A35E | #E84B2A |
| 0 | 51 | 0.0261711 | 2480 | 3456 | AAG,AAG | GAG,AAG | K,K | E,K | FALSE | TRUE | #E84B2A | #16A35E |
| 0 | 63 | 0.1372808 | 2496 | 2480 | AAG,GAG | AAG,AAG | K,E | K,K | TRUE | FALSE | #16A35E | #E84B2A |
| 0 | 63 | 0.1386381 | 2419 | 3395 | AAA,AAG | GAA,AAG | K,K | E,K | FALSE | TRUE | #E84B2A | #16A35E |
| 0 | 64 | 0.0496161 | 2434 | 2418 | AAA,GAA | AAA,AAA | K,E | K,K | TRUE | FALSE | #16A35E | #E84B2A |
| 0 | 64 | 0.0497110 | 2418 | 3394 | AAA,AAA | GAA,AAA | K,K | E,K | FALSE | TRUE | #E84B2A | #16A35E |