From 6fab81ffa8ab311bbd00a4ba49bc9227f6619699 Mon Sep 17 00:00:00 2001 From: Bob Carpenter Date: Wed, 11 Feb 2026 15:50:37 -0500 Subject: [PATCH 1/3] corrected bounds; fixes #1313 --- inst/stan/dist_fit.stan | 57 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/inst/stan/dist_fit.stan b/inst/stan/dist_fit.stan index 3285ff59a..1060bd048 100644 --- a/inst/stan/dist_fit.stan +++ b/inst/stan/dist_fit.stan @@ -1,11 +1,11 @@ data { - int dist; // 0: exp; 1: lnorm; 2: gamma + int dist; // 0: exponential; 1: lognormal; 2: gamma int N; vector[N] low; vector[N] up; - array[dist == 0] real lam_mean; - array[dist > 0] real prior_mean; - array[dist > 0] real prior_sd; + array[dist == 0] real lam_mean; + array[dist > 0] real prior_mean; + array[dist > 0] real prior_sd; array[dist == 2] real par_sigma; } @@ -20,7 +20,7 @@ transformed data { } parameters { - array[dist == 0] real lambda; + array[dist == 0] real lambda_raw; array[dist == 1] real mu; array[dist == 1] real sigma; array[dist == 2] real alpha_raw; @@ -28,42 +28,45 @@ parameters { } transformed parameters{ + array[dist == 0] real lambda; array[dist == 2] real alpha; array[dist == 2] real beta; + if (dist == 0) { + real lb = 0.2 / lam_mean[1]; + real ub = 5 / lam_mean[1]; + lambda[1] = lb + (ub - lb) * lambda_raw[1]; + // implies: lambda[1] ~ uniform(lb, ub) + } + if (dist == 2) { alpha[1] = prior_alpha[1] + par_sigma[1] * alpha_raw[1]; beta[1] = prior_beta[1] + par_sigma[1] * beta_raw[1]; + // implies: alpha[1] ~ normal(prior_alpha[1], par_sigma[1]) + // implies: beta[1] ~ normal(prior_beta[1], par_sigma[1]) } } model { if (dist == 0) { - lambda[1] ~ uniform(1 / (5. * lam_mean[1]), 1 / (0.2 * lam_mean[1])); + for (i in 1:N) { + target += log_diff_exp(exponential_lcdf(up[i] | lambda), + exponential_lcdf(low[i] | lambda)); + } } else if (dist == 1) { mu[1] ~ normal(prior_mean[1], 10); - sigma[1] ~ normal(prior_sd[1], 10) T[0,]; + // T[0, ] only needed if loc or scale arguments are params + sigma[1] ~ normal(prior_sd[1], 10); + for (i in 1:N) { + target += log_diff_exp(lognormal_lcdf(up[i] | mu, sigma), + lognormal_lcdf(low[i] | mu, sigma)); + } } else if (dist == 2) { - alpha_raw[1] ~ normal(0, 1); - beta_raw[1] ~ normal(0, 1); - } - - for(i in 1:N){ - if (dist == 0) { - target += log_diff_exp( - exponential_lcdf(up[i] | lambda), - exponential_lcdf(low[i] | lambda) - ); - } else if (dist == 1) { - target += log_diff_exp( - lognormal_lcdf(up[i] | mu, sigma), - lognormal_lcdf(low[i] | mu, sigma) - ); - } else if (dist == 2) { - target += log_diff_exp( - gamma_lcdf(up[i] | alpha, beta), - gamma_lcdf(low[i] | alpha, beta) - ); + alpha_raw[1] ~ std_normal(); + beta_raw[1] ~ std_normal(); + for (i in 1:N) { + target += log_diff_exp(gamma_lcdf(up[i] | alpha, beta), + gamma_lcdf(low[i] | alpha, beta)); } } } From e1294f85b885f890155482310bbb6bf58e9111d8 Mon Sep 17 00:00:00 2001 From: Bob Carpenter Date: Wed, 11 Feb 2026 15:55:46 -0500 Subject: [PATCH 2/3] news for issue #1313 --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 7264cfa51..b1d774a22 100644 --- a/NEWS.md +++ b/NEWS.md @@ -58,6 +58,7 @@ The function interface remains unchanged. ## Model changes +- Fixed bounds error in parameters for `inst/stan/dist_fit.stan` and added necessary data bounds. - MCMC runs are now initialised with parameter values drawn from a distribution that approximates their prior distributions. - Added an option to compute growth rates using an estimator by Parag et al. (2022) based on total infectiousness rather than new infections, see `growth_method` argument in rt_opts(). - Added support for fitting the susceptible population size. From 01a0ea1a765e78b52f711f466c294b70aff411a1 Mon Sep 17 00:00:00 2001 From: Bob Carpenter Date: Wed, 11 Feb 2026 17:30:12 -0500 Subject: [PATCH 3/3] code review doc patches --- NEWS.md | 2 +- inst/stan/dist_fit.stan | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index b1d774a22..dfc3367a3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -58,7 +58,7 @@ The function interface remains unchanged. ## Model changes -- Fixed bounds error in parameters for `inst/stan/dist_fit.stan` and added necessary data bounds. +- Fixed bounds error in parameters for `dist_fit()` to ensure valid prior and likelihood bounds. - MCMC runs are now initialised with parameter values drawn from a distribution that approximates their prior distributions. - Added an option to compute growth rates using an estimator by Parag et al. (2022) based on total infectiousness rather than new infections, see `growth_method` argument in rt_opts(). - Added support for fitting the susceptible population size. diff --git a/inst/stan/dist_fit.stan b/inst/stan/dist_fit.stan index 1060bd048..041ea9234 100644 --- a/inst/stan/dist_fit.stan +++ b/inst/stan/dist_fit.stan @@ -3,13 +3,16 @@ data { int N; vector[N] low; vector[N] up; - array[dist == 0] real lam_mean; + array[dist == 0] real lam_mean; // should not be exactly 0 array[dist > 0] real prior_mean; array[dist > 0] real prior_sd; array[dist == 2] real par_sigma; } transformed data { + if (dist == 0 && lam_mean[1] == 0) { + reject("lam_mean must be strictly positive, but found 0"); + } array[dist == 2] real prior_alpha; array[dist == 2] real prior_beta; @@ -42,8 +45,9 @@ transformed parameters{ if (dist == 2) { alpha[1] = prior_alpha[1] + par_sigma[1] * alpha_raw[1]; beta[1] = prior_beta[1] + par_sigma[1] * beta_raw[1]; - // implies: alpha[1] ~ normal(prior_alpha[1], par_sigma[1]) - // implies: beta[1] ~ normal(prior_beta[1], par_sigma[1]) + // implies: + // alpha[1] ~ normal(prior_alpha[1], par_sigma[1]) T[prior_alpha[1],] + // beta[1] ~ normal(prior_beta[1], par_sigma[1]) T[prior_beta[2],] } } @@ -55,7 +59,7 @@ model { } } else if (dist == 1) { mu[1] ~ normal(prior_mean[1], 10); - // T[0, ] only needed if loc or scale arguments are params + // T[0, ] omitted in following: it's constant with data args sigma[1] ~ normal(prior_sd[1], 10); for (i in 1:N) { target += log_diff_exp(lognormal_lcdf(up[i] | mu, sigma),