primarycensored
Loading...
Searching...
No Matches
primarycensored.stan
Go to the documentation of this file.
1
16real primarycensored_log_normalizer(real log_cdf_D, real log_cdf_L, real L) {
17 if (!is_inf(L)) {
18 return log_diff_exp(log_cdf_D, log_cdf_L);
19 } else {
20 return log_cdf_D;
21 }
22}
23
38real primarycensored_apply_truncation(real log_cdf, real log_cdf_L,
39 real log_normalizer, real L) {
40 if (!is_inf(L)) {
41 return log_diff_exp(log_cdf, log_cdf_L) - log_normalizer;
42 } else {
43 return log_cdf - log_normalizer;
44 }
45}
46
70 data real L, data real D,
71 data int dist_id, array[] real params, data real pwindow,
72 data int primary_id, array[] real primary_params
73) {
74 vector[2] result;
75 // Internal lower bound for the un-truncated distribution: 0 lets the
76 // `d <= L` early-exit in primarycensored_lcdf return -inf for delays below
77 // the natural support of positive-support distributions; -inf disables that
78 // short-circuit so distributions with support on the reals are integrated.
79 // Expression is inlined (rather than bound to a local) so Stan's data-flow
80 // checker recognises it as data-only.
81
82 // Get CDF at lower truncation point L
83 if (is_inf(L)) {
84 result[1] = negative_infinity();
85 } else {
86 result[1] = primarycensored_lcdf(
87 L | dist_id, params, pwindow,
88 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
89 positive_infinity(), primary_id, primary_params
90 );
91 }
92
93 // Get CDF at upper truncation point D
94 if (is_inf(D)) {
95 result[2] = 0;
96 } else {
97 result[2] = primarycensored_lcdf(
98 D | dist_id, params, pwindow,
99 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
100 positive_infinity(), primary_id, primary_params
101 );
102 }
103
104 return result;
105}
106
123real primarycensored_cdf(data real d, data int dist_id, array[] real params,
124 data real pwindow, data real L, data real D,
125 data int primary_id,
126 array[] real primary_params) {
127 real result;
128 if (d <= L) {
129 return 0;
130 }
131
132 if (d >= D) {
133 return 1;
134 }
135
136 // Check if an analytical solution exists
137 if (check_for_analytical(dist_id, primary_id)) {
138 // Use analytical solution
140 d | dist_id, params, pwindow, L, D, primary_id, primary_params
141 );
142 } else {
143 // Use numerical integration for other cases. The integration variable
144 // ranges over the primary-event time, so the natural lower bound is
145 // d - pwindow. For positive-support delays the integrand `F_delay(t)` is
146 // 0 for t <= 0, so an unclipped lower bound just adds a flat zero region
147 // for negative t. Distributions with support on the reals also accept the
148 // unclipped lower bound directly.
149 real lower_bound = d - pwindow;
150 int n_params = num_elements(params);
151 int n_primary_params = num_elements(primary_params);
152 array[n_params + n_primary_params] real theta = append_array(params, primary_params);
153 array[4] int ids = {dist_id, primary_id, n_params, n_primary_params};
154
155 vector[1] y0 = rep_vector(0.0, 1);
156 result = ode_rk45(primarycensored_ode, y0, lower_bound, {d}, theta, {d, pwindow}, ids)[1, 1];
157
158 // Apply truncation normalization on log scale for numerical stability
159 if (!is_inf(D) || !is_inf(L)) {
160 real log_result = log(result);
161 vector[2] bounds = primarycensored_truncation_bounds(
162 L, D, dist_id, params, pwindow, primary_id, primary_params
163 );
164 real log_cdf_L = bounds[1];
165 real log_cdf_D = bounds[2];
166
167 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
169 log_result, log_cdf_L, log_normalizer, L
170 );
171 result = exp(log_result);
172 }
173 }
174
175 return result;
176}
177
209real primarycensored_lcdf(data real d, data int dist_id, array[] real params,
210 data real pwindow, data real L, data real D,
211 data int primary_id,
212 array[] real primary_params) {
213 real result;
214
215 if (d <= L) {
216 return negative_infinity();
217 }
218
219 if (d >= D) {
220 return 0;
221 }
222
223 // Check if an analytical solution exists. The internal lower bound is 0 for
224 // positive-support delays (lets the d <= L early-exit return -inf for d <= 0)
225 // and -inf for distributions with support on the reals.
226 if (check_for_analytical(dist_id, primary_id)) {
228 d | dist_id, params, pwindow,
229 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
230 positive_infinity(), primary_id, primary_params
231 );
232 } else {
233 // Use numerical integration
234 result = log(primarycensored_cdf(
235 d | dist_id, params, pwindow,
236 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
237 positive_infinity(), primary_id, primary_params
238 ));
239 }
240
241 // Handle truncation normalization
242 if (!is_inf(D) || !is_inf(L)) {
243 vector[2] bounds = primarycensored_truncation_bounds(
244 L, D, dist_id, params, pwindow, primary_id, primary_params
245 );
246 real log_cdf_L = bounds[1];
247 real log_cdf_D = bounds[2];
248
249 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
250 result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);
251 }
252
253 return result;
254}
255
289real primarycensored_lpmf(data int d, data int dist_id, array[] real params,
290 data real pwindow, data real d_upper,
291 data real L, data real D, data int primary_id,
292 array[] real primary_params) {
293 if (d_upper > D) {
294 reject("Upper truncation point is greater than D. It is ", d_upper,
295 " and D is ", D, ". Resolve this by increasing D to be greater or equal to d + swindow or decreasing swindow.");
296 }
297 if (d_upper <= d) {
298 reject("Upper truncation point is less than or equal to d. It is ", d_upper,
299 " and d is ", d, ". Resolve this by increasing d to be less than d_upper.");
300 }
301 if (d < L) {
302 return negative_infinity();
303 }
304 real log_cdf_upper = primarycensored_lcdf(
305 d_upper | dist_id, params, pwindow,
306 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
307 positive_infinity(), primary_id, primary_params
308 );
309 real log_cdf_lower = primarycensored_lcdf(
310 d | dist_id, params, pwindow,
311 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
312 positive_infinity(), primary_id, primary_params
313 );
314
315 // Apply truncation normalization: log((F(d_upper) - F(d)) / (F(D) - F(L)))
316 if (!is_inf(D) || !is_inf(L)) {
317 real log_cdf_D;
318 real log_cdf_L;
319
320 // Get CDF at lower truncation point L
321 if (is_inf(L)) {
322 // No left truncation (L = -inf sentinel)
323 log_cdf_L = negative_infinity();
324 } else if (d == L) {
325 // Reuse already computed CDF at d
326 log_cdf_L = log_cdf_lower;
327 } else {
328 // Compute CDF at L directly
329 log_cdf_L = primarycensored_lcdf(
330 L | dist_id, params, pwindow,
331 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
332 positive_infinity(), primary_id, primary_params
333 );
334 }
335
336 // Get CDF at upper truncation point D
337 if (d_upper == D) {
338 log_cdf_D = log_cdf_upper;
339 } else if (is_inf(D)) {
340 log_cdf_D = 0;
341 } else {
342 log_cdf_D = primarycensored_lcdf(
343 D | dist_id, params, pwindow,
344 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
345 positive_infinity(), primary_id, primary_params
346 );
347 }
348
349 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
350 return log_diff_exp(log_cdf_upper, log_cdf_lower) - log_normalizer;
351 } else {
352 return log_diff_exp(log_cdf_upper, log_cdf_lower);
353 }
354}
355
388real primarycensored_pmf(data int d, data int dist_id, array[] real params,
389 data real pwindow, data real d_upper,
390 data real L, data real D, data int primary_id,
391 array[] real primary_params) {
392 return exp(
394 d | dist_id, params, pwindow, d_upper, L, D, primary_id, primary_params
395 )
396 );
397}
398
441 data int max_delay, data real L, data real D, data int dist_id,
442 array[] real params, data real pwindow,
443 data int primary_id, array[] real primary_params
444) {
445
446 int upper_interval = max_delay + 1;
447 vector[upper_interval] log_pmfs;
448 vector[upper_interval] log_cdfs;
449 real log_normalizer;
450
451 // Check if D is at least max_delay + 1
452 if (D < upper_interval) {
453 reject("D must be at least max_delay + 1");
454 }
455
456 // Compute log CDFs (without truncation normalization). The internal lower
457 // bound below is 0 for positive-support delays and -inf otherwise; it is
458 // inlined rather than bound to a local so Stan's type checker treats it as
459 // data-only.
460 // Start from max(1, floor(L)) to avoid computing unused CDFs when L > 0;
461 // for L <= 0 (including -inf) start at 1 since F(d) = 0 for d <= 0.
462 int start_idx = (!is_inf(L) && L > 0) ? max(1, to_int(floor(L))) : 1;
463 for (d in start_idx:upper_interval) {
464 log_cdfs[d] = primarycensored_lcdf(
465 d | dist_id, params, pwindow,
466 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
467 positive_infinity(), primary_id, primary_params
468 );
469 }
470
471 // Get CDF at lower truncation point L
472 real log_cdf_L;
473 if (is_inf(L)) {
474 // No left truncation (L = -inf sentinel)
475 log_cdf_L = negative_infinity();
476 } else if (L >= 1 && L <= upper_interval && floor(L) == L) {
477 // L is a positive integer within computed range, reuse cached value
478 log_cdf_L = log_cdfs[to_int(L)];
479 } else {
480 // L is outside computed range or non-integer, compute directly
481 log_cdf_L = primarycensored_lcdf(
482 L | dist_id, params, pwindow,
483 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
484 positive_infinity(), primary_id, primary_params
485 );
486 }
487
488 // Compute log normalizer: log(F(D) - F(L))
489 real log_cdf_D;
490 if (D > upper_interval) {
491 if (is_inf(D)) {
492 log_cdf_D = 0; // log(1) = 0 for infinite D
493 } else {
494 log_cdf_D = primarycensored_lcdf(
495 D | dist_id, params, pwindow,
496 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
497 positive_infinity(), primary_id, primary_params
498 );
499 }
500 } else {
501 log_cdf_D = log_cdfs[upper_interval];
502 }
503
504 log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
505
506 // Compute log PMFs: log((F(d) - F(d-1)) / (F(D) - F(L)))
507 for (d in 1:upper_interval) {
508 if (d <= L) {
509 // Delay interval [d-1, d) is entirely at or below L
510 log_pmfs[d] = negative_infinity();
511 } else if (d - 1 < L) {
512 // L falls within interval [d-1, d), so compute mass in [L, d)
513 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_L) - log_normalizer;
514 } else if (d == 1 && dist_has_positive_support(dist_id)) {
515 // First interval [0, 1) with L <= 0 and positive-support delay:
516 // F(0) = 0, so PMF = F(1) / normalizer
517 log_pmfs[d] = log_cdfs[d] - log_normalizer;
518 } else if (d == 1) {
519 // First interval [0, 1) with L <= 0 and real-support delay: F(0) is
520 // non-zero in general, so compute it explicitly.
521 real log_cdf_0 = primarycensored_lcdf(
522 0.0 | dist_id, params, pwindow,
523 negative_infinity(), positive_infinity(),
524 primary_id, primary_params
525 );
526 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_0) - log_normalizer;
527 } else {
528 // Standard case: PMF = (F(d) - F(d-1)) / normalizer
529 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdfs[d-1]) - log_normalizer;
530 }
531 }
532
533 return log_pmfs;
534}
535
575 data int max_delay, data real L, data real D, data int dist_id,
576 array[] real params, data real pwindow,
577 data int primary_id,
578 array[] real primary_params
579) {
580 return exp(
582 max_delay, L, D, dist_id, params, pwindow, primary_id, primary_params
583 )
584 );
585}
int check_for_analytical(int dist_id, int primary_id)
int dist_has_positive_support(data int dist_id)
vector primarycensored_ode(real t, vector y, array[] real theta, array[] real x_r, array[] int x_i)
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_pmf(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_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)
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)
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)
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_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)
real primarycensored_apply_truncation(real log_cdf, real log_cdf_L, real log_normalizer, real L)
real primarycensored_log_normalizer(real log_cdf_D, real log_cdf_L, real L)
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)