EpiNow2 Stan Functions
primarycensored.stan
Go to the documentation of this file.
1// Stan functions from primarycensored version 1.6.0
2real expgrowth_cdf(real x, real xmin, real xmax, real r) {
3 if (x < xmin) {
4 return 0;
5 }
6 if (x > xmax) {
7 return 1;
8 }
9 if (abs(r) < 1e-10) {
10 return (x - xmin) / (xmax - xmin);
11 }
12 return (exp(r * x) - exp(r * xmin)) / (exp(r * xmax) - exp(r * xmin));
13}
14real expgrowth_lcdf(real x, real xmin, real xmax, real r) {
15 if (x < xmin) {
16 return negative_infinity();
17 }
18 if (x > xmax) {
19 return 0;
20 }
21 return log(expgrowth_cdf(x | xmin, xmax, r));
22}
23real expgrowth_lpdf(real x, real xmin, real xmax, real r) {
24 if (x < xmin || x > xmax) {
25 return negative_infinity();
26 }
27 if (abs(r) < 1e-10) {
28 return -log(xmax - xmin);
29 }
30 return log(abs(r)) + r * x -
31 log(abs(exp(r * xmax) - exp(r * xmin)));
32}
33vector primary_lcdf_vec(vector p, int primary_id,
34 array[] real primary_params, data real pwindow) {
35 int N = num_elements(p);
36 vector[N] out;
37 for (i in 1:N) {
38 out[i] = primary_lcdf(p[i] | primary_id, primary_params, pwindow);
39 }
40 return out;
41}
43 data real d, vector boundaries, vector pmf,
44 int primary_id, array[] real primary_params, data real pwindow
45) {
46 int K = num_elements(pmf);
47 // Integration support in u = d - p for p in [0, pwindow]. It is not
48 // clipped at 0 so boundaries that start below zero (delays with negative
49 // support) are handled; for non-negative boundaries the per-bin clip to
50 // [boundaries[k], boundaries[k + 1]] below gives the same result.
51 real u_min = d - pwindow;
52 real u_max = d;
53
54 // Structural-zero short-circuit. Below `boundaries[2]` F_step is zero
55 // and the bin-1 contribution carries `cum_before = 0`, so the integral
56 // collapses to 0. Returning `negative_infinity()` directly keeps
57 // `log(0)` off the autodiff tape so downstream `log_diff_exp(a, -inf)`
58 // evaluates cleanly with a zero gradient w.r.t. `pmf`.
59 if (u_max <= boundaries[2]) return negative_infinity();
60
61 // Sub-interval endpoints in u-space, clipped to [u_min, u_max].
62 vector[K] lo = fmax(u_min, head(boundaries, K));
63 vector[K] hi = fmin(u_max, tail(boundaries, K));
64
65 // F_step is right-continuous and on [b_k, b_{k+1}) takes the value
66 // sum_{j < k} pmf[j] (mass before bin k). cumulative_sum(pmf) gives
67 // the mass through and including bin k, so we shift right by one.
68 vector[K] cum_before;
69 cum_before[1] = 0;
70 if (K > 1) cum_before[2:K] = head(cumulative_sum(pmf), K - 1);
71
72 // 0/1 mask drops bins with `hi <= lo` from the reduction without a
73 // branch in the inner expression. Built on `data`-level inputs.
74 vector[K] active;
75 for (k in 1:K) active[k] = hi[k] > lo[k] ? 1 : 0;
76
77 // F_primary at lo/hi via two vectorised calls; one masked subtraction
78 // gives the per-bin difference for the dot product.
79 vector[K] f_lo = primary_lcdf_vec(d - lo, primary_id, primary_params,
80 pwindow);
81 vector[K] f_hi = primary_lcdf_vec(d - hi, primary_id, primary_params,
82 pwindow);
83 vector[K] f_diff = (exp(f_lo) - exp(f_hi)) .* active;
84
85 real integral = dot_product(cum_before, f_diff);
86
87 // Tail region [boundaries[K+1], u_max]: F_step = 1, contributing
88 // F_primary(d - tail_start) - F_primary(d - u_max).
89 real tail_start = fmax(boundaries[K + 1], u_min);
90 if (tail_start < u_max) {
91 real fp_tail = exp(primary_lcdf(d - tail_start | primary_id,
92 primary_params, pwindow));
93 real fp_end = exp(primary_lcdf(d - u_max | primary_id,
94 primary_params, pwindow));
95 integral += fp_tail - fp_end;
96 }
97
98 return log(integral);
99}
100vector hazards_to_pmf(vector hazards) {
101 int K = num_elements(hazards);
102 vector[K] log_surv;
103 log_surv[1] = 0;
104 if (K > 1) {
105 log_surv[2:K] = cumulative_sum(log1m(hazards[1:(K - 1)]));
106 }
107 return hazards .* exp(log_surv);
108}
110 data real d, vector boundaries, vector hazards,
111 int primary_id, array[] real primary_params, data real pwindow
112) {
113 return discretestep_lcdf(
114 d | boundaries, hazards_to_pmf(hazards), primary_id, primary_params,
115 pwindow
116 );
117}
118real pstep_lcdf(real t, vector boundaries, vector pmf) {
119 int K = num_elements(pmf);
120 if (t < boundaries[2]) return negative_infinity();
121 if (t >= boundaries[K + 1]) return 0;
122 // Right-continuous CDF with jumps at the right edges
123 // boundaries[2], ..., boundaries[K + 1]. F(t) = cum_pmf[k] for
124 // t in [boundaries[k + 1], boundaries[k + 2]); equivalently the
125 // largest k with boundaries[k + 1] <= t. Boundary-on-jump cases
126 // (t == boundaries[k + 1]) advance k, matching R's
127 // `findInterval(left.open = FALSE)`.
128 int k = 1;
129 while (k < K && boundaries[k + 2] <= t) k += 1;
130 return log(cumulative_sum(pmf)[k]);
131}
132real phazard_lcdf(real t, vector boundaries, vector hazards) {
133 return pstep_lcdf(t | boundaries, hazards_to_pmf(hazards));
134}
135int check_for_uniform_terms(int dist_id, int primary_id) {
136 if (primary_id != 1) return 0;
137 return dist_id == 2 || dist_id == 1 || dist_id == 3 || dist_id == 5;
138}
139int check_for_analytical(int dist_id, int primary_id) {
140 // Gamma, Lognormal, Weibull and generalised gamma with a Uniform primary
141 if (check_for_uniform_terms(dist_id, primary_id)) return 1;
142 // Keep this primary list in sync with `primary_lcdf`; see the note above.
143 if (dist_id == 26 || dist_id == 27 || dist_id == 28) {
144 return primary_id == 1 || primary_id == 2;
145 }
146 return 0; // No analytical solution for other combinations
147}
148real primarycensored_uniform_lcdf_from_terms(vector terms_d, vector terms_q,
149 data real pwindow) {
150 real log_A = log_sum_exp(terms_d[1], terms_q[2]);
151 real log_B = log_sum_exp(terms_q[1], terms_d[2]);
152 // Deep enough into the lower tail every term underflows together. Both
153 // are then `-inf` and `log_diff_exp` would give NaN, so return the limit
154 // directly.
155 if (log_A == negative_infinity() && log_B == negative_infinity()) {
156 return negative_infinity();
157 }
158 return log_diff_exp(log_A, log_B) - log(pwindow);
159}
161 array[] real params) {
162 if (t <= 0) {
163 return rep_vector(negative_infinity(), 2);
164 }
165 real shape = params[1];
166 real rate = params[2];
167 // log E where E = k * theta = shape / rate is the mean of the delay
168 real log_E = log(shape) - log(rate);
169 // F_T(t; k) and the recursion to F_T(t; k+1):
170 // P(k+1, y) = P(k, y) - y^k e^{-y} / Gamma(k+1), with y = rate * t
171 real log_F_T_k = gamma_lcdf(t | shape, rate);
172 real gamma_kp1_pdf_log = shape * log(rate * t) - rate * t
173 - lgamma(shape + 1);
174 real log_F_T_kp1 = log_diff_exp(log_F_T_k, gamma_kp1_pdf_log);
175 return [log(t) + log_F_T_k, log_E + log_F_T_kp1]';
176}
177real primarycensored_gamma_uniform_lcdf(data real d, real q,
178 array[] real params,
179 data real pwindow) {
180 return primarycensored_uniform_lcdf_from_terms(
181 primarycensored_gamma_uniform_terms(d, params),
182 primarycensored_gamma_uniform_terms(q, params), pwindow
183 );
184}
185vector primarycensored_lognormal_uniform_terms(real t,
186 array[] real params) {
187 real mu = params[1];
188 real sigma = params[2];
189 real mu_sigma2 = mu + square(sigma);
190 // log E where E = exp(mu + sigma^2/2) is the mean of the delay
191 real log_E = mu + 0.5 * square(sigma);
192 real log_t_F_T = lognormal_lcdf_underflows(t, mu, sigma)
193 ? negative_infinity()
194 : log(t) + lognormal_lcdf(t | mu, sigma);
195 real log_E_tF_T = lognormal_lcdf_underflows(t, mu_sigma2, sigma)
196 ? negative_infinity()
197 : log_E + lognormal_lcdf(t | mu_sigma2, sigma);
198 return [log_t_F_T, log_E_tF_T]';
199}
201 array[] real params,
202 data real pwindow) {
206 );
207}
208real log_weibull_g(real t, real shape, real scale) {
209 real x = pow(t * inv(scale), shape);
210 real a = 1 + inv(shape);
211 return log(gamma_p(a, x)) + lgamma(a);
212}
214 array[] real params) {
215 if (t <= 0) {
216 return rep_vector(negative_infinity(), 2);
217 }
218 real shape = params[1];
219 real scale = params[2];
220 return [
221 log(t) + weibull_lcdf(t | shape, scale),
222 log(scale) + log_weibull_g(t, shape, scale)
223 ]';
224}
225real primarycensored_weibull_uniform_lcdf(data real d, real q,
226 array[] real params,
227 data real pwindow) {
228 return primarycensored_uniform_lcdf_from_terms(
229 primarycensored_weibull_uniform_terms(d, params),
230 primarycensored_weibull_uniform_terms(q, params), pwindow
231 );
232}
233vector primarycensored_gengamma_uniform_terms(real t,
234 array[] real params) {
235 if (t <= 0) {
236 return rep_vector(negative_infinity(), 2);
237 }
238 real shape = params[1];
239 real scale = params[2];
240 real k = params[3];
241 real k_shift = k + inv(shape);
242 real log_E = log(scale) + lgamma(k_shift) - lgamma(k);
243 return [
244 log(t) + gengamma_lcdf(t | shape, scale, k),
245 log_E + gengamma_lcdf(t | shape, scale, k_shift)
246 ]';
247}
249 array[] real params,
250 data real pwindow) {
254 );
255}
256real primarycensored_analytical_lcdf_raw(data real d, int dist_id,
257 array[] real params,
258 data real pwindow,
259 int primary_id,
260 array[] real primary_params) {
261 real q = max({d - pwindow, 0});
262
263 if (dist_id == 2 && primary_id == 1) {
264 return primarycensored_gamma_uniform_lcdf(d | q, params, pwindow);
265 } else if (dist_id == 1 && primary_id == 1) {
266 return primarycensored_lognormal_uniform_lcdf(d | q, params, pwindow);
267 } else if (dist_id == 3 && primary_id == 1) {
268 return primarycensored_weibull_uniform_lcdf(d | q, params, pwindow);
269 } else if (dist_id == 5 && primary_id == 1) {
270 return primarycensored_gengamma_uniform_lcdf(d | q, params, pwindow);
271 } else if (dist_id == 26) {
272 // params = [boundaries (K+1), pmf (K)]; length 2*K + 1.
273 int K = (size(params) - 1) %/% 2;
274 return discretestep_lcdf(
275 d | to_vector(segment(params, 1, K + 1)),
276 to_vector(segment(params, K + 2, K)),
277 primary_id, primary_params, pwindow
278 );
279 } else if (dist_id == 27 || dist_id == 28) {
280 // params = [boundaries (K+1), hazards (K)]; length 2*K + 1. The last
281 // hazard must equal 1. RW (27) and RE (28) only differ in their
282 // prior so they share this likelihood dispatch.
283 int K = (size(params) - 1) %/% 2;
284 return discretehazard_lcdf(
285 d | to_vector(segment(params, 1, K + 1)),
286 to_vector(segment(params, K + 2, K)),
287 primary_id, primary_params, pwindow
288 );
289 }
290 return negative_infinity();
291}
292real primarycensored_analytical_lcdf(data real d, int dist_id,
293 array[] real params,
294 data real pwindow, data real L,
295 data real D, int primary_id,
296 array[] real primary_params) {
297 if (d <= L) return negative_infinity();
298 if (d >= D) return 0;
299
301 d, dist_id, params, pwindow, primary_id, primary_params
302 );
303
304 // Apply truncation normalization
305 if (!is_inf(D) || L > 0) {
306 vector[2] bounds = primarycensored_truncation_bounds(
307 L, D, dist_id, params, pwindow, primary_id, primary_params
308 );
309 real log_cdf_L = bounds[1];
310 real log_cdf_D = bounds[2];
311
312 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
313 result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);
314 }
315
316 return result;
317}
318real primarycensored_analytical_cdf(data real d, int dist_id,
319 array[] real params,
320 data real pwindow, data real L,
321 data real D, int primary_id,
322 array[] real primary_params) {
323 return exp(primarycensored_analytical_lcdf(d | dist_id, params, pwindow, L, D, primary_id, primary_params));
324}
325int check_for_analytical_vectorized(int dist_id, int primary_id,
326 data real pwindow) {
327 return check_for_uniform_terms(dist_id, primary_id) &&
328 pwindow >= 1 && floor(pwindow) == pwindow;
329}
330vector primarycensored_uniform_terms(real t, data int dist_id,
331 array[] real params) {
332 if (dist_id == 2) {
333 return primarycensored_gamma_uniform_terms(t, params);
334 } else if (dist_id == 1) {
336 } else if (dist_id == 3) {
338 } else if (dist_id == 5) {
340 }
341 reject("Invalid distribution identifier: ", dist_id);
342}
344 data int n,
345 data int dist_id,
346 array[] real params,
347 data real pwindow) {
348 int pw = to_int(pwindow);
349 vector[n] log_cdfs;
350 // terms[t + 1] holds the terms at delay t
351 array[n + 1] vector[2] terms;
352 for (t in max(start - pw, 0):n) {
353 terms[t + 1] = primarycensored_uniform_terms(t, dist_id, params);
354 }
355 for (d in start:n) {
357 terms[d + 1], terms[max(d - pw, 0) + 1], pwindow
358 );
359 }
360 return log_cdfs;
361}
362int dist_has_positive_support(data int dist_id) {
363 if (dist_id == 1) return 1; // Lognormal
364 if (dist_id == 2) return 1; // Gamma
365 if (dist_id == 3) return 1; // Weibull
366 if (dist_id == 4) return 1; // Exponential
367 if (dist_id == 5) return 1; // Generalised gamma
368 if (dist_id == 9) return 1; // Beta (support on [0, 1])
369 if (dist_id == 13) return 1; // Chi-square
370 if (dist_id == 16) return 1; // Inverse Gamma
371 if (dist_id == 19) return 1; // Inverse Chi-square
372 if (dist_id == 21) return 1; // Pareto
373 if (dist_id == 22) return 1; // Scaled inverse Chi-square
374 return 0;
375}
376real primary_lcdf(real p, int primary_id, array[] real primary_params,
377 data real pwindow) {
378 if (primary_id == 1) {
379 // Uniform on [0, pwindow]: built-in uniform_lcdf matches the package
380 // primary semantics over [0, pwindow].
381 if (p <= 0) return negative_infinity();
382 if (p >= pwindow) return 0;
383 return uniform_lcdf(p | 0, pwindow);
384 } else if (primary_id == 2) {
385 return expgrowth_lcdf(p | 0, pwindow, primary_params[1]);
386 }
387 reject("primary_lcdf: unsupported primary_id ", primary_id);
388}
389int lognormal_lcdf_underflows(real y, real mu, real sigma) {
390 if (y <= 0) {
391 return 1;
392 }
393 return (log(y) - mu) / sigma < -38 ? 1 : 0;
394}
395real gengamma_lcdf(real y, real shape, real scale, real k) {
396 return gamma_lcdf(pow(y / scale, shape) | k, 1);
397}
398real dist_lcdf(real delay, array[] real params, int dist_id) {
399 if (dist_has_positive_support(dist_id) && delay <= 0) {
400 return negative_infinity();
401 }
402
403 // IDs match pcd_distributions$stan_id in R
404 // Guarded so a lower-tail underflow cannot put a NaN partial on the tape.
405 // The downstream `exp(-inf)` differentiates to 0.
406 if (dist_id == 1) {
407 return lognormal_lcdf_underflows(delay, params[1], params[2])
408 ? negative_infinity()
409 : lognormal_lcdf(delay | params[1], params[2]);
410 }
411 else if (dist_id == 2) return gamma_lcdf(delay | params[1], params[2]);
412 else if (dist_id == 3) return weibull_lcdf(delay | params[1], params[2]);
413 else if (dist_id == 4) return exponential_lcdf(delay | params[1]);
414 else if (dist_id == 5) return gengamma_lcdf(delay | params[1], params[2], params[3]);
415 else if (dist_id == 9) return beta_lcdf(delay | params[1], params[2]);
416 else if (dist_id == 12) return cauchy_lcdf(delay | params[1], params[2]);
417 else if (dist_id == 13) return chi_square_lcdf(delay | params[1]);
418 else if (dist_id == 15) return gumbel_lcdf(delay | params[1], params[2]);
419 else if (dist_id == 16) return inv_gamma_lcdf(delay | params[1], params[2]);
420 else if (dist_id == 17) return logistic_lcdf(delay | params[1], params[2]);
421 else if (dist_id == 18) return normal_lcdf(delay | params[1], params[2]);
422 else if (dist_id == 19) return inv_chi_square_lcdf(delay | params[1]);
423 else if (dist_id == 20) return double_exponential_lcdf(delay | params[1], params[2]);
424 else if (dist_id == 21) return pareto_lcdf(delay | params[1], params[2]);
425 else if (dist_id == 22) return scaled_inv_chi_square_lcdf(delay | params[1], params[2]);
426 else if (dist_id == 23) return student_t_lcdf(delay | params[1], params[2], params[3]);
427 else if (dist_id == 24) return uniform_lcdf(delay | params[1], params[2]);
428 else if (dist_id == 25) return von_mises_lcdf(delay | params[1], params[2]);
429 else if (dist_id == 26) {
430 // Non-parametric step: params = [boundaries (K+1), pmf (K)].
431 int K = (size(params) - 1) %/% 2;
432 return pstep_lcdf(
433 delay | to_vector(segment(params, 1, K + 1)),
434 to_vector(segment(params, K + 2, K))
435 );
436 }
437 else if (dist_id == 27 || dist_id == 28) {
438 // Non-parametric discrete hazard: params = [boundaries (K+1),
439 // hazards (K)] with hazards[K] = 1. RW (27) and RE (28) share the
440 // same likelihood; they only differ in the prior.
441 int K = (size(params) - 1) %/% 2;
442 return phazard_lcdf(
443 delay | to_vector(segment(params, 1, K + 1)),
444 to_vector(segment(params, K + 2, K))
445 );
446 }
447 else reject("Invalid distribution identifier: ", dist_id);
448}
449real primary_lpdf(real x, int primary_id, array[] real params, real xmin, real xmax) {
450 // Implement switch for different primary distributions
451 if (primary_id == 1) return uniform_lpdf(x | xmin, xmax);
452 if (primary_id == 2) return expgrowth_lpdf(x | xmin, xmax, params[1]);
453 // Add more primary distributions as needed
454 reject("Invalid primary distribution identifier");
455}
456vector primarycensored_ode(real t, vector y, array[] real theta,
457 array[] real x_r, array[] int x_i) {
458 real d = x_r[1];
459 int dist_id = x_i[1];
460 int primary_id = x_i[2];
461 real pwindow = x_r[2];
462 int dist_params_len = x_i[3];
463 int primary_params_len = x_i[4];
464
465 // Extract distribution parameters
466 array[dist_params_len] real params;
467 if (dist_params_len) {
468 params = theta[1:dist_params_len];
469 }
470 array[primary_params_len] real primary_params;
471 if (primary_params_len) {
472 int primary_loc = num_elements(theta);
473 primary_params = theta[primary_loc - primary_params_len + 1:primary_loc];
474 }
475
476 real log_cdf = dist_lcdf(t | params, dist_id);
477 real log_primary_pdf = primary_lpdf(d - t | primary_id, primary_params, 0, pwindow);
478
479 return rep_vector(exp(log_cdf + log_primary_pdf), 1);
480}
481real primarycensored_log_normalizer(real log_cdf_D, real log_cdf_L, real L) {
482 if (!is_inf(L)) {
483 return log_diff_exp(log_cdf_D, log_cdf_L);
484 } else {
485 return log_cdf_D;
486 }
487}
488real primarycensored_apply_truncation(real log_cdf, real log_cdf_L,
489 real log_normalizer, real L) {
490 if (!is_inf(L)) {
491 return log_diff_exp(log_cdf, log_cdf_L) - log_normalizer;
492 } else {
493 return log_cdf - log_normalizer;
494 }
495}
497 data real L, data real D,
498 data int dist_id, array[] real params, data real pwindow,
499 data int primary_id, array[] real primary_params
500) {
501 vector[2] result;
502 // Internal lower bound for the un-truncated distribution: 0 lets the
503 // `d <= L` early-exit in primarycensored_lcdf return -inf for delays below
504 // the natural support of positive-support distributions; -inf disables that
505 // short-circuit so distributions with support on the reals are integrated.
506 // Expression is inlined (rather than bound to a local) so Stan's data-flow
507 // checker recognises it as data-only.
508
509 // Get CDF at lower truncation point L
510 if (is_inf(L)) {
511 result[1] = negative_infinity();
512 } else {
513 result[1] = primarycensored_lcdf(
514 L | dist_id, params, pwindow,
515 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
516 positive_infinity(), primary_id, primary_params
517 );
518 }
519
520 // Get CDF at upper truncation point D
521 if (is_inf(D)) {
522 result[2] = 0;
523 } else {
524 result[2] = primarycensored_lcdf(
525 D | dist_id, params, pwindow,
526 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
527 positive_infinity(), primary_id, primary_params
528 );
529 }
530
531 return result;
532}
533real primarycensored_cdf(data real d, data int dist_id, array[] real params,
534 data real pwindow, data real L, data real D,
535 data int primary_id,
536 array[] real primary_params) {
537 real result;
538 if (d <= L) {
539 return 0;
540 }
541
542 if (d >= D) {
543 return 1;
544 }
545
546 // Check if an analytical solution exists
547 if (check_for_analytical(dist_id, primary_id)) {
548 // Use analytical solution
550 d | dist_id, params, pwindow, L, D, primary_id, primary_params
551 );
552 } else {
553 // Use numerical integration for other cases. The integration variable
554 // ranges over the primary-event time, so the natural lower bound is
555 // d - pwindow. For positive-support delays the integrand `F_delay(t)` is
556 // 0 for t <= 0, so an unclipped lower bound just adds a flat zero region
557 // for negative t. Distributions with support on the reals also accept the
558 // unclipped lower bound directly.
559 real lower_bound = d - pwindow;
560 int n_params = num_elements(params);
561 int n_primary_params = num_elements(primary_params);
562 array[n_params + n_primary_params] real theta = append_array(params, primary_params);
563 array[4] int ids = {dist_id, primary_id, n_params, n_primary_params};
564
565 vector[1] y0 = rep_vector(0.0, 1);
566 result = ode_rk45(primarycensored_ode, y0, lower_bound, {d}, theta, {d, pwindow}, ids)[1, 1];
567
568 // Apply truncation normalization on log scale for numerical stability.
569 // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).
570 if (!is_inf(D) || L > 0 ||
571 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
572 real log_result = log(result);
573 vector[2] bounds = primarycensored_truncation_bounds(
574 L, D, dist_id, params, pwindow, primary_id, primary_params
575 );
576 real log_cdf_L = bounds[1];
577 real log_cdf_D = bounds[2];
578
579 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
581 log_result, log_cdf_L, log_normalizer, L
582 );
583 result = exp(log_result);
584 }
585 }
586
587 return result;
588}
589real primarycensored_lcdf(data real d, data int dist_id, array[] real params,
590 data real pwindow, data real L, data real D,
591 data int primary_id,
592 array[] real primary_params) {
593 real result;
594
595 if (d <= L) {
596 return negative_infinity();
597 }
598
599 if (d >= D) {
600 return 0;
601 }
602
603 // Check if an analytical solution exists. The internal lower bound is 0 for
604 // positive-support delays (lets the d <= L early-exit return -inf for d <= 0)
605 // and -inf for distributions with support on the reals.
606 if (check_for_analytical(dist_id, primary_id)) {
608 d | dist_id, params, pwindow,
609 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
610 positive_infinity(), primary_id, primary_params
611 );
612 } else {
613 // Use numerical integration
614 result = log(primarycensored_cdf(
615 d | dist_id, params, pwindow,
616 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
617 positive_infinity(), primary_id, primary_params
618 ));
619 }
620
621 // Handle truncation normalization. Skip when F(L) = 0 makes it a no-op
622 // (positive support, L <= 0) to avoid the cancelling log_diff_exp.
623 if (!is_inf(D) || L > 0 ||
624 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
625 vector[2] bounds = primarycensored_truncation_bounds(
626 L, D, dist_id, params, pwindow, primary_id, primary_params
627 );
628 real log_cdf_L = bounds[1];
629 real log_cdf_D = bounds[2];
630
631 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
632 result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);
633 }
634
635 return result;
636}
637real primarycensored_lpmf(data int d, data int dist_id, array[] real params,
638 data real pwindow, data real d_upper,
639 data real L, data real D, data int primary_id,
640 array[] real primary_params) {
641 if (d_upper > D) {
642 reject("Upper truncation point is greater than D. It is ", d_upper,
643 " and D is ", D, ". Resolve this by increasing D to be greater or equal to d + swindow or decreasing swindow.");
644 }
645 if (d_upper <= d) {
646 reject("Upper truncation point is less than or equal to d. It is ", d_upper,
647 " and d is ", d, ". Resolve this by increasing d to be less than d_upper.");
648 }
649 if (d < L) {
650 return negative_infinity();
651 }
652 real log_cdf_upper = primarycensored_lcdf(
653 d_upper | dist_id, params, pwindow,
654 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
655 positive_infinity(), primary_id, primary_params
656 );
657 real log_cdf_lower = primarycensored_lcdf(
658 d | dist_id, params, pwindow,
659 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
660 positive_infinity(), primary_id, primary_params
661 );
662
663 // Apply truncation normalization: log((F(d_upper) - F(d)) / (F(D) - F(L))).
664 // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).
665 if (!is_inf(D) || L > 0 ||
666 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
667 real log_cdf_D;
668 real log_cdf_L;
669
670 // Get CDF at lower truncation point L
671 if (is_inf(L)) {
672 // No left truncation (L = -inf sentinel)
673 log_cdf_L = negative_infinity();
674 } else if (d == L) {
675 // Reuse already computed CDF at d
676 log_cdf_L = log_cdf_lower;
677 } else {
678 // Compute CDF at L directly
679 log_cdf_L = primarycensored_lcdf(
680 L | dist_id, params, pwindow,
681 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
682 positive_infinity(), primary_id, primary_params
683 );
684 }
685
686 // Get CDF at upper truncation point D
687 if (d_upper == D) {
688 log_cdf_D = log_cdf_upper;
689 } else if (is_inf(D)) {
690 log_cdf_D = 0;
691 } else {
692 log_cdf_D = primarycensored_lcdf(
693 D | dist_id, params, pwindow,
694 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
695 positive_infinity(), primary_id, primary_params
696 );
697 }
698
699 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
700 return log_diff_exp(log_cdf_upper, log_cdf_lower) - log_normalizer;
701 } else {
702 return log_diff_exp(log_cdf_upper, log_cdf_lower);
703 }
704}
705vector primarycensored_lcdf_vectorized(data int start, data int n,
706 data int dist_id, array[] real params,
707 data real pwindow, data int primary_id,
708 array[] real primary_params) {
709 if (check_for_analytical_vectorized(dist_id, primary_id, pwindow)) {
711 start, n, dist_id, params, pwindow
712 );
713 }
714 vector[n] log_cdfs;
715 // The internal lower bound below is 0 for positive-support delays and -inf
716 // otherwise; it is inlined rather than bound to a local so Stan's type
717 // checker treats it as data-only.
718 for (d in start:n) {
719 log_cdfs[d] = primarycensored_lcdf(
720 d | dist_id, params, pwindow,
721 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
722 positive_infinity(), primary_id, primary_params
723 );
724 }
725 return log_cdfs;
726}
728 data int max_delay, data real L, data real D, data int dist_id,
729 array[] real params, data real pwindow,
730 data int primary_id, array[] real primary_params
731) {
732
733 int upper_interval = max_delay + 1;
734 vector[upper_interval] log_pmfs;
735 vector[upper_interval] log_cdfs;
736 real log_normalizer;
737
738 // Check if D is at least max_delay + 1
739 if (D < upper_interval) {
740 reject("D must be at least max_delay + 1");
741 }
742
743 // Compute log CDFs (without truncation normalization).
744 // Start from max(1, floor(L)) to avoid computing unused CDFs when L > 0;
745 // for L <= 0 (including -inf) start at 1 since F(d) = 0 for d <= 0.
746 int start_idx = (!is_inf(L) && L > 0) ? max(1, to_int(floor(L))) : 1;
748 start_idx, upper_interval, dist_id, params, pwindow, primary_id,
749 primary_params
750 );
751
752 // Get CDF at lower truncation point L
753 real log_cdf_L;
754 if (is_inf(L)) {
755 // No left truncation (L = -inf sentinel)
756 log_cdf_L = negative_infinity();
757 } else if (L >= 1 && L <= upper_interval && floor(L) == L) {
758 // L is a positive integer within computed range, reuse cached value
759 log_cdf_L = log_cdfs[to_int(L)];
760 } else {
761 // L is outside computed range or non-integer, compute directly
762 log_cdf_L = primarycensored_lcdf(
763 L | dist_id, params, pwindow,
764 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
765 positive_infinity(), primary_id, primary_params
766 );
767 }
768
769 // Compute log normalizer: log(F(D) - F(L))
770 real log_cdf_D;
771 if (D > upper_interval) {
772 if (is_inf(D)) {
773 log_cdf_D = 0; // log(1) = 0 for infinite D
774 } else {
775 log_cdf_D = primarycensored_lcdf(
776 D | dist_id, params, pwindow,
777 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
778 positive_infinity(), primary_id, primary_params
779 );
780 }
781 } else {
782 log_cdf_D = log_cdfs[upper_interval];
783 }
784
785 log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
786
787 // Compute log PMFs: log((F(d) - F(d-1)) / (F(D) - F(L)))
788 for (d in 1:upper_interval) {
789 if (d <= L) {
790 // Delay interval [d-1, d) is entirely at or below L
791 log_pmfs[d] = negative_infinity();
792 } else if (d - 1 < L) {
793 // L falls within interval [d-1, d), so compute mass in [L, d)
794 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_L) - log_normalizer;
795 } else if (d == 1 && dist_has_positive_support(dist_id)) {
796 // First interval [0, 1) with L <= 0 and positive-support delay:
797 // F(0) = 0, so PMF = F(1) / normalizer
798 log_pmfs[d] = log_cdfs[d] - log_normalizer;
799 } else if (d == 1) {
800 // First interval [0, 1) with L <= 0 and real-support delay: F(0) is
801 // non-zero in general, so compute it explicitly.
802 real log_cdf_0 = primarycensored_lcdf(
803 0.0 | dist_id, params, pwindow,
804 negative_infinity(), positive_infinity(),
805 primary_id, primary_params
806 );
807 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_0) - log_normalizer;
808 } else {
809 // Standard case: PMF = (F(d) - F(d-1)) / normalizer
810 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdfs[d-1]) - log_normalizer;
811 }
812 }
813
814 return log_pmfs;
815}
817 data int max_delay, data real L, data real D, data int dist_id,
818 array[] real params, data real pwindow,
819 data int primary_id,
820 array[] real primary_params
821) {
822 return exp(
824 max_delay, L, D, dist_id, params, pwindow, primary_id, primary_params
825 )
826 );
827}
real phazard_lcdf(real t, vector boundaries, vector hazards)
real dist_lcdf(real delay, array[] real params, int dist_id)
vector primarycensored_sone_lpmf_vectorized(data int max_delay, data real L, data real D, data int dist_id, array[] real params, data real pwindow, data int primary_id, array[] real primary_params)
real primarycensored_apply_truncation(real log_cdf, real log_cdf_L, real log_normalizer, real L)
real primarycensored_cdf(data real d, data int dist_id, array[] real params, data real pwindow, data real L, data real D, data int primary_id, array[] real primary_params)
real primarycensored_log_normalizer(real log_cdf_D, real log_cdf_L, real L)
real pstep_lcdf(real t, vector boundaries, vector pmf)
real log_weibull_g(real t, real shape, real scale)
vector primarycensored_lcdf_vectorized(data int start, data int n, data int dist_id, array[] real params, data real pwindow, data int primary_id, array[] real primary_params)
vector primarycensored_ode(real t, vector y, array[] real theta, array[] real x_r, array[] int x_i)
real primarycensored_weibull_uniform_lcdf(data real d, real q, array[] real params, data real pwindow)
real primary_lcdf(real p, int primary_id, array[] real primary_params, data real pwindow)
real expgrowth_lcdf(real x, real xmin, real xmax, real r)
vector primarycensored_weibull_uniform_terms(real t, array[] real params)
vector primarycensored_lognormal_uniform_terms(real t, array[] real params)
real expgrowth_lpdf(real x, real xmin, real xmax, real r)
vector primarycensored_gengamma_uniform_terms(real t, array[] real params)
int check_for_uniform_terms(int dist_id, int primary_id)
vector primary_lcdf_vec(vector p, int primary_id, array[] real primary_params, data real pwindow)
int lognormal_lcdf_underflows(real y, real mu, real sigma)
real discretestep_lcdf(data real d, vector boundaries, vector pmf, int primary_id, array[] real primary_params, data real pwindow)
vector hazards_to_pmf(vector hazards)
vector primarycensored_sone_pmf_vectorized(data int max_delay, data real L, data real D, data int dist_id, array[] real params, data real pwindow, data int primary_id, array[] real primary_params)
vector primarycensored_gamma_uniform_terms(real t, array[] real params)
int check_for_analytical_vectorized(int dist_id, int primary_id, data real pwindow)
vector primarycensored_analytical_lcdf_vectorized(data int start, data int n, data int dist_id, array[] real params, data real pwindow)
real primarycensored_analytical_lcdf_raw(data real d, int dist_id, array[] real params, data real pwindow, int primary_id, array[] real primary_params)
real primarycensored_lpmf(data int d, data int dist_id, array[] real params, data real pwindow, data real d_upper, data real L, data real D, data int primary_id, array[] real primary_params)
real primarycensored_lcdf(data real d, data int dist_id, array[] real params, data real pwindow, data real L, data real D, data int primary_id, array[] real primary_params)
real primarycensored_lognormal_uniform_lcdf(data real d, real q, array[] real params, data real pwindow)
real discretehazard_lcdf(data real d, vector boundaries, vector hazards, int primary_id, array[] real primary_params, data real pwindow)
real primarycensored_analytical_lcdf(data real d, int dist_id, array[] real params, data real pwindow, data real L, data real D, int primary_id, array[] real primary_params)
real primarycensored_gengamma_uniform_lcdf(data real d, real q, array[] real params, data real pwindow)
int dist_has_positive_support(data int dist_id)
vector primarycensored_uniform_terms(real t, data int dist_id, array[] real params)
real primarycensored_gamma_uniform_lcdf(data real d, real q, array[] real params, data real pwindow)
real primarycensored_uniform_lcdf_from_terms(vector terms_d, vector terms_q, data real pwindow)
real primary_lpdf(real x, int primary_id, array[] real params, real xmin, real xmax)
real gengamma_lcdf(real y, real shape, real scale, real k)
real expgrowth_cdf(real x, real xmin, real xmax, real r)
vector primarycensored_truncation_bounds(data real L, data real D, data int dist_id, array[] real params, data real pwindow, data int primary_id, array[] real primary_params)
real primarycensored_analytical_cdf(data real d, int dist_id, array[] real params, data real pwindow, data real L, data real D, int primary_id, array[] real primary_params)
int check_for_analytical(int dist_id, int primary_id)