Rceattle C++ template
The CEATTLE likelihood: src/TMB/ceattle.cpp and its headers
Loading...
Searching...
No Matches
linkage.hpp
Go to the documentation of this file.
1// =====================================================================
2// linkage.hpp
3//
4// Turns the linkage table (a covariate or time effect on a process parameter)
5// into year-by-year offsets that recruitment, M, growth, catchability and
6// selectivity add to their parameters. Each offset is beta * X(year, column).
7//
8// Example: build_M1(linkages = list(M1 = linkage_spec(~ temp))) makes two rows per
9// species, an (Intercept) held at 0 and a `temp` slope beta. rceattle_apply_M_linkages()
10// fills M_linkage_offset(sp, sex, age, yr) = beta * temp_yr, and ceattle.cpp section 6.1
11// reads M1_at_age = exp(log M1 + M_linkage_offset). The equations for every link and
12// the row / slot / group terms are at ceattle.cpp section 2.3b.
13//
14// Process and parameter codes are in lockstep with R/0-linkage_encode.R
15// (LINKAGE_PROCESS_CODES / LINKAGE_PARAM_CODES); change one, change both.
16// A stratum id of 0 (species, sex, age bin or fleet) means "every level".
17// =====================================================================
18#ifndef RCEATTLE_LINKAGE_HPP
19#define RCEATTLE_LINKAGE_HPP
20
21// Process codes (R/0-linkage_encode.R::LINKAGE_PROCESS_CODES)
22#define RCEATTLE_PROC_RECRUIT 0
23#define RCEATTLE_PROC_M 1
24#define RCEATTLE_PROC_GROWTH 2
25#define RCEATTLE_PROC_Q 3
26#define RCEATTLE_PROC_SEL 4
27#define RCEATTLE_PROC_COMP 5 // prior only, no offset: Dirichlet-multinomial theta
28
29// Growth offsets: log_K, log_L1, log_Linf, log_m (codes 0-3). The SD codes
30// 4 and 5 sit at or above this count, so the growth loop skips them.
31#define RCEATTLE_N_GROWTH_PARAMS 4
32
33// Recruitment offsets: log R0, log alpha, log Beta (the stock-recruit `rec_pars` columns),
34// and R_init, the initial recruitment level, read at year 0 only.
35#define RCEATTLE_N_REC_PARAMS 4
36#define RCEATTLE_REC_R0 0
37#define RCEATTLE_REC_ALPHA 1
38#define RCEATTLE_REC_BETA 2
39#define RCEATTLE_REC_R_INIT 3
40
41// Link codes (LINKAGE_LINK_CODES in R/0-linkage_encode.R); see the note below.
42#define RCEATTLE_LINK_IDENTITY 0
43#define RCEATTLE_LINK_LOG 1
44#define RCEATTLE_LINK_LOGIT 2 // reserved; refused in R
45#define RCEATTLE_LINK_EXPONENTIAL 3 // catchability only (SS3 environmental link type 1)
46
47// Random-effect structures (LINKAGE_STRUCT_CODES). R calls IID "us", which is not
48// glmmTMB's unstructured us().
49#define RCEATTLE_RE_IID 0
50#define RCEATTLE_RE_RW 1
51#define RCEATTLE_RE_AR1 2
52
53// Selectivity parameter codes, under their primary names in LINKAGE_PARAM_CODES$sel
54// (R/0-linkage_encode.R); a form's aliases (sigma_*, peak, right_floor) share them.
55#define RCEATTLE_SEL_SLP_ASC 0 // log_sel_slp, ascending (log slope; DoubleNormal log sigma)
56#define RCEATTLE_SEL_SLP_DESC 1 // log_sel_slp, descending
57#define RCEATTLE_SEL_INF_ASC 2 // sel_inf, ascending (inflection; DoubleNormal peak)
58#define RCEATTLE_SEL_INF_DESC 3 // sel_inf, descending (DoubleNormal logit floor; LogisticPM log first-bin)
59#define RCEATTLE_SEL_COFF 4 // sel_coff, every bin; refused in R, so its offset stays 0
60#define RCEATTLE_SEL_APICAL 5 // log_sel_apical, the per-sex multiplier
61#define RCEATTLE_SEL_DN_PEAK 6 // DoubleNormalSS3 P1, peak (bin number age - minage + 1, or cm)
62#define RCEATTLE_SEL_TOP_LOGIT 7 // P2, top width (logit)
63#define RCEATTLE_SEL_ASCEND_SE 8 // P3, ascending width (log)
64#define RCEATTLE_SEL_DESCEND_SE 9 // P4, descending width (log)
65#define RCEATTLE_SEL_START_LOGIT 10 // P5, initial selectivity (logit)
66#define RCEATTLE_SEL_END_LOGIT 11 // P6, final selectivity (logit)
67
68// Dirichlet-multinomial theta codes, LINKAGE_PARAM_CODES$comp.
69#define RCEATTLE_COMP_THETA_COMP 0 // comp_weights = log theta, per fleet
70#define RCEATTLE_COMP_THETA_CAAL 1 // caal_weights = log theta, per fleet
71#define RCEATTLE_COMP_THETA_DIET 2 // diet_comp_weights = log theta, per predator
72
73/**
74 * @brief Expand a stratum id into the loop range [lo, hi).
75 *
76 * Clamped to `n_levels`, so an id past the last level gives an empty range.
77 *
78 * @param id 1-based stratum id, or 0 for "all levels".
79 * @param n_levels Number of levels the caller loops over.
80 * @param lo,hi [out] First level, and one past the last level.
81 */
82inline void rceattle_stratum_range(int id, int n_levels, int& lo, int& hi) {
83 lo = (id == 0) ? 0 : (id - 1);
84 hi = (id == 0) ? n_levels : id;
85 if (hi > n_levels) hi = n_levels;
86}
87
88
89/**
90 * @brief The linkage table: one entry per row, and the shared design matrix.
91 *
92 * Holds references (to the DATA_ vectors and beta_linkage_eff), so building one copies
93 * nothing. Stratum ids are 1-based, with 0 meaning "every level".
94 */
95template<class Type>
97 const vector<int>& process; // process code (RCEATTLE_PROC_*)
98 const vector<int>& param; // parameter code within the process (codes above)
99 const vector<int>& species; // species id
100 const vector<int>& sex; // sex id
101 const vector<int>& age_bin; // age index (age - minage + 1), not the age itself
102 const vector<int>& fleet; // Fleet_code
103 const vector<int>& X_col; // 0-based column of X
104 const vector<int>& link; // RCEATTLE_LINK_* (note below)
105 const matrix<Type>& X; // design matrix [model year, row 0 = styr; column]
106 const vector<Type>& beta; // the row's estimated beta, or its random-effect value (5.2b)
107};
108
109
110// ---------------------------------------------------------------------
111// The five functions below share one pattern: for each row of `lt` naming this
112// process whose link matches `link_code`, add beta * X(yr, X_col) to the offset
113// for every stratum and year the row covers. `link_code` picks the rows:
114// RCEATTLE_LINK_IDENTITY: beta * x is added to the parameter's natural-scale value
115// (to the stored logit or log for DoubleNormalSS3 P2-P6, DoubleNormal's
116// logit floor and LogisticPM's log first-bin selectivity);
117// RCEATTLE_LINK_LOG: that value is multiplied by exp(beta * x);
118// RCEATTLE_LINK_LOGIT: reserved, refused in R;
119// RCEATTLE_LINK_EXPONENTIAL, catchability only: (log q + any log-link offset) is
120// multiplied by exp(beta * x), SS3's environmental link type 1.
121// Years beyond X keep an offset of 0, so env_data need not cover the
122// projection. See vignette("environmental-linkages-and-priors").
123// ---------------------------------------------------------------------
124
125
126/**
127 * @brief Add growth offsets to `growth_offset[sp, sex, yr, param]`.
128 *
129 * Growth is not age-specific, so the age-bin id is ignored. The SD codes
130 * (sd_L1 = 4, sd_Linf = 5) take intercept rows only and are skipped here.
131 *
132 * @param growth_offset [in,out] Offsets [nspp, max_sex, nyrs, n_growth_params].
133 * @param link_code,lt See the note above.
134 */
135template<class Type>
137 array<Type>& growth_offset,
138 int link_code,
139 const LinkageTable<Type>& lt,
140 int nspp,
141 const vector<int>& nsex,
142 int nyrs)
143{
144 int n = lt.beta.size();
145 if (n == 0) return;
146
147 int yr_hi = std::min(nyrs, (int)lt.X.rows());
148
149 for (int i = 0; i < n; ++i) {
150 if (lt.process(i) != RCEATTLE_PROC_GROWTH) continue;
151 if (lt.link(i) != link_code) continue;
152
153 int param = lt.param(i);
154 if (param < 0 || param >= RCEATTLE_N_GROWTH_PARAMS) continue;
155
156 int xc = lt.X_col(i);
157 Type b = lt.beta(i);
158
159 int sp_lo, sp_hi;
160 rceattle_stratum_range(lt.species(i), nspp, sp_lo, sp_hi);
161
162 for (int sp = sp_lo; sp < sp_hi; ++sp) {
163 int sx_lo, sx_hi;
164 rceattle_stratum_range(lt.sex(i), nsex(sp), sx_lo, sx_hi);
165
166 for (int sx = sx_lo; sx < sx_hi; ++sx) {
167 for (int yr = 0; yr < yr_hi; ++yr) {
168 growth_offset(sp, sx, yr, param) += b * lt.X(yr, xc);
169 }
170 }
171 }
172 }
173}
174
175
176/**
177 * @brief Add natural-mortality offsets to `M_offset[sp, sex, age, yr]`.
178 *
179 * Age-specific, unlike growth. M1 (code 0) is the only parameter.
180 *
181 * @param M_offset [in,out] Offsets [nspp, max_sex, max_age, nyrs].
182 * @param link_code,lt See the note above.
183 */
184template<class Type>
186 array<Type>& M_offset,
187 int link_code,
188 const LinkageTable<Type>& lt,
189 int nspp,
190 const vector<int>& nsex,
191 const vector<int>& nages,
192 int nyrs)
193{
194 int n = lt.beta.size();
195 if (n == 0) return;
196
197 int yr_hi = std::min(nyrs, (int)lt.X.rows());
198
199 for (int i = 0; i < n; ++i) {
200 if (lt.process(i) != RCEATTLE_PROC_M) continue;
201 if (lt.link(i) != link_code) continue;
202
203 int xc = lt.X_col(i);
204 Type b = lt.beta(i);
205
206 int sp_lo, sp_hi;
207 rceattle_stratum_range(lt.species(i), nspp, sp_lo, sp_hi);
208
209 for (int sp = sp_lo; sp < sp_hi; ++sp) {
210 int sx_lo, sx_hi, ab_lo, ab_hi;
211 rceattle_stratum_range(lt.sex(i), nsex(sp), sx_lo, sx_hi);
212 rceattle_stratum_range(lt.age_bin(i), nages(sp), ab_lo, ab_hi);
213
214 for (int sx = sx_lo; sx < sx_hi; ++sx) {
215 for (int ab = ab_lo; ab < ab_hi; ++ab) {
216 for (int yr = 0; yr < yr_hi; ++yr) {
217 M_offset(sp, sx, ab, yr) += b * lt.X(yr, xc);
218 }
219 }
220 }
221 }
222 }
223}
224
225
226/**
227 * @brief Add recruitment offsets to `rec_offset[sp, param, yr]`.
228 *
229 * Recruitment parameters are not sex- or age-specific (`sex_ratio` splits
230 * recruits later), so those ids are ignored. Parameters are RCEATTLE_REC_* (above).
231 *
232 * @param rec_offset [in,out] Offsets [nspp, n_rec_params, nyrs].
233 * @param link_code,lt See the note above.
234 */
235template<class Type>
237 array<Type>& rec_offset,
238 int link_code,
239 const LinkageTable<Type>& lt,
240 int nspp,
241 int nyrs)
242{
243 int n = lt.beta.size();
244 if (n == 0) return;
245
246 int yr_hi = std::min(nyrs, (int)lt.X.rows());
247
248 for (int i = 0; i < n; ++i) {
249 if (lt.process(i) != RCEATTLE_PROC_RECRUIT) continue;
250 if (lt.link(i) != link_code) continue;
251
252 int param = lt.param(i);
253 if (param < 0 || param >= RCEATTLE_N_REC_PARAMS) continue;
254
255 int xc = lt.X_col(i);
256 Type b = lt.beta(i);
257
258 int sp_lo, sp_hi;
259 rceattle_stratum_range(lt.species(i), nspp, sp_lo, sp_hi);
260
261 for (int sp = sp_lo; sp < sp_hi; ++sp) {
262 for (int yr = 0; yr < yr_hi; ++yr) {
263 rec_offset(sp, param, yr) += b * lt.X(yr, xc);
264 }
265 }
266 }
267}
268
269
270/**
271 * @brief Add catchability offsets to `q_offset[fleet, yr]`.
272 *
273 * By fleet, which already implies the species, so species, sex and age ids are
274 * ignored. q (code 0) is the only parameter.
275 *
276 * @param q_offset [in,out] Offsets [n_flt, nyrs].
277 * @param link_code,lt See the note above.
278 */
279template<class Type>
281 matrix<Type>& q_offset,
282 int link_code,
283 const LinkageTable<Type>& lt,
284 int n_flt,
285 int nyrs)
286{
287 int n = lt.beta.size();
288 if (n == 0) return;
289
290 int yr_hi = std::min(nyrs, (int)lt.X.rows());
291
292 for (int i = 0; i < n; ++i) {
293 if (lt.process(i) != RCEATTLE_PROC_Q) continue;
294 if (lt.link(i) != link_code) continue;
295
296 int xc = lt.X_col(i);
297 Type b = lt.beta(i);
298
299 int fl_lo, fl_hi;
300 rceattle_stratum_range(lt.fleet(i), n_flt, fl_lo, fl_hi);
301
302 for (int flt = fl_lo; flt < fl_hi; ++flt) {
303 for (int yr = 0; yr < yr_hi; ++yr) {
304 q_offset(flt, yr) += b * lt.X(yr, xc);
305 }
306 }
307 }
308}
309
310
311/**
312 * @brief Add selectivity offsets, by fleet, sex and year, to one of five arrays.
313 *
314 * Offsets are per fleet; fleets sharing a `Selectivity_index` still share one
315 * parameter block through the map. The param code (RCEATTLE_SEL_*, above) picks
316 * the array: slopes `slp_offset`, inflections `inf_offset`, non-parametric
317 * coefficients `coff_offset` (every bin), apical height `apical_offset`, and
318 * DoubleNormalSS3 P1-P6 (SS3 pattern 24) `dn6_offset`.
319 *
320 * @param slp_offset [in,out] Slope offsets [2, n_flt, max_sex, nyrs].
321 * @param inf_offset [in,out] Inflection offsets [2, n_flt, max_sex, nyrs].
322 * @param coff_offset [in,out] Nonparametric offsets [n_flt, max_sex, n_sel_bins, nyrs].
323 * @param apical_offset [in,out] Apical-height offsets [n_flt, max_sex, nyrs].
324 * @param dn6_offset [in,out] DoubleNormalSS3 offsets [6, n_flt, max_sex, nyrs].
325 * @param link_code,lt See the note above.
326 */
327template<class Type>
329 array<Type>& slp_offset, // [2, n_flt, max_sex, nyrs]
330 array<Type>& inf_offset, // [2, n_flt, max_sex, nyrs]
331 array<Type>& coff_offset, // [n_flt, max_sex, n_sel_bins, nyrs]
332 array<Type>& apical_offset,// [n_flt, max_sex, nyrs]
333 array<Type>& dn6_offset, // [6, n_flt, max_sex, nyrs]
334 int link_code,
335 const LinkageTable<Type>& lt,
336 int n_flt,
337 int max_sex,
338 int n_sel_bins,
339 int nyrs)
340{
341 int n = lt.beta.size();
342 if (n == 0) return;
343
344 int yr_hi = std::min(nyrs, (int)lt.X.rows());
345
346 for (int i = 0; i < n; ++i) {
347 if (lt.process(i) != RCEATTLE_PROC_SEL) continue;
348 if (lt.link(i) != link_code) continue;
349
350 int param = lt.param(i);
351 int xc = lt.X_col(i);
352 Type b = lt.beta(i);
353
354 int fl_lo, fl_hi, sx_lo, sx_hi;
355 rceattle_stratum_range(lt.fleet(i), n_flt, fl_lo, fl_hi);
356 rceattle_stratum_range(lt.sex(i), max_sex, sx_lo, sx_hi);
357
358 for (int flt = fl_lo; flt < fl_hi; ++flt) {
359 for (int sx = sx_lo; sx < sx_hi; ++sx) {
360 for (int yr = 0; yr < yr_hi; ++yr) {
361 Type v = b * lt.X(yr, xc);
362 if (param == RCEATTLE_SEL_SLP_ASC || param == RCEATTLE_SEL_SLP_DESC) {
363 slp_offset(param - RCEATTLE_SEL_SLP_ASC, flt, sx, yr) += v;
364 } else if (param == RCEATTLE_SEL_INF_ASC || param == RCEATTLE_SEL_INF_DESC) {
365 inf_offset(param - RCEATTLE_SEL_INF_ASC, flt, sx, yr) += v;
366 } else if (param == RCEATTLE_SEL_COFF) {
367 for (int bin = 0; bin < n_sel_bins; ++bin) {
368 coff_offset(flt, sx, bin, yr) += v;
369 }
370 } else if (param == RCEATTLE_SEL_APICAL) {
371 apical_offset(flt, sx, yr) += v;
372 } else if (param >= RCEATTLE_SEL_DN_PEAK && param <= RCEATTLE_SEL_END_LOGIT) {
373 dn6_offset(param - RCEATTLE_SEL_DN_PEAK, flt, sx, yr) += v; // P1-P6
374 }
375 }
376 }
377 }
378 }
379}
380
381/**
382 * @brief The random-effect slots of one group, in slot order, which is time order
383 * within a group (.assign_re_registry()).
384 *
385 * @param group_of_slot Each slot's group (ceattle.cpp's linkage_re_sigma: an index, not an sd).
386 * @param grp The group.
387 * @return The group's slot indices, ascending; empty if it has none.
388 */
389inline vector<int> rceattle_group_slots(const vector<int>& group_of_slot, int grp) {
390 int len = 0;
391 for (int s = 0; s < group_of_slot.size(); ++s) if (group_of_slot(s) == grp) len++;
392 vector<int> slots(len);
393 int j = 0;
394 for (int s = 0; s < group_of_slot.size(); ++s) if (group_of_slot(s) == grp) slots(j++) = s;
395 return slots;
396}
397
398
399/**
400 * @brief Add the negative log density of one group's deviations to two accumulators.
401 *
402 * RCEATTLE_RE_IID: sd sigma. RCEATTLE_RE_RW: sd sigma on first differences (the first
403 * deviation has no term of its own; build_map() holds it at its start, 0 by default,
404 * so the base parameter carries the level). RCEATTLE_RE_AR1: stationary, MARGINAL sd
405 * sigma and correlation rho (the glmmTMB / Rogers QAR1 convention).
406 *
407 * @param structure RCEATTLE_RE_IID, RCEATTLE_RE_RW or RCEATTLE_RE_AR1.
408 * @param re The group's deviations, in time order.
409 * @param sigma The group's sd, natural scale (AR1: the marginal sd).
410 * @param rho The AR1 correlation in (-1, 1); read only under RCEATTLE_RE_AR1.
411 * @param nll,nll_unweighted Cells of jnll_comp and unweighted_jnll_comp to add to.
412 */
413template<class Type>
414void rceattle_re_group_nll(int structure, vector<Type> re, Type sigma, Type rho,
415 Type& nll, Type& nll_unweighted)
416{
417 if (structure == RCEATTLE_RE_RW) {
418 for (int t = 1; t < re.size(); ++t) {
419 Type log_dens = dnorm(re(t) - re(t - 1), Type(0), sigma, true);
420 nll -= log_dens;
421 nll_unweighted -= log_dens;
422 }
423 } else if (structure == RCEATTLE_RE_AR1) {
424 Type neg_log_dens = density::SCALE(density::AR1(rho), sigma)(re); // a negative log density
425 nll += neg_log_dens;
426 nll_unweighted += neg_log_dens;
427 } else { // RCEATTLE_RE_IID
428 Type log_dens = sum(dnorm(re, Type(0), sigma, true));
429 nll -= log_dens;
430 nll_unweighted -= log_dens;
431 }
432}
433
434#endif
#define RCEATTLE_SEL_COFF
Definition linkage.hpp:59
#define RCEATTLE_SEL_DN_PEAK
Definition linkage.hpp:61
#define RCEATTLE_N_REC_PARAMS
Definition linkage.hpp:35
#define RCEATTLE_SEL_APICAL
Definition linkage.hpp:60
#define RCEATTLE_SEL_END_LOGIT
Definition linkage.hpp:66
#define RCEATTLE_PROC_RECRUIT
Definition linkage.hpp:22
void rceattle_apply_q_linkages(matrix< Type > &q_offset, int link_code, const LinkageTable< Type > &lt, int n_flt, int nyrs)
Add catchability offsets to q_offset[fleet, yr].
Definition linkage.hpp:280
#define RCEATTLE_PROC_GROWTH
Definition linkage.hpp:24
#define RCEATTLE_N_GROWTH_PARAMS
Definition linkage.hpp:31
#define RCEATTLE_RE_AR1
Definition linkage.hpp:51
void rceattle_apply_sel_linkages(array< Type > &slp_offset, array< Type > &inf_offset, array< Type > &coff_offset, array< Type > &apical_offset, array< Type > &dn6_offset, int link_code, const LinkageTable< Type > &lt, int n_flt, int max_sex, int n_sel_bins, int nyrs)
Add selectivity offsets, by fleet, sex and year, to one of five arrays.
Definition linkage.hpp:328
void rceattle_apply_recruitment_linkages(array< Type > &rec_offset, int link_code, const LinkageTable< Type > &lt, int nspp, int nyrs)
Add recruitment offsets to rec_offset[sp, param, yr].
Definition linkage.hpp:236
#define RCEATTLE_RE_RW
Definition linkage.hpp:50
#define RCEATTLE_PROC_Q
Definition linkage.hpp:25
void rceattle_stratum_range(int id, int n_levels, int &lo, int &hi)
Expand a stratum id into the loop range [lo, hi).
Definition linkage.hpp:82
void rceattle_apply_growth_linkages(array< Type > &growth_offset, int link_code, const LinkageTable< Type > &lt, int nspp, const vector< int > &nsex, int nyrs)
Add growth offsets to growth_offset[sp, sex, yr, param].
Definition linkage.hpp:136
#define RCEATTLE_SEL_SLP_ASC
Definition linkage.hpp:55
void rceattle_re_group_nll(int structure, vector< Type > re, Type sigma, Type rho, Type &nll, Type &nll_unweighted)
Add the negative log density of one group's deviations to two accumulators.
Definition linkage.hpp:414
void rceattle_apply_M_linkages(array< Type > &M_offset, int link_code, const LinkageTable< Type > &lt, int nspp, const vector< int > &nsex, const vector< int > &nages, int nyrs)
Add natural-mortality offsets to M_offset[sp, sex, age, yr].
Definition linkage.hpp:185
#define RCEATTLE_SEL_INF_DESC
Definition linkage.hpp:58
vector< int > rceattle_group_slots(const vector< int > &group_of_slot, int grp)
The random-effect slots of one group, in slot order, which is time order within a group (....
Definition linkage.hpp:389
#define RCEATTLE_PROC_M
Definition linkage.hpp:23
#define RCEATTLE_SEL_SLP_DESC
Definition linkage.hpp:56
#define RCEATTLE_SEL_INF_ASC
Definition linkage.hpp:57
#define RCEATTLE_PROC_SEL
Definition linkage.hpp:26
The linkage table: one entry per row, and the shared design matrix.
Definition linkage.hpp:96
const vector< int > & link
Definition linkage.hpp:104
const vector< int > & process
Definition linkage.hpp:97
const vector< int > & param
Definition linkage.hpp:98
const matrix< Type > & X
Definition linkage.hpp:105
const vector< int > & age_bin
Definition linkage.hpp:101
const vector< int > & X_col
Definition linkage.hpp:103
const vector< int > & species
Definition linkage.hpp:99
const vector< int > & sex
Definition linkage.hpp:100
const vector< Type > & beta
Definition linkage.hpp:106
const vector< int > & fleet
Definition linkage.hpp:102