16  Specifying Biological and Fleet Schedules

Several subcomponent objects in openMSE share a common structure for specifying parameter schedules across simulations and years. This structure applies to the biological subcomponents of Stock:Length, Weight, NaturalMortality, Maturity, and Fecundity, and to the Fleet subcomponents Selectivity and Retention.

Each of these objects has the same three core slots:

This chapter describes the two ways to populate a schedule, the accepted input formats for Pars, the dimension conventions for MeanAt* arrays, and how to supply custom model functions.

16.1 Two Ways to Specify a Schedule

There are two approaches for specifying a biological or fleet schedule. They are mutually exclusive: if Pars contains valid parameter values that can be matched to a model, it takes precedence and any values already in MeanAt* slots will be overwritten.

16.1.2 Option 2: Direct array

Leave Pars = list() (the default) and supply MeanAtAge, MeanAtLength, or MeanAtWeight directly as a numeric array. No model is inferred and no overwriting occurs.

# Natural mortality supplied directly as a Sim × Age × Year array
m_arr <- array(
  c(0.5, 0.4, 0.3, 0.3),
  dim      = c(1, 4, 1),
  dimnames = list(Sim = 1, Age = 1:4, Year = 2000)
)

NaturalMortality(stock) <- NaturalMortality(MeanAtAge = m_arr)

16.2 Input Formats for Pars

Each element of Pars may be supplied in any of the following forms. All are converted internally to Sim × Year arrays by Populate() before the model function is called.

16.2.1 Scalar

A single value applied identically to all simulations and years:

Pars = list(M = 0.2)

16.2.2 Length-2 bounds vector

Values are sampled independently for each simulation from Uniform(lower, upper), producing inter-simulation uncertainty in the parameter. When nSim = 1, the mean of the bounds is used:

Pars = list(M = c(0.1, 0.3))   # draws nSim values from U(0.1, 0.3)

This is the most common format for reflecting parameter uncertainty in an operating model, and is used throughout the built-in example objects.

16.2.3 Length-nSim vector

One value per simulation, held constant across all years. Used when simulation-specific values have already been determined (e.g., drawn from a posterior distribution):

Pars = list(M = c(0.15, 0.18, 0.22, 0.25))   # one value per simulation

16.2.4 Sim × Year array with named dimensions

A fully time-varying specification. Only the years at which the parameter changes need to be included; Extend() forward-fills all intermediate and future years automatically from the most recently supplied value:

m_arr <- array(
  c(0.2, 0.4),
  dim      = c(1, 2),
  dimnames = list(Sim = 1, Year = c(1990, 2010))
)
Pars = list(M = m_arr)
# M = 0.2 from 1990 to 2009, then 0.4 from 2010 onwards

A single-simulation array (Sim dimension of length 1) is replicated to all nSim simulations automatically.

16.2.5 Temporal random walk

Append a companion entry named <ParName>SD to Pars. A mean-preserving log-normal random walk is then applied to the base parameter value across years, generating inter-annual variation within each simulation. The SD entry is removed from Pars after use:

Pars = list(M = 0.2, MSD = 0.1)
# M varies around 0.2 from year to year with log-normal SD of 0.1

This can be combined with any of the above formats for the base parameter. For example, a length-2 bounds vector for M with MSD produces a schedule where the mean level of \(M\) differs across simulations and varies randomly within each simulation over time.

16.3 MeanAt* Array Formats and Dimension Conventions

All MeanAt* arrays use named dimensions. The expected dimension names depend on the type of schedule:

Array Dimensions
MeanAtAge Sim × Age × Year
MeanAtLength Sim × Class × Year
MeanAtWeight Sim × Class × Year

As with Pars, only the years at which the schedule changes need to be supplied; Extend() forward-fills all other years. A single-simulation array is replicated to all nSim simulations automatically.

A plain numeric vector of length nAge is also accepted for MeanAtAge and is promoted to a 1 × nAge × 1 array automatically.

16.4 How MeanAtLength and MeanAtWeight Relate to MeanAtAge

openMSE uses age-based accounting internally, so MeanAtAge is ultimately what the model requires. MeanAtLength and MeanAtWeight serve two roles:

  1. Generating size-structured data: observed length and weight compositions, length-based indices, and similar outputs are produced from the size-based schedules.
  2. Conversion to MeanAtAge: when a schedule is expressed in terms of size (e.g., a length-based maturity ogive or length-based selectivity curve), MeanAtLength or MeanAtWeight is converted to MeanAtAge via the age-length key (ALK) or age-weight key (AWK).

The conversion rules are:

  • If MeanAtLength is populated and MeanAtAge is not, Populate() converts MeanAtLengthMeanAtAge via the ALK.
  • If MeanAtWeight is populated and MeanAtAge is not, Populate() converts MeanAtWeightMeanAtAge via the AWK.
  • If MeanAtAge is already populated it is not overwritten by this conversion.
  • If both MeanAtLength and MeanAtAge are populated, MeanAtAge takes precedence and MeanAtLength is used only for size-based outputs.

For model-based specifications, whether the model populates MeanAtAge directly or via MeanAtLength depends on the model function’s signature: models that accept a Length argument produce MeanAtLength first and then convert to MeanAtAge via the ALK; age-based models populate MeanAtAge directly.

16.5 Model Resolution

When Pars is non-empty and Model is NULL, the model internally scans the candidate model functions for the object class — for example, all functions registered as "LengthModel" for a Length object — and finds the one whose formal argument names match the names in Pars exactly (excluding auxiliary arguments such as Ages, Length, and Weight, and ignoring any SD-suffixed entries used for random walks).

If no unique match is found, an informative error is thrown directing the user to the relevant *Models() function (e.g., ?LengthModels, ?MaturityModels).

You may bypass automatic inference by setting Model explicitly:

  • Character string: the name of a built-in model (must exist as an exported function of the appropriate class).
  • R function: a custom function whose formal arguments match the names in Pars; see Section 16.6.

If Model is already a function object (from a previous call), FindModel() returns it unchanged.

16.6 Custom Models

Any R function can be used as a model by passing it to Model. The function must:

  • accept arguments whose names match the elements of Pars;
  • accept any required auxiliary arguments (Ages, Length, Weight, etc.) as appropriate for the schedule type;
  • return a named array with dimensions Sim × Age × Year (for age-based outputs) or Sim × Class × Year (for size-based outputs).

For example, the following custom natural mortality function produces an age-declining \(M\) schedule:

declining_M <- function(M, Ages) {
 seq(M, M / 2, length.out = length(Ages))
}

NaturalMortality(stock) <- NaturalMortality(
  Pars  = list(M = c(0.3, 0.5)),
  Model = declining_M
)

Here M is sampled from U(0.3, 0.5) across simulations, and declining_M maps each simulated value to an age-varying schedule that declines from M at the youngest age to M/2 at the oldest.

Custom models follow the same Pars input format rules described above, so M can be a scalar, a length-2 bounds vector, a length-nSim vector, or a Sim × Year array. The Pars expansion to Sim × Year arrays is handled by Populate() before the custom function is called, so the function receives a single numeric value for each parameter, not an array.

16.7 Summary

Want to specify… Use
A single fixed value for all sims and years Scalar in Pars
Uniform distribution across simulations Length-2 bounds vector in Pars
Pre-determined per-simulation values Length-nSim vector in Pars
A step-change at a known year Sim × Year array in Pars
Inter-annual random variation within sims <Par>SD companion entry in Pars
A fully custom schedule Direct MeanAt* array, or Pars + custom Model function