|
| 1 | +namespace hsolver { |
| 2 | + |
| 3 | +//============================================================================== |
| 4 | +// CONJUGATE_GRADIENT STRATEGY |
| 5 | +//============================================================================== |
| 6 | + |
| 7 | +// --------------------------------------------------------------------------- |
| 8 | +// Compute gradient: grad_i = H|psi_i> - eps_i * S|psi_i> |
| 9 | +// --------------------------------------------------------------------------- |
| 10 | +template <typename T, typename Device> |
| 11 | +void DiagoPPCG<T, Device>::calc_gradient( |
| 12 | + const Real* /*prec*/, |
| 13 | + const T* hpsi, |
| 14 | + const T* spsi, |
| 15 | + const T* /*psi*/, |
| 16 | + const Real* eigenvalue, |
| 17 | + std::vector<T>& grad) const |
| 18 | +{ |
| 19 | + grad.assign(ld_psi_ * n_band_, T(0)); |
| 20 | + for (int j = 0; j < n_band_; ++j) |
| 21 | + { |
| 22 | + const Real ej = eigenvalue[j]; |
| 23 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 24 | + grad[idx(ig, j, ld_psi_)] = hpsi[idx(ig, j, ld_psi_)] |
| 25 | + - spsi[idx(ig, j, ld_psi_)] * ej; |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +// --------------------------------------------------------------------------- |
| 30 | +// Orthogonalize gradient: grad_j -= sum_i <psi_i|grad_j> * S|psi_i> |
| 31 | +// --------------------------------------------------------------------------- |
| 32 | +template <typename T, typename Device> |
| 33 | +void DiagoPPCG<T, Device>::orth_gradient( |
| 34 | + const T* psi, const T* spsi, |
| 35 | + std::vector<T>& grad) const |
| 36 | +{ |
| 37 | + for (int j = 0; j < n_band_; ++j) |
| 38 | + { |
| 39 | + for (int i = 0; i < n_band_; ++i) |
| 40 | + { |
| 41 | + // Full complex inner product <psi_i | grad_j> |
| 42 | + T coeff = 0; |
| 43 | + const T* pi = psi + i * ld_psi_; |
| 44 | + const T* gj = grad.data() + j * ld_psi_; |
| 45 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 46 | + coeff += std::conj(pi[ig]) * gj[ig]; |
| 47 | + if (std::abs(coeff) <= std::numeric_limits<Real>::epsilon()) |
| 48 | + continue; |
| 49 | + // grad_j -= S|psi_i> * coeff |
| 50 | + const T* si = spsi + i * ld_psi_; |
| 51 | + T* gj_out = grad.data() + j * ld_psi_; |
| 52 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 53 | + gj_out[ig] -= si[ig] * coeff; |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +// --------------------------------------------------------------------------- |
| 59 | +// Polak-Ribiere conjugate gradient update with preconditioning: |
| 60 | +// z_new = -P^{-1} * r_new |
| 61 | +// beta = max(0, <z_new, r_new - r_old> / <z_old, r_old>) |
| 62 | +// d_new = z_new + beta * d_old |
| 63 | +// --------------------------------------------------------------------------- |
| 64 | +template <typename T, typename Device> |
| 65 | +void DiagoPPCG<T, Device>::update_polak_ribiere( |
| 66 | + const std::vector<T>& grad, |
| 67 | + std::vector<T>& p, |
| 68 | + std::vector<T>& grad_old, |
| 69 | + std::vector<T>& z_old, |
| 70 | + std::vector<Real>& beta_denom, |
| 71 | + const Real* prec) const |
| 72 | +{ |
| 73 | + const bool first_iter = p.empty(); |
| 74 | + if (first_iter) |
| 75 | + { |
| 76 | + p.assign(ld_psi_ * n_band_, T(0)); |
| 77 | + z_old.assign(ld_psi_ * n_band_, T(0)); |
| 78 | + beta_denom.assign(n_band_, std::numeric_limits<Real>::infinity()); |
| 79 | + } |
| 80 | + |
| 81 | + std::vector<T> z_new(ld_psi_ * n_band_, T(0)); |
| 82 | + |
| 83 | + for (int j = 0; j < n_band_; ++j) |
| 84 | + { |
| 85 | + const T* g = grad.data() + j * ld_psi_; |
| 86 | + T* pj = p.data() + j * ld_psi_; |
| 87 | + T* zn = z_new.data() + j * ld_psi_; |
| 88 | + T* zo = z_old.data() + j * ld_psi_; |
| 89 | + |
| 90 | + Real beta_num_zr = 0; |
| 91 | + Real beta_num_zo = 0; |
| 92 | + |
| 93 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 94 | + { |
| 95 | + // z_new = -P^{-1} * grad |
| 96 | + T z = -g[ig] / std::max(prec[ig], static_cast<Real>(1.0e-12)); |
| 97 | + zn[ig] = z; |
| 98 | + |
| 99 | + // r_old = -P * z_old (recover old raw residual) |
| 100 | + T r_old = -prec[ig] * zo[ig]; |
| 101 | + |
| 102 | + beta_num_zr += static_cast<Real>(std::real(z * std::conj(g[ig]))); |
| 103 | + beta_num_zo += static_cast<Real>(std::real(z * std::conj(r_old))); |
| 104 | + } |
| 105 | + |
| 106 | + Real beta = 0; |
| 107 | + const Real denom = beta_denom[j]; |
| 108 | + if (denom > static_cast<Real>(1.0e-30)) |
| 109 | + { |
| 110 | + beta = (beta_num_zr - beta_num_zo) / denom; |
| 111 | + if (beta < 0) |
| 112 | + beta = 0; |
| 113 | + } |
| 114 | + |
| 115 | + // d_new = z_new + beta * d_old |
| 116 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 117 | + pj[ig] = zn[ig] + beta * pj[ig]; |
| 118 | + |
| 119 | + // Save <z_new, r_new> as denominator for next iteration. |
| 120 | + beta_denom[j] = beta_num_zr + static_cast<Real>(1.0e-30); |
| 121 | + } |
| 122 | + |
| 123 | + // Persist state for next iteration. |
| 124 | + z_old.swap(z_new); |
| 125 | + grad_old = grad; |
| 126 | +} |
| 127 | + |
| 128 | +// --------------------------------------------------------------------------- |
| 129 | +// Line minimization along search direction: |
| 130 | +// For each band j: find optimal step α by minimizing the Rayleigh quotient |
| 131 | +// in the 2D subspace spanned by |psi_j> and |p_j>. |
| 132 | +// |
| 133 | +// The Rayleigh quotient: |
| 134 | +// R(α) = (h_ii + 2α h_ip + α² h_pp) / (s_ii + 2α s_ip + α² s_pp) |
| 135 | +// |
| 136 | +// Setting dR/dα = 0 gives a QUADRATIC equation A α² + B α + C = 0 with: |
| 137 | +// A = s_ip * h_pp - h_ip * s_pp |
| 138 | +// B = s_ii * h_pp - h_ii * s_pp |
| 139 | +// C = s_ii * h_ip - h_ii * s_ip |
| 140 | +// |
| 141 | +// The linear approximation α = -C / B (dropping the α² term) picks one of |
| 142 | +// the two stationary points more-or-less arbitrarily. For bands far from |
| 143 | +// convergence this can select the MAXIMUM, driving ψ toward high-energy |
| 144 | +// states. We solve the full quadratic and explicitly pick the root with |
| 145 | +// the lower Rayleigh quotient. |
| 146 | +// |
| 147 | +// Update: |psi> += α |p> |
| 148 | +// H|psi> += α H|p> |
| 149 | +// S|psi> += α S|p> |
| 150 | +// --------------------------------------------------------------------------- |
| 151 | +template <typename T, typename Device> |
| 152 | +void DiagoPPCG<T, Device>::line_minimize( |
| 153 | + T* psi, T* hpsi, T* spsi, |
| 154 | + const T* p, const T* hp, const T* sp, |
| 155 | + int ncol) const |
| 156 | +{ |
| 157 | + for (int j = 0; j < ncol; ++j) |
| 158 | + { |
| 159 | + const int off = j * ld_psi_; |
| 160 | + T* pj = psi + off; |
| 161 | + T* hj = hpsi + off; |
| 162 | + T* sj = spsi + off; |
| 163 | + const T* pp = p + off; |
| 164 | + const T* hpp = hp + off; |
| 165 | + const T* spp = sp + off; |
| 166 | + |
| 167 | + Real h_ii = gamma_dot(pj, hj); |
| 168 | + Real s_ii = gamma_dot(pj, sj); |
| 169 | + const T h_ip_c = complex_dot(pj, hpp); |
| 170 | + const T s_ip_c = complex_dot(pj, spp); |
| 171 | + Real h_pp = gamma_dot(pp, hpp); |
| 172 | + Real s_pp = gamma_dot(pp, spp); |
| 173 | + |
| 174 | + // Rotate the search direction so the first-order Rayleigh quotient |
| 175 | + // derivative is real. The scalar alpha solve below stays unchanged for |
| 176 | + // real problems, while complex PW states can use a complex step. |
| 177 | + T phase = T(1); |
| 178 | + const Real lambda = h_ii / std::max(s_ii, static_cast<Real>(1e-30)); |
| 179 | + const T q = h_ip_c - T(lambda) * s_ip_c; |
| 180 | + const Real q_abs = std::abs(q); |
| 181 | + if (q_abs > static_cast<Real>(1e-30)) |
| 182 | + phase = std::conj(q) / q_abs; |
| 183 | + |
| 184 | + Real h_ip = static_cast<Real>(std::real(phase * h_ip_c)); |
| 185 | + Real s_ip = static_cast<Real>(std::real(phase * s_ip_c)); |
| 186 | + |
| 187 | + // Coefficients of A alpha^2 + B alpha + C = 0 |
| 188 | + const Real A = s_ip * h_pp - h_ip * s_pp; |
| 189 | + const Real B = s_ii * h_pp - h_ii * s_pp; |
| 190 | + const Real C = s_ii * h_ip - h_ii * s_ip; |
| 191 | + |
| 192 | + auto ray_quot = [&](Real a) -> Real { |
| 193 | + return (h_ii + static_cast<Real>(2) * a * h_ip + a * a * h_pp) |
| 194 | + / std::max(s_ii + static_cast<Real>(2) * a * s_ip + a * a * s_pp, |
| 195 | + static_cast<Real>(1e-30)); |
| 196 | + }; |
| 197 | + |
| 198 | + Real alpha = 0; |
| 199 | + Real alpha_linear = (std::abs(B) > static_cast<Real>(1e-30)) |
| 200 | + ? -C / B : static_cast<Real>(0); |
| 201 | + |
| 202 | + const Real tol = std::numeric_limits<Real>::epsilon() * static_cast<Real>(100); |
| 203 | + if (std::abs(A) > tol * std::max(static_cast<Real>(1), std::abs(B))) |
| 204 | + { |
| 205 | + const Real disc = B * B - static_cast<Real>(4) * A * C; |
| 206 | + if (disc >= static_cast<Real>(0)) |
| 207 | + { |
| 208 | + const Real sqrt_disc = std::sqrt(disc); |
| 209 | + const Real a1 = (-B + sqrt_disc) / (static_cast<Real>(2) * A); |
| 210 | + const Real a2 = (-B - sqrt_disc) / (static_cast<Real>(2) * A); |
| 211 | + |
| 212 | + const Real r1 = ray_quot(a1); |
| 213 | + const Real r2 = ray_quot(a2); |
| 214 | + const Real r_lin = ray_quot(alpha_linear); |
| 215 | + |
| 216 | + if (r1 < r2 && r1 < r_lin) |
| 217 | + alpha = a1; |
| 218 | + else if (r2 < r1 && r2 < r_lin) |
| 219 | + alpha = a2; |
| 220 | + else |
| 221 | + alpha = alpha_linear; |
| 222 | + } |
| 223 | + else |
| 224 | + { |
| 225 | + alpha = alpha_linear; |
| 226 | + } |
| 227 | + } |
| 228 | + else |
| 229 | + { |
| 230 | + alpha = alpha_linear; |
| 231 | + } |
| 232 | + |
| 233 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 234 | + { |
| 235 | + const T step = T(alpha) * phase; |
| 236 | + pj[ig] += step * pp[ig]; |
| 237 | + hj[ig] += step * hpp[ig]; |
| 238 | + sj[ig] += step * spp[ig]; |
| 239 | + } |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +// --------------------------------------------------------------------------- |
| 244 | +// Cholesky orthonormalization (S-orthonormal): |
| 245 | +// 1. Form S-gram matrix J = psi^H * S * psi |
| 246 | +// 2. Cholesky: J = U^T * U (upper) |
| 247 | +// 3. Invert U: U^{-1} |
| 248 | +// 4. psi *= U^{-1}, Hpsi *= U^{-1}, Spsi *= U^{-1} |
| 249 | +// --------------------------------------------------------------------------- |
| 250 | +template <typename T, typename Device> |
| 251 | +void DiagoPPCG<T, Device>::orth_cholesky( |
| 252 | + T* psi, T* hpsi, T* spsi, int ncol) const |
| 253 | +{ |
| 254 | + // Save original vectors in case Cholesky fails numerically. |
| 255 | + std::vector<T> psi_orig(psi, psi + ld_psi_ * ncol); |
| 256 | + std::vector<T> hpsi_orig(hpsi, hpsi + ld_psi_ * ncol); |
| 257 | + std::vector<T> spsi_orig(spsi, spsi + ld_psi_ * ncol); |
| 258 | + |
| 259 | + // Gram matrix of S-orthonormality: J_{ij} = <psi_i | S | psi_j> |
| 260 | + std::vector<T> gram_s(ncol * ncol, T(0)); |
| 261 | + for (int j = 0; j < ncol; ++j) |
| 262 | + for (int i = 0; i < ncol; ++i) |
| 263 | + gram_s[i + j * ncol] = complex_dot(psi + i * ld_psi_, |
| 264 | + spsi + j * ld_psi_); |
| 265 | + |
| 266 | + bool cholesky_ok = false; |
| 267 | + try |
| 268 | + { |
| 269 | + HermitianLapack<T>::potrf(ncol, gram_s.data()); |
| 270 | + HermitianLapack<T>::trtri(ncol, gram_s.data()); |
| 271 | + |
| 272 | + std::vector<T> tmp(ld_psi_ * ncol, T(0)); |
| 273 | + for (int j = 0; j < ncol; ++j) |
| 274 | + for (int i = 0; i < ncol; ++i) { |
| 275 | + const T uinv = gram_s[i + j * ncol]; |
| 276 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 277 | + tmp[idx(ig, j, ld_psi_)] += psi[idx(ig, i, ld_psi_)] * uinv; |
| 278 | + } |
| 279 | + std::copy(tmp.begin(), tmp.end(), psi); |
| 280 | + |
| 281 | + set_zero(tmp); |
| 282 | + for (int j = 0; j < ncol; ++j) |
| 283 | + for (int i = 0; i < ncol; ++i) { |
| 284 | + const T uinv = gram_s[i + j * ncol]; |
| 285 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 286 | + tmp[idx(ig, j, ld_psi_)] += hpsi[idx(ig, i, ld_psi_)] * uinv; |
| 287 | + } |
| 288 | + std::copy(tmp.begin(), tmp.end(), hpsi); |
| 289 | + |
| 290 | + set_zero(tmp); |
| 291 | + for (int j = 0; j < ncol; ++j) |
| 292 | + for (int i = 0; i < ncol; ++i) { |
| 293 | + const T uinv = gram_s[i + j * ncol]; |
| 294 | + for (int ig = 0; ig < n_dim_; ++ig) |
| 295 | + tmp[idx(ig, j, ld_psi_)] += spsi[idx(ig, i, ld_psi_)] * uinv; |
| 296 | + } |
| 297 | + std::copy(tmp.begin(), tmp.end(), spsi); |
| 298 | + |
| 299 | + cholesky_ok = is_s_orthonormal(psi, spsi, ncol); |
| 300 | + } |
| 301 | + catch (const std::runtime_error&) { cholesky_ok = false; } |
| 302 | + |
| 303 | + if (!cholesky_ok) |
| 304 | + { |
| 305 | + std::copy(psi_orig.begin(), psi_orig.end(), psi); |
| 306 | + std::copy(hpsi_orig.begin(), hpsi_orig.end(), hpsi); |
| 307 | + std::copy(spsi_orig.begin(), spsi_orig.end(), spsi); |
| 308 | + s_gram_schmidt(psi, hpsi, spsi, ncol); |
| 309 | + } |
| 310 | +} |
| 311 | + |
| 312 | +} // namespace hsolver |
0 commit comments