@@ -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
0 commit comments