From 9eac79248efe95b59eb72bf12ff548fff2e9ebae Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Tue, 12 May 2026 10:08:01 -0700 Subject: [PATCH 1/4] Reuse Rt Newton support grid Co-authored-by: Codex --- inst/stan/functions/rt.stan | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/inst/stan/functions/rt.stan b/inst/stan/functions/rt.stan index 0c1a85ce2..0c622176f 100644 --- a/inst/stan/functions/rt.stan +++ b/inst/stan/functions/rt.stan @@ -98,15 +98,20 @@ void rt_lp(array[] real initial_infections_scale, vector bp_effects, * * @ingroup rt_estimation */ -real R_to_r_newton_step(real R, real r, vector pmf) { +real R_to_r_newton_step(real R, real r, vector pmf, vector zero_series) { int len = num_elements(pmf); - vector[len] zero_series = linspaced_vector(len, 0, len - 1); vector[len] exp_r = exp(-r * zero_series); real ret = (R * dot_product(pmf, exp_r) - 1) / (- R * dot_product(pmf .* zero_series, exp_r)); return(ret); } +real R_to_r_newton_step(real R, real r, vector pmf) { + int len = num_elements(pmf); + vector[len] zero_series = linspaced_vector(len, 0, len - 1); + return R_to_r_newton_step(R, r, pmf, zero_series); +} + /** * Estimate the growth rate r from reproduction number R * @@ -128,11 +133,12 @@ real R_to_r_newton_step(real R, real r, vector pmf) { real R_to_r(real R, vector gt_rev_pmf, real abs_tol) { int gt_len = num_elements(gt_rev_pmf); vector[gt_len] gt_pmf = reverse(gt_rev_pmf); - real mean_gt = dot_product(gt_pmf, linspaced_vector(gt_len, 0, gt_len - 1)); + vector[gt_len] zero_series = linspaced_vector(gt_len, 0, gt_len - 1); + real mean_gt = dot_product(gt_pmf, zero_series); real r = fmax((R - 1) / (R * mean_gt), -1); real step = abs_tol + 1; while (abs(step) > abs_tol) { - step = R_to_r_newton_step(R, r, gt_pmf); + step = R_to_r_newton_step(R, r, gt_pmf, zero_series); r -= step; } From 8f131b0eb8c6661d01939b0f321d5d9b93bfd0b9 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Tue, 12 May 2026 10:58:59 -0700 Subject: [PATCH 2/4] Avoid exposed Rt helper overload collision Co-authored-by: Codex --- inst/stan/functions/rt.stan | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/inst/stan/functions/rt.stan b/inst/stan/functions/rt.stan index 0c622176f..f7fc09d23 100644 --- a/inst/stan/functions/rt.stan +++ b/inst/stan/functions/rt.stan @@ -98,7 +98,8 @@ void rt_lp(array[] real initial_infections_scale, vector bp_effects, * * @ingroup rt_estimation */ -real R_to_r_newton_step(real R, real r, vector pmf, vector zero_series) { +real R_to_r_newton_step_with_support(real R, real r, vector pmf, + vector zero_series) { int len = num_elements(pmf); vector[len] exp_r = exp(-r * zero_series); real ret = (R * dot_product(pmf, exp_r) - 1) / @@ -109,7 +110,7 @@ real R_to_r_newton_step(real R, real r, vector pmf, vector zero_series) { real R_to_r_newton_step(real R, real r, vector pmf) { int len = num_elements(pmf); vector[len] zero_series = linspaced_vector(len, 0, len - 1); - return R_to_r_newton_step(R, r, pmf, zero_series); + return R_to_r_newton_step_with_support(R, r, pmf, zero_series); } /** @@ -138,7 +139,7 @@ real R_to_r(real R, vector gt_rev_pmf, real abs_tol) { real r = fmax((R - 1) / (R * mean_gt), -1); real step = abs_tol + 1; while (abs(step) > abs_tol) { - step = R_to_r_newton_step(R, r, gt_pmf, zero_series); + step = R_to_r_newton_step_with_support(R, r, gt_pmf, zero_series); r -= step; } From 187de36a15e1b8ad18fea0a31cbf8bd880508896 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Tue, 12 May 2026 11:53:14 -0700 Subject: [PATCH 3/4] Document Rt Newton support grid Co-authored-by: Codex --- inst/stan/functions/rt.stan | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/stan/functions/rt.stan b/inst/stan/functions/rt.stan index f7fc09d23..fb7c55975 100644 --- a/inst/stan/functions/rt.stan +++ b/inst/stan/functions/rt.stan @@ -94,6 +94,9 @@ void rt_lp(array[] real initial_infections_scale, vector bp_effects, * @param R Reproduction number * @param r Current estimate of the growth rate * @param pmf Generation time probability mass function (first index: 0) + * @param zero_series Precomputed vector of indices `(0, 1, ..., len - 1)` + * used to align `pmf` with lagged terms in the calculation; expected to have + * the same length as `pmf`. * @return The Newton step for updating r * * @ingroup rt_estimation From 9ae081f9498c0e5a1482a4d9184ee601738d53f1 Mon Sep 17 00:00:00 2001 From: Andrew Ho Date: Tue, 12 May 2026 15:05:24 -0700 Subject: [PATCH 4/4] Document Rt Newton optimization Co-authored-by: Codex --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 6152fe738..184ddc14a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -49,6 +49,7 @@ - Delay distribution discretisation now properly accounts for primary event censoring during model fitting, matching the correction already applied on the R side since v1.8.0. This improves accuracy for short delays where the observation window is large relative to the delay. - Left truncation of delay distributions (e.g. excluding generation times of zero) is now handled analytically rather than by zeroing and renormalising, giving more accurate PMFs near the truncation point. +- Reduced repeated Stan work in the Rt-to-growth Newton solver by reusing the generation-time support sequence across iterations. ## Package changes