GM99: the evolution of seed size

Balancing seed number, survival, and competitive ability

What the model represents

Plants have a fixed amount of resource to invest in seeds. A plant can therefore produce many small seeds or fewer large ones. Large seeds are more likely to survive before competition and their seedlings compete more effectively, but each one is more expensive to make.

Seeds land at discrete safe sites, and the number arriving at any site is Poisson distributed. Seedlings at the same site then compete for its resources in a size-asymmetric lottery: larger seeds have an advantage. If that advantage is strong enough, the intermediate seed-size strategy becomes a branching point. Small, numerous seeds can then coexist with large, highly competitive seeds.

The model comes from Geritz, van der Meijden, and Metz (1999), Evolutionary Dynamics of Seed Size and Seedling Competitive Ability.

Fitness equations

Pre-competitive survival and competitive ability:

\[ s(x) = \max\!\left(0,\; 1 - 2e^{-\beta x}\right), \qquad c(x) = e^{\alpha x}. \]

A focal mutant x' lands in a site alongside \(k_j \sim \text{Poisson}(N_j)\) seedlings of each resident strategy x_j. Its expected share of the site, averaged across all possible seed arrivals, is

\[ g = \mathbb{E}\!\left[\frac{c(x')}{c(x') + \sum_j k_j\,c(x_j)}\right]. \]

Invasion fitness is the mutant’s expected number of established offspring (a lifetime reproductive ratio, = 1, i.e. log = 0, at equilibrium):

\[ W(x') = \frac{R}{x'}\, s(x')\, g . \]

A single resident reaches the density N* that solves \(\tfrac{R}{x}s(x)\,\tfrac{1-e^{-N}}{N} = 1\). Only the products \(\alpha R\) and \(\beta R\) affect the outcome. There is no formula for the singular strategy, so we find it numerically. Branching becomes more likely as the competitive asymmetry \(\alpha\) increases.

Example analysis with regnans

library(regnans)

Hold pre-competitive survival at \(\beta R = 15\). Weak competitive asymmetry (\(\alpha R = 4.5\)) gives a single stable strategy, whereas stronger asymmetry (\(\alpha R = 7\)) gives a branching point.

solve_x <- function(alpha, beta = 15, bounds_x = c(0.06, 0.95)) {
  community_start(bounds(x = bounds_x),
                  harness = harness_gm99(alpha = alpha, beta = beta)) |>
    community_solve_singularity()
}

css    <- solve_x(4.5)   # CSS
branch <- solve_x(7.0)   # branching point
c(css = as.numeric(css$traits), branch = as.numeric(branch$traits))
      css    branch 
0.1836378 0.6481794 

We classify each solution using the curvature of invasion fitness at the singular strategy. Negative curvature indicates a fitness maximum and a stable strategy; positive curvature indicates a fitness minimum and branching:

curv <- function(comm, at, d = 1e-4) {
  f <- comm$fitness_function
  (f(at + d) - 2 * f(at) + f(at - d)) / d^2
}
c(css = curv(css, as.numeric(css$traits)),
  branch = curv(branch, as.numeric(branch$traits)))
       css     branch 
-10.564513   1.439901 

As a check, a resident at its own demographic equilibrium must have a lifetime reproductive ratio of exactly 1, or a log fitness of 0:

comm <- community_start(bounds(x = c(0.06, 0.95)),
                        harness = harness_gm99(alpha = 7, beta = 15)) |>
  community_add(trait_matrix(0.645, "x")) |>
  community_demography()
comm$resident_fitness          # ≈ 0
[1] 0

Pairwise invasibility plot

The green regions show which mutant seed sizes can invade each resident seed size. The pattern around the singular strategy shows that mutants on either side can invade, as expected at a branching point. Seed size is a strictly positive trait, so the community uses the log trait scale and the axes follow it.

pip <- community_start(bounds(x = c(0.06, 0.95)), trait_scale = "log",
                       harness = harness_gm99(alpha = 7, beta = 15)) |>
  community_pip()
plot(pip) + ggplot2::ggtitle("GM99 (alpha R = 7, beta R = 15)")

A second branching as competition becomes more asymmetric

At \(\alpha R = 7\) the single seed-size strategy is a branching point. After branching, the community settles at a pair: a small-seeded strategy that makes many seeds, and a large-seeded one that wins the sites it reaches. That pair is the next singular point, and it too can be stable or a branching point.

Here we follow the pair as competitive asymmetry \(\alpha R\) grows. community_continue_singularity() steps through values of \(\alpha R\), predicting where the pair will be from its sensitivity, solving for it from there, and classifying it at each step (the method is described on the DD99 page). community_parameter_map() builds the model as a function of \(\alpha\) (with \(R = 1\)). There is no closed form for this pair, so what to look for is the curvature of invasion fitness at each resident.

Plotting helpers: plot_path(), add_path() and legend_path()
class_col <- c("CSS" = "#2a78d6", "branching point" = "#eb6834", "degenerate" = "grey45")
class_pch <- c("CSS" = 16, "branching point" = 17, "degenerate" = 1)

# One line per resident, each point marked by its classification, and a grey
# band where the classification changes between two parameter values.
add_path <- function(path) {
  ch <- path$changes
  if (nrow(ch) > 0)
    rect(ch$p_before, par("usr")[3], ch$p_after, par("usr")[4],
         col = "grey92", border = NA)
  cls <- path$classification
  for (j in seq_len(ncol(path$traits))) {
    lines(path$p, path$traits[, j], col = "grey40")
    points(path$p, path$traits[, j], pch = class_pch[cls], col = class_col[cls])
  }
}
plot_path <- function(path, xlab, ylab, ylim = range(path$traits), ...) {
  plot(range(path$p), ylim, type = "n", xlab = xlab, ylab = ylab, ...)
  add_path(path)
}
# A legend for the classifications on a plot, and optionally one reference line.
legend_path <- function(where, cls, line = NULL, lty = 2) {
  n <- length(line)
  legend(where, legend = c(cls, line), bty = "n",
         pch = c(class_pch[cls], rep(NA, n)), col = c(class_col[cls], rep("grey40", n)),
         lty = c(rep(0, length(cls)), rep(lty, n)))
}

Solve for the pair from two starting residents, then follow it from \(\alpha R = 7\) to \(7.75\):

pair <- community_solve_singularity(branch, x0 = trait_matrix(c(0.2, 0.7), "x"))
pair$traits
             x
[1,] 0.2061229
[2,] 0.6998292
path_alpha <- community_continue_singularity(pair, community_parameter_map(pair, "alpha"),
                                             seq(7, 7.75, by = 0.125))
path_alpha
<singularity_path: 7 points, p from 7 to 7.75 (coalition of 2)>
  classification: CSS (p 7 to 7.5), branching point (p 7.625 to 7.75) 
  equilibrium solves: 75
path_alpha$changes
  p_before p_after from              to
1      7.5   7.625  CSS branching point

At \(\alpha R = 7\) the pair is a CSS: both residents sit at fitness maxima, so once the population has branched, evolution stops there. As \(\alpha R\) grows, both seed sizes increase, and between \(\alpha R = 7.5\) and \(7.625\) the pair becomes a branching point. The curvature of invasion fitness at each resident shows why. The large-seeded resident stays at a fitness maximum, but the small-seeded resident’s curvature changes sign, so it becomes a fitness minimum and branches again:

curvature <- t(vapply(path_alpha$classifications,
                      function(cl) vapply(cl$hessian, as.numeric, 0), numeric(2)))

par(mfrow = c(1, 2))
plot_path(path_alpha, xlab = expression(alpha * R), ylab = "seed size of each resident",
          ylim = c(0.15, 0.8))
legend_path("right", unique(path_alpha$classification))

matplot(path_alpha$p, curvature, type = "n",
        xlab = expression(alpha * R), ylab = "curvature of invasion fitness")
ch <- path_alpha$changes
rect(ch$p_before, par("usr")[3], ch$p_after, par("usr")[4], col = "grey92", border = NA)
abline(h = 0, lty = 3)
matlines(path_alpha$p, curvature, lty = c(1, 2), col = "grey40")
legend("topleft", legend = c("small-seeded resident", "large-seeded resident"),
       lty = c(1, 2), col = "grey40", bty = "n")

The small-seeded resident’s curvature changes sign from -0.44 at \(\alpha R = 7.5\) to 0.34 at \(7.625\), while the large-seeded resident’s stays near -0.7. Once competition is asymmetric enough, the small-seeded strategy splits in two and the pair gives way to a community of three.