Skip to content

Commit e0145b6

Browse files
committed
update tests
1 parent e734a8a commit e0145b6

4 files changed

Lines changed: 108 additions & 26 deletions

File tree

stan/math/mix/functor/laplace_likelihood.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ inline auto compute_s2(F&& f, Theta&& theta, AMat&& A,
222222
v(j) = 1;
223223
}
224224
w.setZero();
225-
// TODO(Steve): Zip into Eigen indexing
226225
for (int j = 0; j < n_blocks; ++j) {
227226
for (int k = 0; k < hessian_block_size; ++k) {
228227
w(k + j * hessian_block_size)

stan/math/mix/functor/laplace_marginal_density.hpp

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ struct laplace_density_estimates {
8282
K_root(std::move(K_root_)) {}
8383
};
8484

85-
// TODO(Steve): Try to doing cholesky decomposition of the sparse matrix
8685
/**
8786
* Returns the principal square root of a block diagonal matrix.
8887
*/
@@ -138,6 +137,80 @@ inline void block_matrix_sqrt(WRootMat& W_root,
138137
}
139138
}
140139

140+
template <typename WRootMat>
141+
inline void block_matrix_chol_L(WRootMat& W_root,
142+
const Eigen::SparseMatrix<double>& W,
143+
const Eigen::Index block_size) {
144+
int n_block = W.cols() / block_size;
145+
Eigen::MatrixXd local_block(block_size, block_size);
146+
Eigen::MatrixXd local_block_sqrt(block_size, block_size);
147+
Eigen::MatrixXd sqrt_t_mat = Eigen::MatrixXd::Zero(block_size, block_size);
148+
// No block operation available for sparse matrices, so we have to loop
149+
// See https://eigen.tuxfamily.org/dox/group__TutorialSparse.html#title7
150+
for (int i = 0; i < n_block; i++) {
151+
sqrt_t_mat.setZero();
152+
local_block
153+
= W.block(i * block_size, i * block_size, block_size, block_size);
154+
if (Eigen::isnan(local_block.array()).any()) {
155+
throw std::domain_error(
156+
std::string("Error in block_matrix_sqrt: "
157+
"NaNs detected in block diagonal starting at (")
158+
+ std::to_string(i) + ", " + std::to_string(i) + ")");
159+
}
160+
try {
161+
// Compute square root of T
162+
Eigen::LLT<Eigen::Ref<Eigen::MatrixXd>> llt(local_block);
163+
if (llt.info() != Eigen::Success) {
164+
throw std::runtime_error("Cholesky failed on block " + std::to_string(i));
165+
}
166+
const auto Lb = llt.matrixL();
167+
for (int k = 0; k < block_size; k++) {
168+
for (int j = k; j < block_size; j++) {
169+
W_root.coeffRef(i * block_size + j, i * block_size + k)
170+
= Lb(j, k);
171+
}
172+
}
173+
} catch (const std::exception& e) {
174+
// As a backup do the schur decomposition for this block diagonal
175+
local_block
176+
= W.block(i * block_size, i * block_size, block_size, block_size);
177+
// Issue here, sqrt is done over T of the complex schur
178+
Eigen::RealSchur<Eigen::MatrixXd> schurOfA(local_block);
179+
// Compute Schur decomposition of arg
180+
const auto& t_mat = schurOfA.matrixT();
181+
const auto& u_mat = schurOfA.matrixU();
182+
// Check if diagonal of schur is not positive
183+
if ((t_mat.diagonal().array() < 0).any()) {
184+
throw std::domain_error(
185+
std::string("Error in block_matrix_sqrt: "
186+
"values less than 0 detected in block diagonal's schur "
187+
"decomposition starting at (")
188+
+ std::to_string(i) + ", " + std::to_string(i) + ")");
189+
}
190+
try {
191+
// Compute square root of T
192+
Eigen::matrix_sqrt_quasi_triangular(t_mat, sqrt_t_mat);
193+
// Compute square root of arg
194+
local_block_sqrt.noalias() = u_mat * sqrt_t_mat * u_mat.adjoint();
195+
} catch (const std::exception& e) {
196+
throw std::domain_error(
197+
"Error in block_matrix_sqrt: "
198+
"The matrix is not positive definite");
199+
}
200+
for (int k = 0; k < block_size; k++) {
201+
for (int j = 0; j < block_size; j++) {
202+
W_root.coeffRef(i * block_size + j, i * block_size + k)
203+
= local_block_sqrt(j, k);
204+
}
205+
}
206+
throw std::domain_error(
207+
"Error in block_matrix_sqrt: "
208+
"The matrix is not positive definite");
209+
}
210+
}
211+
}
212+
213+
141214
/**
142215
* @brief Performs a simple line search
143216
*
@@ -297,7 +370,6 @@ inline STAN_COLD_PATH void throw_nan(NameStr&& name_str, ParamStr&& param_str,
297370
* log marginal density. The user controls the tolerance (i.e.
298371
* threshold under which change is deemed small enough) and
299372
* maximum number of steps.
300-
* TODO(Charles): add more robust convergence criterion.
301373
*
302374
* A description of this algorithm can be found in:
303375
* - (2023) Margossian, "General Adjoint-Differentiated Laplace approximation",
@@ -421,17 +493,6 @@ inline auto laplace_marginal_density_est(LLFun&& ll_fun, LLTupleArgs&& ll_args,
421493
W_r.coeffRef(i) = std::sqrt(W.coeff(i, i));
422494
}
423495
}
424-
// Eigen::SparseMatrix<double> W_r = W.cwiseSqrt();
425-
// TODO(Charles): Need better way to handle negative diagonals
426-
/*
427-
if (W_is_spd) {
428-
W_r = W.cwiseSqrt();
429-
} else {
430-
W_r = block_matrix_sqrt(W, options.hessian_block_size);
431-
}
432-
*/
433-
// TODO(Steve): Memory can be made once out of the loop
434-
// This is our main cost
435496
B.noalias() = MatrixXd::Identity(theta_size, theta_size)
436497
+ W_r.asDiagonal() * covariance
437498
* W_r.asDiagonal();
@@ -504,7 +565,7 @@ inline auto laplace_marginal_density_est(LLFun&& ll_fun, LLTupleArgs&& ll_args,
504565
"definite");
505566
}
506567
}
507-
block_matrix_sqrt(W_r, W, options.hessian_block_size);
568+
block_matrix_chol_L(W_r, W, options.hessian_block_size);
508569
B.noalias() = MatrixXd::Identity(theta_size, theta_size)
509570
+ W_r * (covariance * W_r);
510571
Eigen::LLT<Eigen::Ref<Eigen::MatrixXd>> llt_B(B);
@@ -567,7 +628,6 @@ inline auto laplace_marginal_density_est(LLFun&& ll_fun, LLTupleArgs&& ll_args,
567628
// Simple Newton step
568629
theta.noalias() = covariance * a;
569630
objective_old = objective_new;
570-
// TODO(Charles) Throw if theta is not finite?
571631
if (unlikely((Eigen::isinf(theta.array()) || Eigen::isnan(theta.array()))
572632
.any())) {
573633
throw_nan("laplace_marginal_density", "theta", theta);
@@ -621,7 +681,6 @@ inline auto laplace_marginal_density_est(LLFun&& ll_fun, LLTupleArgs&& ll_args,
621681
+ laplace_likelihood::log_likelihood(
622682
ll_fun, value_of(theta), ll_args_vals, msgs);
623683

624-
// TODO(Charles): How do we handle NA values in theta?
625684
if (options.max_steps_line_search > 0) {
626685
line_search(objective_new, a, theta, a_prev, ll_fun, ll_args_vals,
627686
covariance, options.max_steps_line_search, objective_old,
@@ -919,7 +978,7 @@ inline auto laplace_marginal_density(const LLFun& ll_fun, LLTupleArgs&& ll_args,
919978
{
920979
nested_rev_autodiff nested;
921980
// Solver 1, 2
922-
arena_t<Eigen::MatrixXd> R;
981+
arena_t<Eigen::MatrixXd> R(theta_0.size(), theta_0.size());
923982
// Solver 3
924983
arena_t<Eigen::MatrixXd> LU_solve_covariance;
925984
// Solver 1, 2, 3
@@ -949,6 +1008,7 @@ inline auto laplace_marginal_density(const LLFun& ll_fun, LLTupleArgs&& ll_args,
9491008
},
9501009
partial_parm, ll_args_filter);
9511010
if (options.solver == 1) {
1011+
if (options.hessian_block_size == 1) {
9521012
// TODO(Steve): Solve without casting from sparse to dense
9531013
Eigen::MatrixXd tmp
9541014
= md_est.L.template triangularView<Eigen::Lower>().solve(
@@ -957,7 +1017,7 @@ inline auto laplace_marginal_density(const LLFun& ll_fun, LLTupleArgs&& ll_args,
9571017
arena_t<Eigen::MatrixXd> C
9581018
= md_est.L.template triangularView<Eigen::Lower>().solve(
9591019
md_est.W_r * md_est.covariance);
960-
if (!ll_args_contain_var && options.hessian_block_size == 1) {
1020+
if constexpr (!ll_args_contain_var) {
9611021
s2.deep_copy(
9621022
(0.5
9631023
* (md_est.covariance.diagonal() - (C.transpose() * C).diagonal())
@@ -972,6 +1032,23 @@ inline auto laplace_marginal_density(const LLFun& ll_fun, LLTupleArgs&& ll_args,
9721032
copy_compute_s2(partial_parm, ll_args_filter);
9731033
set_zero_adjoint(ll_args_filter);
9741034
}
1035+
1036+
} else {
1037+
Eigen::MatrixXd tmp
1038+
= md_est.L.template triangularView<Eigen::Lower>().solve(
1039+
md_est.W_r.toDense());
1040+
R = tmp.transpose() * tmp;
1041+
arena_t<Eigen::MatrixXd> C
1042+
= md_est.L.template triangularView<Eigen::Lower>().solve(
1043+
md_est.W_r * md_est.covariance);
1044+
arena_t<Eigen::MatrixXd> A = md_est.covariance - C.transpose() * C;
1045+
auto s2_tmp = laplace_likelihood::compute_s2(ll_fun, md_est.theta, A,
1046+
options.hessian_block_size,
1047+
ll_args_copy, msgs);
1048+
s2.deep_copy(s2_tmp);
1049+
copy_compute_s2(partial_parm, ll_args_filter);
1050+
set_zero_adjoint(ll_args_filter);
1051+
}
9751052
} else if (options.solver == 2) {
9761053
R = md_est.W_r
9771054
- md_est.W_r * md_est.K_root

stan/math/mix/prob/laplace_latent_poisson_log_exposure_rng.hpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ namespace math {
4343
template <typename YeVec, typename ThetaVec, typename CovarFun,
4444
typename CovarArgs, typename RNG,
4545
require_eigen_t<ThetaVec>* = nullptr>
46-
inline auto // CHECK -- right return type
47-
laplace_latent_tol_poisson_2_log_rng(
46+
inline auto laplace_latent_tol_poisson_2_log_rng(
4847
const std::vector<int>& y, const std::vector<int>& y_index, const YeVec& ye,
4948
ThetaVec&& theta_0, CovarFun&& covariance_function, CovarArgs&& covar_args,
5049
const double tolerance, const int64_t max_num_steps,
@@ -89,8 +88,7 @@ laplace_latent_tol_poisson_2_log_rng(
8988
template <typename YeVec, typename ThetaVec, typename CovarFun,
9089
typename CovarArgs, typename RNG,
9190
require_eigen_t<ThetaVec>* = nullptr>
92-
inline auto // TODO(Steve): Allow scalar or std vector return
93-
laplace_latent_poisson_2_log_rng(const std::vector<int>& y,
91+
inline auto laplace_latent_poisson_2_log_rng(const std::vector<int>& y,
9492
const std::vector<int>& y_index,
9593
const YeVec& ye, ThetaVec&& theta_0,
9694
CovarFun&& covariance_function,

test/unit/math/laplace/laplace_marginal_lpdf_test.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ struct poisson_log_exposure_likelihood {
194194
delta_int, stan::math::add(theta, stan::math::log(ye)));
195195
}
196196
};
197-
/*
197+
198198
TEST_F(laplace_disease_map_test, laplace_marginal) {
199199
using stan::math::laplace_marginal;
200200
using stan::math::laplace_marginal_poisson_log_lpmf;
@@ -480,7 +480,16 @@ TEST_F(laplace_motorcyle_gp_test, gp_motorcycle) {
480480
std::pair(laplace_issue{3, 400, 4}, LaplaceFailures::IterExceeded),
481481
std::pair(laplace_issue{3, 500, 4}, LaplaceFailures::IterExceeded)};
482482

483-
483+
/**
484+
* Note: This test is designed to check the error behavior
485+
* of the laplace_marginal_tol function. We do not force
486+
* a function to fail because some of these errors can be machine
487+
* specific. So for cases we know there can be a test failure for a
488+
* machine we call the function in a try block. if it *does* fail,
489+
* we expect it to be the associated error found in the known_issues array.
490+
* If we have not seen this parameter combination fail before, we run the
491+
* standard AD testing procedure.
492+
*/
484493
for (int solver_num = 1; solver_num < 4; solver_num++) {
485494
for (int max_steps_line_search = 0; max_steps_line_search <= 20;
486495
max_steps_line_search += 10) {
@@ -602,4 +611,3 @@ TEST_F(laplace_motorcyle_gp_test, gp_motorcycle2) {
602611
},
603612
theta0);
604613
}
605-
*/

0 commit comments

Comments
 (0)