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