Skip to contents

This function is used within epidist() to create any custom Stan code which is injected into brms via the stanvars argument. It is unlikely that as a user you will need this function, but we export it nonetheless to be transparent about what exactly is happening inside of a call to epidist().

Usage

epidist_stancode(data, ...)

Arguments

data

An object with class corresponding to an implemented model.

...

Additional arguments passed to fn method.

Value

A list of stanvars objects, or NULL when none are needed.

See also

Examples

data <- sierra_leone_ebola_data |>
  as_epidist_linelist_data(
    pdate_lwr = "date_of_symptom_onset",
    sdate_lwr = "date_of_sample_tested"
  ) |>
  as_epidist_aggregate_data() |>
  as_epidist_marginal_model()
#> ℹ No primary event upper bound provided, using the primary event lower bound + 1 day as the assumed upper bound.
#> ℹ No secondary event upper bound provided, using the secondary event lower bound + 1 day as the assumed upper bound.
#> ℹ No observation time column provided, using 2015-09-14 as the observation date (the maximum of the secondary event upper bound).
#> ! Setting 2394 relative observation times (`relative_obs_time`) greater than 98
#>   (2x the maximum delay) to Inf.
#> ℹ This improves model efficiency by reducing the number of unique observation
#>   times in the data.
#> ℹ The impact on model accuracy should be negligible because these relative
#>   observation times are high enough to cause very limited right truncation.
#> ℹ The original relative observation times are available in
#>   `orig_relative_obs_time`.
#> ℹ Raise `obs_time_threshold` to avoid this behaviour.
family <- epidist_family(data, family = lognormal())
formula <- epidist_formula(data, family = family, formula = mu ~ 1)
#> Warning: Found infinite values in the data, which may cause issues for Stan.
epidist_stancode(data, family = family, formula = formula)
#> [[1]]
#> [[1]]$name
#> [1] ""
#> 
#> [[1]]$sdata
#> NULL
#> 
#> [[1]]$scode
#> [1] "// code chunks used from epidist 1.0.0\n"
#> 
#> [[1]]$block
#> [1] "functions"
#> 
#> [[1]]$position
#> [1] "start"
#> 
#> [[1]]$pll_args
#> character(0)
#> 
#> 
#> [[2]]
#> [[2]]$name
#> [1] ""
#> 
#> [[2]]$sdata
#> NULL
#> 
#> [[2]]$scode
#> [1] "/**\n  * Compute the log probability mass function for a marginal model with\n  * censoring\n  *\n  * This function is designed to be read into R where:\n  * - 'lognormal' is replaced with the target distribution (e.g., 'lognormal')\n  * - 'real mu, real sigma' is replaced with multiple distribution parameters in the format\n  *   \"real paramname1, real paramname2, ...\".\n  * - 'mu, sigma' is replaced with the same parameters as real mu, real sigma but\n  *   reparameterised according to the brms parameterisation for Stan.\n  *\n  * @param y Real value of observed delay\n  * @param real mu, real sigma Distribution parameters (replaced via regex)\n  * @param relative_obs_t Observation time relative to primary window start.\n  *   Passed as D (the relative observation time) to primarycensored.\n  * @param pwindow_width Primary window width (actual time scale)\n  * @param swindow_width Secondary window width (actual time scale)\n  * @param y_upper Upper bound of delay interval\n  * @param delay_min Minimum delay. Passed as L (the left truncation point)\n  *   to primarycensored.\n  * @param primary_params Array of parameters for primary distribution\n  *\n  * @return Log probability mass with censoring adjustment for marginal model\n  */\n  real marginal_lognormal_lpmf(data int y, real mu, real sigma, data real relative_obs_t,\n                            data real pwindow_width, data real swindow_width,\n                            data real y_upper, data real delay_min,\n                            array[] real primary_params) {\n\n  return primarycensored_lpmf(\n      y | 1, {mu, sigma}, pwindow_width, y_upper,\n      delay_min, relative_obs_t, 1, primary_params\n    );\n}"
#> 
#> [[2]]$block
#> [1] "functions"
#> 
#> [[2]]$position
#> [1] "start"
#> 
#> [[2]]$pll_args
#> character(0)
#> 
#> 
#> [[3]]
#> [[3]]$name
#> [1] ""
#> 
#> [[3]]$sdata
#> NULL
#> 
#> [[3]]$scode
#> [1] "// Stan functions from primarycensored version 1.6.0\nreal expgrowth_cdf(real x, real xmin, real xmax, real r) {\n  if (x < xmin) {\n    return 0;\n  }\n  if (x > xmax) {\n    return 1;\n  }\n  if (abs(r) < 1e-10) {\n    return (x - xmin) / (xmax - xmin);\n  }\n  return (exp(r * x) - exp(r * xmin)) / (exp(r * xmax) - exp(r * xmin));\n}\nreal expgrowth_lcdf(real x, real xmin, real xmax, real r) {\n  if (x < xmin) {\n    return negative_infinity();\n  }\n  if (x > xmax) {\n    return 0;\n  }\n  return log(expgrowth_cdf(x | xmin, xmax, r));\n}\nreal expgrowth_lpdf(real x, real xmin, real xmax, real r) {\n  if (x < xmin || x > xmax) {\n    return negative_infinity();\n  }\n  if (abs(r) < 1e-10) {\n    return -log(xmax - xmin);\n  }\n  return log(abs(r)) + r * x -\n    log(abs(exp(r * xmax) - exp(r * xmin)));\n}\nvector primary_lcdf_vec(vector p, int primary_id,\n                        array[] real primary_params, data real pwindow) {\n  int N = num_elements(p);\n  vector[N] out;\n  for (i in 1:N) {\n    out[i] = primary_lcdf(p[i] | primary_id, primary_params, pwindow);\n  }\n  return out;\n}\nreal discretestep_lcdf(\n  data real d, vector boundaries, vector pmf,\n  int primary_id, array[] real primary_params, data real pwindow\n) {\n  int K = num_elements(pmf);\n  // Integration support in u = d - p for p in [0, pwindow]. It is not\n  // clipped at 0 so boundaries that start below zero (delays with negative\n  // support) are handled; for non-negative boundaries the per-bin clip to\n  // [boundaries[k], boundaries[k + 1]] below gives the same result.\n  real u_min = d - pwindow;\n  real u_max = d;\n\n  // Structural-zero short-circuit. Below `boundaries[2]` F_step is zero\n  // and the bin-1 contribution carries `cum_before = 0`, so the integral\n  // collapses to 0. Returning `negative_infinity()` directly keeps\n  // `log(0)` off the autodiff tape so downstream `log_diff_exp(a, -inf)`\n  // evaluates cleanly with a zero gradient w.r.t. `pmf`.\n  if (u_max <= boundaries[2]) return negative_infinity();\n\n  // Sub-interval endpoints in u-space, clipped to [u_min, u_max].\n  vector[K] lo = fmax(u_min, head(boundaries, K));\n  vector[K] hi = fmin(u_max, tail(boundaries, K));\n\n  // F_step is right-continuous and on [b_k, b_{k+1}) takes the value\n  // sum_{j < k} pmf[j] (mass before bin k). cumulative_sum(pmf) gives\n  // the mass through and including bin k, so we shift right by one.\n  vector[K] cum_before;\n  cum_before[1] = 0;\n  if (K > 1) cum_before[2:K] = head(cumulative_sum(pmf), K - 1);\n\n  // 0/1 mask drops bins with `hi <= lo` from the reduction without a\n  // branch in the inner expression. Built on `data`-level inputs.\n  vector[K] active;\n  for (k in 1:K) active[k] = hi[k] > lo[k] ? 1 : 0;\n\n  // F_primary at lo/hi via two vectorised calls; one masked subtraction\n  // gives the per-bin difference for the dot product.\n  vector[K] f_lo = primary_lcdf_vec(d - lo, primary_id, primary_params,\n                                    pwindow);\n  vector[K] f_hi = primary_lcdf_vec(d - hi, primary_id, primary_params,\n                                    pwindow);\n  vector[K] f_diff = (exp(f_lo) - exp(f_hi)) .* active;\n\n  real integral = dot_product(cum_before, f_diff);\n\n  // Tail region [boundaries[K+1], u_max]: F_step = 1, contributing\n  // F_primary(d - tail_start) - F_primary(d - u_max).\n  real tail_start = fmax(boundaries[K + 1], u_min);\n  if (tail_start < u_max) {\n    real fp_tail = exp(primary_lcdf(d - tail_start | primary_id,\n                                    primary_params, pwindow));\n    real fp_end = exp(primary_lcdf(d - u_max | primary_id,\n                                   primary_params, pwindow));\n    integral += fp_tail - fp_end;\n  }\n\n  return log(integral);\n}\nvector hazards_to_pmf(vector hazards) {\n  int K = num_elements(hazards);\n  vector[K] log_surv;\n  log_surv[1] = 0;\n  if (K > 1) {\n    log_surv[2:K] = cumulative_sum(log1m(hazards[1:(K - 1)]));\n  }\n  return hazards .* exp(log_surv);\n}\nreal discretehazard_lcdf(\n  data real d, vector boundaries, vector hazards,\n  int primary_id, array[] real primary_params, data real pwindow\n) {\n  return discretestep_lcdf(\n    d | boundaries, hazards_to_pmf(hazards), primary_id, primary_params,\n    pwindow\n  );\n}\nreal pstep_lcdf(real t, vector boundaries, vector pmf) {\n  int K = num_elements(pmf);\n  if (t < boundaries[2]) return negative_infinity();\n  if (t >= boundaries[K + 1]) return 0;\n  // Right-continuous CDF with jumps at the right edges\n  // boundaries[2], ..., boundaries[K + 1]. F(t) = cum_pmf[k] for\n  // t in [boundaries[k + 1], boundaries[k + 2]); equivalently the\n  // largest k with boundaries[k + 1] <= t. Boundary-on-jump cases\n  // (t == boundaries[k + 1]) advance k, matching R's\n  // `findInterval(left.open = FALSE)`.\n  int k = 1;\n  while (k < K && boundaries[k + 2] <= t) k += 1;\n  return log(cumulative_sum(pmf)[k]);\n}\nreal phazard_lcdf(real t, vector boundaries, vector hazards) {\n  return pstep_lcdf(t | boundaries, hazards_to_pmf(hazards));\n}\nreal primarycensored_log_normalizer(real log_cdf_D, real log_cdf_L, real L) {\n  if (!is_inf(L)) {\n    return log_diff_exp(log_cdf_D, log_cdf_L);\n  } else {\n    return log_cdf_D;\n  }\n}\nreal primarycensored_apply_truncation(real log_cdf, real log_cdf_L,\n                                      real log_normalizer, real L) {\n  if (!is_inf(L)) {\n    return log_diff_exp(log_cdf, log_cdf_L) - log_normalizer;\n  } else {\n    return log_cdf - log_normalizer;\n  }\n}\nvector primarycensored_truncation_bounds(\n  data real L, data real D,\n  data int dist_id, array[] real params, data real pwindow,\n  data int primary_id, array[] real primary_params\n) {\n  vector[2] result;\n  // Internal lower bound for the un-truncated distribution: 0 lets the\n  // `d <= L` early-exit in primarycensored_lcdf return -inf for delays below\n  // the natural support of positive-support distributions; -inf disables that\n  // short-circuit so distributions with support on the reals are integrated.\n  // Expression is inlined (rather than bound to a local) so Stan's data-flow\n  // checker recognises it as data-only.\n\n  // Get CDF at lower truncation point L\n  if (is_inf(L)) {\n    result[1] = negative_infinity();\n  } else {\n    result[1] = primarycensored_lcdf(\n      L | dist_id, params, pwindow,\n      dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n      positive_infinity(), primary_id, primary_params\n    );\n  }\n\n  // Get CDF at upper truncation point D\n  if (is_inf(D)) {\n    result[2] = 0;\n  } else {\n    result[2] = primarycensored_lcdf(\n      D | dist_id, params, pwindow,\n      dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n      positive_infinity(), primary_id, primary_params\n    );\n  }\n\n  return result;\n}\nreal primarycensored_cdf(data real d, data int dist_id, array[] real params,\n                               data real pwindow, data real L, data real D,\n                               data int primary_id,\n                               array[] real primary_params) {\n  real result;\n  if (d <= L) {\n    return 0;\n  }\n\n  if (d >= D) {\n    return 1;\n  }\n\n  // Check if an analytical solution exists\n  if (check_for_analytical(dist_id, primary_id)) {\n    // Use analytical solution\n    result = primarycensored_analytical_cdf(\n      d | dist_id, params, pwindow, L, D, primary_id, primary_params\n    );\n  } else {\n    // Use numerical integration for other cases. The integration variable\n    // ranges over the primary-event time, so the natural lower bound is\n    // d - pwindow. For positive-support delays the integrand `F_delay(t)` is\n    // 0 for t <= 0, so an unclipped lower bound just adds a flat zero region\n    // for negative t. Distributions with support on the reals also accept the\n    // unclipped lower bound directly.\n    real lower_bound = d - pwindow;\n    int n_params = num_elements(params);\n    int n_primary_params = num_elements(primary_params);\n    array[n_params + n_primary_params] real theta = append_array(params, primary_params);\n    array[4] int ids = {dist_id, primary_id, n_params, n_primary_params};\n\n    vector[1] y0 = rep_vector(0.0, 1);\n    result = ode_rk45(primarycensored_ode, y0, lower_bound, {d}, theta, {d, pwindow}, ids)[1, 1];\n\n    // Apply truncation normalization on log scale for numerical stability.\n    // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).\n    if (!is_inf(D) || L > 0 ||\n        (!is_inf(L) && !dist_has_positive_support(dist_id))) {\n      real log_result = log(result);\n      vector[2] bounds = primarycensored_truncation_bounds(\n        L, D, dist_id, params, pwindow, primary_id, primary_params\n      );\n      real log_cdf_L = bounds[1];\n      real log_cdf_D = bounds[2];\n\n      real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);\n      log_result = primarycensored_apply_truncation(\n        log_result, log_cdf_L, log_normalizer, L\n      );\n      result = exp(log_result);\n    }\n  }\n\n  return result;\n}\nreal primarycensored_lcdf(data real d, data int dist_id, array[] real params,\n                                data real pwindow, data real L, data real D,\n                                data int primary_id,\n                                array[] real primary_params) {\n  real result;\n\n  if (d <= L) {\n    return negative_infinity();\n  }\n\n  if (d >= D) {\n    return 0;\n  }\n\n  // Check if an analytical solution exists. The internal lower bound is 0 for\n  // positive-support delays (lets the d <= L early-exit return -inf for d <= 0)\n  // and -inf for distributions with support on the reals.\n  if (check_for_analytical(dist_id, primary_id)) {\n    result = primarycensored_analytical_lcdf(\n      d | dist_id, params, pwindow,\n      dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n      positive_infinity(), primary_id, primary_params\n    );\n  } else {\n    // Use numerical integration\n    result = log(primarycensored_cdf(\n      d | dist_id, params, pwindow,\n      dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n      positive_infinity(), primary_id, primary_params\n    ));\n  }\n\n  // Handle truncation normalization. Skip when F(L) = 0 makes it a no-op\n  // (positive support, L <= 0) to avoid the cancelling log_diff_exp.\n  if (!is_inf(D) || L > 0 ||\n      (!is_inf(L) && !dist_has_positive_support(dist_id))) {\n    vector[2] bounds = primarycensored_truncation_bounds(\n      L, D, dist_id, params, pwindow, primary_id, primary_params\n    );\n    real log_cdf_L = bounds[1];\n    real log_cdf_D = bounds[2];\n\n    real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);\n    result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);\n  }\n\n  return result;\n}\nreal primarycensored_lpmf(data int d, data int dist_id, array[] real params,\n                                data real pwindow, data real d_upper,\n                                data real L, data real D, data int primary_id,\n                                array[] real primary_params) {\n  if (d_upper > D) {\n    reject(\"Upper truncation point is greater than D. It is \", d_upper,\n           \" and D is \", D, \". Resolve this by increasing D to be greater or equal to d + swindow or decreasing swindow.\");\n  }\n  if (d_upper <= d) {\n    reject(\"Upper truncation point is less than or equal to d. It is \", d_upper,\n           \" and d is \", d, \". Resolve this by increasing d to be less than d_upper.\");\n  }\n  if (d < L) {\n    return negative_infinity();\n  }\n  real log_cdf_upper = primarycensored_lcdf(\n    d_upper | dist_id, params, pwindow,\n    dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n    positive_infinity(), primary_id, primary_params\n  );\n  real log_cdf_lower = primarycensored_lcdf(\n    d | dist_id, params, pwindow,\n    dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n    positive_infinity(), primary_id, primary_params\n  );\n\n  // Apply truncation normalization: log((F(d_upper) - F(d)) / (F(D) - F(L))).\n  // Skip when F(L) = 0 makes it a no-op (positive support, L <= 0).\n  if (!is_inf(D) || L > 0 ||\n      (!is_inf(L) && !dist_has_positive_support(dist_id))) {\n    real log_cdf_D;\n    real log_cdf_L;\n\n    // Get CDF at lower truncation point L\n    if (is_inf(L)) {\n      // No left truncation (L = -inf sentinel)\n      log_cdf_L = negative_infinity();\n    } else if (d == L) {\n      // Reuse already computed CDF at d\n      log_cdf_L = log_cdf_lower;\n    } else {\n      // Compute CDF at L directly\n      log_cdf_L = primarycensored_lcdf(\n        L | dist_id, params, pwindow,\n        dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n        positive_infinity(), primary_id, primary_params\n      );\n    }\n\n    // Get CDF at upper truncation point D\n    if (d_upper == D) {\n      log_cdf_D = log_cdf_upper;\n    } else if (is_inf(D)) {\n      log_cdf_D = 0;\n    } else {\n      log_cdf_D = primarycensored_lcdf(\n        D | dist_id, params, pwindow,\n        dist_has_positive_support(dist_id) ? 0.0 : negative_infinity(),\n        positive_infinity(), primary_id, primary_params\n      );\n    }\n\n    real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);\n    return log_diff_exp(log_cdf_upper, log_cdf_lower) - log_normalizer;\n  } else {\n    return log_diff_exp(log_cdf_upper, log_cdf_lower);\n  }\n}\nint check_for_uniform_terms(int dist_id, int primary_id) {\n  if (primary_id != 1) return 0;\n  return dist_id == 2 || dist_id == 1 || dist_id == 3 || dist_id == 5;\n}\nint check_for_analytical(int dist_id, int primary_id) {\n  // Gamma, Lognormal, Weibull and generalised gamma with a Uniform primary\n  if (check_for_uniform_terms(dist_id, primary_id)) return 1;\n  // Keep this primary list in sync with `primary_lcdf`; see the note above.\n  if (dist_id == 26 || dist_id == 27 || dist_id == 28) {\n    return primary_id == 1 || primary_id == 2;\n  }\n  return 0; // No analytical solution for other combinations\n}\nreal primarycensored_uniform_lcdf_from_terms(vector terms_d, vector terms_q,\n                                             data real pwindow) {\n  real log_A = log_sum_exp(terms_d[1], terms_q[2]);\n  real log_B = log_sum_exp(terms_q[1], terms_d[2]);\n  // Deep enough into the lower tail every term underflows together. Both\n  // are then `-inf` and `log_diff_exp` would give NaN, so return the limit\n  // directly.\n  if (log_A == negative_infinity() && log_B == negative_infinity()) {\n    return negative_infinity();\n  }\n  return log_diff_exp(log_A, log_B) - log(pwindow);\n}\nvector primarycensored_gamma_uniform_terms(real t,\n                                           array[] real params) {\n  if (t <= 0) {\n    return rep_vector(negative_infinity(), 2);\n  }\n  real shape = params[1];\n  real rate = params[2];\n  // log E where E = k * theta = shape / rate is the mean of the delay\n  real log_E = log(shape) - log(rate);\n  // F_T(t; k) and the recursion to F_T(t; k+1):\n  // P(k+1, y) = P(k, y) - y^k e^{-y} / Gamma(k+1), with y = rate * t\n  real log_F_T_k = gamma_lcdf(t | shape, rate);\n  real gamma_kp1_pdf_log = shape * log(rate * t) - rate * t\n                           - lgamma(shape + 1);\n  real log_F_T_kp1 = log_diff_exp(log_F_T_k, gamma_kp1_pdf_log);\n  return [log(t) + log_F_T_k, log_E + log_F_T_kp1]';\n}\nreal primarycensored_gamma_uniform_lcdf(data real d, real q,\n                                        array[] real params,\n                                        data real pwindow) {\n  return primarycensored_uniform_lcdf_from_terms(\n    primarycensored_gamma_uniform_terms(d, params),\n    primarycensored_gamma_uniform_terms(q, params), pwindow\n  );\n}\nvector primarycensored_lognormal_uniform_terms(real t,\n                                               array[] real params) {\n  real mu = params[1];\n  real sigma = params[2];\n  real mu_sigma2 = mu + square(sigma);\n  // log E where E = exp(mu + sigma^2/2) is the mean of the delay\n  real log_E = mu + 0.5 * square(sigma);\n  real log_t_F_T = lognormal_lcdf_underflows(t, mu, sigma)\n                   ? negative_infinity()\n                   : log(t) + lognormal_lcdf(t | mu, sigma);\n  real log_E_tF_T = lognormal_lcdf_underflows(t, mu_sigma2, sigma)\n                    ? negative_infinity()\n                    : log_E + lognormal_lcdf(t | mu_sigma2, sigma);\n  return [log_t_F_T, log_E_tF_T]';\n}\nreal primarycensored_lognormal_uniform_lcdf(data real d, real q,\n                                            array[] real params,\n                                            data real pwindow) {\n  return primarycensored_uniform_lcdf_from_terms(\n    primarycensored_lognormal_uniform_terms(d, params),\n    primarycensored_lognormal_uniform_terms(q, params), pwindow\n  );\n}\nreal log_weibull_g(real t, real shape, real scale) {\n  real x = pow(t * inv(scale), shape);\n  real a = 1 + inv(shape);\n  return log(gamma_p(a, x)) + lgamma(a);\n}\nvector primarycensored_weibull_uniform_terms(real t,\n                                             array[] real params) {\n  if (t <= 0) {\n    return rep_vector(negative_infinity(), 2);\n  }\n  real shape = params[1];\n  real scale = params[2];\n  return [\n    log(t) + weibull_lcdf(t | shape, scale),\n    log(scale) + log_weibull_g(t, shape, scale)\n  ]';\n}\nreal primarycensored_weibull_uniform_lcdf(data real d, real q,\n                                          array[] real params,\n                                          data real pwindow) {\n  return primarycensored_uniform_lcdf_from_terms(\n    primarycensored_weibull_uniform_terms(d, params),\n    primarycensored_weibull_uniform_terms(q, params), pwindow\n  );\n}\nvector primarycensored_gengamma_uniform_terms(real t,\n                                              array[] real params) {\n  if (t <= 0) {\n    return rep_vector(negative_infinity(), 2);\n  }\n  real shape = params[1];\n  real scale = params[2];\n  real k = params[3];\n  real k_shift = k + inv(shape);\n  real log_E = log(scale) + lgamma(k_shift) - lgamma(k);\n  return [\n    log(t) + gengamma_lcdf(t | shape, scale, k),\n    log_E + gengamma_lcdf(t | shape, scale, k_shift)\n  ]';\n}\nreal primarycensored_gengamma_uniform_lcdf(data real d, real q,\n                                           array[] real params,\n                                           data real pwindow) {\n  return primarycensored_uniform_lcdf_from_terms(\n    primarycensored_gengamma_uniform_terms(d, params),\n    primarycensored_gengamma_uniform_terms(q, params), pwindow\n  );\n}\nreal primarycensored_analytical_lcdf_raw(data real d, int dist_id,\n                                         array[] real params,\n                                         data real pwindow,\n                                         int primary_id,\n                                         array[] real primary_params) {\n  real q = max({d - pwindow, 0});\n\n  if (dist_id == 2 && primary_id == 1) {\n    return primarycensored_gamma_uniform_lcdf(d | q, params, pwindow);\n  } else if (dist_id == 1 && primary_id == 1) {\n    return primarycensored_lognormal_uniform_lcdf(d | q, params, pwindow);\n  } else if (dist_id == 3 && primary_id == 1) {\n    return primarycensored_weibull_uniform_lcdf(d | q, params, pwindow);\n  } else if (dist_id == 5 && primary_id == 1) {\n    return primarycensored_gengamma_uniform_lcdf(d | q, params, pwindow);\n  } else if (dist_id == 26) {\n    // params = [boundaries (K+1), pmf (K)]; length 2*K + 1.\n    int K = (size(params) - 1) %/% 2;\n    return discretestep_lcdf(\n      d | to_vector(segment(params, 1, K + 1)),\n          to_vector(segment(params, K + 2, K)),\n          primary_id, primary_params, pwindow\n    );\n  } else if (dist_id == 27 || dist_id == 28) {\n    // params = [boundaries (K+1), hazards (K)]; length 2*K + 1. The last\n    // hazard must equal 1. RW (27) and RE (28) only differ in their\n    // prior so they share this likelihood dispatch.\n    int K = (size(params) - 1) %/% 2;\n    return discretehazard_lcdf(\n      d | to_vector(segment(params, 1, K + 1)),\n          to_vector(segment(params, K + 2, K)),\n          primary_id, primary_params, pwindow\n    );\n  }\n  return negative_infinity();\n}\nreal primarycensored_analytical_lcdf(data real d, int dist_id,\n                                           array[] real params,\n                                           data real pwindow, data real L,\n                                           data real D, int primary_id,\n                                           array[] real primary_params) {\n  if (d <= L) return negative_infinity();\n  if (d >= D) return 0;\n\n  real result = primarycensored_analytical_lcdf_raw(\n    d, dist_id, params, pwindow, primary_id, primary_params\n  );\n\n  // Apply truncation normalization\n  if (!is_inf(D) || L > 0) {\n    vector[2] bounds = primarycensored_truncation_bounds(\n      L, D, dist_id, params, pwindow, primary_id, primary_params\n    );\n    real log_cdf_L = bounds[1];\n    real log_cdf_D = bounds[2];\n\n    real log_normalizer = primarycensored_log_normalizer(log_cdf_D, log_cdf_L, L);\n    result = primarycensored_apply_truncation(result, log_cdf_L, log_normalizer, L);\n  }\n\n  return result;\n}\nreal primarycensored_analytical_cdf(data real d, int dist_id,\n                                          array[] real params,\n                                          data real pwindow, data real L,\n                                          data real D, int primary_id,\n                                          array[] real primary_params) {\n  return exp(primarycensored_analytical_lcdf(d | dist_id, params, pwindow, L, D, primary_id, primary_params));\n}\nint dist_has_positive_support(data int dist_id) {\n  if (dist_id == 1) return 1;   // Lognormal\n  if (dist_id == 2) return 1;   // Gamma\n  if (dist_id == 3) return 1;   // Weibull\n  if (dist_id == 4) return 1;   // Exponential\n  if (dist_id == 5) return 1;   // Generalised gamma\n  if (dist_id == 9) return 1;   // Beta (support on [0, 1])\n  if (dist_id == 13) return 1;  // Chi-square\n  if (dist_id == 16) return 1;  // Inverse Gamma\n  if (dist_id == 19) return 1;  // Inverse Chi-square\n  if (dist_id == 21) return 1;  // Pareto\n  if (dist_id == 22) return 1;  // Scaled inverse Chi-square\n  return 0;\n}\nreal primary_lcdf(real p, int primary_id, array[] real primary_params,\n                  data real pwindow) {\n  if (primary_id == 1) {\n    // Uniform on [0, pwindow]: built-in uniform_lcdf matches the package\n    // primary semantics over [0, pwindow].\n    if (p <= 0) return negative_infinity();\n    if (p >= pwindow) return 0;\n    return uniform_lcdf(p | 0, pwindow);\n  } else if (primary_id == 2) {\n    return expgrowth_lcdf(p | 0, pwindow, primary_params[1]);\n  }\n  reject(\"primary_lcdf: unsupported primary_id \", primary_id);\n}\nint lognormal_lcdf_underflows(real y, real mu, real sigma) {\n  if (y <= 0) {\n    return 1;\n  }\n  return (log(y) - mu) / sigma < -38 ? 1 : 0;\n}\nreal gengamma_lcdf(real y, real shape, real scale, real k) {\n  return gamma_lcdf(pow(y / scale, shape) | k, 1);\n}\nreal dist_lcdf(real delay, array[] real params, int dist_id) {\n  if (dist_has_positive_support(dist_id) && delay <= 0) {\n    return negative_infinity();\n  }\n\n  // IDs match pcd_distributions$stan_id in R\n  // Guarded so a lower-tail underflow cannot put a NaN partial on the tape.\n  // The downstream `exp(-inf)` differentiates to 0.\n  if (dist_id == 1) {\n    return lognormal_lcdf_underflows(delay, params[1], params[2])\n           ? negative_infinity()\n           : lognormal_lcdf(delay | params[1], params[2]);\n  }\n  else if (dist_id == 2) return gamma_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 3) return weibull_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 4) return exponential_lcdf(delay | params[1]);\n  else if (dist_id == 5) return gengamma_lcdf(delay | params[1], params[2], params[3]);\n  else if (dist_id == 9) return beta_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 12) return cauchy_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 13) return chi_square_lcdf(delay | params[1]);\n  else if (dist_id == 15) return gumbel_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 16) return inv_gamma_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 17) return logistic_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 18) return normal_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 19) return inv_chi_square_lcdf(delay | params[1]);\n  else if (dist_id == 20) return double_exponential_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 21) return pareto_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 22) return scaled_inv_chi_square_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 23) return student_t_lcdf(delay | params[1], params[2], params[3]);\n  else if (dist_id == 24) return uniform_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 25) return von_mises_lcdf(delay | params[1], params[2]);\n  else if (dist_id == 26) {\n    // Non-parametric step: params = [boundaries (K+1), pmf (K)].\n    int K = (size(params) - 1) %/% 2;\n    return pstep_lcdf(\n      delay | to_vector(segment(params, 1, K + 1)),\n              to_vector(segment(params, K + 2, K))\n    );\n  }\n  else if (dist_id == 27 || dist_id == 28) {\n    // Non-parametric discrete hazard: params = [boundaries (K+1),\n    // hazards (K)] with hazards[K] = 1. RW (27) and RE (28) share the\n    // same likelihood; they only differ in the prior.\n    int K = (size(params) - 1) %/% 2;\n    return phazard_lcdf(\n      delay | to_vector(segment(params, 1, K + 1)),\n              to_vector(segment(params, K + 2, K))\n    );\n  }\n  else reject(\"Invalid distribution identifier: \", dist_id);\n}\nreal primary_lpdf(real x, int primary_id, array[] real params, real xmin, real xmax) {\n  // Implement switch for different primary distributions\n  if (primary_id == 1) return uniform_lpdf(x | xmin, xmax);\n  if (primary_id == 2) return expgrowth_lpdf(x | xmin, xmax, params[1]);\n  // Add more primary distributions as needed\n  reject(\"Invalid primary distribution identifier\");\n}\nvector primarycensored_ode(real t, vector y, array[] real theta,\n                            array[] real x_r, array[] int x_i) {\n  real d = x_r[1];\n  int dist_id = x_i[1];\n  int primary_id = x_i[2];\n  real pwindow = x_r[2];\n  int dist_params_len = x_i[3];\n  int primary_params_len = x_i[4];\n\n  // Extract distribution parameters\n  array[dist_params_len] real params;\n  if (dist_params_len) {\n    params = theta[1:dist_params_len];\n  }\n  array[primary_params_len] real primary_params;\n  if (primary_params_len) {\n    int primary_loc = num_elements(theta);\n    primary_params = theta[primary_loc - primary_params_len + 1:primary_loc];\n  }\n\n  real log_cdf = dist_lcdf(t | params, dist_id);\n  real log_primary_pdf = primary_lpdf(d - t | primary_id, primary_params, 0, pwindow);\n\n  return rep_vector(exp(log_cdf + log_primary_pdf), 1);\n}"
#> 
#> [[3]]$block
#> [1] "functions"
#> 
#> [[3]]$position
#> [1] "start"
#> 
#> [[3]]$pll_args
#> character(0)
#> 
#> 
#> [[4]]
#> [[4]]$name
#> [1] ""
#> 
#> [[4]]$sdata
#> NULL
#> 
#> [[4]]$scode
#> [1] "array[0] real primary_params;"
#> 
#> [[4]]$block
#> [1] "parameters"
#> 
#> [[4]]$position
#> [1] "start"
#> 
#> [[4]]$pll_args
#> character(0)
#> 
#> 
#> attr(,"class")
#> [1] "stanvars"