Rceattle C++ template
The CEATTLE likelihood: src/TMB/ceattle.cpp and its headers
Loading...
Searching...
No Matches
comp_sim.hpp
Go to the documentation of this file.
1/**
2 * @file comp_sim.hpp
3 * @brief Random draws for composition-style observations, used by SIMULATE.
4 *
5 * Every composition likelihood in the model -- age/length comps, conditional
6 * age-at-length, and stomach contents -- is a multinomial or a Dirichlet-multinomial
7 * over a vector of bins, and TMB supplies neither draw: `distributions_R.hpp` stops
8 * at univariate generators (`rbinom`, `rgamma`, `runif`, ...). These are the two
9 * multivariate draws built from them, both the standard constructions and exact given
10 * `N` and `p`, with `N` rounded to a whole count the only approximation either makes:
11 *
12 * - A multinomial is drawn as a sequence of conditional binomials: having placed the
13 * first `a` bins, the count in bin `a+1` is binomial in the observations still
14 * unplaced, with probability rescaled by the probability still unspent -- one
15 * `rbinom` per bin. One categorical variate per observation instead (as WHAM's
16 * `rmultinom` does, src/age_comp_sim.hpp) is equally valid but costs one uniform
17 * per observation, which stomach sample sizes make expensive.
18 * - A Dirichlet-multinomial is a multinomial whose probabilities are a Dirichlet
19 * draw, itself a vector of independent gammas divided by its own sum.
20 *
21 * Both return COUNTS summing to `N`, as the densities in `helper_functions.hpp` and
22 * `comp_osa.hpp` read them; the caller converts back to its data object's scale. They
23 * run only inside `SIMULATE{}`, where `Type` is always double and there is no AD tape,
24 * so `CppAD::Integer()` and the R generators underneath are safe.
25 */
26
27#ifndef RCEATTLE_COMP_SIM_HPP
28#define RCEATTLE_COMP_SIM_HPP
29
30/**
31 * @brief Draw multinomial counts by sequential conditional binomials.
32 *
33 * @param N Number of observations to place. Rounded to a whole number, as the binomial
34 * size must be an integer; effective sample sizes reaching here are whole counts
35 * (stomachs sampled, fish aged) or a weighting of them.
36 * @param p Bin probabilities. Rescaled internally, so they need not sum to 1, but must
37 * be non-negative.
38 * @return Counts, one per bin, summing to the rounded `N`.
39 */
40template<class Type>
41vector<Type> rmultinom_rce(Type N, vector<Type> p)
42{
43 int dim = p.size();
44 vector<Type> x(dim);
45 x.setZero();
46 if(dim == 0) return x;
47
48 // Round to a whole number for rbinom. Clamp first: CppAD::Integer saturates on
49 // some platforms and wraps to INT_MIN on others, which would make n_left
50 // negative and silently draw nothing.
51 double n_req = asDouble(N) + 0.5;
52 if(!(n_req > 0.0)) return x;
53 if(n_req > 2147483000.0) n_req = 2147483000.0;
54 Type n_left = Type(CppAD::Integer(n_req));
55 Type p_left = p.sum();
56
57 for(int a = 0; a < dim - 1; a++){
58 if(!(n_left > Type(0))) break; // everything already placed
59 // Probability of landing in bin a given it did not land in bins 0..a-1.
60 Type pa = (p_left > Type(0)) ? p(a) / p_left : Type(0);
61 if(pa < Type(0)) pa = Type(0);
62 if(pa > Type(1)) pa = Type(1); // guards rounding at the last bin
63 x(a) = rbinom(n_left, pa);
64 n_left -= x(a);
65 p_left -= p(a);
66 }
67 // The final bin is whatever is left: fixed by the sum-to-N constraint, never
68 // drawn, exactly as the density treats it.
69 if(n_left > Type(0)) x(dim - 1) = n_left;
70 return x;
71}
72
73
74/**
75 * @brief Draw Dirichlet proportions as normalized independent gammas.
76 *
77 * With a very small concentration every gamma can underflow to zero in double
78 * precision, leaving nothing to normalize; onset is around `sum(alpha)` below 1e-3.
79 * The limit of a Dirichlet as `alpha -> 0` is degenerate -- all the mass in ONE bin,
80 * chosen with probability proportional to `alpha` -- so that is what is returned. A
81 * flat vector would be the opposite of the near-degenerate composition the parameters
82 * describe, turning a concentrated diet into a diffuse one.
83 *
84 * @param alpha Concentration parameters, one per bin, non-negative.
85 * @return Proportions summing to 1.
86 */
87template<class Type>
88vector<Type> rdirichlet_rce(vector<Type> alpha)
89{
90 int dim = alpha.size();
91 vector<Type> x(dim);
92 x.setZero();
93 if(dim == 0) return x;
94
95 for(int a = 0; a < dim; a++){
96 x(a) = (alpha(a) > Type(0)) ? rgamma(alpha(a), Type(1)) : Type(0);
97 }
98 Type tot = x.sum();
99 if(!(tot > Type(0))){
100 // Everything underflowed: the degenerate limit, one bin in proportion to alpha.
101 Type a_tot = alpha.sum();
102 x.setZero();
103 if(a_tot > Type(0)){
104 Type u = runif(Type(0), Type(1)) * a_tot;
105 Type acc = 0.0;
106 for(int a = 0; a < dim; a++){
107 acc += alpha(a);
108 if(u <= acc){ x(a) = Type(1); return x; }
109 }
110 }
111 x(dim - 1) = Type(1);
112 return x;
113 }
114 return x / tot;
115}
116
117
118/**
119 * @brief Draw Dirichlet-multinomial counts.
120 *
121 * @param N Number of observations to place.
122 * @param alpha Concentration parameters, one per bin; here `predicted proportion * N *
123 * theta`. The draw reproduces the MEAN the density predicts but not every moment of
124 * it: the density is evaluated on offset data while the draw is on the raw scale.
125 * @return Counts, one per bin, summing to the rounded `N`.
126 */
127template<class Type>
128vector<Type> rdirmultinom_rce(Type N, vector<Type> alpha)
129{
130 vector<Type> p = rdirichlet_rce(alpha);
131 return rmultinom_rce(N, p);
132}
133
134#endif
vector< Type > rdirmultinom_rce(Type N, vector< Type > alpha)
Draw Dirichlet-multinomial counts.
Definition comp_sim.hpp:128
vector< Type > rdirichlet_rce(vector< Type > alpha)
Draw Dirichlet proportions as normalized independent gammas.
Definition comp_sim.hpp:88
vector< Type > rmultinom_rce(Type N, vector< Type > p)
Draw multinomial counts by sequential conditional binomials.
Definition comp_sim.hpp:41