
Guide: Changing model parameters
Source:vignettes/guide-change-parameters.Rmd
guide-change-parameters.RmdMizer offers several ways to change a model, and it is easy to be unsure which one to reach for. The guiding principle:
Change the model at the highest level that expresses your intent, and let mizer propagate the change downwards. Drop to a lower level only when you deliberately want to override mizer’s calculation.
The levels of a mizer model
A mizer model is built in layers, and almost every change is a choice of which layer to reach into:
| Level | What it is | Change it with |
|---|---|---|
| 1. Size-independent parameters | the high-level inputs: per-species scalars (w_inf,
beta, gamma, h,
erepro, …), fishing gears, resource scalars, and
interactions |
species_params(params) <-,
gear_params(params) <-,
resource_params(params) <-,
interaction_matrix()
|
| 2. Size-dependent rates | arrays over size that mizer calculates from those parameters (search volume, metabolic rate, predation kernel, resource capacity, selectivity, …) | the set…() functions |
| 3. Rate functions | the functions mizer calls to compute the rates during a simulation |
setRateFunction(),
setComponent()
|
Levels 1 and 2 cover almost all everyday work. Most level-1 parameters exist to calculate the level-2 arrays, so changing a parameter normally updates the corresponding array automatically — that is what “propagating downwards” means. Species parameters, gear parameters and resource parameters all sit together at the top level: each is a set of scalar inputs from which mizer builds the arrays below.
Level 3 is for going beyond mizer’s built-in behaviour and is covered at the end.
Level 1: species parameters
A size-structured model potentially has a huge number of parameters, because rates must be specified at every size. Mizer assumes sensible functional forms for the size dependence, so you only supply a small number of scalars. Mizer also sets or calculates defaults for those you do not supply — see Calculation of Default Parameter Values — and keeps track of which values you gave and which it calculated.
| Accessor | Returns |
|---|---|
given_species_params(params) |
only the parameters you supplied explicitly |
calculated_species_params(params) |
the parameters mizer derived or defaulted |
species_params(params) |
everything (given, with calculated filling the gaps) |
Rule: change species parameters with
species_params(params) <-. It detects what you
changed, records it as given (so defaults can no longer
overwrite it), and triggers recalculation of the derived scalars
and the size-dependent rate arrays that depend on
it.
species_params(params)$beta <- 150 # recorded as given; also rebuilds the predation kernelgiven_species_params(params) <- has a different job:
it declares exactly which values count as explicit input. Every
non-NA entry assigned there is recorded as given, even if
its number is already present in species_params(). This is
how to protect a value that mizer calculated:
given_species_params(params)$q <- species_params(params)$qSetting an entry to NA, or removing its column, hands it
back to mizer’s calculation. Merely protecting the current value changes
its provenance, not the current model, so mizer does not rebuild the
rate arrays. A changed value rebuilds only the quantities that can
depend on that parameter; observation, direct-runtime and unrelated
custom columns do not trigger a full setParams() call.
Unknown columns on an extension object remain conservative and do
trigger recalculation, because an extension setter may use them.
given_species_params<-() also warns whenever
a change you asked for cannot take effect: the parameter is overridden
by another one you have already given, it feeds a rate array you set by
hand, or it is a gear parameter that mizer reads from
gear_params(). species_params<-() stays
quiet about all three.
Turning the commentary up or down. Mizer reports the
choices it makes — defaults it filled in, inputs it adjusted,
instructions it could not carry out — at a level set by
info_level. Most set…() and
new…() functions take it as an argument; for the ones that
do not, including species_params(params) <- and the rate
setters, set the option instead:
options(mizer_info_level = 1) # only what matters: warnings and adjustments
options(mizer_info_level = 0) # complete silence
params <- setExtMort(params, info_level = 0) # just this one callThe default is 3 — every default mizer filled in. Level 1 keeps the reports that tell you something went differently from how you asked, such as the “has not taken effect” warnings above. Level 0 is silence, not “warnings only”: it drops those too, so reach for 1 rather than 0 while you are still finding out what a model does.
When you edit a whole table rather than a single column, read it back from the same accessor you assign to:
gsp <- given_species_params(params)
gsp$beta <- 150
given_species_params(params) <- gspHanding the full species_params() table
to given_species_params(params) <- deliberately records
every non-NA value in it as given, freezing parameters you
never touched — on NS_params it turns
312 given entries into 396. It does not recalculate the current model
because all those values already agree with it, but future changes will
no longer recalculate them.
Version note. Older guidance said to avoid
species_params(params) <-because it bypassed thegiven_species_paramsprotection and skipped recalculation. That is now fixed.
Columns come back as named vectors:
species_params(params)$w_mat # named by species
given_species_params(params)$gamma # NA where you never set itFor a fuller description of the individual parameters see the help
page of species_params().
How a species-parameter change propagates
Many species parameters exist only to set up a rate array; changing one re-runs the relevant setter automatically:
| Species parameter(s) | Sets up | via |
|---|---|---|
gamma, q
|
search volume | setSearchVolume() |
h, n
|
maximum intake rate | setMaxIntakeRate() |
k, ks, p
|
metabolic rate | setMetabolicRate() |
z0, z_ext, d
|
external mortality | setExtMort() |
beta, sigma,
pred_kernel_type
|
predation kernel | setPredKernel() |
w_mat, w_mat25, w_repro_max,
m
|
reproduction allocation | setReproduction() |
pred_kernel_type chooses the shape of the
kernel, and each shape reads its own parameter columns. Changing it
therefore also changes which columns matter:
pred_kernel_type |
Parameter columns |
|---|---|
"lognormal" (default) |
beta, sigma
|
"truncated_lognormal" |
beta, sigma (cut off at
beta * exp(3 * sigma)) |
"box" |
ppmr_min, ppmr_max
|
"power_law" |
kernel_exp, kernel_l_l,
kernel_u_l, kernel_l_r,
kernel_u_r
|
"gaussian_mixture" |
kernel_p, kernel_mean,
kernel_sd (multimodal preferences) |
Any function <name>_pred_kernel(ppmr, ...) you
define yourself can be named in pred_kernel_type too; its
arguments become the required columns.
Other species parameters are used directly and build
no array (changing them just changes the model): alpha
(assimilation), w_min (egg size), erepro and
R_max (reproduction), interaction_resource,
and the length–weight parameters a, b.
Gotchas from the way defaults are calculated
Mizer fills in what you did not supply, and the derivations are
chained: age_mat →
h → gamma and ks. Changing one
parameter therefore rarely changes just that one thing.
The three sections below cover the traps this creates; for the mathematical derivations themselves see Calculation of Default Parameter Values.
Whether a value is given determines whether it is recalculated
For derived parameters, given_species_params()
distinguishes fixed input from a cached calculated value.
Giving a value switches off the recalculation from another
parameter. given_species_params<-() warns when
you set a parameter that would usually be used to recalculate another
parameter but this recalculation is suppressed because that other
parameter has been given explicitly.
| If you have given… | this is ignored | |
|---|---|---|
gamma |
f0 |
|
ks |
fc |
|
h |
age_mat,
k_vb
|
|
age_mat |
k_vb |
To let mizer re-calculate a parameter you previously supplied, clear
it by setting it to NA in
given_species_params(params).
Finally, w_inf is not a purely dynamic parameter.
Changing it re-derives w_mat, h,
gamma and ks, but the size grid is
fixed at construction, so enlarging w_inf beyond
the maximum grid size gives repeated warnings that “the maximum weight
of a species is larger than the maximum weight of the model”. To expand
the size range, use adjustSizeGrid(params, new_max_w = ...)
or rebuild the model.
Chained derivations and the cancellation trap
When parameters are not given, mizer calculates them along a dependency chain:
Inputs: [age_mat / k_vb, w_mat, w_min, alpha, f0, fc]
│
▼
[ h ] ─────────┬─────────┐
│ │ │
│ │ (with f0, kappa, lambda, kernel)
│ │ │ (with fc, alpha, w_mat)
▼ ▼ ▼
[ intake_max ] [ gamma ] [ ks ]
When gamma (the search-volume coefficient) is not
supplied, mizer derives it from the target feeding level
f0 — roughly
gamma ∝ h · f0 / (1 - f0) — so that the feeding level comes
out at f0.
A consequence often missed: raising h does
not lower the feeding level, because the derived
gamma compensates and the feeding level stays pinned at
f0. To make growth more or less resource-dependent (a
stronger or weaker density dependence, the “phantom-jam” feedback),
change f0, not h: low
f0 makes juvenile growth strongly resource-limited,
f0 near 1 makes it nearly saturated and
resource-insensitive.
ks does the same thing one level down. When it is not
supplied it is derived as
ks = fc · alpha · h · w_mat^(n - p), so raising
h does not bring a species closer to
starvation: the metabolic coefficient scales with it and the critical
feeding level stays pinned at fc.
Intent-first guide:
| Desired Goal | Change This | Do NOT Change | Why / What Happens |
|---|---|---|---|
| the feeding level (resource sensitivity of growth) | f0 |
h |
Raising h auto-scales gamma, keeping
feeding level at f0. |
| the critical feeding level (starvation risk) | fc |
ks, h
|
Raising h auto-scales ks, keeping critical
level at fc. |
| growth speed across all sizes, holding feeding level fixed | h |
f0 |
h scales maximum intake while feeding levels stay
fixed. |
| bespoke search volume / metabolism |
gamma, ks
|
f0, fc
|
Giving gamma or ks disconnects the
automatic derivation. |
Corollary: h = Inf (a deliberately “no-satiation” model)
makes the derived gamma and ks non-finite and
throws an error asking you to supply gamma and
ks explicitly.
The reverse ripple effect from age at maturity. The
chain also runs the other way. When age_mat is given (or
k_vb, from which mizer estimates it), h is
derived from it:
h = (w_mat^(1-n) - w_min^(1-n)) / (age_mat * (1-n) * alpha * (f0 - fc))
So f0 and fc are not local edits to feeding
levels — when age_mat is given they move h,
and h carries the change on into gamma and
ks. The same goes for w_mat, which is never
just the maturity size: it feeds h through the formula
above, ks through the w_mat^(n - p) factor,
and w_mat25. And beta/sigma feed
the gamma derivation because that integrates the predation
kernel.
If a user is puzzled that “changing w_mat changed my
growth curve everywhere”, this is why. To pin a parameter against the
chain, give it explicitly — a given value is never recalculated.
f0 and fc are calibration targets, not
model outputs
gamma is calibrated against an idealised
reference world, not against your model: a pure power-law
resource kappa * w^-lambda, with all fish abundances set to
zero, and — under defaults_edition() < 2
— with interaction_resource forced to 1. So f0
is the feeding level a species would have in that world.
- The realised feeding level in an assembled model
differs, often a lot. In
NS_paramsthe feeding levels atw_matrun from 0.58 to 0.91 against a storedf0of 0.6. Read the realised value withgetFeedingLevel(params), and the realised critical value withgetCriticalFeedingLevel(params); do not readf0andfcout ofspecies_params()and call them the model’s feeding levels. - Setting
interaction_resourceto anything other than 1 leavesgammauntouched under edition 1, so the reduction falls straight through to the realised feeding level and can starve a species outright. Underdefaults_edition() >= 2,interaction_resourceis included in the calibration. - The resource scalars that enter the calibration do
trigger a re-derivation. Since mizer 3.3, changing
lambdarecalculates everyqandgammathat mizer calculated, and changingkapparecalculates every calculatedgamma; the conditionq = n + lambda - 2that makes the feeding level size-independent is maintained for you. A value you gave is protected, as everywhere else — so on a model that suppliesgammain its species parameters (NS_paramsdoes) nothing moves, and the realised feeding level shifts with the new resource. To let mizer re-derive a given value, clear it first:
resource_params(params)$lambda <- 2.2 # calculated q and gamma follow
given_species_params(params)$gamma <- NA # hand a *given* gamma back to mizerConversely, to hold calculated values against a resource change, record them as given before you make it:
given_species_params(params)$q <- species_params(params)$q
resource_params(params)$lambda <- 2.2 # q now stays putLevel 1: fishing gear parameters
Gears, selectivity, and catchability live in
gear_params(params), one row per gear–species pair (row
names "species, gear"). Assigning to it recomputes the
fishing arrays.
gp <- gear_params(params)
gp["Cod, Otter", "catchability"] <- 0.8
gear_params(params) <- gp # rebuilds fishing mortalityUse setFishing() for
supplying selectivity/catchability arrays directly or setting baseline
effort. See the guide to setting up
fishing.
Level 1: resource parameters
The resource works just like the species parameters.
resource_params(params) returns a named list of scalars
(kappa, lambda, r_pp,
n, w_pp_cutoff). These parameters set up the
resource size-spectrum arrays, and — like
species_params<- — assigning to it rebuilds
those arrays by calling setResource():
resource_params(params)$kappa <- 1e11 # rebuilds the carrying capacity (cc_pp)
resource_params(params)$lambda <- 2.05 # rebuilds cc_pp (slope)
resource_params(params)$r_pp <- 10 # rebuilds the replenishment rate (rr_pp)| Scalar(s) | Rebuilds |
|---|---|
kappa, lambda,
w_pp_cutoff
|
resource carrying capacity (cc_pp) |
r_pp, n
|
resource replenishment rate (rr_pp) |
kappa, lambda
|
also the calculated gamma (and, for
lambda, q) and hence the search volume |
That last row is easy to miss: the resource power law is the
reference spectrum against which the search volume is calibrated, so a
resource change reaches the species too. See “f0
and fc are calibration targets” above.
The size-resolved resource arrays themselves can also be set directly; that is a level-2 change and is covered under the resource arrays below.
Level 1: the interaction matrix
The species × species interaction matrix is read with
interaction_matrix() and set with
interaction_matrix<- or setInteraction().
The resource interaction is the interaction_resource
species-parameter column instead.
inter <- interaction_matrix(params)
inter["Cod", "Herring"] <- 0.5
interaction_matrix(params) <- inter # or params <- setInteraction(params, inter)Level 2: setting a rate array directly — and the freeze trap
Everything above changes level-1 parameters and lets mizer rebuild the level-2 arrays. This section is about reaching into level 2, when you need a size-dependence mizer does not produce by default.
Each rate array has a direct setter/getter — metab(params)
to read it and metab(params) <- to write it, search_vol(params)/search_vol(params) <-,
and so on. This is the way to impose an array of your own. It must have
the same dimensions as the one it replaces (species × size, species in
the model’s order), so the safest recipe is to read the current array,
modify it, and write it back:
my_metab <- metab(params) # species × size, right dims and names
my_metab["Cod", ] <- 0.5 * w(params)^0.8 # bespoke size dependence for one species
metab(params) <- my_metabNote that the direct setter modifies params in
place — no reassignment.
Trap: supplying an array freezes it. Mizer marks it “set manually” and will no longer update it from species parameters. After this, changing the feeding species parameter has no effect on that rate:
search_vol(params) <- my_array # frozen
given_species_params(params)$gamma <- 2 * gamma # search volume UNCHANGED now
#> Warning: Your change to the species parameter `gamma` has not taken effect
#> because the search volume has been set manually ...Since mizer 3.3 that second line warns you that the change did not
reach the model. The warning names the parameters that were ignored and
the set…(params, reset = TRUE) call that hands the rate
back to mizer. The species parameter table still records the new value,
so the table and the model disagree until you do one or the other.
To hand control back to mizer, call the rate’s set…()
function with reset = TRUE. These
functions recompute the array from the current species parameters — but
a bare set…(params) will not touch a
frozen array; it leaves the manual value in place and warns:
params <- setSearchVolume(params, reset = TRUE) # drop the override, recompute
params <- setMetabolicRate(params) # recompute, unless frozen
params <- setParams(params) # rebuild ALL rate arrays at once
params <- setParams(params, reset = TRUE) # ...and thaw every frozen oneNote that setParams() does not touch the resource: the
resource rate, capacity, level and dynamics belong to
setResource(), and passing one of its arguments to
setParams() is an error.
Unlike the direct setters, the set…() functions return a
new object, so they must be reassigned. They also
accept the array itself, as the argument named like the direct
setter:
params <- setMetabolicRate(params, metab = my_metab)is equivalent to
metab(params) <- my_metaband offers no advantage over it.
If a user reports “I changed
gamma/h/beta but nothing
happened,” suspect a manually-set (frozen) rate array — recompute it
with set…(params, reset = TRUE). The reset
argument works the same way for every rate setter, including
setFishing() and setResource().
The full set of size-dependent rate arrays, each with its direct
setter/getter and the set…() function that recomputes
it:
| Rate array | Direct setter / getter | Recompute with |
|---|---|---|
| search volume | search_vol(params) <- |
setSearchVolume() |
| maximum intake rate | intake_max(params) <- |
setMaxIntakeRate() |
| metabolic rate | metab(params) <- |
setMetabolicRate() |
| external mortality | ext_mort(params) <- |
setExtMort() |
| external encounter rate | ext_encounter(params) <- |
setExtEncounter() |
| external diffusion | ext_diffusion(params) <- |
setExtDiffusion() |
| predation kernel | pred_kernel(params) <- |
setPredKernel() |
| maturity ogive | maturity(params) <- |
setReproduction() |
| reproduction allocation | repro_prop(params) <- |
setReproduction() |
The fishing arrays (selectivity, catchability) behave the same way; see the fishing section above and the guide to setting up fishing.
The resource arrays
The resource arrays work just like the species rate arrays: you can
write them directly instead of letting
resource_params(params) <- calculate them from the
scalars, and doing so freezes them (“set manually”), so
a later scalar change no longer touches them.
| Function | Sets (and freezes) |
|---|---|
resource_capacity(params) <- |
the carrying capacity over size |
resource_rate(params) <- |
the replenishment (regeneration) rate over size |
resource_level(params) <- |
the resource level (fraction of capacity) |
setResource(params, …) |
any of the above, plus lambda, n,
w_pp_cutoff, resource_dynamics
|
resource_capacity(params) <- my_capacity # array over size; now frozen
resource_params(params)$kappa <- 1e11 # ignored while cc_pp is frozen
params <- setResource(params, reset = TRUE) # unfreeze: recompute from the scalarsBalancing the resource
The resource has a steady state: the abundance at which replenishment
exactly matches the rate at which consumers eat it.
Balancing means adjusting the replenishment rate and
the carrying capacity together so that the resource abundance the model
currently holds is that steady state. Once a model has been
calibrated to steady state this matters a lot: an unbalanced change to
the resource leaves the resource off its steady state, and the whole
model then drifts the next time you project() it.
Because the balance condition ties rate and capacity together,
supply only one of resource_rate,
resource_capacity and resource_level
— mizer derives the other. Supplying two of them while balancing is an
error. The remaining freedom in the pair does not change the steady
state at all; it controls only the resource dynamics around
it:
- high rate, low capacity (resource level near 1): the resource regenerates fast and is barely depleted by consumption, so it behaves almost like a fixed background spectrum and competition for it has little effect;
- low rate, high capacity (small resource level): the resource is strongly depleted by consumption and the model is much more sensitive to changes in that competition.
resource_level(params) — the ratio of current abundance
to capacity — is therefore the natural single knob. Under the default
semichemostat dynamics the balance condition, in terms of that level
ℓ(w), the current abundance N_R(w) and the consumption mortality μ_R(w)
from getResourceMort(),
is just
r_R(w) = μ_R(w) · ℓ(w) / (1 − ℓ(w)) c_R(w) = N_R(w) / ℓ(w)
Balancing is a feature of setResource() alone. The
array, level and dynamics setters call it and so balance by
default; resource_params(params) <- does
not balance, so changing kappa or
r_pp that way rebuilds the arrays but shifts the steady
state. To change a resource coefficient and stay balanced, go through
setResource():
params <- setResource(params, resource_capacity = new_kappa) # rebalances the rate
params <- setResource(params, resource_rate = new_r_pp) # rebalances the capacity
resource_level(params) <- 0.5 # balances by default
resource_capacity(params, balance = FALSE) <- my_capacity # no rebalancingTwo fine points:
-
Frozen arrays are protected from incidental
balancing. If a balancing operation is not given a replacement
rate or capacity (e.g. you only changed
resource_dynamics), a manually-set array is left alone and a warning is issued instead of being silently overwritten; usereset = TRUEto recompute it deliberately. But when you do supply one side explicitly, mizer derives the other even if that other one was frozen — passbalance = FALSEto keep it. - Balancing can fail, with messages such as “I can’t balance the resource if the capacity is less than the current abundance”. That is a statement about the model, not a bug: no positive rate can hold the resource at an abundance above its capacity.
Balancing needs a balance_<resource_dynamics>()
function; mizer supplies one for the semichemostat and logistic
dynamics. For the equations of each, and for the full
argument-by-argument behaviour, see the help pages of
setResource(), resource_semichemostat()
and resource_logistic().
Level 3: replacing a rate function
Levels 1 and 2 change the numbers mizer uses; level 3
changes the code. At every time step of a
project() run, mizer calls a set of functions to compute
the instantaneous rates (encounter, feeding level, mortality,
reproduction, …). setRateFunction() swaps any one of these
for a function of your own, without touching the species parameters or
the rate arrays:
params <- setRateFunction(params, "Mort", "myMort") # use myMort() for total mortality
getRateFunction(params) # list the current rate functionsReach for this when the change cannot be expressed as a number at
all. The clearest case is time dependence: species
parameters and rate arrays are fixed for the whole simulation, but a
rate function receives the current time t and can
therefore change as the run proceeds — seasonal forcing, a warming
trend, a management measure that switches on in a given year.
seasonalMort <- function(params, t, ...) {
mizerMort(params, t = t, ...) * (1 + 0.3 * sin(2 * pi * t)) # t in years
}
params <- setRateFunction(params, "Mort", "seasonalMort")Two things to know before writing one: extra parameters your function
needs go in other_params(params),
and the function must be defined in the global environment or in
a package, because mizer stores its name rather than the
function itself. For which rates can be replaced, the signature and
return shape each one requires, and the rule that a rate must never jump
as a function of abundance, see the guide to extending mizer — as for
setComponent(), which adds a whole new ecosystem
component.
Which should I use?
| I want to change… | Use |
|---|---|
a per-species value (beta, w_mat,
h, erepro, …) |
species_params(params) <- … |
| protect a value mizer has calculated | copy it from species_params(params)
into given_species_params(params)
|
| let mizer calculate a value again | set it to NA in given_species_params(params)
|
| fishing gears / selectivity / catchability | gear_params(params) <- … |
| baseline effort or selectivity/catchability arrays | setFishing(params, …) |
the resource (kappa, lambda,
r_pp, …) |
resource_params(params) <- … |
| species interactions | setInteraction(params, …) |
| a size-dependent rate, keeping it tied to the parameters | change the underlying species parameter |
| a size-dependent rate to a bespoke array (freezing it) | the direct setter metab(params) <- …,
or the matching set…() with the array argument |
| a frozen rate back to its default form | set…(params, reset = TRUE) |
| everything after several edits |
setParams(params)
(not the resource — see setResource()) |
| how a rate is computed (e.g. to make it time-dependent) | setRateFunction(params, …) |
| the model’s set of dynamical components |
setComponent()
(see the guide to extending
mizer) |
Quick reference
# ── Species parameters (preferred: triggers recalculation) ────────────────────
species_params(params)$beta <- 150
species_params(params) # view all (given + calculated)
calculated_species_params(params) # only what mizer derived
given_species_params(params)$q <- species_params(params)$q # protect current q
given_species_params(params)$q <- NA # let mizer calculate q again
# ── Size-dependent rate arrays ────────────────────────────────────────────────
params <- setMetabolicRate(params) # recompute from species params
metab(params) <- my_array # direct setter; freezes the array
params <- setSearchVolume(params, search_vol = my_array) # same, via set...()
params <- setSearchVolume(params, reset = TRUE) # unfreeze: recompute again
params <- setParams(params) # rebuild ALL rate arrays (not resource)
# ── Fishing ───────────────────────────────────────────────────────────────────
gear_params(params) <- gp
params <- setFishing(params, initial_effort = c(Otter = 1))
# ── Resource (scalars rebuild the arrays, like species parameters) ────────────
resource_params(params)$kappa <- 1e11 # rescales the carrying capacity
resource_capacity(params) <- my_capacity # bespoke array; freezes it
params <- setResource(params, reset = TRUE) # unfreeze: recompute from scalars
# ── Interaction ───────────────────────────────────────────────────────────────
params <- setInteraction(params, inter)
# ── Rate functions (level 3: replace how a rate is computed) ──────────────────
seasonalMort <- function(params, t, ...) {
mizerMort(params, t = t, ...) * (1 + 0.3 * sin(2 * pi * t)) # t in years
}
params <- setRateFunction(params, "Mort", "seasonalMort") # now time-dependent
other_params(params)$my_param <- 42 # params for your function