diff --git a/posts/2026-07-07-mizer-3-2-announcement/mizer-3-2-announcement.Rmd b/posts/2026-07-07-mizer-3-2-announcement/mizer-3-2-announcement.Rmd new file mode 100644 index 0000000..18737c5 --- /dev/null +++ b/posts/2026-07-07-mizer-3-2-announcement/mizer-3-2-announcement.Rmd @@ -0,0 +1,228 @@ +--- +title: "Announcing mizer 3.2" +description: | + A quick follow-up to mizer 3.1 that fixes two things we were not happy to + leave standing: the resource-setting functions behaved inconsistently, and a + flaw in the extension mechanism prevented two extension packages from being + chained together. Along the way 3.2 also brings a handful of new analysis + functions and cheatsheets. +author: + - name: Gustav Delius +date: 2026-07-07 +output: + distill::distill_article: + self_contained: false + toc: true +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) +``` + +We released [mizer 3.1](https://blog.mizer.sizespectrum.org/posts/2026-06-26-mizer-3-1-announcement/) +only a few weeks ago, so why 3.2 already? Three reasons, and all are the kind of +thing that is better fixed sooner than lived with. The first is that setting +species parameters had become confusing, with the default setter silently failing +to protect user changes from being overwritten. The second is that the functions +for setting the background resource had grown inconsistent with each other and +with the rest of mizer, in a way that could silently discard your changes. The +third is that the extension mechanism had a structural flaw that made it +impossible to combine two independently developed extension packages in one model. +None could wait for the next feature release. + +## Safer species parameter setting + +When you change a species parameter in mizer, you want two things to happen: the parameter should be updated, and any dependent parameters or rate arrays (like the maturity ogive or the maximum intake rate) should be recalculated automatically. You also want mizer to remember that you set this parameter explicitly, so it doesn't overwrite it with a calculated default later. + +Previously, using the standard `species_params<-()` setter would update the parameter but bypass the protection mechanism (the `given_species_params` slot) and fail to trigger a recalculation. Users were instead encouraged to use `given_species_params<-()`. However, most existing scripts already used `species_params<-()`, and a single use of `given_species_params<-()` could unintentionally overwrite everything `species_params<-()` had done. + +mizer 3.2 fixes this by making `species_params<-()` the smart, default setter it was always meant to be. It now automatically detects your changes by comparing them against the existing parameters, records them in `given_species_params` to protect them, and silently triggers the recalculation of any dependent parameters and rate arrays. This restores expected behaviour and makes `species_params<-()` the recommended setter for scripts. + +The `given_species_params<-()` setter remains as an explicit alternative that is particularly useful during interactive sessions, because it issues warnings if you change a parameter whose effect is overridden by another parameter that has already been given. + +## Consistent resource setting + +mizer's background resource is described by a few scalar parameters — a +carrying-capacity coefficient `kappa` and exponent `lambda`, a replenishment +rate `r_pp` and exponent `n` — from which mizer builds the size-dependent +capacity (`cc_pp`) and rate (`rr_pp`) arrays that the dynamics actually use. You +can set the model at either level: change a scalar and let mizer rebuild the +arrays, or supply an array directly and have it left alone ("frozen"). + +The problem was that the different ways of doing this did not agree. Setting +species parameters with `species_params<-` rebuilds the affected species rates +from the new values, cleanly and predictably. The resource setters did not +follow the same rule. Some of them tried to *balance* the resource — adjusting +the arrays so that the current community stays exactly at its steady state — +whether or not you asked for it. As a side effect, editing a rate-side scalar +such as `r_pp` could be silently overwritten by the balancing step, so your +change simply vanished. Successive edits could overwrite one another rather than +accumulate. + +mizer 3.2 makes the resource setters behave like the species setters: + +- Assigning to `resource_params()` rebuilds the size-dependent capacity and rate + arrays from the resource parameters, while leaving any array you have set + manually (frozen) untouched. It no longer balances. + +- Balancing the resource to preserve the steady state is now solely the job of + `setResource()`. The individual setters `resource_rate<-`, + `resource_capacity<-`, `resource_level<-` and `resource_dynamics<-` gained a + `balance` argument so you can switch it off explicitly, for example + + ```r + resource_capacity(params, balance = FALSE) <- my_capacity + ``` + +- `setResource()` no longer silently overwrites a frozen rate or capacity array + when it balances. If you have set an array by hand, that array wins and mizer + warns you rather than quietly replacing it. + +The upshot is that setting a resource parameter now does exactly what setting a +species parameter does: it takes effect. If you have code that relied on the old +balancing behaviour, the new +[*Upgrading mizer*](https://sizespectrum.org/mizer/articles/upgrading.html) +vignette explains what changed and how to adapt. + +## Extensions that chain in any order + +mizer's real power comes from its extension packages. Each one — mizerReef for +coral-reef communities, mizerMR for multiple background resources, and others — +registers itself with mizer and contributes its own versions of the rate +functions. When you call a generic such as `getEncounter()`, mizer walks a chain +of extensions, letting each one add its contribution via `NextMethod()`. + +In principle two extensions that touch different parts of the model should +compose: you should be able to run a reef model *and* give it several background +resources. In practice this did not work. Each extension defined its marker +class statically as a direct sibling of `MizerParams` +(`setClass("mizerReef", contains = "MizerParams")`, +`setClass("mizerMR", contains = "MizerParams")`). Two siblings cannot be +arranged into a single inheritance chain, so an object could carry one extension +or the other, but never both. Whichever you applied second could not sit in the +right place in the class hierarchy. + +mizer 3.2 removes the static class. An installed extension is now recognised +from the S3 methods it registers for its marker class (for example +`getEncounter.mizerMR`), rather than from a statically declared S4 class. This +lets mizer build the marker class dynamically when the package is loaded and +insert it into the S4 hierarchy at the correct position relative to whatever +other extensions are already active — in whichever order they were loaded. The +class chain becomes genuinely linear, `mizerMR` extending `mizerReef` extending +`MizerParams` (or the reverse, depending on load order), and both sets of +methods dispatch correctly. + +Here is what that now makes possible. Load both extensions — the one you load +last sits at the front of the chain: + +```r +library(mizerReef) +library(mizerMR) +``` + +Start from mizerReef's example Caribbean reef model, which has a single +background resource plus the reef's algae and detritus components: + +```r +params <- caribbean_3_model +class(params) +#> [1] "mizerReef" +``` + +Now hand the resource over to mizerMR, splitting the single background spectrum +into a small-plankton and a large-plankton pool: + +```r +wf <- w_full(params) +resource_params <- data.frame( + resource = c("small plankton", "large plankton"), + kappa = 1e11, + lambda = 2.05, + w_min = c(min(wf), 1), + w_max = c(1, max(params@w)), + r_pp = 4, + n = 2 / 3 +) +params <- setMultipleResources(params, resource_params = resource_params) +``` + +The returned object now belongs to a class that chains **both** extensions: + +```r +class(params) +#> [1] "mizerMR" +is(params, "mizerReef") +#> [1] TRUE +is(params, "mizerMR") +#> [1] TRUE +``` + +`class()` reports the most-derived class, but the object still inherits from +`mizerReef`, so reef generics and multiple-resource generics both dispatch. You +can give each species its own preference for each resource, retune with +mizerReef's `reefSteady()` — which is blissfully unaware that the background is +now several resources, because mizerMR handles that further down the chain — and +plot the combined model with a single `plotSpectra()` call: + +```r +params <- reefSteady(params) +plotSpectra(params, power = 2) +``` + +Neither package needs to know the other exists; they only need to be polite +about calling `NextMethod()`. The full worked example, including different +per-species resource preferences, is in mizerReef's +[*Combining mizerReef with mizerMR*](https://sizespectrum.org/mizerReef/articles/multiple-resources.html) +vignette, and the mechanics of the chain are described in +`vignette("using-extension-packages", package = "mizer")`. + +## Also in 3.2 + +While the two fixes above were the reason for the release, 3.2 also gathers up a +number of improvements: + +- **`steadyNewton()`** (experimental) finds a steady state by solving the + steady-state equation directly with a Newton-type root finder instead of + running the dynamics to convergence. Unlike `steady()` it converges even when + the steady state is dynamically unstable, and it discovers the support of the + steady state automatically. + +- **`adjustSizeGrid()`** adjusts the size grid of a model to a new minimum and/or + maximum size. It can both expand and truncate the grid, warning if it would + discard non-negligible abundance. + +- **`project()`** gains a `callback` argument, letting you run your own function + at each saved time step. + +- **Nicer output.** Printing a rate array (as returned by `getEncounter()`, + `getBiomass()`, `getFMort()`, `NResource()` and friends) now shows the actual + values, truncated to fit the console, instead of a per-species min/mean/max + summary. Columns accessed with `$` on a `species_params` or `gear_params` + object come back named by species, so you can tell the entries apart. + +- **Fewer surprises with gears.** Setting `sel_func` on a `gear_params` object + now automatically adds the argument columns that selectivity function needs as + `NA` columns, ready to fill in. `plotYieldGear()` gained the `log_x`, `log_y` + and `log` arguments to match `plotYield()`. + +- **Three new cheatsheets** — *Model Setup and Calibration*, *Fishing*, and + *Changing Model Parameters* — join the analysis-and-plotting cheatsheet, which + now covers the newer plotting functions. + +For the complete list see the +[changelog](https://sizespectrum.org/mizer/news/index.html). + +## Upgrading + +```r +install.packages("mizer") +``` + +Existing `MizerParams` and `MizerSim` objects are upgraded automatically when +you load them with `readParams()` or `readSim()`. The one change that may affect +existing scripts is the resource-setting behaviour described above; the +[*Upgrading mizer*](https://sizespectrum.org/mizer/articles/upgrading.html) +vignette walks through it. + +As always, we welcome bug reports and feature requests on +[GitHub](https://github.com/sizespectrum/mizer/issues).