101 const Eigen::VectorXd& v,
const Eigen::MatrixXd& C_alpha,
102 Index nocclevels_alpha,
const Eigen::MatrixXd& C_beta,
104 double finite_diff_step)
const {
105 Index nao_alpha = C_alpha.rows();
106 Index nvirt_alpha = nao_alpha - nocclevels_alpha;
107 Index nao_beta = C_beta.rows();
108 Index nvirt_beta = nao_beta - nocclevels_beta;
109 Index n_ov_alpha = nocclevels_alpha * nvirt_alpha;
110 Index n_ov_beta = nocclevels_beta * nvirt_beta;
113 v, nao_alpha, nocclevels_alpha, nao_beta, nocclevels_beta);
114 kappa_alpha_trial *= finite_diff_step;
115 kappa_beta_trial *= finite_diff_step;
126 auto EvaluateBothGradientsAt = [&](
const Eigen::MatrixXd& kappa_alpha,
127 const Eigen::MatrixXd& kappa_beta)
128 -> std::pair<Eigen::MatrixXd, Eigen::MatrixXd> {
129 Eigen::MatrixXd C_alpha_rot =
131 (Eigen::MatrixXd::Identity(nao_alpha, nao_alpha) + kappa_alpha);
132 Eigen::MatrixXd nonortho_alpha =
133 C_alpha_rot.transpose() *
S_->Matrix() * C_alpha_rot;
134 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_alpha(nonortho_alpha);
135 C_alpha_rot = C_alpha_rot * es_alpha.operatorInverseSqrt();
137 Eigen::MatrixXd C_beta_rot =
138 C_beta * (Eigen::MatrixXd::Identity(nao_beta, nao_beta) + kappa_beta);
139 Eigen::MatrixXd nonortho_beta =
140 C_beta_rot.transpose() *
S_->Matrix() * C_beta_rot;
141 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_beta(nonortho_beta);
142 C_beta_rot = C_beta_rot * es_beta.operatorInverseSqrt();
144 Eigen::MatrixXd C_alpha_occ_rot = C_alpha_rot.leftCols(nocclevels_alpha);
145 Eigen::MatrixXd D_alpha_rot = C_alpha_occ_rot * C_alpha_occ_rot.transpose();
146 Eigen::MatrixXd C_beta_occ_rot = C_beta_rot.leftCols(nocclevels_beta);
147 Eigen::MatrixXd D_beta_rot = C_beta_occ_rot * C_beta_occ_rot.transpose();
149 SpinFock H_rot = coupled_fock_builder(D_alpha_rot, D_beta_rot);
150 Eigen::MatrixXd F_MO_alpha_rot =
151 C_alpha_rot.transpose() * H_rot.
alpha * C_alpha_rot;
152 Eigen::MatrixXd F_MO_beta_rot =
153 C_beta_rot.transpose() * H_rot.
beta * C_beta_rot;
154 return {F_MO_alpha_rot, F_MO_beta_rot};
157 auto [F_MO_alpha_plus, F_MO_beta_plus] =
158 EvaluateBothGradientsAt(kappa_alpha_trial, kappa_beta_trial);
159 auto [F_MO_alpha_minus, F_MO_beta_minus] =
160 EvaluateBothGradientsAt(-kappa_alpha_trial, -kappa_beta_trial);
162 Eigen::VectorXd sigma(n_ov_alpha + n_ov_beta);
163 for (
Index i = 0; i < nocclevels_alpha; ++i) {
164 for (
Index a = 0; a < nvirt_alpha; ++a) {
165 double g_plus_ia = F_MO_alpha_plus(i, nocclevels_alpha + a);
166 double g_minus_ia = F_MO_alpha_minus(i, nocclevels_alpha + a);
167 sigma(i * nvirt_alpha + a) =
168 (g_plus_ia - g_minus_ia) / (2.0 * finite_diff_step);
171 for (
Index i = 0; i < nocclevels_beta; ++i) {
172 for (
Index a = 0; a < nvirt_beta; ++a) {
173 double g_plus_ia = F_MO_beta_plus(i, nocclevels_beta + a);
174 double g_minus_ia = F_MO_beta_minus(i, nocclevels_beta + a);
175 sigma(n_ov_alpha + i * nvirt_beta + a) =
176 (g_plus_ia - g_minus_ia) / (2.0 * finite_diff_step);
184 Index nocclevels,
double& predicted_energy_change)
const {
192 Eigen::MatrixXd F_MO = C.transpose() * H_AO * C;
198 Eigen::MatrixXd kappa = Eigen::MatrixXd::Zero(nao, nao);
205 Eigen::MatrixXd h_matrix = Eigen::MatrixXd::Zero(nao, nao);
223 constexpr double kMinGap = 1
e-3;
231 constexpr double kMaxKappaElement = 0.1;
232 for (
Index i = 0; i < nocclevels; ++i) {
233 for (
Index a = nocclevels; a < nao; ++a) {
234 double gap = std::max(std::abs(eps(a) - eps(i)), kMinGap);
238 double h_ia = 2.0 * gap;
239 double kappa_ia = -F_MO(i, a) / h_ia;
240 kappa_ia = std::clamp(kappa_ia, -kMaxKappaElement, kMaxKappaElement);
241 kappa(i, a) = kappa_ia;
242 kappa(a, i) = -kappa_ia;
243 h_matrix(i, a) = h_ia;
247 double knorm = kappa.norm();
258 predicted_energy_change = 0.0;
259 for (
Index i = 0; i < nocclevels; ++i) {
260 for (
Index a = nocclevels; a < nao; ++a) {
261 double kappa_ia = kappa(i, a);
262 predicted_energy_change +=
263 F_MO(i, a) * kappa_ia + 0.5 * h_matrix(i, a) * kappa_ia * kappa_ia;
272 Eigen::MatrixXd C_new = C * (Eigen::MatrixXd::Identity(nao, nao) + kappa);
273 Eigen::MatrixXd nonortho = C_new.transpose() *
S_->Matrix() * C_new;
274 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_ortho(nonortho);
275 return C_new * es_ortho.operatorInverseSqrt();
326 Index nocclevels_alpha,
const Eigen::MatrixXd& H_AO_beta,
329 double& predicted_energy_change)
const {
331 Index nvirt_alpha = nao_alpha - nocclevels_alpha;
332 Index n_ov_alpha = nocclevels_alpha * nvirt_alpha;
333 const Eigen::MatrixXd& C_alpha = MOs_alpha.
eigenvectors();
334 const Eigen::VectorXd& eps_alpha = MOs_alpha.
eigenvalues();
337 Index nvirt_beta = nao_beta - nocclevels_beta;
338 Index n_ov_beta = nocclevels_beta * nvirt_beta;
339 const Eigen::MatrixXd& C_beta = MOs_beta.
eigenvectors();
340 const Eigen::VectorXd& eps_beta = MOs_beta.
eigenvalues();
342 Index n_ov = n_ov_alpha + n_ov_beta;
344 Eigen::MatrixXd F_MO_alpha = C_alpha.transpose() * H_AO_alpha * C_alpha;
345 Eigen::MatrixXd F_MO_beta = C_beta.transpose() * H_AO_beta * C_beta;
361 Eigen::VectorXd g(n_ov);
362 Eigen::VectorXd diag_h(n_ov);
363 constexpr double kMinGap = 1
e-3;
364 for (
Index i = 0; i < nocclevels_alpha; ++i) {
365 for (
Index a = 0; a < nvirt_alpha; ++a) {
366 g(i * nvirt_alpha + a) = F_MO_alpha(i, nocclevels_alpha + a);
367 double gap = std::max(
368 std::abs(eps_alpha(nocclevels_alpha + a) - eps_alpha(i)), kMinGap);
369 diag_h(i * nvirt_alpha + a) = 2.0 * gap;
372 for (
Index i = 0; i < nocclevels_beta; ++i) {
373 for (
Index a = 0; a < nvirt_beta; ++a) {
374 g(n_ov_alpha + i * nvirt_beta + a) = F_MO_beta(i, nocclevels_beta + a);
375 double gap = std::max(
376 std::abs(eps_beta(nocclevels_beta + a) - eps_beta(i)), kMinGap);
377 diag_h(n_ov_alpha + i * nvirt_beta + a) = 2.0 * gap;
381 double alpha_min = 1.0;
382 double alpha_max = 1000.0;
383 Eigen::VectorXd best_kappa_flat = Eigen::VectorXd::Zero(n_ov);
384 double best_mu = 0.0;
386 Eigen::MatrixXd initial_guess = Eigen::MatrixXd::Zero(1 + n_ov, 2);
387 initial_guess(0, 0) = 1.0;
388 double gnorm = g.norm();
390 initial_guess.block(1, 1, n_ov, 1) = g / gnorm;
392 initial_guess(1, 1) = 1.0;
405 auto SolveForAlpha = [&](
double alpha_try, Eigen::VectorXd& kappa_flat_out,
406 double& mu_out) ->
bool {
407 CoupledAugmentedHessianOperator op{g,
413 coupled_fock_builder,
421 solver.
solve(op, 1, initial_guess);
422 if (solver.
info() != Eigen::ComputationInfo::Success) {
427 double v0 = eigvec(0);
428 if (std::abs(v0) < 1
e-8) {
429 kappa_flat_out = Eigen::VectorXd::Zero(g.size());
432 kappa_flat_out = eigvec.tail(g.size()) / v0;
436 double alpha_try = alpha_min;
437 constexpr int kMaxBisectionIters = 20;
438 bool have_converged_once =
false;
439 for (
int bisection_iter = 0; bisection_iter < kMaxBisectionIters;
441 Eigen::VectorXd kappa_flat;
443 bool converged = SolveForAlpha(alpha_try, kappa_flat, mu);
455 alpha_min = alpha_try;
456 alpha_try = 0.5 * (alpha_min + alpha_max);
459 have_converged_once =
true;
460 double step_norm = kappa_flat.norm() / alpha_try;
461 best_kappa_flat = kappa_flat;
463 if (std::abs(step_norm - trust_radius) < 0.01 * trust_radius) {
466 if (step_norm > trust_radius) {
467 alpha_min = alpha_try;
469 alpha_max = alpha_try;
471 alpha_try = 0.5 * (alpha_min + alpha_max);
474 if (!have_converged_once) {
482 throw std::runtime_error(
483 "CoupledAugmentedHessianStep: DavidsonSolver failed to converge "
484 "for every bisection trial (all " +
485 std::to_string(kMaxBisectionIters) +
486 " attempts) -- no genuine augmented-Hessian step could be "
492 <<
" CoupledAugmentedHessianStep bisection diagnostic: "
495 <<
", achieved step_norm=" << (best_kappa_flat.norm() / alpha_try)
496 <<
", requested trust_radius=" << trust_radius << std::flush;
499 best_kappa_flat, nao_alpha, nocclevels_alpha, nao_beta, nocclevels_beta);
508 predicted_energy_change =
509 0.5 * (g.dot(best_kappa_flat) + best_mu * best_kappa_flat.squaredNorm());
511 Eigen::MatrixXd C_alpha_new =
512 C_alpha * (Eigen::MatrixXd::Identity(nao_alpha, nao_alpha) + kappa_alpha);
513 Eigen::MatrixXd nonortho_alpha =
514 C_alpha_new.transpose() *
S_->Matrix() * C_alpha_new;
515 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_alpha(nonortho_alpha);
516 C_alpha_new = C_alpha_new * es_alpha.operatorInverseSqrt();
518 Eigen::MatrixXd C_beta_new =
519 C_beta * (Eigen::MatrixXd::Identity(nao_beta, nao_beta) + kappa_beta);
520 Eigen::MatrixXd nonortho_beta =
521 C_beta_new.transpose() *
S_->Matrix() * C_beta_new;
522 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_beta(nonortho_beta);
523 C_beta_new = C_beta_new * es_beta.operatorInverseSqrt();
525 return {C_alpha_new, C_beta_new};
604 <<
" Direct-minimization step check: actual dE=" << actual_change
638 <<
" Direct-minimization trust radius fell "
639 "below its own finite-difference resolution floor ("
641 <<
") without an accepted step -- "
642 "falling back to mixing instead of continuing to shrink."
653 }
else if (r <= 0.25) {
656 }
else if (r > 0.75) {
672 totE_.push_back(totE);
763 bool diis_error =
false;
769 Eigen::MatrixXd H_guess_alpha =
H.alpha;
770 Eigen::MatrixXd H_guess_beta =
H.beta;
776 Eigen::VectorXd coeffs;
782 diis_error = !
adiis_.Info() || coeffs.size() == 0;
784 <<
TimeStamp() <<
" Using ADIIS for next UKS guess" << std::flush;
786 coeffs =
diis_.CalcCoeff();
787 diis_error = !
diis_.Info() || coeffs.size() == 0;
789 <<
TimeStamp() <<
" Using DIIS for next UKS guess" << std::flush;
807 bool trailing_average_stalled =
false;
810 double mean_ratio = 0.0;
811 Index ratio_count = 0;
818 if (ratio_count > 0) {
819 mean_ratio /= double(ratio_count);
853 trailing_average_stalled) &&
863 << (trailing_average_stalled ?
" (or trailing average stalled)"
865 <<
", switching to direct-minimization step" << std::flush;
876 double predicted_change_alpha = 0.0;
877 double predicted_change_beta = 0.0;
878 Eigen::MatrixXd C_new_alpha;
879 Eigen::MatrixXd C_new_beta;
898 double predicted_change_combined = 0.0;
902 predicted_change_combined);
910 predicted_change_alpha = 0.5 * predicted_change_combined;
911 predicted_change_beta = 0.5 * predicted_change_combined;
919 predicted_change_alpha + predicted_change_beta;
930 (C_new_alpha.transpose() *
H.alpha * C_new_alpha).diagonal();
932 (C_new_beta.transpose() *
H.beta * C_new_beta).diagonal();
936 return dmatout_direct;
939 <<
TimeStamp() <<
" (A)DIIS failed using mixing instead"
941 H_guess_alpha =
H.alpha;
942 H_guess_beta =
H.beta;
945 H_guess_alpha.setZero();
946 H_guess_beta.setZero();
947 for (
Index i = 0; i < coeffs.size(); ++i) {
948 if (std::abs(coeffs(i)) < 1
e-8) {
988 double ramp_fraction =
991 double mixingparameter_alpha_current =
994 double mixingparameter_beta_current =
997 dmatout.
alpha = mixingparameter_alpha_current * dmat.
alpha +
998 (1.0 - mixingparameter_alpha_current) * dmatout.
alpha;
999 dmatout.
beta = mixingparameter_beta_current * dmat.
beta +
1000 (1.0 - mixingparameter_beta_current) * dmatout.
beta;
1002 <<
TimeStamp() <<
" Using coupled UKS mixing with adaptive alpha="
1003 << mixingparameter_alpha_current
1006 <<
", ramp fraction=" << ramp_fraction <<
")" << std::flush;