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 // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).
160 if (!is_inf(D) || L > 0 ||
161 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
162 real log_result = log(result);
163 vector[2] bounds = primarycensored_truncation_bounds(
164 L, D, dist_id, params, pwindow, primary_id, primary_params
165 );
166 real log_cdf_L = bounds[1];
167 real log_cdf_D = bounds[2];
168
169 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
171 log_result, log_cdf_L, log_normalizer, L
172 );
173 result = exp(log_result);
174 }
175 }
176
177 return result;
178}
179
211real primarycensored_lcdf(data real d, data int dist_id, array[] real params,
212 data real pwindow, data real L, data real D,
213 data int primary_id,
214 array[] real primary_params) {
215 real result;
216
217 if (d <= L) {
218 return negative_infinity();
219 }
220
221 if (d >= D) {
222 return 0;
223 }
224
225 // Check if an analytical solution exists. The internal lower bound is 0 for
226 // positive-support delays (lets the d <= L early-exit return -inf for d <= 0)
227 // and -inf for distributions with support on the reals.
228 if (check_for_analytical(dist_id, primary_id)) {
230 d | dist_id, params, pwindow,
231 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
232 positive_infinity(), primary_id, primary_params
233 );
234 } else {
235 // Use numerical integration
236 result = log(primarycensored_cdf(
237 d | dist_id, params, pwindow,
238 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
239 positive_infinity(), primary_id, primary_params
240 ));
241 }
242
243 // Handle truncation normalization. Skip when F(L) = 0 makes it a no-op
244 // (positive support, L <= 0) to avoid the cancelling log_diff_exp.
245 if (!is_inf(D) || L > 0 ||
246 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
247 vector[2] bounds = primarycensored_truncation_bounds(
248 L, D, dist_id, params, pwindow, primary_id, primary_params
249 );
250 real log_cdf_L = bounds[1];
251 real log_cdf_D = bounds[2];
252
253 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
254 result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);
255 }
256
257 return result;
258}
259
293real primarycensored_lpmf(data int d, data int dist_id, array[] real params,
294 data real pwindow, data real d_upper,
295 data real L, data real D, data int primary_id,
296 array[] real primary_params) {
297 if (d_upper > D) {
298 reject("Upper truncation point is greater than D. It is ", d_upper,
299 " and D is ", D, ". Resolve this by increasing D to be greater or equal to d + swindow or decreasing swindow.");
300 }
301 if (d_upper <= d) {
302 reject("Upper truncation point is less than or equal to d. It is ", d_upper,
303 " and d is ", d, ". Resolve this by increasing d to be less than d_upper.");
304 }
305 if (d < L) {
306 return negative_infinity();
307 }
308 real log_cdf_upper = primarycensored_lcdf(
309 d_upper | dist_id, params, pwindow,
310 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
311 positive_infinity(), primary_id, primary_params
312 );
313 real log_cdf_lower = primarycensored_lcdf(
314 d | dist_id, params, pwindow,
315 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
316 positive_infinity(), primary_id, primary_params
317 );
318
319 // Apply truncation normalization: log((F(d_upper) - F(d)) / (F(D) - F(L))).
320 // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).
321 if (!is_inf(D) || L > 0 ||
322 (!is_inf(L) && !dist_has_positive_support(dist_id))) {
323 real log_cdf_D;
324 real log_cdf_L;
325
326 // Get CDF at lower truncation point L
327 if (is_inf(L)) {
328 // No left truncation (L = -inf sentinel)
329 log_cdf_L = negative_infinity();
330 } else if (d == L) {
331 // Reuse already computed CDF at d
332 log_cdf_L = log_cdf_lower;
333 } else {
334 // Compute CDF at L directly
335 log_cdf_L = primarycensored_lcdf(
336 L | dist_id, params, pwindow,
337 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
338 positive_infinity(), primary_id, primary_params
339 );
340 }
341
342 // Get CDF at upper truncation point D
343 if (d_upper == D) {
344 log_cdf_D = log_cdf_upper;
345 } else if (is_inf(D)) {
346 log_cdf_D = 0;
347 } else {
348 log_cdf_D = primarycensored_lcdf(
349 D | dist_id, params, pwindow,
350 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
351 positive_infinity(), primary_id, primary_params
352 );
353 }
354
355 real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
356 return log_diff_exp(log_cdf_upper, log_cdf_lower) - log_normalizer;
357 } else {
358 return log_diff_exp(log_cdf_upper, log_cdf_lower);
359 }
360}
361
394real primarycensored_pmf(data int d, data int dist_id, array[] real params,
395 data real pwindow, data real d_upper,
396 data real L, data real D, data int primary_id,
397 array[] real primary_params) {
398 return exp(
400 d | dist_id, params, pwindow, d_upper, L, D, primary_id, primary_params
401 )
402 );
403}
404
447 data int max_delay, data real L, data real D, data int dist_id,
448 array[] real params, data real pwindow,
449 data int primary_id, array[] real primary_params
450) {
451
452 int upper_interval = max_delay + 1;
453 vector[upper_interval] log_pmfs;
454 vector[upper_interval] log_cdfs;
455 real log_normalizer;
456
457 // Check if D is at least max_delay + 1
458 if (D < upper_interval) {
459 reject("D must be at least max_delay + 1");
460 }
461
462 // Compute log CDFs (without truncation normalization). The internal lower
463 // bound below is 0 for positive-support delays and -inf otherwise; it is
464 // inlined rather than bound to a local so Stan's type checker treats it as
465 // data-only.
466 // Start from max(1, floor(L)) to avoid computing unused CDFs when L > 0;
467 // for L <= 0 (including -inf) start at 1 since F(d) = 0 for d <= 0.
468 int start_idx = (!is_inf(L) && L > 0) ? max(1, to_int(floor(L))) : 1;
469 for (d in start_idx:upper_interval) {
470 log_cdfs[d] = primarycensored_lcdf(
471 d | dist_id, params, pwindow,
472 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
473 positive_infinity(), primary_id, primary_params
474 );
475 }
476
477 // Get CDF at lower truncation point L
478 real log_cdf_L;
479 if (is_inf(L)) {
480 // No left truncation (L = -inf sentinel)
481 log_cdf_L = negative_infinity();
482 } else if (L >= 1 && L <= upper_interval && floor(L) == L) {
483 // L is a positive integer within computed range, reuse cached value
484 log_cdf_L = log_cdfs[to_int(L)];
485 } else {
486 // L is outside computed range or non-integer, compute directly
487 log_cdf_L = primarycensored_lcdf(
488 L | dist_id, params, pwindow,
489 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
490 positive_infinity(), primary_id, primary_params
491 );
492 }
493
494 // Compute log normalizer: log(F(D) - F(L))
495 real log_cdf_D;
496 if (D > upper_interval) {
497 if (is_inf(D)) {
498 log_cdf_D = 0; // log(1) = 0 for infinite D
499 } else {
500 log_cdf_D = primarycensored_lcdf(
501 D | dist_id, params, pwindow,
502 dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),
503 positive_infinity(), primary_id, primary_params
504 );
505 }
506 } else {
507 log_cdf_D = log_cdfs[upper_interval];
508 }
509
510 log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);
511
512 // Compute log PMFs: log((F(d) - F(d-1)) / (F(D) - F(L)))
513 for (d in 1:upper_interval) {
514 if (d <= L) {
515 // Delay interval [d-1, d) is entirely at or below L
516 log_pmfs[d] = negative_infinity();
517 } else if (d - 1 < L) {
518 // L falls within interval [d-1, d), so compute mass in [L, d)
519 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_L) - log_normalizer;
520 } else if (d == 1 && dist_has_positive_support(dist_id)) {
521 // First interval [0, 1) with L <= 0 and positive-support delay:
522 // F(0) = 0, so PMF = F(1) / normalizer
523 log_pmfs[d] = log_cdfs[d] - log_normalizer;
524 } else if (d == 1) {
525 // First interval [0, 1) with L <= 0 and real-support delay: F(0) is
526 // non-zero in general, so compute it explicitly.
527 real log_cdf_0 = primarycensored_lcdf(
528 0.0 | dist_id, params, pwindow,
529 negative_infinity(), positive_infinity(),
530 primary_id, primary_params
531 );
532 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdf_0) - log_normalizer;
533 } else {
534 // Standard case: PMF = (F(d) - F(d-1)) / normalizer
535 log_pmfs[d] = log_diff_exp(log_cdfs[d], log_cdfs[d-1]) - log_normalizer;
536 }
537 }
538
539 return log_pmfs;
540}
541
581 data int max_delay, data real L, data real D, data int dist_id,
582 array[] real params, data real pwindow,
583 data int primary_id,
584 array[] real primary_params
585) {
586 return exp(
588 max_delay, L, D, dist_id, params, pwindow, primary_id, primary_params
589 )
590 );
591}
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)