24 Life History Schedules
Life history schedules are fully-dimensioned \(n_\text{sim} \times n_\text{age} \times T\) arrays generated by PopulateStock() from the biological subcomponent objects of a Stock object.
All schedules share the age and year indexing described in Section 23.1. Where a schedule depends on length or weight, it is computed at mean length or weight first and then converted to the age dimension via the age–length or age–weight key. See Chapter 16 for the options for specifying these arrays.
All schedules carry a simulation dimension (indexed \(s = 1, \ldots, n_\text{sim}\)), but since simulations are independent the simulation index is omitted from equations below for clarity. All calculations are performed independently for each stock.
24.1 Length-at-Age
Mean length-at-age is computed by PopulateLength(). Available models via LengthModels():
von Bertalanffy (vonBert, default): \[\bar{L}_{a,t} = L_{\infty,t} \left(1 - e^{-K_{t}(a - t_{0,t})}\right)\]
where \(L_{\infty}\) is the asymptotic length, \(K\) is the growth coefficient, and \(t_0\) is the theoretical age at zero length. All parameters are \(\text{Sim} \times \text{Year}\) arrays after sampling (see Section 23.3), so growth can vary across both simulations and time.
Brody (Brody): \[\bar{L}_{a,t} = L_\infty - (L_\infty - L_0)\,e^{-Ka}\]
Gompertz (Gompertz): \[\bar{L}_{a,t} = L_\infty \exp\!\left(-\exp(-g(a-a^*))\right)\]
Richards (Richards) — generalised von Bertalanffy with shape parameter \(d\); \(d = 1\) recovers von Bertalanffy, \(d < 1\) gives faster early growth, \(d > 1\) slower: \[\bar{L}_{a,t} = L_\infty \left(1 - e^{-K(a-t_0)}\right)^{1/d}\]
Negative values (arising when \(a < t_0\)) are set to zero. Length-at-age is stored in Stock@Length@MeanAtAge.
24.2 Weight-at-Age
Mean weight-at-age is computed by PopulateWeight(). Three models are available:
Weight-at-Age (WeightatAge, direct allometric on age): \[\bar{W}_{a,t} = a_w \cdot a^{b_w}\]
Weight-at-Length (WeightatLength, allometric on length class \(\ell\), then converted via ALK): \[W(\ell) = \alpha\, \ell^{\,\beta}\]
Weight-at-Mean-Length (WeightatMeanLength, allometric evaluated at \(\bar{L}_{a,t}\)): \[\bar{W}_{a,t} = \alpha\, \bar{L}_{a,t}^{\,\beta}\]
The default and most common approach is weight-at-mean-length. Weight-at-age is stored in Stock@Weight@MeanAtAge.
24.3 Age–Length and Age–Weight Keys
The age–length key (ALK) is a \(n_\text{sim} \times n_\text{age} \times n_\text{class} \times T\) array where element \(\text{ALK}_{a,k,t}\) gives the probability that an individual of age \(a\) in year \(t\) falls into size class \(k\). Size classes are indexed by their lower bounds \(\ell_1 < \ell_2 < \cdots < \ell_{n_\text{class}}\), so class \(k\) spans the interval \([\ell_k, \ell_{k+1})\) and the final class captures all sizes above \(\ell_{n_\text{class}}\).
The same procedure and functions are used to construct the age–weight key (AWK), stored in Stock@Weight@AWK. The AWK is only built when Weight@CVatAge is specified; it is used to convert weight-based schedules to the age dimension when they cannot be derived from the ALK (e.g. maturity-at-weight or M-at-weight).
24.3.1 Normal distribution
Individual length-at-age is assumed to follow a truncated normal with mean \(\bar{L}_{a,t}\) and standard deviation \(\sigma_{a,t} = \text{CV}_{a,t} \cdot \bar{L}_{a,t}\), truncated to the interval \([\bar{L}_{a,t} - c\,\sigma_{a,t},\; \bar{L}_{a,t} + c\,\sigma_{a,t}]\) where \(c\) is TruncSD (default 2). The CDF of this truncated normal evaluated at size \(q\) is
\[\Psi_{a,t}(q) = \frac{\Phi\!\left(\dfrac{q - \bar{L}_{a,t}}{\sigma_{a,t}}\right) - \Phi(-c)}{\Phi(c) - \Phi(-c)}\]
where \(\Phi\) is the standard normal CDF.
24.3.2 Lognormal distribution
When Dist = "lognormal", sizes are log-normally distributed. The same truncated-normal CDF is applied to \(\log(\ell)\) with reparameterised moments:
\[\mu^*_{a,t} = \log\!\bigl(\bar{L}_{a,t}\bigr) - \tfrac{1}{2}\,\text{CV}^2_{a,t}, \qquad \sigma^*_{a,t} = \text{CV}_{a,t}\]
The truncation in log-space is at \(\mu^*_{a,t} \pm c\,\sigma^*_{a,t}\).
24.3.3 Bin probabilities
ALK entries are the probability mass in each bin, computed from \(\Psi_{a,t}\):
\[\text{ALK}_{a,k,t} = \begin{cases} \Psi_{a,t}(\ell_2) & k = 1 \\ \Psi_{a,t}(\ell_{k+1}) - \Psi_{a,t}(\ell_k) & 1 < k < n_\text{class} \\ 1 - \Psi_{a,t}(\ell_{n_\text{class}}) & k = n_\text{class} \end{cases}\]
The first bin assumes \(\Psi_{a,t}(\ell_1) = 0\), which holds when \(\ell_1\) is at or below the lower truncation point of the size distribution for all ages. The default class vector starts at 0, satisfying this for any realistic growth model. Rows of the ALK (and AWK) sum to one by construction. Both keys are computed by CalcAgeSizeKey() and stored in Stock@Length@ALK and Stock@Weight@AWK respectively.
24.3.4 Converting size-based schedules to age
Any schedule \(\phi_k\) defined over size classes (e.g. maturity-at-length, M-at-length, selectivity-at-length) is converted to an age-based schedule by taking the expectation over the ALK (or AWK):
\[\phi_{a,t} = \sum_{k=1}^{n_\text{class}} \text{ALK}_{a,k,t} \cdot \phi_k\]
This matrix multiplication is applied during PopulateStock() for all size-based biological schedules.
24.3.5 Converting age-based schedules to size
The reverse operation is needed when age-based schedules must be expressed over size classes, for example when computing catch-at-size. Here the ALK is first column-standardised so that each size class sums to one across ages:
\[\widetilde{\text{ALK}}_{a,k,t} = \frac{\text{ALK}_{a,k,t}}{\displaystyle\sum_{a'} \text{ALK}_{a',k,t}}\]
The at-size schedule is then the expectation of the age-based schedule weighted by the relative age composition within each size class:
\[\phi_{k,t} = \sum_{a} \phi_{a,t} \cdot \widetilde{\text{ALK}}_{a,k,t}\]
Because coarse age discretisation can leave some size classes unpopulated in the ALK, if the number of age classes is below a minimum threshold (default 50) both MeanAtAge and the ALK are linearly interpolated to a finer age grid before conversion. This is handled internally by AtAge2AtSize().
24.4 Natural Mortality
Natural mortality-at-age is computed by PopulateNaturalMortality(). All values must be in per-time-step units matching Ages@Units. Three model families are available:
Constant M (MortalityAtAge): \[M_{a,t} = M_t\]
Lorenzen M-at-length (LorenzenMortalityLength, converted to age via ALK): \[M(\ell) = M_\text{ref} \left(\frac{\ell}{L_\text{ref}}\right)^{-0.288}\]
Lorenzen M-at-weight (LorenzenMortalityWeight, converted to age via ALK): \[M(w) = M_\text{ref} \left(\frac{w}{W_\text{ref}}\right)^{-0.288}\]
The Lorenzen models produce age-varying M that declines with body size. Natural mortality is stored in Stock@NaturalMortality@MeanAtAge.
24.5 Maturity-at-Age
Maturity-at-age is computed by PopulateMaturity(). All three models use a logistic 50/95 parameterisation:
\[\text{mat}(x) = \frac{1}{1 + \exp\!\left(-\ln(19) \cdot \dfrac{x - x_{50}}{x_{95} - x_{50}}\right)}\]
where \(x_{50}\) is the value at 50% maturity and \(x_{95} - x_{50}\) is the interval to 95% maturity. Available models via MaturityModels():
-
MaturityAtAge: \(x\) is age -
MaturityAtLength: \(x\) is length class (converted to age via ALK) -
MaturityAtWeight: \(x\) is weight class (converted to age via AWK)
Maturity-at-age is stored in Stock@Maturity@MeanAtAge.
24.5.1 Semelparity
For semelparous species, mature individuals die after spawning. The Semelparous array in the Maturity object stores the proportion of mature individuals that die post-spawning; for iteroparous species this array is zero.
24.6 Fecundity-at-Age
Fecundity-at-age is computed by PopulateFecundity(). When no model is specified, fecundity defaults to mature body mass:
\[f_{a,t} = \bar{W}_{a,t} \cdot \text{mat}_{a,t}\]
When a model is supplied, fecundity uses the same logistic 50/95 form as maturity but scaled to a maximum fecundity MaxFec:
\[f(x) = \frac{\texttt{MaxFec}}{1 + \exp\!\left(-\ln(19) \cdot \dfrac{x - x_{50}}{x_{95} - x_{50}}\right)}\]
Available models via FecundityModels():
-
FecundityAtAge: \(x\) is age -
FecundityAtLength: \(x\) is length class (converted to age via ALK) -
FecundityAtWeight: \(x\) is weight class (converted to age via AWK)
Fecundity is used as the spawning index \(S\) in stock–recruitment calculations and is stored in Stock@Fecundity@MeanAtAge.
24.7 Stock–Recruitment
The stock–recruitment relationship (SRR) defines expected recruitment \(R\) as a function of spawning production \(S\). Three models are available:
Beverton–Holt (BevertonHolt, parameterised via steepness \(h\), the fraction of unfished recruitment produced at 20% of unfished spawning production): \[R = \frac{\alpha S}{1 + \beta S}, \quad \alpha = \frac{4h}{(1-h)\,\phi_0}, \quad \beta = \frac{5h-1}{(1-h)\,\phi_0\,R_0}\]
Ricker (Ricker, parameterised via Ricker steepness \(h_R\), defined analogously but on the Ricker curve): \[R = \alpha S\, e^{-\beta S}, \quad \alpha = \frac{(5h_R)^{1.25}}{\phi_0}, \quad \beta = \frac{\ln\!\left[(5h_R)^{1.25}\right]}{\phi_0\,R_0}\]
Hockey-stick (HockeyStick, parameterised via hinge point \(\mathtt{Shinge} \in (0, 1]\), the fraction of unfished spawning production at which recruitment saturates): \[R = \begin{cases} \dfrac{R_0}{S_\text{hinge}} S & S < S_\text{hinge} \\ R_0 & S \ge S_\text{hinge} \end{cases}\]
where \(\phi_0 = S_0 / R_0\) is the unfished spawning production per recruit and \(S_\text{hinge} = S_0 \cdot \mathtt{Shinge}\).
24.7.1 Recruitment Deviations
Realised recruitment in each time step is:
\[R_t = \bar{R}_t \cdot \delta_t\]
where \(\bar{R}_t\) is the expected recruitment from the SRR and \(\delta_t\) is a log-normal deviation generated by GenRecDevs(). Deviations are drawn from a truncated normal on the log scale:
\[\log \delta_t \sim \mathcal{N}\!\left(\mu_\delta,\; \sigma_\delta\right), \quad \mu_\delta = -\tfrac{1}{2}\sigma_\delta^2 \cdot \frac{1-\rho}{\sqrt{1-\rho^2}}\]
where \(\sigma_\delta\) is SRR@SD, \(\rho\) is the autocorrelation coefficient SRR@AC, and the mean adjustment \(\mu_\delta\) ensures approximate lognormal bias-correction under autocorrelation. Autocorrelation is applied as a first-order process:
\[\log\delta_t = \rho\,\log\delta_{t-1} + \epsilon_t\sqrt{1-\rho^2}, \quad \epsilon_t \sim \mathcal{N}(\mu_\delta, \sigma_\delta)\]
Deviations are truncated at \(\pm\,\mathtt{TruncSD} \times \sigma_\delta\) (default \(\mathtt{TruncSD} = 2\)) to prevent extreme recruitment events. Separate deviation arrays are generated for the initial age classes (RecDevInit), historical years (RecDevHist), and projection years (RecDevProj). Any of these arrays can be supplied directly by the analyst instead of being generated stochastically.
For multi-stock operating models, analysts can optionally call GenMultiStockRecDevs() to replace the independently generated projection deviations with correlated deviations drawn from a multivariate AR(1) process, preserving cross-stock covariance estimated from the historical deviations. This is not applied automatically.
