Returns the rate at which the abundances would change if the model were
projected forward from its current initial state, relative to those
abundances. At a steady state this is zero, so it answers the question that
every calibration workflow otherwise has to remember to ask: is this model
still at its steady state?
Arguments
- params
A MizerParams object.
- effort
The fishing effort at which to evaluate the residual. By default the initial effort stored in
params, which is the effort the model's steady state belongs to.- dt
The step length used for the resource and other components, whose dynamics functions are only available as one-step maps. Smaller is more accurate. Not used for the consumers, whose rate is exact.
Value
An ArraySpeciesBySize object (species x size) of per-capita rates
of change in 1/year, NA where the density is zero. It carries two
further attributes:
resourceThe per-capita rate of change of the resource, a numeric vector over
w_full,NAwhere the resource density is zero.otherA named list with one entry per other component, holding its per-capita rate of change, or
NAfor a component whose state is not numeric.
Details
The value is a per-capita rate of change, in units of 1/year:
$$R_i(w) = \frac{1}{N_i(w)}\frac{dN_i(w)}{dt}.$$
A value of 1e-8 means nothing is moving. A value of 0.05 means that size
class would change by about 5% over the first year of a projection, and
-0.05 that it would shrink by about that much. The sign is therefore the
direction the model would drift.
For the consumers this is exact, not a finite-difference approximation: the
backward-Euler transport coefficients used by project() satisfy
\(A N - S = -dt\,dN/dt\) identically, so evaluating them at dt = 1 gives
the instantaneous rate with no time-discretisation error. The resource and
other components have arbitrary user-supplied dynamics functions, so their
rates are obtained by taking one short step of length dt, accurate to
\(O(dt)\).
Everything is evaluated at the model's own stored state — initialN(),
initialNResource(), initialNOther() — using the model's own reproduction
function and its own resource_dynamics. Nothing is substituted or held
fixed. The number therefore answers exactly "if I called project() now,
would anything move?", which is why it works for every model rather than only
for the semichemostat resource that steadyNewton() requires.
Reading the result
The returned array is an ArraySpeciesBySize object, so it prints, summarises and plots itself:
res <- getSteadyResidual(params)
summary(res) # per-species minimum, mean and maximum
plot(res) # which species, and at which sizesThe plot is the diagnostic one: a model that is off steady state is usually off in one species, or one part of the size range, and the plot says which.
Size classes with no fish in them carry no information about steadiness — the
relative rate of change of a zero density is undefined — so they are returned
as NA. Use na.rm = TRUE in any summary, as the examples above do.
Do not reduce this to its maximum
max(abs(res)) is a tempting single-number verdict and a misleading one. The
per-capita rate of a single size class is dominated by the fastest-relaxing
cells, and near the egg size those turn over in hours: a model settled for
every practical purpose can carry a cell rate of \(10^4\)/year there while
nothing observable moves. Under the second-order scheme (see
second_order_w()) this is severe enough to reverse the ordering between a
converged model and one that has just been knocked off its steady state.
What mizer's own checks — the summary() line, and
project(check_steady = TRUE) — judge instead is the relative rate of change
of each species' biomass, which weights each size class by the mass it
holds, and is the drift the user would actually see in plotBiomass(). Use
this array to find out where a model is unsteady, and those checks to find
out whether it is.
See also
isSteady(), steady(), steadyNewton(), getStability()
Other summary functions:
getBiomass(),
getDiet(),
getGrowthCurves(),
getN(),
getSSB(),
getTrophicLevel(),
getTrophicLevelBySpecies(),
getYield(),
getYieldGear(),
sizeIntegral()
Examples
summary(getSteadyResidual(NS_params))
#> Steady-state residual [1/year]
#> 12 species x 100 sizes
#>
#> Species Min Mean Max
#> Sprat -0.32010387 0.0014932474 0.01010982
#> Sandeel -0.54637042 -0.0057605610 0.01148404
#> N.pout -0.77251244 0.0065228584 0.03400740
#> Herring -0.17077775 0.0019195590 0.02414716
#> Dab -0.78476050 -0.0005368299 0.01726561
#> Whiting -0.68433745 0.0024537842 0.02278352
#> Sole -0.55536026 -0.0009899767 0.01048570
#> Gurnard -0.04784682 0.0091143308 0.01765771
#> Plaice -0.25831571 0.0065772666 0.02037855
#> Haddock -0.84155214 0.0038039116 0.02842493
#> Cod -1.23640575 -0.0047555940 0.01977439
#> Saithe -0.84979728 -0.0040508052 0.01096811
# \donttest{
# Matching biomasses moves the model off its steady state, and the plot
# shows which species and which sizes have moved.
params <- NS_params
species_params(params)$biomass_observed <-
c(0.8, 61, 12, 35, 1.6, 20, 10, 7.6, 135, 60, 30, 78)
species_params(params)$biomass_cutoff <- 10
params <- calibrateBiomass(params)
params <- matchBiomasses(params)
#> `matchBiomasses()` has rescaled the model and so moved it off its steady state. Run `steady()` to settle it again. You can check with `getSteadyResidual()`.
plot(getSteadyResidual(params))
# }
