Rceattle C++ template
The CEATTLE likelihood: src/TMB/ceattle.cpp and its headers
Loading...
Searching...
No Matches
likelihood.hpp
Go to the documentation of this file.
1#ifndef LIKELIHOOD_HPP
2#define LIKELIHOOD_HPP
3
4/**
5 * @file likelihood.hpp
6 * @brief Likelihood math for section 13 of ceattle.cpp: the composition row
7 * density and linkage prior families that several terms share, and the
8 * composition plus-group fold. The loops over observations, and the SIMULATE
9 * draws beside each density, stay in ceattle.cpp.
10 */
11
12
13/**
14 * @brief Composition tail accumulation (AFSC ac_yng / ac_old).
15 *
16 * Bins below `yng` are added into `yng` and bins above `old` into `old`, and only
17 * bins yng..old are kept. Joint-sex rows are folded per sex block, so a fold never
18 * crosses from females into males. yng = 1 with old >= nbins is a no-op.
19 *
20 * @param obs,hat Observed and expected proportions; replaced by the folded vectors.
21 * @param n_comp Number of bins in the row; replaced by the folded count.
22 * @param nbins_blk Bins per sex block (ages or lengths).
23 * @param nblk Sex blocks in the row: 2 for joint-sex comps, else 1.
24 * @param yng,old First and last bin kept, 1-based ordinals on the fleet's comp dimension.
25 */
26template<class Type>
27void fold_comp_tails(vector<Type>& obs, vector<Type>& hat, int& n_comp,
28 int nbins_blk, int nblk, int yng, int old)
29{
30 if (yng < 1) yng = 1;
31 if (old < 1 || old > nbins_blk) old = nbins_blk;
32 if (yng > old) yng = old; // data_check() validates; keeps 1 <= yng <= old
33 if (!(yng > 1 || old < nbins_blk)) return; // no fold needed
34
35 int nkeep = old - yng + 1;
36 vector<Type> obs_acc(nblk * nkeep); obs_acc.setZero();
37 vector<Type> hat_acc(nblk * nkeep); hat_acc.setZero();
38 for (int b = 0; b < nblk; b++) {
39 for (int j = 0; j < nbins_blk; j++) {
40 int tgt = j;
41 if (j < yng - 1) tgt = yng - 1;
42 if (j > old - 1) tgt = old - 1;
43 obs_acc(b * nkeep + tgt - (yng - 1)) += obs(b * nbins_blk + j);
44 hat_acc(b * nkeep + tgt - (yng - 1)) += hat(b * nbins_blk + j);
45 }
46 }
47 obs = obs_acc;
48 hat = hat_acc;
49 n_comp = nblk * nkeep;
50}
51
52
53/**
54 * @brief Negative log-likelihood of one composition row: an age or length
55 * composition, or one length bin of conditional age-at-length (CAAL).
56 *
57 * Likelihood types:
58 * - -1 AFSC/AMAK multinomial pseudo-likelihood, log L = sum N (o + c) log((p + c) / (o + c)).
59 * Not a density, so OSA residualizes it as a multinomial.
60 * - 0 Multinomial, via the conditional-binomial form in comp_osa.hpp.
61 * - 1 Dirichlet-multinomial with concentration N * p * theta (Thorson et al. 2017).
62 *
63 * The data weight multiplies the multinomial log-likelihood (types -1 and 0); the
64 * Dirichlet-multinomial carries its weight as theta instead. `unweighted_nll` gets the
65 * row without either, in ordinary fitting only.
66 *
67 * @param ll_type Likelihood type, -1, 0 or 1.
68 * @param osa_mode 0 = ordinary fitting; 1 or 2 = OSA build (2 adds the CDF terms).
69 * @param obs_prop,hat_prop Observed and expected proportions, after any tail fold.
70 * @param offset Constant c added to both, to keep log(0) out (comp_offset).
71 * @param n_samp Input sample size N.
72 * @param weight Data weight on the multinomial log-likelihood.
73 * @param dm_theta Dirichlet-multinomial theta, exp() of the log weight.
74 * @param obsvec,keep,osa_start OSA observation vector, its keep indicator, and this
75 * row's first position in them (< 0 = row not in obsvec).
76 * @param type_name Data column named in the error for an unknown type.
77 * @param nll,unweighted_nll Cells of jnll_comp and unweighted_jnll_comp to add to.
78 */
79template<class Type>
80void comp_row_nll(int ll_type, int osa_mode,
81 vector<Type> obs_prop, vector<Type> hat_prop,
82 Type offset, Type n_samp, Type weight, Type dm_theta,
83 vector<Type>& obsvec, data_indicator<vector<Type>, Type>& keep,
84 int osa_start, const char* type_name,
85 Type& nll, Type& unweighted_nll)
86{
87 int n_comp = obs_prop.size();
88
89 // Offset proportions; observed counts are proportions times N.
90 vector<Type> obs_n = obs_prop;
91 vector<Type> hat_p = hat_prop;
92 obs_n += offset;
93 hat_p += offset;
94 obs_n *= n_samp;
95
96 // DM concentration. The count total N * (1 + offset * nbins) is the effective
97 // sample size ddirmultinom() reads, here and in the AFSC linear-DM form.
98 vector<Type> alphas = obs_n.sum() * hat_p * dm_theta;
99 vector<Type> unweighted_alphas = obs_n.sum() * hat_p;
100
101 if (osa_mode == 0) {
102 switch (ll_type) {
103 case -1:
104 for (int bin = 0; bin < n_comp; bin++) {
105 nll -= weight * n_samp * (obs_prop(bin) + offset) *
106 log((hat_prop(bin) + offset) / (obs_prop(bin) + offset));
107 unweighted_nll -= n_samp * (obs_prop(bin) + offset) *
108 log((hat_prop(bin) + offset) / (obs_prop(bin) + offset));
109 }
110 break;
111
112 case 0: {
113 data_indicator<vector<Type>, Type> keep_ones(obs_n, true);
114 nll -= weight * dmultinom_osa(obs_n, hat_p, keep_ones, 1, 1);
115 unweighted_nll -= dmultinom_osa(obs_n, hat_p, keep_ones, 1, 1);
116 break;
117 }
118
119 case 1:
120 nll -= ddirmultinom(obs_n, alphas, true);
121 unweighted_nll -= ddirmultinom(obs_n, unweighted_alphas, true);
122 break;
123
124 default:
125 error("Invalid '%s'", type_name);
126 }
127 } else if (osa_start >= 0) {
128 // OSA: unweighted and keep-gated, read from obsvec so oneStepPredict() can
129 // residualize each bin. The beta-binomial has no elementary CDF, so the DM
130 // gets no CDF term.
131 vector<Type> osa_x = obsvec.segment(osa_start, n_comp);
132 if (ll_type == 1) {
133 nll -= ddirmultinom_osa(osa_x, alphas, keep.segment(osa_start, n_comp), 1, 1);
134 } else {
135 nll -= dmultinom_osa(osa_x, hat_p, keep.segment(osa_start, n_comp), 1, 1, osa_mode == 2);
136 }
137 }
138}
139
140
141/**
142 * @brief Log density of a linkage prior on the natural scale.
143 *
144 * Families: 1 = normal(p1, p2); 2 = lognormal, log x ~ normal(p1 - b * p2^2 / 2, p2),
145 * so exp(p1) is the mean when bias_adjust_proc b = 1 and the median when 0;
146 * 3 = gamma(shape p1, rate p2); 4 = beta(p1, p2). Any other code returns 0.
147 *
148 * @param family Prior family code, from linkage_spec(priors = ).
149 * @param value The parameter, on the scale it is stored.
150 * @param value_is_log True if `value` is a log, so the prior reads exp(value).
151 * @param p1,p2 The family's two parameters, as listed above.
152 * @param bias_adjust_proc 1 to centre the lognormal on its mean, 0 on its median.
153 * @return The log prior density (0 for an unknown family).
154 */
155template<class Type>
156Type linkage_log_prior(int family, Type value, bool value_is_log,
157 Type p1, Type p2, Type bias_adjust_proc)
158{
159 Type x = value_is_log ? exp(value) : value;
160 switch (family) {
161 case 1:
162 return dnorm(x, p1, p2, true);
163 case 2: {
164 // A stored log is used as is rather than re-logged.
165 Type log_x = value_is_log ? value : log(x);
166 return dnorm(log_x, p1 - bias_adjust_proc * square(p2) / 2.0, p2, true);
167 }
168 case 3:
169 return dgamma(x, p1, Type(1.0) / p2, true);
170 case 4:
171 return dbeta(x, p1, p2, true);
172 default:
173 return Type(0.0);
174 }
175}
176
177#endif
Type ddirmultinom_osa(vector< Type > obs, vector< Type > alpha, data_indicator< vector< Type >, Type > keep, int do_osa, int give_log)
Keep-aware Dirichlet-multinomial log-density via conditional beta-binomials.
Definition comp_osa.hpp:252
Type dmultinom_osa(vector< Type > x, vector< Type > p, data_indicator< vector< Type >, Type > keep, int do_osa, int give_log, int do_cdf=0)
Keep-aware multinomial log-density via conditional binomials.
Definition comp_osa.hpp:159
Type ddirmultinom(vector< Type > obs, vector< Type > alpha, int do_log)
Type square(Type x)
Type linkage_log_prior(int family, Type value, bool value_is_log, Type p1, Type p2, Type bias_adjust_proc)
Log density of a linkage prior on the natural scale.
void fold_comp_tails(vector< Type > &obs, vector< Type > &hat, int &n_comp, int nbins_blk, int nblk, int yng, int old)
Composition tail accumulation (AFSC ac_yng / ac_old).
void comp_row_nll(int ll_type, int osa_mode, vector< Type > obs_prop, vector< Type > hat_prop, Type offset, Type n_samp, Type weight, Type dm_theta, vector< Type > &obsvec, data_indicator< vector< Type >, Type > &keep, int osa_start, const char *type_name, Type &nll, Type &unweighted_nll)
Negative log-likelihood of one composition row: an age or length composition, or one length bin of co...