From 4b60683b65b723a8adf4bf1a2434aaab5f8f8344 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Sat, 18 Jul 2026 12:56:35 +0300 Subject: [PATCH 01/12] feat: add laplace_latent_solve(_tol) to signatures and update Typechecker --- src/frontend/Typechecker.ml | 2 ++ src/stan_math_signatures/Stan_math_signatures.ml | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/frontend/Typechecker.ml b/src/frontend/Typechecker.ml index 23a05639d..f83371141 100644 --- a/src/frontend/Typechecker.ml +++ b/src/frontend/Typechecker.ml @@ -919,6 +919,8 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = lik_args @ (hbs_arg :: cov_fun_type :: cov_tupl :: control_args) in let return_type = if String.is_suffix id.name ~suffix:"_rng" then UnsizedType.UVector + else if String.is_substring id.name ~substring:"_solve" then + UnsizedType.UTuple [UVector; UMatrix] else UnsizedType.UReal in mk_fun_app ~is_cond_dist ~loc (StanLib (Fun_kind.suffix_from_name id.name)) diff --git a/src/stan_math_signatures/Stan_math_signatures.ml b/src/stan_math_signatures/Stan_math_signatures.ml index 49dedc2d9..c4f2d5e0a 100644 --- a/src/stan_math_signatures/Stan_math_signatures.ml +++ b/src/stan_math_signatures/Stan_math_signatures.ml @@ -153,8 +153,9 @@ let is_reduce_sum_fn f = let embedded_laplace_functions = [ (* general fns *) "laplace_marginal"; "laplace_marginal_tol" - ; "laplace_latent_rng"; "laplace_latent_tol_rng"; (* "helpers" *) - "laplace_marginal_bernoulli_logit_lpmf" + ; "laplace_latent_rng"; "laplace_latent_tol_rng" + ; "laplace_latent_solve"; "laplace_latent_solve_tol" + ; (* "helpers" *) "laplace_marginal_bernoulli_logit_lpmf" ; "laplace_marginal_tol_bernoulli_logit_lpmf" ; "laplace_marginal_neg_binomial_2_log_lpmf" ; "laplace_marginal_tol_neg_binomial_2_log_lpmf" From ccd9f1c860a685318846fd8d29a596fae893724e Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Sat, 18 Jul 2026 13:13:10 +0300 Subject: [PATCH 02/12] tests: update test and reference file to include laplace_latent_solve(_tol) --- test/integration/good/code-gen/cpp.expected | 293 +++++++++++++----- .../good/code-gen/laplace_functionals.stan | 15 + 2 files changed, 238 insertions(+), 70 deletions(-) diff --git a/test/integration/good/code-gen/cpp.expected b/test/integration/good/code-gen/cpp.expected index 4d1a9b293..09e355264 100644 --- a/test/integration/good/code-gen/cpp.expected +++ b/test/integration/good/code-gen/cpp.expected @@ -7956,18 +7956,27 @@ namespace laplace_functionals_model_namespace { using stan::model::model_base_crtp; using namespace stan::math; stan::math::profile_map profiles__; -static constexpr std::array locations_array__ = +static constexpr std::array locations_array__ = {" (found before start of program)", " (in 'laplace_functionals.stan', line 43, column 2 to column 22)", " (in 'laplace_functionals.stan', line 44, column 2 to column 20)", " (in 'laplace_functionals.stan', line 45, column 2 to column 20)", - " (in 'laplace_functionals.stan', line 62, column 2 to line 64, column 67)", - " (in 'laplace_functionals.stan', line 66, column 2 to line 72, column 99)", + " (in 'laplace_functionals.stan', line 73, column 2 to line 75, column 67)", + " (in 'laplace_functionals.stan', line 77, column 2 to line 83, column 99)", + " (in 'laplace_functionals.stan', line 85, column 2 to line 87, column 65)", " (in 'laplace_functionals.stan', line 48, column 2 to column 55)", " (in 'laplace_functionals.stan', line 49, column 2 to column 61)", " (in 'laplace_functionals.stan', line 50, column 2 to column 21)", " (in 'laplace_functionals.stan', line 52, column 2 to line 53, column 65)", " (in 'laplace_functionals.stan', line 55, column 2 to line 58, column 83)", + " (in 'laplace_functionals.stan', line 60, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 60, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 60, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 60, column 2 to line 62, column 65)", + " (in 'laplace_functionals.stan', line 64, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 64, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 64, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 64, column 2 to line 69, column 77)", " (in 'laplace_functionals.stan', line 19, column 2 to column 12)", " (in 'laplace_functionals.stan', line 20, column 2 to column 20)", " (in 'laplace_functionals.stan', line 21, column 8 to column 13)", @@ -7991,8 +8000,11 @@ static constexpr std::array locations_array__ = " (in 'laplace_functionals.stan', line 38, column 2 to column 17)", " (in 'laplace_functionals.stan', line 39, column 2 to column 32)", " (in 'laplace_functionals.stan', line 40, column 2 to column 28)", - " (in 'laplace_functionals.stan', line 62, column 9 to column 14)", - " (in 'laplace_functionals.stan', line 66, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 73, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 77, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 85, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 85, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 85, column 37 to column 42)", " (in 'laplace_functionals.stan', line 7, column 4 to column 61)", " (in 'laplace_functionals.stan', line 5, column 34 to line 8, column 3)", " (in 'laplace_functionals.stan', line 12, column 11 to column 16)", @@ -8085,7 +8097,7 @@ ll_function(const T0__& theta_arg__, const T1__& eta, const T2__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 36; + current_statement__ = 48; return stan::math::neg_binomial_2_lpmf(y, stan::math::exp(stan::math::add(log_ye, theta)), eta); } catch (const std::exception& e) { @@ -8117,25 +8129,25 @@ K_function(const T0__& x, const T1__& n_obs, const T2__& alpha, const T3__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 38; + current_statement__ = 50; stan::math::validate_non_negative_index("K", "n_obs", n_obs); - current_statement__ = 39; + current_statement__ = 51; stan::math::validate_non_negative_index("K", "n_obs", n_obs); Eigen::Matrix K = Eigen::Matrix::Constant(n_obs, n_obs, DUMMY_VAR__); - current_statement__ = 40; + current_statement__ = 52; stan::model::assign(K, stan::math::gp_exp_quad_cov(x, alpha, rho), "assigning variable K"); - current_statement__ = 42; + current_statement__ = 54; for (int i = 1; i <= n_obs; ++i) { - current_statement__ = 41; + current_statement__ = 53; stan::model::assign(K, (stan::model::rvalue(K, "K", stan::model::index_uni(i), stan::model::index_uni(i)) + 1e-8), "assigning variable K", stan::model::index_uni(i), stan::model::index_uni(i)); } - current_statement__ = 43; + current_statement__ = 55; return K; } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8185,29 +8197,29 @@ class laplace_functionals_model final : public model_base_crtp::min(); pos__ = 1; - current_statement__ = 11; + current_statement__ = 20; context__.validate_dims("data initialization", "n_obs", "int", std::vector{}); n_obs = std::numeric_limits::min(); - current_statement__ = 11; + current_statement__ = 20; n_obs = context__.vals_i("n_obs")[(1 - 1)]; - current_statement__ = 12; + current_statement__ = 21; context__.validate_dims("data initialization", "n_coordinates", "int", std::vector{}); n_coordinates = std::numeric_limits::min(); - current_statement__ = 12; + current_statement__ = 21; n_coordinates = context__.vals_i("n_coordinates")[(1 - 1)]; - current_statement__ = 13; + current_statement__ = 22; stan::math::validate_non_negative_index("y", "n_obs", n_obs); - current_statement__ = 14; + current_statement__ = 23; context__.validate_dims("data initialization", "y", "int", std::vector{static_cast(n_obs)}); y = std::vector(n_obs, std::numeric_limits::min()); - current_statement__ = 14; + current_statement__ = 23; y = context__.vals_i("y"); - current_statement__ = 15; + current_statement__ = 24; stan::math::validate_non_negative_index("ye", "n_obs", n_obs); - current_statement__ = 16; + current_statement__ = 25; context__.validate_dims("data initialization", "ye", "double", std::vector{static_cast(n_obs)}); ye_data__ = Eigen::Matrix::Constant(n_obs, @@ -8216,7 +8228,7 @@ class laplace_functionals_model final : public model_base_crtp ye_flat__; - current_statement__ = 16; + current_statement__ = 25; ye_flat__ = context__.vals_r("ye"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_obs; ++sym1__) { @@ -8225,12 +8237,12 @@ class laplace_functionals_model final : public model_base_crtp{static_cast(n_obs), static_cast(n_coordinates)}); @@ -8239,7 +8251,7 @@ class laplace_functionals_model final : public model_base_crtp::quiet_NaN())); { std::vector x_flat__; - current_statement__ = 19; + current_statement__ = 28; x_flat__ = context__.vals_r("x"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_coordinates; ++sym1__) { @@ -8251,79 +8263,88 @@ class laplace_functionals_model final : public model_base_crtp{}); rho_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 20; + current_statement__ = 29; rho_location_prior = context__.vals_r("rho_location_prior")[(1 - 1)]; - current_statement__ = 21; + current_statement__ = 30; context__.validate_dims("data initialization", "rho_scale_prior", "double", std::vector{}); rho_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 21; + current_statement__ = 30; rho_scale_prior = context__.vals_r("rho_scale_prior")[(1 - 1)]; - current_statement__ = 22; + current_statement__ = 31; context__.validate_dims("data initialization", "alpha_location_prior", "double", std::vector{}); alpha_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 22; + current_statement__ = 31; alpha_location_prior = context__.vals_r("alpha_location_prior")[(1 - 1)]; - current_statement__ = 23; + current_statement__ = 32; context__.validate_dims("data initialization", "alpha_scale_prior", "double", std::vector{}); alpha_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 23; + current_statement__ = 32; alpha_scale_prior = context__.vals_r("alpha_scale_prior")[(1 - 1)]; - current_statement__ = 24; + current_statement__ = 33; stan::math::validate_non_negative_index("log_ye", "n_obs", n_obs); - current_statement__ = 25; + current_statement__ = 34; log_ye_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&log_ye) Eigen::Map>(log_ye_data__.data(), n_obs); - current_statement__ = 25; + current_statement__ = 34; stan::model::assign(log_ye, stan::math::log(ye), "assigning variable log_ye"); - current_statement__ = 26; + current_statement__ = 35; stan::math::validate_non_negative_index("theta_0", "n_obs", n_obs); - current_statement__ = 27; + current_statement__ = 36; theta_0_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&theta_0) Eigen::Map>(theta_0_data__.data(), n_obs); - current_statement__ = 27; + current_statement__ = 36; stan::model::assign(theta_0, stan::math::rep_vector(0.0, n_obs), "assigning variable theta_0"); - current_statement__ = 28; + current_statement__ = 37; tolerance = std::numeric_limits::quiet_NaN(); - current_statement__ = 28; + current_statement__ = 37; tolerance = 1e-6; - current_statement__ = 29; + current_statement__ = 38; max_num_steps = std::numeric_limits::min(); - current_statement__ = 29; + current_statement__ = 38; max_num_steps = 100; - current_statement__ = 30; + current_statement__ = 39; hessian_block_size = std::numeric_limits::min(); - current_statement__ = 30; + current_statement__ = 39; hessian_block_size = 1; - current_statement__ = 31; + current_statement__ = 40; solver = std::numeric_limits::min(); - current_statement__ = 31; + current_statement__ = 40; solver = 1; - current_statement__ = 32; + current_statement__ = 41; max_steps_line_search = std::numeric_limits::min(); - current_statement__ = 32; + current_statement__ = 41; max_steps_line_search = 0; - current_statement__ = 33; + current_statement__ = 42; allow_fallthrough = std::numeric_limits::min(); - current_statement__ = 33; + current_statement__ = 42; allow_fallthrough = 1; - current_statement__ = 34; + current_statement__ = 43; stan::math::validate_non_negative_index("theta", "n_obs", n_obs); - current_statement__ = 35; + current_statement__ = 44; stan::math::validate_non_negative_index("theta2", "n_obs", n_obs); + current_statement__ = 45; + stan::math::validate_non_negative_index("mean_chol_genquan.1", "n_obs", + n_obs); + current_statement__ = 46; + stan::math::validate_non_negative_index("mean_chol_genquan.2", "n_obs", + n_obs); + current_statement__ = 47; + stan::math::validate_non_negative_index("mean_chol_genquan.2", "n_obs", + n_obs); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); } @@ -8373,22 +8394,22 @@ class laplace_functionals_model final : public model_base_crtp(0, lp__); { - current_statement__ = 6; + current_statement__ = 7; lp_accum__.add(stan::math::inv_gamma_lpdf(rho, rho_location_prior, rho_scale_prior)); - current_statement__ = 7; + current_statement__ = 8; lp_accum__.add(stan::math::inv_gamma_lpdf(alpha, alpha_location_prior, alpha_scale_prior)); - current_statement__ = 8; + current_statement__ = 9; lp_accum__.add(stan::math::normal_lpdf(eta, static_cast(0), static_cast(1))); - current_statement__ = 9; + current_statement__ = 10; lp_accum__.add(stan::math::laplace_marginal(ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), hessian_block_size, K_function_functor__(), std::forward_as_tuple(x, n_obs, alpha, rho), pstream__)); - current_statement__ = 10; + current_statement__ = 11; lp_accum__.add(stan::math::laplace_marginal_tol( ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), @@ -8397,6 +8418,51 @@ class laplace_functionals_model final : public model_base_crtp, + Eigen::Matrix> mean_chol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 15; + stan::model::assign(mean_chol, + stan::math::laplace_latent_solve(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), + "assigning variable mean_chol"); + current_statement__ = 16; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 17; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 18; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + std::tuple, + Eigen::Matrix> mean_chol_tol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 19; + stan::model::assign(mean_chol_tol, + stan::math::laplace_latent_solve_tol(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), + std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough), pstream__), + "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8441,22 +8507,22 @@ class laplace_functionals_model final : public model_base_crtp(0, lp__); { - current_statement__ = 6; + current_statement__ = 7; lp_accum__.add(stan::math::inv_gamma_lpdf(rho, rho_location_prior, rho_scale_prior)); - current_statement__ = 7; + current_statement__ = 8; lp_accum__.add(stan::math::inv_gamma_lpdf(alpha, alpha_location_prior, alpha_scale_prior)); - current_statement__ = 8; + current_statement__ = 9; lp_accum__.add(stan::math::normal_lpdf(eta, static_cast(0), static_cast(1))); - current_statement__ = 9; + current_statement__ = 10; lp_accum__.add(stan::math::laplace_marginal(ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), hessian_block_size, K_function_functor__(), std::forward_as_tuple(x, n_obs, alpha, rho), pstream__)); - current_statement__ = 10; + current_statement__ = 11; lp_accum__.add(stan::math::laplace_marginal_tol( ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), @@ -8465,6 +8531,51 @@ class laplace_functionals_model final : public model_base_crtp, + Eigen::Matrix> mean_chol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 15; + stan::model::assign(mean_chol, + stan::math::laplace_latent_solve(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), + "assigning variable mean_chol"); + current_statement__ = 16; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 17; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 18; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + std::tuple, + Eigen::Matrix> mean_chol_tol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 19; + stan::model::assign(mean_chol_tol, + stan::math::laplace_latent_solve_tol(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), + std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough), pstream__), + "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8552,8 +8663,24 @@ class laplace_functionals_model final : public model_base_crtp, Eigen::Matrix> + mean_chol_genquan = + std::tuple, Eigen::Matrix>{ + Eigen::Matrix::Constant(n_obs, + std::numeric_limits::quiet_NaN()), + Eigen::Matrix::Constant(n_obs, n_obs, + std::numeric_limits::quiet_NaN())}; + current_statement__ = 6; + stan::model::assign(mean_chol_genquan, + stan::math::laplace_latent_solve(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), + "assigning variable mean_chol_genquan"); out__.write(theta); out__.write(theta2); + out__.write(std::get<0>(mean_chol_genquan)); + out__.write(std::get<1>(mean_chol_genquan)); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); } @@ -8635,7 +8762,8 @@ class laplace_functionals_model final : public model_base_crtp{"alpha", "rho", "eta"}; if (emit_transformed_parameters__) {} if (emit_generated_quantities__) { - std::vector temp{"theta", "theta2"}; + std::vector + temp{"theta", "theta2", "mean_chol_genquan.1", "mean_chol_genquan.2"}; names__.reserve(names__.size() + temp.size()); names__.insert(names__.end(), temp.begin(), temp.end()); } @@ -8650,7 +8778,10 @@ class laplace_functionals_model final : public model_base_crtp> temp{std::vector{static_cast(n_obs)}, - std::vector{static_cast(n_obs)}}; + std::vector{static_cast(n_obs)}, + std::vector{static_cast(n_obs)}, + std::vector{static_cast(n_obs), + static_cast(n_obs)}}; dimss__.reserve(dimss__.size() + temp.size()); dimss__.insert(dimss__.end(), temp.begin(), temp.end()); } @@ -8672,6 +8803,17 @@ class laplace_functionals_model final : public model_base_crtp inline void @@ -8709,7 +8862,7 @@ class laplace_functionals_model final : public model_base_crtp params_i; @@ -8727,7 +8880,7 @@ class laplace_functionals_model final : public model_base_crtp(num_to_write, diff --git a/test/integration/good/code-gen/laplace_functionals.stan b/test/integration/good/code-gen/laplace_functionals.stan index 8948773b2..7c0d384bc 100644 --- a/test/integration/good/code-gen/laplace_functionals.stan +++ b/test/integration/good/code-gen/laplace_functionals.stan @@ -57,6 +57,17 @@ model { (theta_0, tolerance, max_num_steps, solver, max_steps_line_search, allow_fallthrough)); + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + = laplace_latent_solve(ll_function, (eta, log_ye, y), hessian_block_size, + K_function, (x, n_obs, alpha, rho)); + + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_tol + = laplace_latent_solve_tol(ll_function, (eta, log_ye, y), + hessian_block_size, K_function, + (x, n_obs, alpha, rho), + (theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough)); + } generated quantities { vector[n_obs] theta = laplace_latent_rng(ll_function, (eta, log_ye, y), @@ -71,4 +82,8 @@ generated quantities { (theta_0, tolerance, max_num_steps, solver, max_steps_line_search, allow_fallthrough)); + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_genquan + = laplace_latent_solve(ll_function, (eta, log_ye, y), hessian_block_size, + K_function, (x, n_obs, alpha, rho)); + } From 9f2814ed5c0047920ed0430d122c8064495073bd Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Sat, 18 Jul 2026 13:28:51 +0300 Subject: [PATCH 03/12] tests: add bad pathway tests and update stanc.expected --- .../embedded_laplace/bad_solve_return.stan | 39 ++++++++++++++++++ .../bad/embedded_laplace/bad_solve_tol.stan | 40 +++++++++++++++++++ .../bad/embedded_laplace/stanc.expected | 29 ++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 test/integration/bad/embedded_laplace/bad_solve_return.stan create mode 100644 test/integration/bad/embedded_laplace/bad_solve_tol.stan diff --git a/test/integration/bad/embedded_laplace/bad_solve_return.stan b/test/integration/bad/embedded_laplace/bad_solve_return.stan new file mode 100644 index 000000000..2843f1bce --- /dev/null +++ b/test/integration/bad/embedded_laplace/bad_solve_return.stan @@ -0,0 +1,39 @@ +functions { + // specify negative binomial likelihood with mean offset + real ll_function(vector theta, // latent Gaussian + real eta, vector log_ye, // mean offset + array[] int y) { + // observed count + return neg_binomial_2_lpmf(y | exp(log_ye + theta), eta); + } + + // specify covariance function + matrix K_function(array[] vector x, int n_obs, real alpha, real rho) { + matrix[n_obs, n_obs] K = gp_exp_quad_cov(x, alpha, rho); + for (i in 1 : n_obs) + K[i, i] += 1e-8; + return K; + } +} +data { + int n_obs; + int n_coordinates; + array[n_obs] int y; + vector[n_obs] ye; + array[n_obs] vector[n_coordinates] x; +} + +transformed data { + vector[n_obs] log_ye = log(ye); +} +parameters { + real alpha; + real rho; + real eta; +} + +generated quantities { + // laplace_latent_solve returns tuple(vector, matrix), not vector + vector[n_obs] theta = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, + K_function, (x, n_obs, alpha, rho)); +} diff --git a/test/integration/bad/embedded_laplace/bad_solve_tol.stan b/test/integration/bad/embedded_laplace/bad_solve_tol.stan new file mode 100644 index 000000000..fbec3ff86 --- /dev/null +++ b/test/integration/bad/embedded_laplace/bad_solve_tol.stan @@ -0,0 +1,40 @@ +functions { + // specify negative binomial likelihood with mean offset + real ll_function(vector theta, // latent Gaussian + real eta, vector log_ye, // mean offset + array[] int y) { + // observed count + return neg_binomial_2_lpmf(y | exp(log_ye + theta), eta); + } + + // specify covariance function + matrix K_function(array[] vector x, int n_obs, real alpha, real rho) { + matrix[n_obs, n_obs] K = gp_exp_quad_cov(x, alpha, rho); + for (i in 1 : n_obs) + K[i, i] += 1e-8; + return K; + } +} +data { + int n_obs; + int n_coordinates; + array[n_obs] int y; + vector[n_obs] ye; + array[n_obs] vector[n_coordinates] x; +} + +transformed data { + vector[n_obs] log_ye = log(ye); +} +parameters { + real alpha; + real rho; + real eta; +} + +generated quantities { + // _tol variant requires a trailing control-parameter tuple + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + = laplace_latent_solve_tol(ll_function, (eta, log_ye, y), 1, K_function, + (x, n_obs, alpha, rho)); +} diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index 99aa7728e..cf9f91cb6 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -325,6 +325,35 @@ Semantic error in 'bad_overload.stan', line 2, column 7 to column 51: ------------------------------------------------- Identifier "laplace_marginal_tol_neg_binomial_2_log_lpmf" clashes with a non-overloadable Stan Math library function. +[exit 1] + $ stanc bad_solve_return.stan +Semantic error in 'bad_solve_return.stan', line 37, column 2 to line 38, column 81: + ------------------------------------------------- + 35: generated quantities { + 36: // laplace_latent_solve returns tuple(vector, matrix), not vector + 37: vector[n_obs] theta = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, + ^ + 38: K_function, (x, n_obs, alpha, rho)); + 39: } + ------------------------------------------------- + +Ill-typed assignment statement. +Expected the right hand side to have a type matching the destination (vector). +Instead found type tuple(vector, matrix). +[exit 1] + $ stanc bad_solve_tol.stan +Semantic error in 'bad_solve_tol.stan', line 38, column 8 to line 39, column 56: + ------------------------------------------------- + 36: // _tol variant requires a trailing control-parameter tuple + 37: tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + 38: = laplace_latent_solve_tol(ll_function, (eta, log_ye, y), 1, K_function, + ^ + 39: (x, n_obs, alpha, rho)); + 40: } + ------------------------------------------------- + +Missing control parameter tuple at the end of the call to "laplace_latent_solve_tol". +Expected a tuple of 6 arguments for the control parameters. [exit 1] $ stanc bad_theta0.stan Semantic error in 'bad_theta0.stan', line 43, column 9 to column 16: From 6e2c28bb0f0babc8c2e92729d8153ea9f0d89ef4 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Sat, 18 Jul 2026 15:14:43 +0300 Subject: [PATCH 04/12] fix: run formatter --- src/stan_math_signatures/Stan_math_signatures.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stan_math_signatures/Stan_math_signatures.ml b/src/stan_math_signatures/Stan_math_signatures.ml index c4f2d5e0a..c21409acc 100644 --- a/src/stan_math_signatures/Stan_math_signatures.ml +++ b/src/stan_math_signatures/Stan_math_signatures.ml @@ -153,9 +153,9 @@ let is_reduce_sum_fn f = let embedded_laplace_functions = [ (* general fns *) "laplace_marginal"; "laplace_marginal_tol" - ; "laplace_latent_rng"; "laplace_latent_tol_rng" - ; "laplace_latent_solve"; "laplace_latent_solve_tol" - ; (* "helpers" *) "laplace_marginal_bernoulli_logit_lpmf" + ; "laplace_latent_rng"; "laplace_latent_tol_rng"; "laplace_latent_solve" + ; "laplace_latent_solve_tol"; (* "helpers" *) + "laplace_marginal_bernoulli_logit_lpmf" ; "laplace_marginal_tol_bernoulli_logit_lpmf" ; "laplace_marginal_neg_binomial_2_log_lpmf" ; "laplace_marginal_tol_neg_binomial_2_log_lpmf" From ec9a2f897c7d062f7f573ccdc9ec4a21aa5bfb14 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Sat, 18 Jul 2026 18:04:19 +0300 Subject: [PATCH 05/12] fix: ensure laplace_latent_solve is called in generated quantities block --- test/integration/good/code-gen/cpp.expected | 336 ++++++++---------- .../good/code-gen/laplace_functionals.stan | 20 +- 2 files changed, 154 insertions(+), 202 deletions(-) diff --git a/test/integration/good/code-gen/cpp.expected b/test/integration/good/code-gen/cpp.expected index 09e355264..d94bfa4f3 100644 --- a/test/integration/good/code-gen/cpp.expected +++ b/test/integration/good/code-gen/cpp.expected @@ -7956,27 +7956,20 @@ namespace laplace_functionals_model_namespace { using stan::model::model_base_crtp; using namespace stan::math; stan::math::profile_map profiles__; -static constexpr std::array locations_array__ = +static constexpr std::array locations_array__ = {" (found before start of program)", " (in 'laplace_functionals.stan', line 43, column 2 to column 22)", " (in 'laplace_functionals.stan', line 44, column 2 to column 20)", " (in 'laplace_functionals.stan', line 45, column 2 to column 20)", - " (in 'laplace_functionals.stan', line 73, column 2 to line 75, column 67)", - " (in 'laplace_functionals.stan', line 77, column 2 to line 83, column 99)", - " (in 'laplace_functionals.stan', line 85, column 2 to line 87, column 65)", + " (in 'laplace_functionals.stan', line 62, column 2 to line 64, column 67)", + " (in 'laplace_functionals.stan', line 66, column 2 to line 72, column 99)", + " (in 'laplace_functionals.stan', line 74, column 2 to line 76, column 65)", + " (in 'laplace_functionals.stan', line 78, column 2 to line 83, column 77)", " (in 'laplace_functionals.stan', line 48, column 2 to column 55)", " (in 'laplace_functionals.stan', line 49, column 2 to column 61)", " (in 'laplace_functionals.stan', line 50, column 2 to column 21)", " (in 'laplace_functionals.stan', line 52, column 2 to line 53, column 65)", " (in 'laplace_functionals.stan', line 55, column 2 to line 58, column 83)", - " (in 'laplace_functionals.stan', line 60, column 15 to column 20)", - " (in 'laplace_functionals.stan', line 60, column 30 to column 35)", - " (in 'laplace_functionals.stan', line 60, column 37 to column 42)", - " (in 'laplace_functionals.stan', line 60, column 2 to line 62, column 65)", - " (in 'laplace_functionals.stan', line 64, column 15 to column 20)", - " (in 'laplace_functionals.stan', line 64, column 30 to column 35)", - " (in 'laplace_functionals.stan', line 64, column 37 to column 42)", - " (in 'laplace_functionals.stan', line 64, column 2 to line 69, column 77)", " (in 'laplace_functionals.stan', line 19, column 2 to column 12)", " (in 'laplace_functionals.stan', line 20, column 2 to column 20)", " (in 'laplace_functionals.stan', line 21, column 8 to column 13)", @@ -8000,11 +7993,14 @@ static constexpr std::array locations_array__ = " (in 'laplace_functionals.stan', line 38, column 2 to column 17)", " (in 'laplace_functionals.stan', line 39, column 2 to column 32)", " (in 'laplace_functionals.stan', line 40, column 2 to column 28)", - " (in 'laplace_functionals.stan', line 73, column 9 to column 14)", - " (in 'laplace_functionals.stan', line 77, column 9 to column 14)", - " (in 'laplace_functionals.stan', line 85, column 15 to column 20)", - " (in 'laplace_functionals.stan', line 85, column 30 to column 35)", - " (in 'laplace_functionals.stan', line 85, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 62, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 66, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 74, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 74, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 74, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 78, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 78, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 78, column 37 to column 42)", " (in 'laplace_functionals.stan', line 7, column 4 to column 61)", " (in 'laplace_functionals.stan', line 5, column 34 to line 8, column 3)", " (in 'laplace_functionals.stan', line 12, column 11 to column 16)", @@ -8097,7 +8093,7 @@ ll_function(const T0__& theta_arg__, const T1__& eta, const T2__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 48; + current_statement__ = 44; return stan::math::neg_binomial_2_lpmf(y, stan::math::exp(stan::math::add(log_ye, theta)), eta); } catch (const std::exception& e) { @@ -8129,25 +8125,25 @@ K_function(const T0__& x, const T1__& n_obs, const T2__& alpha, const T3__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 50; + current_statement__ = 46; stan::math::validate_non_negative_index("K", "n_obs", n_obs); - current_statement__ = 51; + current_statement__ = 47; stan::math::validate_non_negative_index("K", "n_obs", n_obs); Eigen::Matrix K = Eigen::Matrix::Constant(n_obs, n_obs, DUMMY_VAR__); - current_statement__ = 52; + current_statement__ = 48; stan::model::assign(K, stan::math::gp_exp_quad_cov(x, alpha, rho), "assigning variable K"); - current_statement__ = 54; + current_statement__ = 50; for (int i = 1; i <= n_obs; ++i) { - current_statement__ = 53; + current_statement__ = 49; stan::model::assign(K, (stan::model::rvalue(K, "K", stan::model::index_uni(i), stan::model::index_uni(i)) + 1e-8), "assigning variable K", stan::model::index_uni(i), stan::model::index_uni(i)); } - current_statement__ = 55; + current_statement__ = 51; return K; } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8197,29 +8193,29 @@ class laplace_functionals_model final : public model_base_crtp::min(); pos__ = 1; - current_statement__ = 20; + current_statement__ = 13; context__.validate_dims("data initialization", "n_obs", "int", std::vector{}); n_obs = std::numeric_limits::min(); - current_statement__ = 20; + current_statement__ = 13; n_obs = context__.vals_i("n_obs")[(1 - 1)]; - current_statement__ = 21; + current_statement__ = 14; context__.validate_dims("data initialization", "n_coordinates", "int", std::vector{}); n_coordinates = std::numeric_limits::min(); - current_statement__ = 21; + current_statement__ = 14; n_coordinates = context__.vals_i("n_coordinates")[(1 - 1)]; - current_statement__ = 22; + current_statement__ = 15; stan::math::validate_non_negative_index("y", "n_obs", n_obs); - current_statement__ = 23; + current_statement__ = 16; context__.validate_dims("data initialization", "y", "int", std::vector{static_cast(n_obs)}); y = std::vector(n_obs, std::numeric_limits::min()); - current_statement__ = 23; + current_statement__ = 16; y = context__.vals_i("y"); - current_statement__ = 24; + current_statement__ = 17; stan::math::validate_non_negative_index("ye", "n_obs", n_obs); - current_statement__ = 25; + current_statement__ = 18; context__.validate_dims("data initialization", "ye", "double", std::vector{static_cast(n_obs)}); ye_data__ = Eigen::Matrix::Constant(n_obs, @@ -8228,7 +8224,7 @@ class laplace_functionals_model final : public model_base_crtp ye_flat__; - current_statement__ = 25; + current_statement__ = 18; ye_flat__ = context__.vals_r("ye"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_obs; ++sym1__) { @@ -8237,12 +8233,12 @@ class laplace_functionals_model final : public model_base_crtp{static_cast(n_obs), static_cast(n_coordinates)}); @@ -8251,7 +8247,7 @@ class laplace_functionals_model final : public model_base_crtp::quiet_NaN())); { std::vector x_flat__; - current_statement__ = 28; + current_statement__ = 21; x_flat__ = context__.vals_r("x"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_coordinates; ++sym1__) { @@ -8263,87 +8259,93 @@ class laplace_functionals_model final : public model_base_crtp{}); rho_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 29; + current_statement__ = 22; rho_location_prior = context__.vals_r("rho_location_prior")[(1 - 1)]; - current_statement__ = 30; + current_statement__ = 23; context__.validate_dims("data initialization", "rho_scale_prior", "double", std::vector{}); rho_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 30; + current_statement__ = 23; rho_scale_prior = context__.vals_r("rho_scale_prior")[(1 - 1)]; - current_statement__ = 31; + current_statement__ = 24; context__.validate_dims("data initialization", "alpha_location_prior", "double", std::vector{}); alpha_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 31; + current_statement__ = 24; alpha_location_prior = context__.vals_r("alpha_location_prior")[(1 - 1)]; - current_statement__ = 32; + current_statement__ = 25; context__.validate_dims("data initialization", "alpha_scale_prior", "double", std::vector{}); alpha_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 32; + current_statement__ = 25; alpha_scale_prior = context__.vals_r("alpha_scale_prior")[(1 - 1)]; - current_statement__ = 33; + current_statement__ = 26; stan::math::validate_non_negative_index("log_ye", "n_obs", n_obs); - current_statement__ = 34; + current_statement__ = 27; log_ye_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&log_ye) Eigen::Map>(log_ye_data__.data(), n_obs); - current_statement__ = 34; + current_statement__ = 27; stan::model::assign(log_ye, stan::math::log(ye), "assigning variable log_ye"); - current_statement__ = 35; + current_statement__ = 28; stan::math::validate_non_negative_index("theta_0", "n_obs", n_obs); - current_statement__ = 36; + current_statement__ = 29; theta_0_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&theta_0) Eigen::Map>(theta_0_data__.data(), n_obs); - current_statement__ = 36; + current_statement__ = 29; stan::model::assign(theta_0, stan::math::rep_vector(0.0, n_obs), "assigning variable theta_0"); - current_statement__ = 37; + current_statement__ = 30; tolerance = std::numeric_limits::quiet_NaN(); - current_statement__ = 37; + current_statement__ = 30; tolerance = 1e-6; - current_statement__ = 38; + current_statement__ = 31; max_num_steps = std::numeric_limits::min(); - current_statement__ = 38; + current_statement__ = 31; max_num_steps = 100; - current_statement__ = 39; + current_statement__ = 32; hessian_block_size = std::numeric_limits::min(); - current_statement__ = 39; + current_statement__ = 32; hessian_block_size = 1; - current_statement__ = 40; + current_statement__ = 33; solver = std::numeric_limits::min(); - current_statement__ = 40; + current_statement__ = 33; solver = 1; - current_statement__ = 41; + current_statement__ = 34; max_steps_line_search = std::numeric_limits::min(); - current_statement__ = 41; + current_statement__ = 34; max_steps_line_search = 0; - current_statement__ = 42; + current_statement__ = 35; allow_fallthrough = std::numeric_limits::min(); - current_statement__ = 42; + current_statement__ = 35; allow_fallthrough = 1; - current_statement__ = 43; + current_statement__ = 36; stan::math::validate_non_negative_index("theta", "n_obs", n_obs); - current_statement__ = 44; + current_statement__ = 37; stan::math::validate_non_negative_index("theta2", "n_obs", n_obs); - current_statement__ = 45; - stan::math::validate_non_negative_index("mean_chol_genquan.1", "n_obs", + current_statement__ = 38; + stan::math::validate_non_negative_index("mean_chol.1", "n_obs", n_obs); + current_statement__ = 39; + stan::math::validate_non_negative_index("mean_chol.2", "n_obs", n_obs); + current_statement__ = 40; + stan::math::validate_non_negative_index("mean_chol.2", "n_obs", n_obs); + current_statement__ = 41; + stan::math::validate_non_negative_index("mean_chol_tol.1", "n_obs", n_obs); - current_statement__ = 46; - stan::math::validate_non_negative_index("mean_chol_genquan.2", "n_obs", + current_statement__ = 42; + stan::math::validate_non_negative_index("mean_chol_tol.2", "n_obs", n_obs); - current_statement__ = 47; - stan::math::validate_non_negative_index("mean_chol_genquan.2", "n_obs", + current_statement__ = 43; + stan::math::validate_non_negative_index("mean_chol_tol.2", "n_obs", n_obs); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8394,22 +8396,22 @@ class laplace_functionals_model final : public model_base_crtp(0, lp__); { - current_statement__ = 7; + current_statement__ = 8; lp_accum__.add(stan::math::inv_gamma_lpdf(rho, rho_location_prior, rho_scale_prior)); - current_statement__ = 8; + current_statement__ = 9; lp_accum__.add(stan::math::inv_gamma_lpdf(alpha, alpha_location_prior, alpha_scale_prior)); - current_statement__ = 9; + current_statement__ = 10; lp_accum__.add(stan::math::normal_lpdf(eta, static_cast(0), static_cast(1))); - current_statement__ = 10; + current_statement__ = 11; lp_accum__.add(stan::math::laplace_marginal(ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), hessian_block_size, K_function_functor__(), std::forward_as_tuple(x, n_obs, alpha, rho), pstream__)); - current_statement__ = 11; + current_statement__ = 12; lp_accum__.add(stan::math::laplace_marginal_tol( ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), @@ -8418,51 +8420,6 @@ class laplace_functionals_model final : public model_base_crtp, - Eigen::Matrix> mean_chol = - std::tuple, - Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, - DUMMY_VAR__), - Eigen::Matrix::Constant(n_obs, n_obs, - DUMMY_VAR__)}; - current_statement__ = 15; - stan::model::assign(mean_chol, - stan::math::laplace_latent_solve(ll_function_functor__(), - std::forward_as_tuple(eta, log_ye, y), hessian_block_size, - K_function_functor__(), - std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), - "assigning variable mean_chol"); - current_statement__ = 16; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - current_statement__ = 17; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - current_statement__ = 18; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - std::tuple, - Eigen::Matrix> mean_chol_tol = - std::tuple, - Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, - DUMMY_VAR__), - Eigen::Matrix::Constant(n_obs, n_obs, - DUMMY_VAR__)}; - current_statement__ = 19; - stan::model::assign(mean_chol_tol, - stan::math::laplace_latent_solve_tol(ll_function_functor__(), - std::forward_as_tuple(eta, log_ye, y), hessian_block_size, - K_function_functor__(), - std::forward_as_tuple(x, n_obs, alpha, rho), - std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, - max_steps_line_search, allow_fallthrough), pstream__), - "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8507,22 +8464,22 @@ class laplace_functionals_model final : public model_base_crtp(0, lp__); { - current_statement__ = 7; + current_statement__ = 8; lp_accum__.add(stan::math::inv_gamma_lpdf(rho, rho_location_prior, rho_scale_prior)); - current_statement__ = 8; + current_statement__ = 9; lp_accum__.add(stan::math::inv_gamma_lpdf(alpha, alpha_location_prior, alpha_scale_prior)); - current_statement__ = 9; + current_statement__ = 10; lp_accum__.add(stan::math::normal_lpdf(eta, static_cast(0), static_cast(1))); - current_statement__ = 10; + current_statement__ = 11; lp_accum__.add(stan::math::laplace_marginal(ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), hessian_block_size, K_function_functor__(), std::forward_as_tuple(x, n_obs, alpha, rho), pstream__)); - current_statement__ = 11; + current_statement__ = 12; lp_accum__.add(stan::math::laplace_marginal_tol( ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), @@ -8531,51 +8488,6 @@ class laplace_functionals_model final : public model_base_crtp, - Eigen::Matrix> mean_chol = - std::tuple, - Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, - DUMMY_VAR__), - Eigen::Matrix::Constant(n_obs, n_obs, - DUMMY_VAR__)}; - current_statement__ = 15; - stan::model::assign(mean_chol, - stan::math::laplace_latent_solve(ll_function_functor__(), - std::forward_as_tuple(eta, log_ye, y), hessian_block_size, - K_function_functor__(), - std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), - "assigning variable mean_chol"); - current_statement__ = 16; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - current_statement__ = 17; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - current_statement__ = 18; - stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", - n_obs); - std::tuple, - Eigen::Matrix> mean_chol_tol = - std::tuple, - Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, - DUMMY_VAR__), - Eigen::Matrix::Constant(n_obs, n_obs, - DUMMY_VAR__)}; - current_statement__ = 19; - stan::model::assign(mean_chol_tol, - stan::math::laplace_latent_solve_tol(ll_function_functor__(), - std::forward_as_tuple(eta, log_ye, y), hessian_block_size, - K_function_functor__(), - std::forward_as_tuple(x, n_obs, alpha, rho), - std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, - max_steps_line_search, allow_fallthrough), pstream__), - "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8664,23 +8576,41 @@ class laplace_functionals_model final : public model_base_crtp, Eigen::Matrix> - mean_chol_genquan = + mean_chol = std::tuple, Eigen::Matrix>{ Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()), Eigen::Matrix::Constant(n_obs, n_obs, std::numeric_limits::quiet_NaN())}; current_statement__ = 6; - stan::model::assign(mean_chol_genquan, + stan::model::assign(mean_chol, stan::math::laplace_latent_solve(ll_function_functor__(), std::forward_as_tuple(eta, log_ye, y), hessian_block_size, K_function_functor__(), std::forward_as_tuple(x, n_obs, alpha, rho), pstream__), - "assigning variable mean_chol_genquan"); + "assigning variable mean_chol"); + std::tuple, Eigen::Matrix> + mean_chol_tol = + std::tuple, Eigen::Matrix>{ + Eigen::Matrix::Constant(n_obs, + std::numeric_limits::quiet_NaN()), + Eigen::Matrix::Constant(n_obs, n_obs, + std::numeric_limits::quiet_NaN())}; + current_statement__ = 7; + stan::model::assign(mean_chol_tol, + stan::math::laplace_latent_solve_tol(ll_function_functor__(), + std::forward_as_tuple(eta, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, alpha, rho), + std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough), pstream__), + "assigning variable mean_chol_tol"); out__.write(theta); out__.write(theta2); - out__.write(std::get<0>(mean_chol_genquan)); - out__.write(std::get<1>(mean_chol_genquan)); + out__.write(std::get<0>(mean_chol)); + out__.write(std::get<1>(mean_chol)); + out__.write(std::get<0>(mean_chol_tol)); + out__.write(std::get<1>(mean_chol_tol)); } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); } @@ -8763,7 +8693,8 @@ class laplace_functionals_model final : public model_base_crtp - temp{"theta", "theta2", "mean_chol_genquan.1", "mean_chol_genquan.2"}; + temp{"theta", "theta2", "mean_chol.1", "mean_chol.2", + "mean_chol_tol.1", "mean_chol_tol.2"}; names__.reserve(names__.size() + temp.size()); names__.insert(names__.end(), temp.begin(), temp.end()); } @@ -8780,6 +8711,9 @@ class laplace_functionals_model final : public model_base_crtp{static_cast(n_obs)}, std::vector{static_cast(n_obs)}, std::vector{static_cast(n_obs)}, + std::vector{static_cast(n_obs), + static_cast(n_obs)}, + std::vector{static_cast(n_obs)}, std::vector{static_cast(n_obs), static_cast(n_obs)}}; dimss__.reserve(dimss__.size() + temp.size()); @@ -8804,13 +8738,24 @@ class laplace_functionals_model final : public model_base_crtp inline void @@ -8862,7 +8818,7 @@ class laplace_functionals_model final : public model_base_crtp params_i; @@ -8880,7 +8836,7 @@ class laplace_functionals_model final : public model_base_crtp(num_to_write, diff --git a/test/integration/good/code-gen/laplace_functionals.stan b/test/integration/good/code-gen/laplace_functionals.stan index 7c0d384bc..ba0fa4026 100644 --- a/test/integration/good/code-gen/laplace_functionals.stan +++ b/test/integration/good/code-gen/laplace_functionals.stan @@ -57,17 +57,6 @@ model { (theta_0, tolerance, max_num_steps, solver, max_steps_line_search, allow_fallthrough)); - tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol - = laplace_latent_solve(ll_function, (eta, log_ye, y), hessian_block_size, - K_function, (x, n_obs, alpha, rho)); - - tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_tol - = laplace_latent_solve_tol(ll_function, (eta, log_ye, y), - hessian_block_size, K_function, - (x, n_obs, alpha, rho), - (theta_0, tolerance, max_num_steps, solver, - max_steps_line_search, allow_fallthrough)); - } generated quantities { vector[n_obs] theta = laplace_latent_rng(ll_function, (eta, log_ye, y), @@ -82,8 +71,15 @@ generated quantities { (theta_0, tolerance, max_num_steps, solver, max_steps_line_search, allow_fallthrough)); - tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_genquan + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol = laplace_latent_solve(ll_function, (eta, log_ye, y), hessian_block_size, K_function, (x, n_obs, alpha, rho)); + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_tol + = laplace_latent_solve_tol(ll_function, (eta, log_ye, y), + hessian_block_size, K_function, + (x, n_obs, alpha, rho), + (theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough)); + } From 0d1bb6d08926b9e81904f41197c999f88e761423 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Wed, 29 Jul 2026 08:59:23 +0300 Subject: [PATCH 06/12] fix: update Typechecker; laplace_latent_solve only allowed in GQ block --- src/frontend/Semantic_error.ml | 8 ++++ src/frontend/Semantic_error.mli | 1 + src/frontend/Typechecker.ml | 11 +++++ .../bad/embedded_laplace/bad_solve_model.stan | 40 +++++++++++++++++++ .../bad/embedded_laplace/stanc.expected | 15 +++++++ 5 files changed, 75 insertions(+) create mode 100644 test/integration/bad/embedded_laplace/bad_solve_model.stan diff --git a/src/frontend/Semantic_error.ml b/src/frontend/Semantic_error.ml index 331dbcde4..cb6b2b1e0 100644 --- a/src/frontend/Semantic_error.ml +++ b/src/frontend/Semantic_error.ml @@ -491,6 +491,7 @@ end module ExpressionError = struct type t = + | InvalidLaplaceLatentSolveFunction | InvalidSizeDeclRng | InvalidRngFunction | InvalidUnnormalizedFunction of string @@ -510,6 +511,10 @@ module ExpressionError = struct | IllTypedPostfixOperator of Operator.t * UnsizedType.t let pp ppf = function + | InvalidLaplaceLatentSolveFunction -> + Fmt.text ppf + "Functions laplace_latent_solve and laplace_latent_solve_tol are only \ + allowed in the generated quantities block." | InvalidSizeDeclRng -> Fmt.pf ppf "Random number generators are not allowed in top level size \ @@ -969,6 +974,9 @@ let ident_has_unnormalized_suffix loc name = let invalid_decl_rng_fn loc = (loc, ExpressionError ExpressionError.InvalidSizeDeclRng) +let invalid_laplace_latent_solve_fn loc = + (loc, ExpressionError ExpressionError.InvalidLaplaceLatentSolveFunction) + let invalid_rng_fn loc = (loc, ExpressionError ExpressionError.InvalidRngFunction) diff --git a/src/frontend/Semantic_error.mli b/src/frontend/Semantic_error.mli index 354d1558f..f9d6f7615 100644 --- a/src/frontend/Semantic_error.mli +++ b/src/frontend/Semantic_error.mli @@ -164,6 +164,7 @@ val ident_not_in_scope : -> t val invalid_decl_rng_fn : Location_span.t -> t +val invalid_laplace_latent_solve_fn : Location_span.t -> t val invalid_rng_fn : Location_span.t -> t val invalid_unnormalized_fn : Location_span.t -> string -> t val udf_is_unnormalized_fn : Location_span.t -> string -> t diff --git a/src/frontend/Typechecker.ml b/src/frontend/Typechecker.ml index f83371141..be5e34f3c 100644 --- a/src/frontend/Typechecker.ml +++ b/src/frontend/Typechecker.ml @@ -496,6 +496,16 @@ let verify_fn_rng cf loc id = || cf.current_block = TData) then Semantic_error.invalid_rng_fn loc |> error +let is_laplace_latent_solve name = + Stan_math_signatures.is_embedded_laplace_fn name + && String.is_substring name ~substring:"_solve" + +(** Laplace latent solve can only be used in Generated Quantities block. *) +let verify_laplace_latent_solve cf loc id = + if is_laplace_latent_solve id.name then + if cf.current_block <> GQuant then + Semantic_error.invalid_laplace_latent_solve_fn loc |> error + let mk_fun_app ~is_cond_dist ~loc kind name args ~type_ : Ast.typed_expression = let fn = if is_cond_dist then CondDistApp (kind, name, args) @@ -975,6 +985,7 @@ and check_funapp loc cf tenv ~is_cond_dist id (es : Ast.typed_expression list) = verify_fn_target_plus_equals cf loc id; verify_fn_jacobian_plus_equals cf loc tenv id es; verify_fn_rng cf loc id; + verify_laplace_latent_solve cf loc id; verify_unnormalized cf loc id; res diff --git a/test/integration/bad/embedded_laplace/bad_solve_model.stan b/test/integration/bad/embedded_laplace/bad_solve_model.stan new file mode 100644 index 000000000..3474c58c7 --- /dev/null +++ b/test/integration/bad/embedded_laplace/bad_solve_model.stan @@ -0,0 +1,40 @@ +functions { + // specify negative binomial likelihood with mean offset + real ll_function(vector theta, // latent Gaussian + real eta, vector log_ye, // mean offset + array[] int y) { + // observed count + return neg_binomial_2_lpmf(y | exp(log_ye + theta), eta); + } + + // specify covariance function + matrix K_function(array[] vector x, int n_obs, real alpha, real rho) { + matrix[n_obs, n_obs] K = gp_exp_quad_cov(x, alpha, rho); + for (i in 1 : n_obs) + K[i, i] += 1e-8; + return K; + } +} +data { + int n_obs; + int n_coordinates; + array[n_obs] int y; + vector[n_obs] ye; + array[n_obs] vector[n_coordinates] x; +} + +transformed data { + vector[n_obs] log_ye = log(ye); +} +parameters { + real alpha; + real rho; + real eta; +} + +model { + // laplace_latent_solve is only callable in Generated Quantities block + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, + K_function, (x, n_obs, alpha, rho)); +} diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index cf9f91cb6..95b9c1391 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -325,6 +325,21 @@ Semantic error in 'bad_overload.stan', line 2, column 7 to column 51: ------------------------------------------------- Identifier "laplace_marginal_tol_neg_binomial_2_log_lpmf" clashes with a non-overloadable Stan Math library function. +[exit 1] + $ stanc bad_solve_model.stan +Semantic error in 'bad_solve_model.stan', line 38, column 8 to line 39, column 64: + ------------------------------------------------- + 36: // laplace_latent_solve is only callable in Generated Quantities block + 37: tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + 38: = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, + ^ + 39: K_function, (x, n_obs, alpha, rho)); + 40: } + ------------------------------------------------- + +Functions laplace_latent_solve and laplace_latent_solve_tol are only allowed +in the generated quantities +block. [exit 1] $ stanc bad_solve_return.stan Semantic error in 'bad_solve_return.stan', line 37, column 2 to line 38, column 81: From 7301b804d6feb8141dc3c0b5cb0bd6c84e5bddc0 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Wed, 29 Jul 2026 09:12:49 +0300 Subject: [PATCH 07/12] fix: Ocaml formatting --- src/frontend/Semantic_error.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/Semantic_error.ml b/src/frontend/Semantic_error.ml index cb6b2b1e0..678dcf48a 100644 --- a/src/frontend/Semantic_error.ml +++ b/src/frontend/Semantic_error.ml @@ -513,8 +513,8 @@ module ExpressionError = struct let pp ppf = function | InvalidLaplaceLatentSolveFunction -> Fmt.text ppf - "Functions laplace_latent_solve and laplace_latent_solve_tol are only \ - allowed in the generated quantities block." + "Functions laplace_latent_solve and laplace_latent_solve_tol are \ + only allowed in the generated quantities block." | InvalidSizeDeclRng -> Fmt.pf ppf "Random number generators are not allowed in top level size \ From 641c70b8eef1f27c4fd9eafdbad2ccf501058760 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Wed, 29 Jul 2026 09:13:54 +0300 Subject: [PATCH 08/12] tests: delete weak bad test --- .../embedded_laplace/bad_solve_return.stan | 39 ------------------- .../bad/embedded_laplace/stanc.expected | 15 ------- 2 files changed, 54 deletions(-) delete mode 100644 test/integration/bad/embedded_laplace/bad_solve_return.stan diff --git a/test/integration/bad/embedded_laplace/bad_solve_return.stan b/test/integration/bad/embedded_laplace/bad_solve_return.stan deleted file mode 100644 index 2843f1bce..000000000 --- a/test/integration/bad/embedded_laplace/bad_solve_return.stan +++ /dev/null @@ -1,39 +0,0 @@ -functions { - // specify negative binomial likelihood with mean offset - real ll_function(vector theta, // latent Gaussian - real eta, vector log_ye, // mean offset - array[] int y) { - // observed count - return neg_binomial_2_lpmf(y | exp(log_ye + theta), eta); - } - - // specify covariance function - matrix K_function(array[] vector x, int n_obs, real alpha, real rho) { - matrix[n_obs, n_obs] K = gp_exp_quad_cov(x, alpha, rho); - for (i in 1 : n_obs) - K[i, i] += 1e-8; - return K; - } -} -data { - int n_obs; - int n_coordinates; - array[n_obs] int y; - vector[n_obs] ye; - array[n_obs] vector[n_coordinates] x; -} - -transformed data { - vector[n_obs] log_ye = log(ye); -} -parameters { - real alpha; - real rho; - real eta; -} - -generated quantities { - // laplace_latent_solve returns tuple(vector, matrix), not vector - vector[n_obs] theta = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, - K_function, (x, n_obs, alpha, rho)); -} diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index 95b9c1391..fddb2fc57 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -340,21 +340,6 @@ Semantic error in 'bad_solve_model.stan', line 38, column 8 to line 39, column 6 Functions laplace_latent_solve and laplace_latent_solve_tol are only allowed in the generated quantities block. -[exit 1] - $ stanc bad_solve_return.stan -Semantic error in 'bad_solve_return.stan', line 37, column 2 to line 38, column 81: - ------------------------------------------------- - 35: generated quantities { - 36: // laplace_latent_solve returns tuple(vector, matrix), not vector - 37: vector[n_obs] theta = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, - ^ - 38: K_function, (x, n_obs, alpha, rho)); - 39: } - ------------------------------------------------- - -Ill-typed assignment statement. -Expected the right hand side to have a type matching the destination (vector). -Instead found type tuple(vector, matrix). [exit 1] $ stanc bad_solve_tol.stan Semantic error in 'bad_solve_tol.stan', line 38, column 8 to line 39, column 56: From 9d02affa2f82757087eade4f3276aac66c29b2d5 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Fri, 7 Aug 2026 18:13:53 +0300 Subject: [PATCH 09/12] fix: add suggestions by @nhuurre --- src/frontend/Semantic_error.ml | 4 +- src/frontend/Typechecker.ml | 21 +- .../bad/embedded_laplace/bad_solve_cov.stan | 40 +++ ...ad_solve_model.stan => bad_solve_lik.stan} | 0 .../bad/embedded_laplace/stanc.expected | 24 +- test/integration/good/code-gen/cpp.expected | 234 +++++++++++++----- .../good/code-gen/laplace_functionals.stan | 10 + 7 files changed, 251 insertions(+), 82 deletions(-) create mode 100644 test/integration/bad/embedded_laplace/bad_solve_cov.stan rename test/integration/bad/embedded_laplace/{bad_solve_model.stan => bad_solve_lik.stan} (100%) diff --git a/src/frontend/Semantic_error.ml b/src/frontend/Semantic_error.ml index 678dcf48a..98223e20b 100644 --- a/src/frontend/Semantic_error.ml +++ b/src/frontend/Semantic_error.ml @@ -513,8 +513,8 @@ module ExpressionError = struct let pp ppf = function | InvalidLaplaceLatentSolveFunction -> Fmt.text ppf - "Functions laplace_latent_solve and laplace_latent_solve_tol are \ - only allowed in the generated quantities block." + "Arguments to laplace_latent_solve and laplace_latent_solve_tol \ + must be data if used outside the generated quantities block." | InvalidSizeDeclRng -> Fmt.pf ppf "Random number generators are not allowed in top level size \ diff --git a/src/frontend/Typechecker.ml b/src/frontend/Typechecker.ml index be5e34f3c..79093ba22 100644 --- a/src/frontend/Typechecker.ml +++ b/src/frontend/Typechecker.ml @@ -500,12 +500,6 @@ let is_laplace_latent_solve name = Stan_math_signatures.is_embedded_laplace_fn name && String.is_substring name ~substring:"_solve" -(** Laplace latent solve can only be used in Generated Quantities block. *) -let verify_laplace_latent_solve cf loc id = - if is_laplace_latent_solve id.name then - if cf.current_block <> GQuant then - Semantic_error.invalid_laplace_latent_solve_fn loc |> error - let mk_fun_app ~is_cond_dist ~loc kind name args ~type_ : Ast.typed_expression = let fn = if is_cond_dist then CondDistApp (kind, name, args) @@ -886,6 +880,13 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = else (* likelihood callback check *) match tes with + | _ :: {emeta={ad_level; loc; _}; expr} :: _ + when is_laplace_latent_solve id.name && UnsizedType.is_autodifftype ad_level -> + let es = match expr with | TupleExpr es -> es | _ -> [] in + let loc = List.find_map es ~f:(fun {emeta={loc;ad_level;_};_} -> + Option.some_if (UnsizedType.is_autodifftype ad_level) loc) + |> Option.value ~default:loc in + Semantic_error.invalid_laplace_latent_solve_fn loc |> error | {expr= Variable lik_fun; _} :: lik_tupl :: tes -> let lik_fun, lik_tupl = (* adds the function name to the global list that is checked @@ -916,6 +917,13 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = |> error in (* Check the remaining arguments: initial guess, covariance, and tolerances *) match rest with + | _ :: {emeta={ad_level; loc; _}; expr} :: _ + when is_laplace_latent_solve id.name && UnsizedType.is_autodifftype ad_level -> + let es = match expr with | TupleExpr es -> es | _ -> [] in + let loc = List.find_map es ~f:(fun {emeta={loc;ad_level;_};_} -> + Option.some_if (UnsizedType.is_autodifftype ad_level) loc) + |> Option.value ~default:loc in + Semantic_error.invalid_laplace_latent_solve_fn loc |> error | {expr= Variable cov_fun; _} :: cov_tupl :: control_args -> let cov_fun_type, cov_tupl = check_function_callable_with_tuple cf tenv id cov_fun cov_tupl @@ -985,7 +993,6 @@ and check_funapp loc cf tenv ~is_cond_dist id (es : Ast.typed_expression list) = verify_fn_target_plus_equals cf loc id; verify_fn_jacobian_plus_equals cf loc tenv id es; verify_fn_rng cf loc id; - verify_laplace_latent_solve cf loc id; verify_unnormalized cf loc id; res diff --git a/test/integration/bad/embedded_laplace/bad_solve_cov.stan b/test/integration/bad/embedded_laplace/bad_solve_cov.stan new file mode 100644 index 000000000..da7391f63 --- /dev/null +++ b/test/integration/bad/embedded_laplace/bad_solve_cov.stan @@ -0,0 +1,40 @@ +functions { + // specify negative binomial likelihood with mean offset + real ll_function(vector theta, // latent Gaussian + real eta, vector log_ye, // mean offset + array[] int y) { + // observed count + return neg_binomial_2_lpmf(y | exp(log_ye + theta), eta); + } + + // specify covariance function + matrix K_function(array[] vector x, int n_obs, real alpha, real rho) { + matrix[n_obs, n_obs] K = gp_exp_quad_cov(x, alpha, rho); + for (i in 1 : n_obs) + K[i, i] += 1e-8; + return K; + } +} +data { + int n_obs; + int n_coordinates; + array[n_obs] int y; + vector[n_obs] ye; + array[n_obs] vector[n_coordinates] x; +} + +transformed data { + vector[n_obs] log_ye = log(ye); +} +parameters { + real alpha; + real rho; + real eta; +} + +model { + // laplace_latent_solve is only callable in Generated Quantities block + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + = laplace_latent_solve(ll_function, (1.0, log_ye, y), 1, + K_function, (x, n_obs, alpha, rho)); +} \ No newline at end of file diff --git a/test/integration/bad/embedded_laplace/bad_solve_model.stan b/test/integration/bad/embedded_laplace/bad_solve_lik.stan similarity index 100% rename from test/integration/bad/embedded_laplace/bad_solve_model.stan rename to test/integration/bad/embedded_laplace/bad_solve_lik.stan diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index fddb2fc57..2988ef383 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -326,19 +326,33 @@ Semantic error in 'bad_overload.stan', line 2, column 7 to column 51: Identifier "laplace_marginal_tol_neg_binomial_2_log_lpmf" clashes with a non-overloadable Stan Math library function. [exit 1] - $ stanc bad_solve_model.stan -Semantic error in 'bad_solve_model.stan', line 38, column 8 to line 39, column 64: + $ stanc bad_solve_cov.stan +Semantic error in 'bad_solve_cov.stan', line 39, column 52 to column 57: + ------------------------------------------------- + 37: tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + 38: = laplace_latent_solve(ll_function, (1.0, log_ye, y), 1, + 39: K_function, (x, n_obs, alpha, rho)); + ^ + 40: } + ------------------------------------------------- + +Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data +when used outside the generated quantities +block. +[exit 1] + $ stanc bad_solve_lik.stan +Semantic error in 'bad_solve_lik.stan', line 38, column 43 to column 46: ------------------------------------------------- 36: // laplace_latent_solve is only callable in Generated Quantities block 37: tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol 38: = laplace_latent_solve(ll_function, (eta, log_ye, y), 1, - ^ + ^ 39: K_function, (x, n_obs, alpha, rho)); 40: } ------------------------------------------------- -Functions laplace_latent_solve and laplace_latent_solve_tol are only allowed -in the generated quantities +Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data +when used outside the generated quantities block. [exit 1] $ stanc bad_solve_tol.stan diff --git a/test/integration/good/code-gen/cpp.expected b/test/integration/good/code-gen/cpp.expected index d94bfa4f3..5b5477484 100644 --- a/test/integration/good/code-gen/cpp.expected +++ b/test/integration/good/code-gen/cpp.expected @@ -7956,20 +7956,28 @@ namespace laplace_functionals_model_namespace { using stan::model::model_base_crtp; using namespace stan::math; stan::math::profile_map profiles__; -static constexpr std::array locations_array__ = +static constexpr std::array locations_array__ = {" (found before start of program)", " (in 'laplace_functionals.stan', line 43, column 2 to column 22)", " (in 'laplace_functionals.stan', line 44, column 2 to column 20)", " (in 'laplace_functionals.stan', line 45, column 2 to column 20)", - " (in 'laplace_functionals.stan', line 62, column 2 to line 64, column 67)", - " (in 'laplace_functionals.stan', line 66, column 2 to line 72, column 99)", - " (in 'laplace_functionals.stan', line 74, column 2 to line 76, column 65)", - " (in 'laplace_functionals.stan', line 78, column 2 to line 83, column 77)", + " (in 'laplace_functionals.stan', line 72, column 2 to line 74, column 67)", + " (in 'laplace_functionals.stan', line 76, column 2 to line 82, column 99)", + " (in 'laplace_functionals.stan', line 84, column 2 to line 86, column 65)", + " (in 'laplace_functionals.stan', line 88, column 2 to line 93, column 77)", " (in 'laplace_functionals.stan', line 48, column 2 to column 55)", " (in 'laplace_functionals.stan', line 49, column 2 to column 61)", " (in 'laplace_functionals.stan', line 50, column 2 to column 21)", " (in 'laplace_functionals.stan', line 52, column 2 to line 53, column 65)", " (in 'laplace_functionals.stan', line 55, column 2 to line 58, column 83)", + " (in 'laplace_functionals.stan', line 60, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 60, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 60, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 60, column 2 to line 62, column 63)", + " (in 'laplace_functionals.stan', line 64, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 64, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 64, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 64, column 2 to line 69, column 77)", " (in 'laplace_functionals.stan', line 19, column 2 to column 12)", " (in 'laplace_functionals.stan', line 20, column 2 to column 20)", " (in 'laplace_functionals.stan', line 21, column 8 to column 13)", @@ -7993,14 +8001,14 @@ static constexpr std::array locations_array__ = " (in 'laplace_functionals.stan', line 38, column 2 to column 17)", " (in 'laplace_functionals.stan', line 39, column 2 to column 32)", " (in 'laplace_functionals.stan', line 40, column 2 to column 28)", - " (in 'laplace_functionals.stan', line 62, column 9 to column 14)", - " (in 'laplace_functionals.stan', line 66, column 9 to column 14)", - " (in 'laplace_functionals.stan', line 74, column 15 to column 20)", - " (in 'laplace_functionals.stan', line 74, column 30 to column 35)", - " (in 'laplace_functionals.stan', line 74, column 37 to column 42)", - " (in 'laplace_functionals.stan', line 78, column 15 to column 20)", - " (in 'laplace_functionals.stan', line 78, column 30 to column 35)", - " (in 'laplace_functionals.stan', line 78, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 72, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 76, column 9 to column 14)", + " (in 'laplace_functionals.stan', line 84, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 84, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 84, column 37 to column 42)", + " (in 'laplace_functionals.stan', line 88, column 15 to column 20)", + " (in 'laplace_functionals.stan', line 88, column 30 to column 35)", + " (in 'laplace_functionals.stan', line 88, column 37 to column 42)", " (in 'laplace_functionals.stan', line 7, column 4 to column 61)", " (in 'laplace_functionals.stan', line 5, column 34 to line 8, column 3)", " (in 'laplace_functionals.stan', line 12, column 11 to column 16)", @@ -8093,7 +8101,7 @@ ll_function(const T0__& theta_arg__, const T1__& eta, const T2__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 44; + current_statement__ = 52; return stan::math::neg_binomial_2_lpmf(y, stan::math::exp(stan::math::add(log_ye, theta)), eta); } catch (const std::exception& e) { @@ -8125,25 +8133,25 @@ K_function(const T0__& x, const T1__& n_obs, const T2__& alpha, const T3__& // suppress unused var warning (void) DUMMY_VAR__; try { - current_statement__ = 46; + current_statement__ = 54; stan::math::validate_non_negative_index("K", "n_obs", n_obs); - current_statement__ = 47; + current_statement__ = 55; stan::math::validate_non_negative_index("K", "n_obs", n_obs); Eigen::Matrix K = Eigen::Matrix::Constant(n_obs, n_obs, DUMMY_VAR__); - current_statement__ = 48; + current_statement__ = 56; stan::model::assign(K, stan::math::gp_exp_quad_cov(x, alpha, rho), "assigning variable K"); - current_statement__ = 50; + current_statement__ = 58; for (int i = 1; i <= n_obs; ++i) { - current_statement__ = 49; + current_statement__ = 57; stan::model::assign(K, (stan::model::rvalue(K, "K", stan::model::index_uni(i), stan::model::index_uni(i)) + 1e-8), "assigning variable K", stan::model::index_uni(i), stan::model::index_uni(i)); } - current_statement__ = 51; + current_statement__ = 59; return K; } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8193,29 +8201,29 @@ class laplace_functionals_model final : public model_base_crtp::min(); pos__ = 1; - current_statement__ = 13; + current_statement__ = 21; context__.validate_dims("data initialization", "n_obs", "int", std::vector{}); n_obs = std::numeric_limits::min(); - current_statement__ = 13; + current_statement__ = 21; n_obs = context__.vals_i("n_obs")[(1 - 1)]; - current_statement__ = 14; + current_statement__ = 22; context__.validate_dims("data initialization", "n_coordinates", "int", std::vector{}); n_coordinates = std::numeric_limits::min(); - current_statement__ = 14; + current_statement__ = 22; n_coordinates = context__.vals_i("n_coordinates")[(1 - 1)]; - current_statement__ = 15; + current_statement__ = 23; stan::math::validate_non_negative_index("y", "n_obs", n_obs); - current_statement__ = 16; + current_statement__ = 24; context__.validate_dims("data initialization", "y", "int", std::vector{static_cast(n_obs)}); y = std::vector(n_obs, std::numeric_limits::min()); - current_statement__ = 16; + current_statement__ = 24; y = context__.vals_i("y"); - current_statement__ = 17; + current_statement__ = 25; stan::math::validate_non_negative_index("ye", "n_obs", n_obs); - current_statement__ = 18; + current_statement__ = 26; context__.validate_dims("data initialization", "ye", "double", std::vector{static_cast(n_obs)}); ye_data__ = Eigen::Matrix::Constant(n_obs, @@ -8224,7 +8232,7 @@ class laplace_functionals_model final : public model_base_crtp ye_flat__; - current_statement__ = 18; + current_statement__ = 26; ye_flat__ = context__.vals_r("ye"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_obs; ++sym1__) { @@ -8233,12 +8241,12 @@ class laplace_functionals_model final : public model_base_crtp{static_cast(n_obs), static_cast(n_coordinates)}); @@ -8247,7 +8255,7 @@ class laplace_functionals_model final : public model_base_crtp::quiet_NaN())); { std::vector x_flat__; - current_statement__ = 21; + current_statement__ = 29; x_flat__ = context__.vals_r("x"); pos__ = 1; for (int sym1__ = 1; sym1__ <= n_coordinates; ++sym1__) { @@ -8259,92 +8267,92 @@ class laplace_functionals_model final : public model_base_crtp{}); rho_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 22; + current_statement__ = 30; rho_location_prior = context__.vals_r("rho_location_prior")[(1 - 1)]; - current_statement__ = 23; + current_statement__ = 31; context__.validate_dims("data initialization", "rho_scale_prior", "double", std::vector{}); rho_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 23; + current_statement__ = 31; rho_scale_prior = context__.vals_r("rho_scale_prior")[(1 - 1)]; - current_statement__ = 24; + current_statement__ = 32; context__.validate_dims("data initialization", "alpha_location_prior", "double", std::vector{}); alpha_location_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 24; + current_statement__ = 32; alpha_location_prior = context__.vals_r("alpha_location_prior")[(1 - 1)]; - current_statement__ = 25; + current_statement__ = 33; context__.validate_dims("data initialization", "alpha_scale_prior", "double", std::vector{}); alpha_scale_prior = std::numeric_limits::quiet_NaN(); - current_statement__ = 25; + current_statement__ = 33; alpha_scale_prior = context__.vals_r("alpha_scale_prior")[(1 - 1)]; - current_statement__ = 26; + current_statement__ = 34; stan::math::validate_non_negative_index("log_ye", "n_obs", n_obs); - current_statement__ = 27; + current_statement__ = 35; log_ye_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&log_ye) Eigen::Map>(log_ye_data__.data(), n_obs); - current_statement__ = 27; + current_statement__ = 35; stan::model::assign(log_ye, stan::math::log(ye), "assigning variable log_ye"); - current_statement__ = 28; + current_statement__ = 36; stan::math::validate_non_negative_index("theta_0", "n_obs", n_obs); - current_statement__ = 29; + current_statement__ = 37; theta_0_data__ = Eigen::Matrix::Constant(n_obs, std::numeric_limits::quiet_NaN()); new (&theta_0) Eigen::Map>(theta_0_data__.data(), n_obs); - current_statement__ = 29; + current_statement__ = 37; stan::model::assign(theta_0, stan::math::rep_vector(0.0, n_obs), "assigning variable theta_0"); - current_statement__ = 30; + current_statement__ = 38; tolerance = std::numeric_limits::quiet_NaN(); - current_statement__ = 30; + current_statement__ = 38; tolerance = 1e-6; - current_statement__ = 31; + current_statement__ = 39; max_num_steps = std::numeric_limits::min(); - current_statement__ = 31; + current_statement__ = 39; max_num_steps = 100; - current_statement__ = 32; + current_statement__ = 40; hessian_block_size = std::numeric_limits::min(); - current_statement__ = 32; + current_statement__ = 40; hessian_block_size = 1; - current_statement__ = 33; + current_statement__ = 41; solver = std::numeric_limits::min(); - current_statement__ = 33; + current_statement__ = 41; solver = 1; - current_statement__ = 34; + current_statement__ = 42; max_steps_line_search = std::numeric_limits::min(); - current_statement__ = 34; + current_statement__ = 42; max_steps_line_search = 0; - current_statement__ = 35; + current_statement__ = 43; allow_fallthrough = std::numeric_limits::min(); - current_statement__ = 35; + current_statement__ = 43; allow_fallthrough = 1; - current_statement__ = 36; + current_statement__ = 44; stan::math::validate_non_negative_index("theta", "n_obs", n_obs); - current_statement__ = 37; + current_statement__ = 45; stan::math::validate_non_negative_index("theta2", "n_obs", n_obs); - current_statement__ = 38; + current_statement__ = 46; stan::math::validate_non_negative_index("mean_chol.1", "n_obs", n_obs); - current_statement__ = 39; + current_statement__ = 47; stan::math::validate_non_negative_index("mean_chol.2", "n_obs", n_obs); - current_statement__ = 40; + current_statement__ = 48; stan::math::validate_non_negative_index("mean_chol.2", "n_obs", n_obs); - current_statement__ = 41; + current_statement__ = 49; stan::math::validate_non_negative_index("mean_chol_tol.1", "n_obs", n_obs); - current_statement__ = 42; + current_statement__ = 50; stan::math::validate_non_negative_index("mean_chol_tol.2", "n_obs", n_obs); - current_statement__ = 43; + current_statement__ = 51; stan::math::validate_non_negative_index("mean_chol_tol.2", "n_obs", n_obs); } catch (const std::exception& e) { @@ -8420,6 +8428,51 @@ class laplace_functionals_model final : public model_base_crtp, + Eigen::Matrix> mean_chol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 16; + stan::model::assign(mean_chol, + stan::math::laplace_latent_solve(ll_function_functor__(), + std::forward_as_tuple(1.0, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, 1.0, 1.0), pstream__), + "assigning variable mean_chol"); + current_statement__ = 17; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 18; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 19; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + std::tuple, + Eigen::Matrix> mean_chol_tol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 20; + stan::model::assign(mean_chol_tol, + stan::math::laplace_latent_solve_tol(ll_function_functor__(), + std::forward_as_tuple(1.0, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, 1.0, 1.0), + std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough), pstream__), + "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); @@ -8488,6 +8541,51 @@ class laplace_functionals_model final : public model_base_crtp, + Eigen::Matrix> mean_chol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 16; + stan::model::assign(mean_chol, + stan::math::laplace_latent_solve(ll_function_functor__(), + std::forward_as_tuple(1.0, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, 1.0, 1.0), pstream__), + "assigning variable mean_chol"); + current_statement__ = 17; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 18; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + current_statement__ = 19; + stan::math::validate_non_negative_index("mean_chol_tol", "n_obs", + n_obs); + std::tuple, + Eigen::Matrix> mean_chol_tol = + std::tuple, + Eigen::Matrix>{Eigen::Matrix::Constant(n_obs, + DUMMY_VAR__), + Eigen::Matrix::Constant(n_obs, n_obs, + DUMMY_VAR__)}; + current_statement__ = 20; + stan::model::assign(mean_chol_tol, + stan::math::laplace_latent_solve_tol(ll_function_functor__(), + std::forward_as_tuple(1.0, log_ye, y), hessian_block_size, + K_function_functor__(), + std::forward_as_tuple(x, n_obs, 1.0, 1.0), + std::forward_as_tuple(theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough), pstream__), + "assigning variable mean_chol_tol"); } } catch (const std::exception& e) { stan::lang::rethrow_located(e, locations_array__[current_statement__]); diff --git a/test/integration/good/code-gen/laplace_functionals.stan b/test/integration/good/code-gen/laplace_functionals.stan index ba0fa4026..b9d0a2ef0 100644 --- a/test/integration/good/code-gen/laplace_functionals.stan +++ b/test/integration/good/code-gen/laplace_functionals.stan @@ -57,6 +57,16 @@ model { (theta_0, tolerance, max_num_steps, solver, max_steps_line_search, allow_fallthrough)); + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol + = laplace_latent_solve(ll_function, (1.0, log_ye, y), hessian_block_size, + K_function, (x, n_obs, 1.0, 1.0)); + + tuple(vector[n_obs], matrix[n_obs, n_obs]) mean_chol_tol + = laplace_latent_solve_tol(ll_function, (1.0, log_ye, y), + hessian_block_size, K_function, + (x, n_obs, 1.0, 1.0), + (theta_0, tolerance, max_num_steps, solver, + max_steps_line_search, allow_fallthrough)); } generated quantities { vector[n_obs] theta = laplace_latent_rng(ll_function, (eta, log_ye, y), From c56fae10fc0fc094896c6afab036b4533f35986a Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Fri, 7 Aug 2026 18:29:40 +0300 Subject: [PATCH 10/12] fix: run formatter --- src/frontend/Semantic_error.ml | 4 ++-- src/frontend/Typechecker.ml | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/frontend/Semantic_error.ml b/src/frontend/Semantic_error.ml index 98223e20b..b610958af 100644 --- a/src/frontend/Semantic_error.ml +++ b/src/frontend/Semantic_error.ml @@ -513,8 +513,8 @@ module ExpressionError = struct let pp ppf = function | InvalidLaplaceLatentSolveFunction -> Fmt.text ppf - "Arguments to laplace_latent_solve and laplace_latent_solve_tol \ - must be data if used outside the generated quantities block." + "Arguments to laplace_latent_solve and laplace_latent_solve_tol must \ + be data if used outside the generated quantities block." | InvalidSizeDeclRng -> Fmt.pf ppf "Random number generators are not allowed in top level size \ diff --git a/src/frontend/Typechecker.ml b/src/frontend/Typechecker.ml index 79093ba22..2cc23587a 100644 --- a/src/frontend/Typechecker.ml +++ b/src/frontend/Typechecker.ml @@ -880,11 +880,13 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = else (* likelihood callback check *) match tes with - | _ :: {emeta={ad_level; loc; _}; expr} :: _ - when is_laplace_latent_solve id.name && UnsizedType.is_autodifftype ad_level -> - let es = match expr with | TupleExpr es -> es | _ -> [] in - let loc = List.find_map es ~f:(fun {emeta={loc;ad_level;_};_} -> - Option.some_if (UnsizedType.is_autodifftype ad_level) loc) + | _ :: {emeta= {ad_level; loc; _}; expr} :: _ + when is_laplace_latent_solve id.name + && UnsizedType.is_autodifftype ad_level -> + let es = match expr with TupleExpr es -> es | _ -> [] in + let loc = + List.find_map es ~f:(fun {emeta= {loc; ad_level; _}; _} -> + Option.some_if (UnsizedType.is_autodifftype ad_level) loc) |> Option.value ~default:loc in Semantic_error.invalid_laplace_latent_solve_fn loc |> error | {expr= Variable lik_fun; _} :: lik_tupl :: tes -> @@ -917,11 +919,13 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = |> error in (* Check the remaining arguments: initial guess, covariance, and tolerances *) match rest with - | _ :: {emeta={ad_level; loc; _}; expr} :: _ - when is_laplace_latent_solve id.name && UnsizedType.is_autodifftype ad_level -> - let es = match expr with | TupleExpr es -> es | _ -> [] in - let loc = List.find_map es ~f:(fun {emeta={loc;ad_level;_};_} -> - Option.some_if (UnsizedType.is_autodifftype ad_level) loc) + | _ :: {emeta= {ad_level; loc; _}; expr} :: _ + when is_laplace_latent_solve id.name && UnsizedType.is_autodifftype ad_level + -> + let es = match expr with TupleExpr es -> es | _ -> [] in + let loc = + List.find_map es ~f:(fun {emeta= {loc; ad_level; _}; _} -> + Option.some_if (UnsizedType.is_autodifftype ad_level) loc) |> Option.value ~default:loc in Semantic_error.invalid_laplace_latent_solve_fn loc |> error | {expr= Variable cov_fun; _} :: cov_tupl :: control_args -> From afbfd1f7baab11cdf788187a513f30c3fb39a3f8 Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Fri, 7 Aug 2026 18:40:43 +0300 Subject: [PATCH 11/12] fix: update stanc.expected to new formatting --- test/integration/bad/embedded_laplace/stanc.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index 2988ef383..87390ed6a 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -337,7 +337,7 @@ Semantic error in 'bad_solve_cov.stan', line 39, column 52 to column 57: ------------------------------------------------- Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data -when used outside the generated quantities +if used outside the generated quantities block. [exit 1] $ stanc bad_solve_lik.stan @@ -352,7 +352,7 @@ Semantic error in 'bad_solve_lik.stan', line 38, column 43 to column 46: ------------------------------------------------- Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data -when used outside the generated quantities +if used outside the generated quantities block. [exit 1] $ stanc bad_solve_tol.stan From bac41c8b2c2297d2f74ecd290621276953f25c4c Mon Sep 17 00:00:00 2001 From: Brian Ward Date: Fri, 7 Aug 2026 13:17:10 -0400 Subject: [PATCH 12/12] Error message consistency --- src/frontend/Semantic_error.ml | 17 +++++++++-------- src/frontend/Semantic_error.mli | 3 ++- src/frontend/Typechecker.ml | 4 ++-- .../bad/embedded_laplace/stanc.expected | 14 ++++++++------ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/frontend/Semantic_error.ml b/src/frontend/Semantic_error.ml index b28d133e1..d31ba7374 100644 --- a/src/frontend/Semantic_error.ml +++ b/src/frontend/Semantic_error.ml @@ -69,6 +69,7 @@ module TypeError = struct | IlltypedLaplaceHessianBlockSize of string * (UnsizedType.autodifftype * UnsizedType.t) option | IlltypedLaplaceTolArgs of string * SignatureMismatch.function_mismatch + | IlltypedLaplaceLatentSolveArgs of string | AmbiguousFunctionPromotion of string * UnsizedType.t list option @@ -328,6 +329,11 @@ module TypeError = struct Fmt.pf ppf "@[The %s to %a@ must be@ %a.%a@]" (laplace_tolerance_arg_name n) quoted name expected_types [expected] found_type found + | IlltypedLaplaceLatentSolveArgs name -> + Fmt.pf ppf + "@[All arguments to %a must be a data-only@ if used outside the \ + generated quantities@ block.@ %a@]" + quoted name SignatureMismatch.data_only_msg () | AmbiguousFunctionPromotion (name, arg_tys, signatures) -> let pp_sig ppf (rt, args, _) = Fmt.pf ppf "@[(@[%a@]) => %a@]" @@ -491,7 +497,6 @@ end module ExpressionError = struct type t = - | InvalidLaplaceLatentSolveFunction | InvalidSizeDeclRng | InvalidRngFunction | InvalidUnnormalizedFunction of string @@ -511,10 +516,6 @@ module ExpressionError = struct | IllTypedPostfixOperator of Operator.t * UnsizedType.t let pp ppf = function - | InvalidLaplaceLatentSolveFunction -> - Fmt.text ppf - "Arguments to laplace_latent_solve and laplace_latent_solve_tol must \ - be data if used outside the generated quantities block." | InvalidSizeDeclRng -> Fmt.pf ppf "Random number generators are not allowed in top level size \ @@ -897,6 +898,9 @@ let illtyped_laplace_hessian_block_size_arg loc name arg_ty = let illtyped_laplace_tolerance_args loc name mismatch = (loc, TypeError (TypeError.IlltypedLaplaceTolArgs (name, mismatch))) +let illtyped_laplace_latent_solve_args loc name = + (loc, TypeError (TypeError.IlltypedLaplaceLatentSolveArgs name)) + let ambiguous_function_promotion loc name arg_tys signatures = ( loc , TypeError (TypeError.AmbiguousFunctionPromotion (name, arg_tys, signatures)) @@ -977,9 +981,6 @@ let ident_has_unnormalized_suffix loc name = let invalid_decl_rng_fn loc = (loc, ExpressionError ExpressionError.InvalidSizeDeclRng) -let invalid_laplace_latent_solve_fn loc = - (loc, ExpressionError ExpressionError.InvalidLaplaceLatentSolveFunction) - let invalid_rng_fn loc = (loc, ExpressionError ExpressionError.InvalidRngFunction) diff --git a/src/frontend/Semantic_error.mli b/src/frontend/Semantic_error.mli index f9d6f7615..817baaa30 100644 --- a/src/frontend/Semantic_error.mli +++ b/src/frontend/Semantic_error.mli @@ -125,6 +125,8 @@ val illtyped_laplace_hessian_block_size_arg : val illtyped_laplace_tolerance_args : Location_span.t -> string -> SignatureMismatch.function_mismatch -> t +val illtyped_laplace_latent_solve_args : Location_span.t -> string -> t + val nonreturning_fn_expected_returning_found : Location_span.t -> string -> Location_span.t option -> t @@ -164,7 +166,6 @@ val ident_not_in_scope : -> t val invalid_decl_rng_fn : Location_span.t -> t -val invalid_laplace_latent_solve_fn : Location_span.t -> t val invalid_rng_fn : Location_span.t -> t val invalid_unnormalized_fn : Location_span.t -> string -> t val udf_is_unnormalized_fn : Location_span.t -> string -> t diff --git a/src/frontend/Typechecker.ml b/src/frontend/Typechecker.ml index 35c38a26f..5da35b5b3 100644 --- a/src/frontend/Typechecker.ml +++ b/src/frontend/Typechecker.ml @@ -889,7 +889,7 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = List.find_map es ~f:(fun {emeta= {loc; ad_level; _}; _} -> Option.some_if (UnsizedType.is_autodifftype ad_level) loc) |> Option.value ~default:loc in - Semantic_error.invalid_laplace_latent_solve_fn loc |> error + Semantic_error.illtyped_laplace_latent_solve_args loc id.name |> error | {expr= Variable lik_fun; _} :: lik_tupl :: tes -> let lik_fun, lik_tupl = (* adds the function name to the global list that is checked @@ -928,7 +928,7 @@ and check_laplace_fn ~is_cond_dist loc cf tenv id tes = List.find_map es ~f:(fun {emeta= {loc; ad_level; _}; _} -> Option.some_if (UnsizedType.is_autodifftype ad_level) loc) |> Option.value ~default:loc in - Semantic_error.invalid_laplace_latent_solve_fn loc |> error + Semantic_error.illtyped_laplace_latent_solve_args loc id.name |> error | {expr= Variable cov_fun; _} :: cov_tupl :: control_args -> let cov_fun_type, cov_tupl = check_function_callable_with_tuple cf tenv id cov_fun cov_tupl diff --git a/test/integration/bad/embedded_laplace/stanc.expected b/test/integration/bad/embedded_laplace/stanc.expected index 87390ed6a..12a256ff6 100644 --- a/test/integration/bad/embedded_laplace/stanc.expected +++ b/test/integration/bad/embedded_laplace/stanc.expected @@ -336,9 +336,10 @@ Semantic error in 'bad_solve_cov.stan', line 39, column 52 to column 57: 40: } ------------------------------------------------- -Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data -if used outside the generated quantities -block. +All arguments to "laplace_latent_solve" must be a data-only +if used outside the generated quantities block. (Local variables are assumed +to depend on parameters; same goes for function inputs unless they are marked +with the keyword "data".) [exit 1] $ stanc bad_solve_lik.stan Semantic error in 'bad_solve_lik.stan', line 38, column 43 to column 46: @@ -351,9 +352,10 @@ Semantic error in 'bad_solve_lik.stan', line 38, column 43 to column 46: 40: } ------------------------------------------------- -Arguments to laplace_latent_solve and laplace_latent_solve_tol must be data -if used outside the generated quantities -block. +All arguments to "laplace_latent_solve" must be a data-only +if used outside the generated quantities block. (Local variables are assumed +to depend on parameters; same goes for function inputs unless they are marked +with the keyword "data".) [exit 1] $ stanc bad_solve_tol.stan Semantic error in 'bad_solve_tol.stan', line 38, column 8 to line 39, column 56: