36bool SolveProjected(
const Eigen::MatrixXd& Hs,
const Eigen::VectorXd& gs,
37 double a, Eigen::VectorXd& xs,
double& mu) {
38 const Index m = Hs.rows();
39 Eigen::MatrixXd M = Eigen::MatrixXd::Zero(m + 1, m + 1);
40 M.block(0, 1, 1, m) = a * gs.transpose();
41 M.block(1, 0, m, 1) = a * gs;
42 M.bottomRightCorner(m, m) = Hs;
43 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(M);
44 const Eigen::VectorXd
y = es.eigenvectors().col(0);
45 if (std::abs(
y(0)) < 1
e-14) {
48 xs =
y.tail(m) / (a *
y(0));
49 mu = es.eigenvalues()(0);
58double SearchAlpha(
const Eigen::MatrixXd& Hs,
const Eigen::VectorXd& gs,
60 Eigen::VectorXd& xs,
double& mu) {
61 Eigen::VectorXd xs_hi;
63 if (SolveProjected(Hs, gs, opt.alpha_max, xs_hi, mu_hi) &&
64 xs_hi.norm() > opt.lower_fraction * trust_radius) {
71 double hi = opt.alpha_max;
72 for (
int k = 0; k < 100; ++k) {
73 const double a = std::sqrt(lo * hi);
76 if (!SolveProjected(Hs, gs, a, xs_a, mu_a)) {
83 const double norm = xs_a.norm();
84 if (norm > trust_radius) {
86 }
else if (norm < opt.lower_fraction * trust_radius) {
97bool Orthonormalize(
const Eigen::MatrixXd& B, Eigen::VectorXd& v) {
98 const double norm0 = v.norm();
102 for (
int pass = 0; pass < 2; ++pass) {
104 v -= B * (B.transpose() * v);
107 const double norm = v.norm();
108 if (norm < 1
e-10 * norm0) {
119 const Index n = g.size();
121 result.
x = Eigen::VectorXd::Zero(n);
122 const double gnorm = g.norm();
123 if (!(gnorm > 0.0)) {
127 const double eta = std::min(opt.
max_forcing, gnorm);
128 const double tolerance = std::max(eta * gnorm, opt.
residual_floor);
130 Eigen::MatrixXd B(n, 0);
131 Eigen::MatrixXd HB(n, 0);
132 auto add = [&](Eigen::VectorXd v) {
133 if (!Orthonormalize(B, v)) {
136 const Eigen::MatrixXd Hv = hessian(v);
138 B.conservativeResize(n, B.cols() + 1);
139 B.col(B.cols() - 1) = v;
140 HB.conservativeResize(n, HB.cols() + 1);
141 HB.col(HB.cols() - 1) = Hv.col(0);
144 if (subspace !=
nullptr && subspace->
B.cols() > 0 &&
145 subspace->
B.rows() == n && subspace->
g.size() == n &&
146 (subspace->
g - g).norm() <= 1
e-12 * gnorm) {
163 Eigen::VectorXd x_full = Eigen::VectorXd::Zero(n);
164 double g_dot_x = 0.0;
168 Hs = B.transpose() * HB;
169 Hs = 0.5 * (Hs + Hs.transpose()).eval();
170 gs = B.transpose() * g;
175 if (!SolveProjected(Hs, gs, alpha, xs, mu)) {
178 if (xs.norm() > trust_radius) {
179 alpha = SearchAlpha(Hs, gs, trust_radius, opt, xs, mu);
184 g_dot_x = gs.dot(xs);
185 x_H_x = xs.dot(Hs * xs);
186 result.
alpha = alpha;
190 const Eigen::VectorXd r = HB * xs + g - mu * x_full;
202 Eigen::MatrixXd W(B.cols(), 2);
205 Eigen::HouseholderQR<Eigen::MatrixXd> qr(W);
206 const Eigen::MatrixXd Q =
207 qr.householderQ() * Eigen::MatrixXd::Identity(B.cols(), 2);
209 HB = (HB * Q).eval();
213 Eigen::VectorXd t(n);
214 for (
Index k = 0; k < n; ++k) {
215 t(k) = -r(k) / std::max(diag(k) - mu, 1
e-4);
222 if (subspace !=
nullptr) {
234 const double norm = x_full.norm();
235 const double s = (norm > trust_radius) ? trust_radius / norm : 1.0;
236 result.
linear = s * g_dot_x;
237 result.
predicted = s * g_dot_x + 0.5 * s * s * x_H_x;
238 result.
x = s * x_full;
244 const double radius_before =
radius_;
245 const bool accepted =
Update(predicted, actual);
246 if (!accepted && step_norm > 0.0) {
247 const double c = actual - linear;
249 if (linear < 0.0 && c > 0.0) {
250 factor = std::clamp(-linear / (2.0 * c), 0.1, 0.7);
252 radius_ = factor * std::min(radius_before, step_norm);
262 ratio_ = (std::abs(predicted) > 0.0) ? actual / predicted : -1.0;
269 }
else if (
ratio_ > 0.75) {
276 const Eigen::VectorXd& x,
Index nocc) {
277 const Index nmo = C.cols();
278 const Index nvirt = nmo - nocc;
279 if (nocc == 0 || nvirt == 0) {
282 const Eigen::Map<
const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic,
284 X(x.data(), nocc, nvirt);
291 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(X * X.transpose());
292 const Eigen::MatrixXd& U = es.eigenvectors();
293 const Index m = es.eigenvalues().size();
294 Eigen::VectorXd cos_s(m);
295 Eigen::VectorXd sinc_s(m);
296 Eigen::VectorXd cosm1_s2(m);
297 for (
Index k = 0; k < m; ++k) {
298 const double s2 = std::max(es.eigenvalues()(k), 0.0);
299 const double s = std::sqrt(s2);
300 cos_s(k) = std::cos(s);
301 sinc_s(k) = (s > 1
e-4) ? std::sin(s) / s : 1.0 - s2 / 6.0;
302 cosm1_s2(k) = (s > 1
e-4) ? (std::cos(s) - 1.0) / s2 : -0.5 + s2 / 24.0;
304 const Eigen::MatrixXd UtX = U.transpose() * X;
305 const Eigen::MatrixXd E_oo = U * cos_s.asDiagonal() * U.transpose();
306 const Eigen::MatrixXd E_ov = U * sinc_s.asDiagonal() * UtX;
307 const Eigen::MatrixXd E_vv = Eigen::MatrixXd::Identity(nvirt, nvirt) +
308 UtX.transpose() * cosm1_s2.asDiagonal() * UtX;
310 const auto C_occ = C.leftCols(nocc);
311 const auto C_virt = C.rightCols(nvirt);
312 Eigen::MatrixXd result(C.rows(), nmo);
313 result.leftCols(nocc) = C_occ * E_oo - C_virt * E_ov.transpose();
314 result.rightCols(nvirt) = C_occ * E_ov + C_virt * E_vv;
319 const Index nvirt = M.cols() - nocc;
320 Eigen::VectorXd out(nocc * nvirt);
321 for (
Index i = 0; i < nocc; ++i) {
322 for (
Index a = 0; a < nvirt; ++a) {
323 out(i * nvirt + a) = M(i, nocc + a);
static constexpr double noise_floor
bool Update(double predicted, double actual)
Trust-region augmented-Hessian (TRAH) step for orbital rotations, after Helmich-Paris,...
std::function< Eigen::MatrixXd(const Eigen::MatrixXd &)> HessianProduct
Hessian applied to the columns of V (n x k), returning n x k.
StepResult Step(const Eigen::VectorXd &g, const Eigen::VectorXd &diag, const HessianProduct &hessian, double trust_radius, const StepOptions &opt=StepOptions(), Subspace *subspace=nullptr)
Eigen::MatrixXd RotateOrbitals(const Eigen::MatrixXd &C, const Eigen::VectorXd &x, Index nocc)
Eigen::VectorXd OccVirt(const Eigen::MatrixXd &M, Index nocc)
Charge transport classes.
Provides a means for comparing floating point numbers.