Using and adapting the mizer extension template
Source:vignettes/mizerExtensionTemplate.Rmd
mizerExtensionTemplate.RmdWhat this package is
mizerExtensionTemplate is a working mizer extension
package that you can clone and adapt. Every line of code is commented to
explain what is being done and why. Read the source
files alongside this vignette.
For the full conceptual background see:
-
vignette("guide-extend-mizer", package = "mizer")— all five extension mechanisms with worked examples. -
vignette("guide-create-extension-package", package = "mizer")— turning a script into a composable, shareable package.
The extension at a glance
mizerExtensionTemplate adds three things to a standard
mizer model:
| Mechanism | What it adds | Where |
|---|---|---|
setExtEncounter() |
Fixed allometric extra food | constructor.R |
setExtMort() |
Fixed background mortality | constructor.R |
projectEncounter S3 method |
Seasonal encounter multiplier | rate-methods.R |
setComponent("plankton") |
Dynamical plankton spectrum |
constructor.R + component-functions.R
|
getBiomass S3 methods |
Includes plankton in output | generic-methods.R |
Classed arrays with a type
|
planktonLevel() plots as a proportion |
component-functions.R |
signal_info() |
Reports a choice, obeying info_level
|
constructor.R |
| Bundled data object |
example_params ready to use |
data/, R/data.R, .onLoad
|
Bundled example model
The package ships a ready-made example_params object — a
three-species (Sprat, Herring, Cod) model built with
newExtensionTemplateParams(). It is stored in
data/example_params.rda and lazy-loaded by R, but the
.onLoad hook replaces the plain binding with an active
binding so that every access returns an object with the correct S4
extension class:
class(example_params) # mizerExtensionTemplate, not plain MizerParams
#> [1] "mizerExtensionTemplate"
#> attr(,"package")
#> [1] "mizerExtensionTemplate"
getBiomass(example_params) # Plankton entry is present
#> Sprat Herring Cod Plankton
#> 1.630305e+08 9.125316e+07 1.494402e+08 2.673020e+12You can use it directly without calling
newExtensionTemplateParams():
sim <- project(example_params, t_max = 5)
plotBiomass(sim)Quick start (build your own)
params <- newExtensionTemplateParams(NS_species_params)
sim <- project(params, t_max = 10)
plotBiomass(sim) # Plankton column appears automatically#> Warning in plotDataFrame(plot_dat, params, xlab = "Year", ylab = y_label, :
#> missing legend in params@linecolour, some groups won't be displayed
#> Warning: Removed 11 rows containing missing values or values outside the scale range
#> (`geom_line()`).

How the seasonal encounter works
The seasonal multiplier peaks at t = 0.25 (quarter-year)
and troughs at t = 0.75. With the default amplitude of 0.2
the encounter rate varies by ±20 % around its annual mean.
params_s <- newExtensionTemplateParams(NS_species_params, season_amplitude = 0.4)
enc_t0 <- getEncounter(params_s, t = 0) # multiplier = 1.0
enc_t025 <- getEncounter(params_s, t = 0.25) # multiplier = 1.4
range(enc_t025 / enc_t0, na.rm = TRUE)
#> [1] 1.4 1.4This seasonal effect is implemented in
projectEncounter.mizerExtensionTemplate()
(rate-methods.R). It calls NextMethod() to get
the base encounter rate (including the plankton contribution), then
multiplies by the seasonal factor. Using a project* method
rather than setRateFunction() means another extension
package can also modify the encounter rate without conflict.
How the plankton component works
The plankton is a vector spectrum stored on the full resource size grid. At each time step:
-
planktonEncounter()adds the plankton encounter contribution togetEncounter()by presenting the plankton as an extra resource. -
planktonDynamics()updates the plankton by solving the ODEdP/dt = r(K − P) − m·Panalytically over the time step, wheremis the predation mortality on the plankton spectrum.
plotDiet(params, species = "Cod")
Returning arrays that plot themselves properly
When your extension returns a size- or time-resolved quantity, wrap
it in one of mizer’s array classes and say what kind of quantity the
values are, with the type argument. There are three types:
"value" for a rate or an amount, "density" for
an amount per gram of body weight, and "proportion" for a
fraction.
type is not decoration — it decides how the array is
plotted. A "density" gets multiplied by the
dw/dl Jacobian when it is plotted against a length axis,
because a density per gram is not a density per centimetre. A
"proportion" gets a linear y axis showing the whole of the
interval from 0 to 1, so the value can be read against the scale it
belongs to.
planktonLevel() (component-functions.R) is
the template’s example. It returns the plankton abundance as a fraction
of its carrying capacity, mirroring mizer’s own
resource_level():
lev <- planktonLevel(params)
attr(lev, "type")
#> [1] "proportion"Because it declares itself a proportion, plotting it needs no further instruction — the y axis runs from 0 to 1 rather than being fitted to the data:
plot(planktonLevel(params))
#> Warning: Removed 47 rows containing missing values or values outside the scale range
#> (`geom_line()`).
Declare type for every array you return, including when
it is the default "value" — as
getBiomass.mizerExtensionTemplateSim() does. If you omit
it, mizer falls back to guessing from value_name and
units, which is there for backwards compatibility and is
easy to fall foul of.
Telling the user what your extension decided
When your extension makes a choice on the user’s behalf, report it
through mizer’s own mechanism rather than with message() or
warning(). A plain message() ignores
info_level, is not collected with the other reports, and is
swallowed on the species_params<-() path.
The constructor takes an info_level argument, forwards
it to newMultispeciesParams(), wraps its body in
with_info_level() and raises one report of its own with
signal_info(). The result is that the template’s report
arrives in the same block as mizer’s, and obeys the same switch. This
template defaults to info_level = 0 to keep its examples
quiet, so ask for the reports:
params_loud <- newExtensionTemplateParams(NS_species_params, info_level = 3)
#> No h provided for some species, so using age at maturity to calculate it.
#> Because you have n != p, the default value for `h` is not very good.
#> Because the age at maturity is not known, I need to fall back to using
#> von Bertalanffy parameters, where available, and this is not reliable.
#> Using z0 = z0pre * w_inf ^ z0exp for calculated z0 values.
#> Using f0, h, lambda, kappa and the predation kernel to calculate gamma.
#> Setting the plankton capacity to half the resource capacity.The last line is the template’s own. level decides how
much it takes to silence a report: ours is level 3, chatter that only
the default shows, so info_level = 1 keeps mizer’s
important reports and drops ours.
params_terse <- newExtensionTemplateParams(NS_species_params, info_level = 1)
#> Because you have n != p, the default value for `h` is not very good.In your own constructor, default the argument to
default_info_level() instead, so it follows the
mizer_info_level option as mizer’s own constructors do.
Take the argument explicitly either way: hard-coding
info_level in the call to
newMultispeciesParams() makes a user who passes their own
collide with it.
Adapting this template for your extension
Rename the package: search and replace
mizerExtensionTemplate→ your package name throughout all files, includingDESCRIPTION, the R files,NAMESPACE, and this vignette.-
Decide: metadata-only or dispatching?
-
Metadata-only (like
mizerStarvation): deletemizerExtensionTemplate-class.Rand remove thecoerceToExtensionClass()call at the end of the constructor. Keepparams@extensions <- getRegisteredExtensions(). -
Dispatching (like
mizerShelf): keep everything and define S3 methods for the generics you need to override.
-
Metadata-only (like
Remove mechanisms you don’t need: each of the five mechanisms in
constructor.Ris independent. Delete the blocks that do not apply to your extension.Replace the plankton component with your own component, or remove
setComponent()entirely if you don’t need a dynamical state variable.Run
devtools::document()to regenerateNAMESPACEfrom the roxygen2 tags, thendevtools::test()anddevtools::check()before publishing.