name: understand-size-spectrum-dynamics description: >- Understand how mizer models behave: which quantities you impose and which the model produces for itself, the food and predation feedback loops that couple species, what sets the slope of the steady-state spectrum and the timescale of its dynamics, and the two distinct kinds of density dependence. Use whenever reasoning about why a species collapses, explodes, or oscillates, why growth or mortality is not what you asked for, why changing one species moved another, why a model is insensitive to fishing, why feeding levels drift when the resource is eaten down, or what the Sheldon spectrum, feeding level, reproduction level (R_max), resource level and trophic cascades mean in a mizer model.
Mizer models fish communities structured by individual body size rather than age or fixed trophic levels. To diagnose model behaviour you have to reason from size-structured principles, not from age-based or biomass-pool intuition.
The single most useful habit is knowing which side of the line a quantity sits on: what you set, and what the model works out for itself. This is the distinction that explains most surprises.
What you impose is never an outcome. It is:
beta, sigma), the interaction matrix $\theta_{ij}$. Together these
give a rate of predation events per predator–prey size pair: how fast a
predator of size $w$ would eat prey of size $w_p$ if it met them.w_mat, w_repro_max).kappa, lambda, w_pp_cutoff,
r_pp), external mortality z0, fishing effort and selectivity, erepro
and R_max.What the model produces is everything you would actually want to observe:
the feeding level $f_i(w)$, the growth rate $g_i(w)$ (including whether a fish
reaches w_mat at all), the predation mortality $\mu_p(w)$, the abundance
spectra $N_i(w)$ and their slope, the realised recruitment $R_i$ and reproduction
level $r_i$, the resource abundance $N_R(w)$ — and the steady state itself.
Note where the resource sits: you impose the ceiling it grows towards and the speed at which it grows there, but the standing resource abundance is emergent, because the fish eat it. See "The resource is a state variable too" below.
The most important consequence, and the one most often missed: growth and predation mortality are not two mechanisms. They are the same predation events, counted twice.
When a predator of size $w$ eats a prey of size $w_p$, one event has two bookkeeping consequences: the predator gains mass $w_p$, and the prey dies. So mizer takes the one pairwise predation rate above and integrates it two different ways:
| Emergent quantity | Sum the same predation rate over… | Weighted by | So it responds to | |---|---|---|---| | Encounter → feeding level → growth of species $i$ | all prey small enough for $i$ to eat | prey mass $w_p$ (mass gained) | the abundance of $i$'s prey — bottom-up | | Predation mortality on species $i$ | all predators big enough to eat $i$ | nothing (deaths counted, not mass) | the abundance of $i$'s predators — top-down |
Same kernel $\phi$, same search volume $\gamma$, same interaction matrix $\theta$; the same handling limitation also appears on both sides through the factor $1-f$. As a predator becomes satiated, its successful prey-removal rate per unit available prey falls, while its realised intake approaches the maximum intake rate. The other differences are which variable is integrated out — prey size or predator size — and whether you weight by the prey's mass or just count the death.
Changes to the encounter pathway therefore usually affect both predator growth and prey mortality, so those are not independent dials. But growth also depends on assimilation, metabolism and reproductive allocation: changing one of those can alter growth without changing prey removal. A joint growth-and-mortality problem points first to the shared encounter parameters; an isolated growth problem need not.
Further consequences worth internalising:
matchGrowth() adjusts physiological
coefficients until the emergent growth matches observation; it does not
write a growth curve into the model. Same for mortality. See the
calibrate-model skill.tuneSteadyState() exists.plotSpectra(params, power = 2) shows biomass per log size bin, which
is the view in which the Sheldon spectrum is flat.lambda sets the resource spectrum the fish feed
against. See "The community slope" below.w_min up to w_max. A species spectrum that looks like a
power law over its whole range is a species that is not experiencing the
maturity-driven growth slowdown — usually a sign something is wrong.All the dynamics come from one piece of accounting. Pick a size bracket $[w, w + dw]$ and ask what changes the number of fish in it. Individuals do not jump around the size axis: they enter only by growing in through the bottom edge, and leave only by growing out through the top edge or by dying. So
rate of change = what grows in − what grows out − what dies
That is the whole content of the McKendrick–von Foerster equation. The quantity doing the growing-in and growing-out is the flux $J_i(w)$: the rate (individuals per year) at which fish of species $i$ grow past size $w$. It is just density times speed, $g_i(w) N_i(w)$ — how many fish are sitting at that size, times how fast they are moving through it. The dying term is $\mu_i(w) N_i(w)$.
Three things follow, and they cover most of what you need:
w_repro_max piling fish up before mortality removes them.With growth diffusion switched on (off by default) individuals of the same size grow at slightly different speeds, which adds a spreading term to the flux; the bookkeeping is unchanged.
Close the abundance dependence on the two integrals above — let the prey and predator abundances themselves respond — and each becomes a feedback loop. Nearly every dynamical surprise in mizer comes from one of the two. Both run through body size, which is what makes them different from food-web models.
The food loop (bottom-up, stabilising for the consumer). More consumers →
prey depleted → encountered food $E_i(w)$ falls → feeding level $f_i(w)$ falls →
growth slows → fish spend longer at small sizes where mortality is high →
fewer consumers. Diagnose with plotFeedingLevel() and resource_level().
The predation loop (top-down, and the source of cascades). More predators →
predation mortality $\mu_p$ on their prey sizes rises → prey abundance falls →
which relieves predation on whatever the prey were eating. Diagnose with
plotPredMort() and plotDiet().
The loops act on different sizes of the same species at the same time. A fish is prey early and predator late, so a change can travel up the spectrum through one loop and back down through the other. That round trip is what produces trophic cascades and, when the delay around it matches a generation time, oscillations.
Two knobs decide how tightly the loops are coupled:
beta (preferred predator/prey mass ratio, default 30): a predator
eats prey about 30× lighter — roughly 1.5 orders of magnitude down the
spectrum. This sets how far apart in size two species have to be before one
eats the other.sigma (kernel width in natural log, default 2): wide. At $\pm 1$
s.d. the kernel spans mass ratios from about 4 to 200, and it is cut off at
ratio 1 (nothing eats prey larger than itself). A narrow kernel couples
narrow size bands tightly and is a classic cause of limit cycles; a wide one
averages over many prey sizes and damps oscillations.The food loop bottoms out in the resource spectrum, and the resource is not a
fixed food supply: by default it has dynamics of its own and gets eaten down.
What you impose is a capacity $c_R(w) = \kappa w^{-\lambda}$ (up to
w_pp_cutoff, zero above) and a replenishment rate
$r_R(w) = r_{pp} w^{n-1}$; the standing abundance $N_R(w)$ that fish actually
feed on is emergent. The default dynamics
(resource_semichemostat()) let
each size replenish towards its capacity and be consumed at the rate
$\mu_R(w)$ that the fish impose (getResourceMort()).
Note what is absent: there is no flux along the size axis. Resource sizes do not grow into one another, so a hole eaten in the resource at one size stays at that size and can spread only through fish that feed across it.
The dimensionless diagnostic is the resource level
$L(w) = N_R(w)/c_R(w)$ (resource_level()) —
how close the resource is running to its ceiling. At the semichemostat steady
state $N_R = r_R c_R/(r_R + \mu_R)$, so $L = r_R/(r_R + \mu_R)$ and
$$\frac{d \ln N_R}{d \ln \mu_R} = -(1 - L)$$
Read exactly like $1 - r_i$ for recruitment: $1 - L$ is the fraction of a change in consumption pressure that actually shows up in food availability.
Because $L$ is a ratio at a given abundance, it is the dial to turn after
calibration: setResource() with resource_level = 0.5 sets the capacity from
the current abundance, leaving the steady state exactly where it was and
changing only how stiff the food supply is around it. resource_dynamics =
"resource_constant" is the limit $L \to 1$ taken exactly, and is the clean way
to switch the food loop off deliberately when isolating a feedback (see the
run-simulation skill).
newMultispeciesParams() sets $c_R = \kappa w^{-\lambda}$, which is also the
initial abundance, so $L = 1$ everywhere and the resource is not at a fixed
point: replenishment is zero while consumption is not. Project such a model
and the resource is drawn down, feeding levels fall, and growth slows — with
nothing having been changed. This is normal, and it is why calibration comes
before interpretation.tuneSteadyState() rebalances the resource at the end. It holds the resource fixed
while converging the fish, then recomputes the capacity from the (preserved)
rate so that the converged state is a steady state of the resource too. The
abundance you calibrated against is kept; the capacity moves above it. So
after tuneSteadyState() the resource level is an emergent, size-dependent quantity —
lowest where the fish feed hardest, near 1 where consumption is negligible.$r_R \propto w^{n-1}$, the same $w^{-1/3}$ gradient as everything else, but
spread over far more orders of magnitude: with the default r_pp of 10, the
smallest resource sizes replenish tens of thousands of times a year, while at
the w_pp_cutoff of 10 g the rate is under 5 per year — comparable to fish
rates, and precisely at the sizes fish eat most. So the resource is a fast,
quasi-static variable at the bottom and a genuinely dynamical one at the top —
which is why getStability() always perturbs it alongside the fish rather than
slaving it to them; see the analyse-stability skill. Raising r_pp makes the resource stiffer and the model less sensitive to
competition for food; lowering it makes the resource a slow variable that can
carry oscillations of its own.
Energy flows through an individual in three stages.
1 — Encounter and feeding level. A predator of size $w$ searches volume $\gamma_i w^q$ and encounters prey according to the predation kernel and the interaction matrix. Encountered food is turned into a dimensionless feeding level $f_i(w) \in [0,1]$ by a Holling type II response — $f = 0$ is starvation, $f = 1$ is complete satiation. Mizer targets $f_0 \approx 0.6$ by default.
Feeding level is the first thing to look at in almost every diagnosis, because it is dimensionless and has an absolute scale: you can tell at a glance whether a number is wrong. $f < 0.2$ anywhere in the juvenile range means the species is starving; $f > 0.9$ everywhere means the species is satiated and effectively decoupled from variation in its prey — its growth will not respond to food very much, and its predation pressure per unit prey is reduced by the $1-f$ factor (see below). Its total biomass intake can nevertheless remain near its maximum.
2 — Net available energy. Assimilated intake ($\alpha$, default 0.6) minus standard and activity metabolism, floored at zero. If it hits zero the fish has no surplus for growth or reproduction and simply stops.
3 — Allocation. Around maturity, an increasing fraction $\psi_i(w)$ goes to
reproduction and the rest to growth; w_mat is the 50% point of the maturity
ogive, not a hard threshold. Under defaults edition 1, $\psi$ is forced to 1
above w_repro_max, so growth halts there. Under edition 2 it continues to
follow the maturity ogive times the reproductive proportion and need not reach
1. Increasing allocation still slows growth and helps turn the species spectrum
into a dome: decelerating fish pile up (see the traffic-jam effect below) and
mortality then removes them.
Two different slopes get called "the slope", and they are not the same object. The species slope is the one that follows from the bookkeeping. The community slope, $-\lambda$, is a property of the sum over all species and the resource; it is discussed after.
This is the bookkeeping above, made quantitative. For one species $i$, setting "in − out − deaths" to zero and solving gives the local log-log slope of that species' spectrum, ignoring diffusion:
$$\frac{d \ln N_i}{d \ln w} = -\frac{\mu_i(w)}{g_i(w)/w} - \frac{d \ln g_i}{d \ln w}$$
Read it as: the slope is minus the ratio of mortality to mass-specific
growth, minus how fast growth itself is accelerating. With allometric rates
$g \propto w^n$ the second term is just $n$ (the n species parameter;
newMultispeciesParams() sets 2/3 by default).
This one ratio explains most species-spectrum shapes. High mortality or stunted growth ⇒ steep drop, few fish survive to large size. Fast growth relative to mortality ⇒ shallow spectrum. Note that nothing here mentions $\lambda$: a species' slope is set by its own mortality and growth, whatever the community around it is doing.
$\lambda$ is not derived by the model. It is a parameter you set (lambda,
default 2.05): the exponent of the resource spectrum the fish feed against. What
mizer does is not to predict $\lambda$ but to keep the model self-consistent
with it, in four steps:
q to $\lambda - 2 + n$. Against a
$w^{-\lambda}$ background, a predator searching at rate $\gamma w^q$
encounters food scaling as $w^{q + 2 - \lambda}$; that default is exactly the
$q$ making it $w^n$ — the same power as maximum intake $h w^n$. Numerator and
denominator of the feeding level then scale together, so $f$ comes out
independent of size. (Verify on any fresh model: getFeedingLevel() is flat
at f0 across the juvenile range.)w_max — together with the resource, and you
get back approximately $w^{-\lambda}$. The loop closes.Step 4 is exact only in the idealised scaling model, where many species are
spread evenly over a wide range of w_max. In a model with a handful of real
species it is approximate: the resource carries the small-size end, and the
community spectrum is bumpy where species domes overlap unevenly. Do not expect
a fitted community slope to come out at exactly lambda.
The practical value of this is diagnostic. The consistency is a chain, and
breaking any link shows up in the feeding level first. Change lambda without
letting q follow, set q by hand, or deplete the resource away from its
assumed power law, and $f$ acquires a trend in size; species spectra then curve
instead of running straight, and the community spectrum stops being a power law.
So when a spectrum looks curved, check plotFeedingLevel() for a size trend
before touching anything structural.
Size sets speed, and this is why age-based intuition misleads.
Mass-specific growth $g/w \propto w^{n-1} \approx w^{-1/3}$: a fish 1000× heavier grows 10× more slowly in relative terms. Mortality scales the same way ($\mu \propto w^{n-1}$; mizer's default external mortality is $z_0 = 0.6\,w_{\inf}^{\,n-1}$). So small fish live fast and die young, large fish live slow — and the whole spectrum runs on a gradient of timescales rather than a single one.
Time to grow from egg to w_max scales as $w_{\max}^{\,1-n} \approx
w_{\max}^{1/3}$. A species 1000× larger takes roughly 10× longer to mature.
Practical consequences:
project()
run to even be visible.tuneSteadyState() run that looks
converged for small species may be nowhere near it for large ones.This distinction decides how a model responds to fishing, and confusing the two is a common source of wrong conclusions.
1 — Imposed, via R_max. Egg production $R_{p}$ is converted to realised
recruitment $R$ by a Beverton–Holt relation, summarised by the reproduction
level $r_i = R_i/R_{\max,i} \in [0,1)$ (reproduction_level()) — the fraction
of its ceiling that a species' recruitment is currently running at.
The number that matters is how much of a change in egg production survives into recruitment. For Beverton–Holt that elasticity works out to exactly $$\frac{d \ln R_i}{d \ln R_{p,i}} = 1 - r_i$$ so the reproduction level reads directly as the fraction of the response that gets suppressed. This makes the direction easy to keep straight:
R_max defaults to $\infty$, which is
$r_i = 0$ and no imposed density dependence at all.Sanity check if you ever doubt the direction: $R_{\max} = \infty$ must mean no density dependence, and it gives $r_i = 0$. Low means weak.
2 — Emergent, via the two feedback loops. With $R_{\max} = \infty$ a species still is not unregulated: more juveniles deplete the shared resource (food loop) and attract predation (predation loop). This regulation is produced by the model, is size-structured, and spills over onto other species. In a default mizer model it is the only density dependence there is.
How much of it there is at the resource end is set by the resource level: at
$L \to 1$ the shared resource cannot be depleted, so that half of the emergent
regulation is missing, whatever R_max says.
Why it matters: both can stabilise a model, but they respond to fishing
completely differently. Imposed density dependence is species-local and unmoved
by ecosystem change; emergent density dependence weakens when you fish down the
predators supplying it. A model made stable by a low R_max is a different
ecosystem from one that is stable on its own. If a species seems inert to
fishing, check reproduction_level() first — a high value means you are looking
at an assumption, not a result.
Because trophic role changes with size, mizer shows feedbacks absent from classical food-web models:
run-simulation skill on numerical diffusion before concluding a wave is
physically absent.Check emergent properties before changing structural parameters.
| Symptom | Likely cause | What to inspect |
|---|---|---|
| Species collapses during project() | Starving larvae, intense juvenile predation, or too little egg production | plotFeedingLevel(), plotDiet(), getPredMort() |
| Biomass oscillates in regular cycles | Reproduction level near 0 (little recruitment damping — mizer's default), narrow kernel (sigma too small), or knife-edge fishing | reproduction_level(), getStability() (see the analyse-stability skill) |
| Growth slows before w_mat | Food limitation at intermediate sizes; resource depleted or w_pp_cutoff too low | plotGrowthCurves(), resource_level(), plotSpectra() |
| Feeding levels drift down during project() although nothing was changed | The resource was left at its capacity ($L = 1$, as freshly built) and is being eaten down towards its true fixed point | resource_level(), plot(initialNResource(params)); run tuneSteadyState() first |
| Growth is not what matchGrowth() asked for | Growth is emergent — the food to support it is not there | plotFeedingLevel(), then the calibrate-model skill |
| Tuning one species drops another | Predation overlap or resource competition in shared juvenile size bins | interaction_matrix(), plotDiet() |
| Species insensitive to fishing | Strong imposed density dependence ($r_i$ high, near 1 — only $1-r_i$ of any change in egg production gets through), or an effectively infinite resource | reproduction_level(), resource_level() |
| Species spectrum curves instead of running straight | lambda, q and n no longer mutually consistent, so feeding level is size-dependent | plotFeedingLevel() — is it flat in size? |
| Community slope isn't lambda | Expected only in the idealised scaling model; with few species the domes don't sum to a clean power law | getCommunitySlope(), plotSpectra() — compare against species domes, not against lambda |
| Species won't stay at steady state | Fixed point is dynamically unstable, not a numerical failure | getSteadyResidual(), findSteadyState(solver = "newton") |
Work outwards from the individual to the ecosystem; most problems resolve at step 2 or 3.
plotFeedingLevel(params). It is dimensionless with
an absolute scale, so it is the fastest read. Flat near 0.6 is healthy; near 0
is starvation; near 1 is decoupling; sloping in size means the
lambda/q/n consistency is broken. If it is low, separate "the food is
not there" from "the fish is not catching it": resource_level(params) well
below 1 over the sizes in question means the resource is being eaten down,
and a level of exactly 1 everywhere means the model was never calibrated.plotGrowthCurves(params). Does the fish reach w_mat in a
plausible time? Growth failure downstream of a feeding-level problem is not a
separate bug.plotPredMort(params) and getMort(params). Split
predation, fishing and external contributions before blaming any one of them.reproduction_level(params). Anything below ~0.2
supplies little recruitment damping and can make oscillations more likely;
anything above ~0.85 makes the species inert to fishing by assumption.interaction_matrix(), beta,
sigma, resource settings.Before concluding that dynamics are absent, check whether the default
first-order scheme is damping them (second_order_w; see the run-simulation
skill). Before concluding a state is unstable, check it is actually a fixed
point (getSteadyResidual()) — an unbalanced resource, still sitting at its
capacity, is the commonest reason a state is not one.
The equations behind the reasoning above, for when you need the exact form.
Dynamics — the McKendrick–von Foerster equation: the "in − out − deaths" balance above, written as a continuity equation in size.
$$\frac{\partial N_i(w,t)}{\partial t} + \frac{\partial J_i(w,t)}{\partial w} = -\mu_i(w,t) N_i(w,t)$$
The derivative $\partial J_i/\partial w$ is the net loss by growth: flux out at
the top of the bracket minus flux in at the bottom. The right-hand side is the
loss by death. Growth flux $J_i = g_i N_i$, boundary condition
$J_i(w_{\min}, t) = R_i(t)$ at the egg size, $g_i$ in
$\text{g}\cdot\text{year}^{-1}$ and $\mu_i$ in $\text{year}^{-1}$. With growth
diffusion switched on (D_ext > 0, off by default) the flux gains a term
$-\tfrac{1}{2}\,\partial(D_i N_i)/\partial w$, which smooths sharp peaks and
spreads cohorts.
The two integrals of the same predation rate. Encountered food — integrate over prey size $w_p$, weighting by the mass $w_p$ gained:
$$E_i(w) = \gamma_i(w) \int \Big(\theta_{ip} N_R(w_p) + \sum_j \theta_{ij} N_j(w_p)\Big)\, \phi_i(w/w_p)\, w_p\, dw_p$$
Predation mortality — integrate the same thing over predator size $w'$, counting deaths rather than mass:
$$\mu_{p,i}(w) = \sum_j \int \theta_{ji}\,\big(1 - f_j(w')\big)\,\gamma_j (w')^q\,\phi_j(w'/w)\,N_j(w')\,dw'$$
The shared factors $\gamma$, $\phi$ and $\theta$ are what makes these two sides of one coin. The $(1 - f_j)$ factor is worth noting: satiated predators kill less, and a species whose predators are all near $f = 1$ experiences less mortality than their abundance alone suggests. It applies to the growth side too — realised intake is $f_i h_i w^n = (1 - f_i) E_i$. As $f_i$ approaches 1, that intake approaches the maximum $h_i w^n$; it does not approach zero.
Feeding level from encountered food $E_i$ and maximum intake $h_i w^n$:
$$f_i(w) = \frac{E_i(w)}{E_i(w) + h_i w^n}$$
Net energy after assimilation ($\alpha$) and metabolism ($k_s w^p$, plus an activity term $k w$ that defaults to zero):
$$E_{\text{net},i}(w) = \max\big(0,\; \alpha_i f_i(w) h_i w^n - k_{s,i} w^p - k_i w\big)$$
Growth, after the reproductive allocation $\psi_i(w)$:
$$g_i(w) = \big(1 - \psi_i(w)\big) E_{\text{net},i}(w)$$
Predation kernel (lognormal, the default), for mass ratio $\text{ppmr} = w/w_p \ge 1$ and zero below:
$$\phi_i(\text{ppmr}) = \exp!\left[\frac{-\big(\ln(\text{ppmr}/\beta_i)\big)^2}{2\sigma_i^2}\right]$$
Resource dynamics — the default semichemostat, applied to each size independently (no transport along the size axis):
$$\frac{\partial N_R(w,t)}{\partial t} = r_R(w)\big[c_R(w) - N_R(w,t)\big] - \mu_R(w,t) N_R(w,t)$$
with replenishment rate $r_R(w) = r_{pp} w^{n-1}$, capacity
$c_R(w) = \kappa w^{-\lambda}$ below w_pp_cutoff and zero above, and $\mu_R$
the consumption by the fish. The steady state is
$N_R = r_R c_R/(r_R + \mu_R)$, i.e. resource level
$L = N_R/c_R = r_R/(r_R + \mu_R)$. resource_logistic()
replaces the bracket by $N_R(1 - N_R/c_R)$ and
resource_constant() holds $N_R$ fixed.
Egg production and Beverton–Holt density dependence:
$$R_{p,i} = \frac{\epsilon_i}{2 w_{\min}} \int \psi_i(w)\, E_{\text{net},i}(w)\, N_i(w)\, dw, \qquad R_i = \frac{R_{\max,i} R_{p,i}}{R_{\max,i} + R_{p,i}}$$
Defaults worth remembering: beta 30, sigma 2, lambda 2.05,
w_pp_cutoff 10 g, kappa 1e11, r_pp 10,
resource_dynamics "resource_semichemostat",
alpha 0.6, f0 0.6, n 2/3, p 0.7,
q $= \lambda - 2 + n$, erepro 1, R_max $\infty$, w_min 0.001 g,
w_mat $= w_{\inf}/4$, z0 $= 0.6\,w_{\inf}^{\,n-1}$.
For the complete mathematical formulation see the model description article.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.