@@ -444,7 +444,7 @@ inline void llt_with_jitter(LLT& llt_B, B_t& B, double min_jitter = 1e-10,
444444 }
445445 if (llt_B.info () != Eigen::Success) {
446446 throw std::domain_error (
447- " laplace_marginal_density: Cholesky (Diag) failed" );
447+ " laplace_marginal_density: Cholesky failed after adding jitter up to " + std::to_string (jitter_try) );
448448 }
449449 }
450450}
@@ -942,16 +942,12 @@ inline auto run_newton_loop(SolverPolicy& solver, NewtonStateT& state,
942942 scratch.alpha () = 1.0 ;
943943 update_fun (scratch, state.curr (), state.prev (), scratch.eval_ ,
944944 state.wolfe_info .p_ );
945- bool run_convergence_check = true ;
946945 if (scratch.alpha () <= options.line_search .min_alpha ) {
947946 state.wolfe_status .accept_ = false ;
948947 finish_update = true ;
949- run_convergence_check = false ;
950948 } else if (options.line_search .max_iterations == 0 ) {
951949 state.curr ().update (scratch);
952950 state.wolfe_status .accept_ = true ;
953- finish_update = false ;
954- run_convergence_check = false ;
955951 } else {
956952 Eigen::VectorXd s = scratch.a () - state.prev ().a ();
957953 auto full_step_grad
@@ -964,16 +960,15 @@ inline auto run_newton_loop(SolverPolicy& solver, NewtonStateT& state,
964960 state.wolfe_status = internal::wolfe_line_search (
965961 state.wolfe_info , update_fun, options.line_search , msgs);
966962 }
967- if (run_convergence_check) {
968- /* *
969- * Stop when objective change is small, or when a rejected Wolfe step
970- * fails to improve; finish_update then exits the Newton loop.
971- */
972- finish_update = std::abs (state.curr ().obj () - state.prev ().obj ())
973- < options.tolerance
974- || (!state.wolfe_status .accept_
975- && state.curr ().obj () <= state.prev ().obj ());
976- }
963+ /* *
964+ * Stop when objective change is small, or when a rejected Wolfe step
965+ * fails to improve; finish_update then exits the Newton loop.
966+ */
967+ bool objective_converged = std::abs (state.curr ().obj () - state.prev ().obj ())
968+ < options.tolerance ;
969+ bool search_failed = (!state.wolfe_status .accept_
970+ && state.curr ().obj () <= state.prev ().obj ());
971+ finish_update = objective_converged || search_failed;
977972 }
978973 if (finish_update) {
979974 if (!state.final_loop && state.wolfe_status .accept_ ) {
@@ -1039,6 +1034,52 @@ inline decltype(auto) theta_init_impl(Eigen::Index theta_size, Opts&& options) {
10391034 }
10401035}
10411036
1037+ /* *
1038+ * @brief Create the update function for the line search, capturing necessary
1039+ * references.
1040+ * @tparam ObjFun Callable type for the objective function (accepting (a, theta))
1041+ * @tparam ThetaGradFun Callable type for the theta gradient function (accepting theta)
1042+ * @tparam Covariance Type of the covariance matrix
1043+ * @tparam Options Type of the options struct containing line search parameters
1044+ * @param[in] obj_fun Objective function functor
1045+ * @param[in] theta_grad_f Theta gradient functor
1046+ * @param[in] covariance Prior covariance matrix Sigma
1047+ * @param[in] options Options struct containing line search parameters
1048+ * @return A callable update function for the line search, with signature:
1049+ * ```
1050+ * bool update_fun(proposal, curr, prev, eval_in, p)
1051+ * ```
1052+ */
1053+ template <typename ObjFun, typename ThetaGradFun, typename Covariance, typename Options>
1054+ inline auto create_update_fun (ObjFun&& obj_fun, ThetaGradFun&& theta_grad_f,
1055+ Covariance&& covariance, Options&& options) {
1056+ auto update_step = [&covariance, &obj_fun, &theta_grad_f](
1057+ auto & proposal, auto && /* curr */ , auto && prev,
1058+ auto & eval_in, auto && p) {
1059+ try {
1060+ proposal.a () = prev.a () + eval_in.alpha () * p;
1061+ proposal.theta ().noalias () = covariance * proposal.a ();
1062+ proposal.theta_grad () = theta_grad_f (proposal.theta ());
1063+ eval_in.obj () = obj_fun (proposal.a (), proposal.theta ());
1064+ eval_in.dir ()
1065+ = (-covariance * proposal.a () + covariance * proposal.theta_grad ())
1066+ .dot (p);
1067+ return std::isfinite (eval_in.obj ()) && std::isfinite (eval_in.dir ());
1068+ } catch (const std::exception&) {
1069+ return false ;
1070+ }
1071+ };
1072+ auto backoff = [&options](auto & eval) {
1073+ eval.alpha () *= options.line_search .tau ;
1074+ return eval.alpha () > options.line_search .min_alpha ;
1075+ };
1076+ return [update_step_ = std::move (update_step), backoff_ = std::move (backoff)](
1077+ auto & proposal, auto && curr, auto && prev, auto & eval_in, auto && p) {
1078+ return internal::retry_evaluate (update_step_, proposal, curr, prev,
1079+ eval_in, p, backoff_);
1080+ };
1081+ }
1082+
10421083/* *
10431084 * For a latent Gaussian model with hyperparameters phi and
10441085 * latent variables theta, and observations y, this function computes
@@ -1098,9 +1139,7 @@ inline auto laplace_marginal_density_est(
10981139 const laplace_options<InitTheta>& options, std::ostream* msgs) {
10991140 internal::validate_laplace_options (" laplace_marginal_density" , options,
11001141 covariance);
1101-
11021142 const Eigen::Index theta_size = covariance.rows ();
1103-
11041143 // Wolfe optimizes over the latent 'a' space
11051144 auto obj_fun = [&ll_fun, &ll_args, &msgs](const Eigen::VectorXd& a_val,
11061145 auto && theta_val) -> double {
@@ -1113,36 +1152,8 @@ inline auto laplace_marginal_density_est(
11131152 };
11141153 decltype (auto ) theta_init = theta_init_impl<InitTheta>(theta_size, options);
11151154 internal::NewtonState state (theta_size, obj_fun, theta_grad_f, theta_init);
1116- // 'a' gradient
1117- auto update_fun = [&covariance, &obj_fun, &theta_grad_f, &options]() {
1118- auto update_step = [&covariance, &obj_fun, &theta_grad_f](
1119- auto & proposal, auto && /* curr */ , auto && prev,
1120- auto & eval_in, auto && p) {
1121- try {
1122- proposal.a () = prev.a () + eval_in.alpha () * p;
1123- proposal.theta ().noalias () = covariance * proposal.a ();
1124- proposal.theta_grad () = theta_grad_f (proposal.theta ());
1125- eval_in.obj () = obj_fun (proposal.a (), proposal.theta ());
1126- eval_in.dir ()
1127- = (-covariance * proposal.a () + covariance * proposal.theta_grad ())
1128- .dot (p);
1129- return std::isfinite (eval_in.obj ()) && std::isfinite (eval_in.dir ());
1130- } catch (const std::exception&) {
1131- return false ;
1132- }
1133- };
1134- auto backoff = [&options](auto & eval) {
1135- eval.alpha () *= options.line_search .tau ;
1136- return eval.alpha () > options.line_search .min_alpha ;
1137- };
1138- return
1139- [update_step_ = std::move (update_step), backoff_ = std::move (backoff)](
1140- auto & proposal, auto && curr, auto && prev, auto & eval_in, auto && p) {
1141- return internal::retry_evaluate (update_step_, proposal, curr, prev,
1142- eval_in, p, backoff_);
1143- };
1144- }();
11451155 // Start with safe step size
1156+ auto update_fun = create_update_fun (std::move (obj_fun), std::move (theta_grad_f), covariance, options);
11461157 Eigen::Index step_iter = 0 ;
11471158 try {
11481159 if (options.solver == 1 ) {
0 commit comments