votca 2024.2-dev
Loading...
Searching...
No Matches
pmlocalization.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2023 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 * Reference- A fast intrinsic localization procedure applicable for ab initio
18 * and semiempirical linear combination of atomic orbital wave functions J.
19 * Chem. Phys. 90, 4916 (1989); https://doi.org/10.1063/1.456588 János Pipek and
20 * Paul G. Mezey
21 */
22
24#include "votca/xtp/aomatrix.h"
25#include <limits>
26
27namespace votca {
28namespace xtp {
29
31
32 if (method_ == "jacobi-sweeps") {
34 << TimeStamp() << " Using Jacobi-Sweeps" << std::flush;
35 computePML_JS(orbitals);
36 } else if (method_ == "unitary-optimizer") {
38 << TimeStamp() << " Using Unitary Optimizer" << std::flush;
39 computePML_UT(orbitals);
40 }
41}
42
43double PMLocalization::cost(const Eigen::MatrixXd &W,
44 const std::vector<Eigen::MatrixXd> &Sat_all,
45 const Index nat) const {
46
47 double Dinv = 0.0;
48 double p = 2.0; // standard PM
49 for (Index iat = 0; iat < nat; iat++) {
50 Eigen::MatrixXd qw = Sat_all[iat] * W;
51 for (Index i = 0; i < W.cols(); i++) {
52 double Qa = W.col(i).transpose() * qw.col(i);
53 Dinv += std::pow(Qa, p);
54 }
55 }
56 return Dinv;
57}
58
59std::pair<double, Eigen::MatrixXd> PMLocalization::cost_derivative(
60 const Eigen::MatrixXd &W, const std::vector<Eigen::MatrixXd> &Sat_all,
61 const Index nat) const {
62 Eigen::MatrixXd Jderiv = Eigen::MatrixXd::Zero(n_occs_, n_occs_);
63 double Dinv = 0.0;
64 double p = 2.0; // standard PM
65 for (Index iat = 0; iat < nat; iat++) {
66 Eigen::MatrixXd qw = Sat_all[iat] * W;
67 for (Index i = 0; i < W.cols(); i++) {
68 double qwp = W.col(i).transpose() * qw.col(i);
69 Dinv += std::pow(qwp, p);
70 double t = p * std::pow(qwp, p - 1);
71 for (Index j = 0; j < W.cols(); j++) {
72 Jderiv(j, i) += t * qw(j, i);
73 }
74 }
75 }
76 return {Dinv, Jderiv};
77}
78
79Eigen::VectorXd PMLocalization::fit_polynomial(const Eigen::VectorXd &x,
80 const Eigen::VectorXd &y) const {
81
82 // Fit function to polynomial
83
84 if (x.size() != y.size()) {
85 throw std::runtime_error("x and y have different dimensions!\n");
86 }
87 Index N = x.size();
88 Index deg = N;
89
90 // Form matrix
91 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(N, deg);
92 for (Index i = 0; i < N; i++) {
93 for (Index j = 0; j < deg; j++) {
94 A(i, j) = std::pow(x(i), j);
95 }
96 }
97 // Solve for coefficients: A * c = y
98 return A.colPivHouseholderQr().solve(y);
99}
100
101double PMLocalization::find_smallest_step(const Eigen::VectorXd &coeff) const {
102
103 // get the complex roots of the polynomial
104 Eigen::VectorXcd complex_roots =
105 find_complex_roots(coeff.cast<std::complex<double>>());
106
107 // Real roots
108 std::vector<double> real_roots;
109 for (Index i = 0; i < complex_roots.size(); i++) {
110 if (std::abs(std::imag(complex_roots(i))) <
111 10 * std::numeric_limits<double>::epsilon()) {
112 real_roots.push_back(std::real(complex_roots(i)));
113 }
114 }
115
116 // Sort roots
117 std::sort(real_roots.begin(), real_roots.end());
118
119 double step = 0.0;
120 // for (Index i = 0; i < Index(real_roots.size()); i++) {
121 for (auto root : real_roots) {
122 // Omit extremely small steps because they might get you stuck.
123 if (root > std::sqrt(std::numeric_limits<double>::epsilon())) {
124 step = root;
125 break;
126 }
127 }
128 return step;
129}
130
132 const Eigen::VectorXcd &coeff) const {
133
134 // Coefficient of highest order term must be nonzero.
135 Index order = coeff.size();
136 while (coeff(order - 1) == 0.0 && order >= 1) order--;
137
138 if (order == 1) {
139 // Zeroth degree - no zeros!
141 << TimeStamp() << " Polynomial is constant - no zero can be found."
142 << std::flush;
143 }
144
145 // Form companion matrix
146 Eigen::MatrixXcd cmat = companion_matrix(coeff.head(order));
147
148 // and diagonalize it
149 Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es(cmat);
150
151 // Return the roots (unsorted)
152 return es.eigenvalues();
153}
154
156 const Eigen::VectorXcd &c) const {
157 if (c.size() <= 1) {
158 // Dummy return
159 Eigen::MatrixXcd dum;
160 return dum;
161 }
162
163 // Form companion matrix
164 Index N = c.size() - 1;
165 if (c(N) == 0.0) {
166 throw std::runtime_error("Coefficient of highest term vanishes!\n");
167 }
168
169 Eigen::MatrixXcd companion = Eigen::MatrixXcd::Zero(N, N);
170
171 // First row - coefficients normalized to that of highest term.
172 for (Index j = 0; j < N; j++) {
173 companion(0, j) = -c(N - (j + 1)) / c(N);
174 }
175
176 // Fill out the unit matrix part
177 for (Index j = 1; j < N; j++) {
178 companion(j, j - 1) = 1.0;
179 }
180
181 return companion;
182}
183
184Eigen::MatrixXd PMLocalization::rotate_W(const double step,
185 const Eigen::MatrixXd &W,
186 const Eigen::VectorXcd &eval,
187 const Eigen::MatrixXcd &evec) const {
188
189 Eigen::VectorXcd temp = (step * eval).array().exp();
190 Eigen::MatrixXd W_rotated =
191 (evec * temp.asDiagonal() * evec.adjoint()).real() * W; //.real because
192 // we assume
193 // only real W
194
195 return W_rotated;
196}
197
199
200 Eigen::MatrixXd occupied_orbitals = orbitals.MOs().eigenvectors().leftCols(
201 orbitals.getNumberOfAlphaElectrons());
202
203 // initialize a unitary matrix (as Idenity matrix for now)
205 W_ = Eigen::MatrixXd::Identity(n_occs_, n_occs_);
206
207 // prepare Mulliken charges
208 // get overlap matrix
209 aobasis_ = orbitals.getDftBasis();
210 AOOverlap overlap;
211 overlap.Fill(aobasis_);
212 overlap_ = overlap.Matrix();
214 Index numatoms = Index(numfuncpatom_.size());
215
216 // could be a bit memory expensive
217 std::vector<Eigen::MatrixXd> Sat_all = setup_pop_matrices(occupied_orbitals);
218 XTP_LOG(Log::info, log_) << TimeStamp() << " Calculated charge matrices"
219 << std::flush;
220
221 // initialize Riemannian gradient and search direction matrices
222 G_ = Eigen::MatrixXd::Zero(n_occs_, n_occs_);
223 H_ = Eigen::MatrixXd::Zero(n_occs_, n_occs_);
224
225 bool converged = false;
226 Index iteration = 0;
227 std::string update_type;
228 while (!converged) {
229 // Store old gradient and search direction
230 G_old_ = G_;
231 H_old_ = H_;
232
233 // calculate cost and its derivative wrt unitary matrix for current W
234 auto [J, Jderiv] = cost_derivative(W_, Sat_all, numatoms);
235 J_ = J;
237 << TimeStamp() << " Calculated cost function and its W-derivative"
238 << std::flush;
239
240 // calculate Riemannian derivative
241 G_ = Jderiv * W_.transpose() - W_ * Jderiv.transpose();
242
243 if (iteration == 0 || (iteration - 1) % W_.cols() == 0) {
244 // calculate search direction using SDSA for now
245 update_type = "SDSA";
246 H_ = G_;
247 } else {
248 // calculate search direction from CGPR update
249 update_type = "CGPR";
250 double gamma = inner_prod(G_ - G_old_, G_) / inner_prod(G_old_, G_old_);
251 H_ = G_ + gamma * H_old_;
252 // make sure H_ is exactly skew symmetric
253 H_ = 0.5 * (H_ - H_.transpose());
254
255 // Check that update is OK
256 if (inner_prod(G_, H_) < 0.0) {
257 H_ = G_;
259 << TimeStamp() << "CG search direction reset." << std::flush;
260 }
261 }
262
263 Index orderW = 4; // for PM
264 // H is skew symmetric, real so should have purely imaginary eigenvalues
265 // in pairs +-eval, and 0, if dim is odd.
266 Eigen::EigenSolver<Eigen::MatrixXd> es(H_);
267 Eigen::VectorXcd Hval = es.eigenvalues();
268 Eigen::MatrixXcd Hvec = es.eigenvectors();
269 double wmax = Hval.cwiseAbs().maxCoeff();
270 double Tmu = 2.0 * tools::conv::Pi / (static_cast<double>(orderW) * wmax);
271 double step;
272 // line optimization via polynomial fit
273 Index npoints = 4; // to be adapted/adaptable
274 double deltaTmu = Tmu / static_cast<double>(npoints - 1);
275 int halved = 0;
276
277 // finding optimal step
278 while (true) {
279
280 Eigen::VectorXd mu(npoints);
281 Eigen::VectorXd derivative_points(npoints); // cost function derivative
282 Eigen::VectorXd cost_points(npoints); // cost function value
283
284 for (Index i = 0; i < npoints; i++) {
285 mu(i) = static_cast<double>(i) * deltaTmu;
286 Eigen::MatrixXd W_rotated = rotate_W(mu(i), W_, Hval, Hvec);
287
288 // calculate cost and derivative for this rotated W matrix
289 auto [cost, der] = cost_derivative(W_rotated, Sat_all, numatoms);
290 cost_points(i) = cost;
291 derivative_points(i) =
292 2.0 *
293 std::real((der * W_rotated.transpose() * H_.transpose()).trace());
294 }
295
296 // Check sign of the derivative
297 if (derivative_points(0) < 0.0) {
299 << TimeStamp() << "Derivative is negative!" << mu << " "
300 << derivative_points << std::flush;
301 }
302
303 // Fit to polynomial of order p
304 Eigen::VectorXd polyfit_coeff = fit_polynomial(mu, derivative_points);
305
306 // Find step as smallest real zero of the polynomial
307 step = find_smallest_step(polyfit_coeff);
308
309 // is step too far?
310 if (step > 0.0 && step <= Tmu) {
311 // is in range, let's continue
312 Eigen::MatrixXd W_new = rotate_W(step, W_, Hval, Hvec);
313
314 // has objective function value changed in the right direction?
315 double J_new = cost(W_new, Sat_all, numatoms);
316 double delta_J = J_new - J_;
317
318 if (delta_J > 0.0) {
319 // we accept and update
320 W_old_ = W_;
321 W_ = W_new;
322 J_old_ = J_;
323 J_ = J_new;
324 break;
325 } else {
327 << TimeStamp()
328 << " WARNING: Cost function is decreasing. deltaJ = "
329 << delta_J << std::flush;
330
331 if (halved < 10) {
333 << TimeStamp() << "Trying halved maximum step size "
334 << std::flush;
335 halved++;
336 deltaTmu /= 2.0;
337 continue;
338 } else {
339 throw std::runtime_error(
340 "Problem in polynomial line search - could not find suitable "
341 "extremum!\n");
342 }
343 }
344 } else {
345 // now adjust step search, if original step went too far
347 << TimeStamp()
348 << "Step went beyond max step, trying reduced max step..."
349 << std::flush;
350 if (halved < 4) {
351 halved++;
352 deltaTmu /= 2.0;
353 continue;
354 } else {
355 throw std::runtime_error(
356 "Problem in polynomial line search - could not find suitable "
357 "extremum!\n");
358 }
359 }
360 }
361
362 double G_norm = inner_prod(G_, G_);
363
365 << (boost::format(" UT iteration = %1$6i (%6$4.s) Tmu = %4$4.2e mu_opt "
366 "= %5$1.4f |deltaJ| = %2$4.2e |G| = %3$4.2e") %
367 (iteration) % std::abs(J_ - J_old_) % G_norm % Tmu % step %
368 update_type)
369 .str()
370 << std::flush;
371
372 if (iteration > 0 &&
373 (G_norm < G_threshold_ && std::abs(J_ - J_old_) < J_threshold_)) {
374 converged = true;
375 }
376 iteration++;
377 }
378
379 // all done, what are the actual LMOS?
381 (W_.transpose() * occupied_orbitals.transpose()).transpose(); //?
383 // determine the energies of the localized orbitals
384 Eigen::VectorXd energies = calculate_lmo_energies(orbitals);
385
386 // sort the LMOS according to energies
387 auto [LMOS, LMOS_energies] = sort_lmos(energies);
388 // print info
389 for (Index i = 0; i < LMOS_energies.size(); i++) {
391 << (boost::format(" LMO index = %1$4i Energy = %2$10.5f Hartree ") %
392 (i) % LMOS_energies(i))
393 .str()
394 << std::flush;
395 }
396 orbitals.setLMOs(LMOS);
397 orbitals.setLMOs_energies(LMOS_energies);
398}
399
401 // initialize with occupied CMOs
402 localized_orbitals_ = orbitals.MOs().eigenvectors().leftCols(
403 orbitals.getNumberOfAlphaElectrons());
404 aobasis_ = orbitals.getDftBasis();
405 AOOverlap overlap;
406 overlap.Fill(aobasis_);
407 overlap_ = overlap.Matrix();
408
409 XTP_LOG(Log::error, log_) << std::flush;
411 << TimeStamp() << " Starting localization of orbitals" << std::flush;
412
413 // determine initial penalty_function
415
416 Index iteration = 1;
417
418 while (iteration < nrOfIterations_) {
419
420 XTP_LOG(Log::info, log_) << "Iteration: " << iteration << std::flush;
421
422 Index maxrow, maxcol;
423 double max_penalty = PM_penalty_.maxCoeff(&maxrow, &maxcol);
424
426 << "maximum of penalty function: " << max_penalty << std::flush;
427
428 if (max_penalty < convergence_limit_) break;
429
431 << "Orbitals to be changed: " << maxrow << " " << maxcol << std::flush;
432
433 Eigen::MatrixX2d max_orbs(localized_orbitals_.rows(), 2);
434 max_orbs << localized_orbitals_.col(maxrow),
435 localized_orbitals_.col(maxcol);
436 Eigen::MatrixX2d rotated_orbs = rotateorbitals(max_orbs, maxrow, maxcol);
437 localized_orbitals_.col(maxrow) = rotated_orbs.col(0);
438 localized_orbitals_.col(maxcol) = rotated_orbs.col(1);
439
440 update_penalty(maxrow, maxcol);
441
442 iteration++;
443 }
444 if (iteration == nrOfIterations_) {
445 throw std::runtime_error(
446 "Localization with Jacobi-Sweeps did not converge");
447 }
448 XTP_LOG(Log::error, log_) << TimeStamp() << " Orbitals localized after "
449 << iteration + 1 << " iterations" << std::flush;
450
451 // check if localized orbitals orthonormal, if nor warn
453
454 // determine the energies of the localized orbitals
455 Eigen::VectorXd energies = calculate_lmo_energies(orbitals);
456
457 // sort the LMOS according to energies
458 auto [LMOS, LMOS_energies] = sort_lmos(energies);
459 // print info
460 for (Index i = 0; i < LMOS_energies.size(); i++) {
462 << (boost::format(" LMO index = %1$4i Energy = %2$10.5f Hartree ") %
463 (i) % LMOS_energies(i))
464 .str()
465 << std::flush;
466 }
467 orbitals.setLMOs(LMOS);
468 orbitals.setLMOs_energies(LMOS_energies);
469}
470
471// sort the LMOs according to their energy
472std::pair<Eigen::MatrixXd, Eigen::VectorXd> PMLocalization::sort_lmos(
473 const Eigen::VectorXd &energies) {
474 Eigen::VectorXd LMOS_energies(energies.size());
475 Eigen::MatrixXd LMOS(localized_orbitals_.rows(), localized_orbitals_.cols());
476
477 // sort the LMOs according to energy
478 std::vector<std::pair<double, int>> vp;
479
480 // Inserting element in pair vector
481 // to keep track of previous indexes
482 for (int i = 0; i < energies.size(); ++i) {
483 vp.push_back(std::make_pair(energies(i), i));
484 }
485 // Sorting pair vector
486 std::sort(vp.begin(), vp.end());
487
488 for (Index i = 0; i < energies.size(); i++) {
489 LMOS_energies(i) = vp[i].first;
490 LMOS.col(i) = localized_orbitals_.col(vp[i].second);
491 }
492
493 return {LMOS, LMOS_energies};
494}
495
496// calculate energies of LMOs
498 const Orbitals &orbitals) {
499 Eigen::MatrixXd h = overlap_ * orbitals.MOs().eigenvectors() *
500 orbitals.MOs().eigenvalues().asDiagonal() *
501 orbitals.MOs().eigenvectors().transpose() * overlap_;
502
503 return (localized_orbitals_.transpose() * h * localized_orbitals_).diagonal();
504}
505
506// check orthonormality of LMOs
508
509 Eigen::MatrixXd norm =
511 Eigen::MatrixXd check_norm =
512 norm - Eigen::MatrixXd::Identity(norm.rows(), norm.cols());
513 bool not_orthonormal = (check_norm.cwiseAbs().array() > 1e-5).any();
514 if (not_orthonormal) {
516 << " WARNING: Localized orbtials are not "
517 "orthonormal. Proceed with caution! "
518 << std::flush;
519 XTP_LOG(Log::info, log_) << TimeStamp() << " LMOs * S * LMOs" << std::flush;
520 for (Index i = 0; i < norm.rows(); i++) {
521 for (Index j = 0; j < norm.cols(); j++) {
522 if (std::abs(check_norm(i, j)) > 1e-5) {
524 << TimeStamp()
525 << (boost::format(" Element (%1$4i,%2$4i) = %3$8.2e") % (i) % j %
526 norm(i, j))
527 .str()
528 << std::flush;
529 }
530 }
531 }
532 }
533
534 return;
535}
536
537// Function to rotate the 2 maximum orbitals (s and t)
538Eigen::MatrixX2d PMLocalization::rotateorbitals(const Eigen::MatrixX2d &maxorbs,
539 const Index s, const Index t) {
540 const double gamma =
541 0.25 *
542 asin(B_(s, t) / sqrt((A_(s, t) * A_(s, t)) + (B_(s, t) * B_(s, t))));
543 Eigen::MatrixX2d rotatedorbitals(maxorbs.rows(), 2);
544 rotatedorbitals.col(0) =
545 (std::cos(gamma) * maxorbs.col(0)) + (std::sin(gamma) * maxorbs.col(1));
546 rotatedorbitals.col(1) = -1 * (std::sin(gamma) * maxorbs.col(0)) +
547 (std::cos(gamma) * maxorbs.col(1));
548 XTP_LOG(Log::info, log_) << "Sine of the rotation angle = " << std::sin(gamma)
549 << std::flush;
550 return rotatedorbitals;
551}
552
553Eigen::VectorXd PMLocalization::pop_per_atom(const Eigen::VectorXd &orbital) {
554
555 Eigen::RowVectorXd MullikenPop_orb_per_basis =
556 (orbital.asDiagonal() * overlap_ * orbital.asDiagonal()).colwise().sum();
557 Index start = 0;
558
559 Eigen::VectorXd per_atom = Eigen::VectorXd::Zero(Index(numfuncpatom_.size()));
560 for (Index atom_id = 0; atom_id < Index(numfuncpatom_.size()); atom_id++) {
561 per_atom(atom_id) =
562 MullikenPop_orb_per_basis.segment(start, numfuncpatom_[atom_id]).sum();
563 start += numfuncpatom_[atom_id];
564 }
565
566 return per_atom;
567}
568
569// Determine PM cost function based on Mulliken populations
571
572 PM_penalty_ = Eigen::MatrixXd::Zero(localized_orbitals_.cols(),
573 localized_orbitals_.cols());
574 // Variable names A and B are used directly as described in the paper above
575 A_ = Eigen::MatrixXd::Zero(localized_orbitals_.cols(),
576 localized_orbitals_.cols());
577 B_ = A_;
578
580
581 // get the s-s elements first ("diagonal in orbital")
582 MullikenPop_orb_per_atom_ = Eigen::MatrixXd::Zero(
583 localized_orbitals_.cols(), Index(numfuncpatom_.size()));
584#pragma omp parallel for
585 for (Index s = 0; s < localized_orbitals_.cols(); s++) {
587 }
588
589// now we only need to calculate the off-diagonals explicitly
590#pragma omp parallel for
591 for (Index s = 0; s < localized_orbitals_.cols(); s++) {
592 Eigen::MatrixXd s_overlap =
593 localized_orbitals_.col(s).asDiagonal() * overlap_;
594
595 for (Index t = s + 1; t < localized_orbitals_.cols(); t++) {
596
597 Eigen::Vector2d temp = offdiag_penalty_elements(s_overlap, s, t);
598
599 A_(s, t) = temp(0);
600 B_(s, t) = temp(1);
601 PM_penalty_(s, t) =
602 A_(s, t) + sqrt((A_(s, t) * A_(s, t)) + (B_(s, t) * B_(s, t)));
603 }
604 }
605 return;
606}
607
608std::vector<Eigen::MatrixXd> PMLocalization::setup_pop_matrices(
609 const Eigen::MatrixXd &occ_orbitals) {
610
611 // initialize everything
612 Index numatoms = Index(numfuncpatom_.size());
613 Index noccs = occ_orbitals.cols();
614
615 std::vector<Eigen::MatrixXd> Qat;
616 for (Index iat = 0; iat < numatoms; iat++) {
617 Qat.push_back(Eigen::MatrixXd::Zero(noccs, noccs));
618 }
619
621
622 // get the s-s elements first ("diagonal in orbital")
624 Eigen::MatrixXd::Zero(noccs, Index(numfuncpatom_.size()));
625#pragma omp parallel for
626 for (Index s = 0; s < noccs; s++) {
627 MullikenPop_orb_per_atom_.row(s) = pop_per_atom(occ_orbitals.col(s));
628 }
629
630 // put this on the diagonals
631 for (Index iat = 0; iat < numatoms; iat++) {
632 Qat[iat].diagonal() = MullikenPop_orb_per_atom_.col(iat);
633 }
634
635// now fill the offdiagonals
636#pragma omp parallel for
637 for (Index s = 0; s < noccs; s++) {
638
639 Eigen::MatrixXd s_overlap = occ_orbitals.col(s).asDiagonal() * overlap_;
640
641 for (Index t = s + 1; t < noccs; t++) {
642
643 Eigen::MatrixXd splitwiseMullikenPop_orb_SandT =
644 s_overlap * occ_orbitals.col(t).asDiagonal();
645 Eigen::RowVectorXd MullikenPop_orb_SandT_per_basis =
646 0.5 * (splitwiseMullikenPop_orb_SandT.colwise().sum() +
647 splitwiseMullikenPop_orb_SandT.rowwise().sum().transpose());
648
649 Index start = 0;
650
651 for (Index atom_id = 0; atom_id < Index(numfuncpatom_.size());
652 atom_id++) {
653 Qat[atom_id](s, t) = MullikenPop_orb_SandT_per_basis
654 .segment(start, numfuncpatom_[atom_id])
655 .sum();
656 Qat[atom_id](t, s) = Qat[atom_id](s, t);
657
658 start += numfuncpatom_[atom_id];
659 }
660 }
661 }
662
663 return Qat;
664}
665
667 const Eigen::MatrixXd &s_overlap, Index s, Index t) {
668
669 Eigen::MatrixXd splitwiseMullikenPop_orb_SandT =
670 s_overlap * localized_orbitals_.col(t).asDiagonal();
671 Eigen::RowVectorXd MullikenPop_orb_SandT_per_basis =
672 0.5 * (splitwiseMullikenPop_orb_SandT.colwise().sum() +
673 splitwiseMullikenPop_orb_SandT.rowwise().sum().transpose());
674
675 Index start =
676 0; // This helps to sum only over the basis functions on an atom
677 double Ast = 0;
678 double Bst = 0;
679
680 for (Index atom_id = 0; atom_id < Index(numfuncpatom_.size()); atom_id++) {
681 double MullikenPop_orb_SandT_per_atom =
682 MullikenPop_orb_SandT_per_basis.segment(start, numfuncpatom_[atom_id])
683 .sum();
684
685 Ast += MullikenPop_orb_SandT_per_atom * MullikenPop_orb_SandT_per_atom -
686 0.25 * ((MullikenPop_orb_per_atom_(s, atom_id) -
687 MullikenPop_orb_per_atom_(t, atom_id)) *
688 (MullikenPop_orb_per_atom_(s, atom_id) -
689 MullikenPop_orb_per_atom_(t, atom_id)));
690
691 Bst += MullikenPop_orb_SandT_per_atom *
692 (MullikenPop_orb_per_atom_(s, atom_id) -
693 MullikenPop_orb_per_atom_(t, atom_id));
694 start += numfuncpatom_[atom_id];
695 }
696
697 Eigen::Vector2d out(Ast, Bst);
698
699 return out;
700}
701
702// Update PM cost function based on Mulliken populations after rotations
704
705 // update the get the s-s elements for orb1 and orb2
706#pragma omp parallel for
707 for (Index s = 0; s < localized_orbitals_.cols(); s++) {
708 if (s == orb1 || s == orb2) {
709
712 }
713 }
714
715// now we only need to calculate the off-diagonals explicitly for all
716// pairs involving orb1 or orb2
717#pragma omp parallel for
718 for (Index s = 0; s < localized_orbitals_.cols(); s++) {
719 Eigen::MatrixXd s_overlap =
720 localized_orbitals_.col(s).asDiagonal() * overlap_;
721
722 for (Index t = s + 1; t < localized_orbitals_.cols(); t++) {
723
724 // we do this only if any of s or t matches orb1 or orb2
725 if (s == orb1 || s == orb2 || t == orb1 || t == orb2) {
726
727 Eigen::Vector2d temp = offdiag_penalty_elements(s_overlap, s, t);
728 A_(s, t) = temp(0);
729 B_(s, t) = temp(1);
730 PM_penalty_(s, t) =
731 A_(s, t) + sqrt((A_(s, t) * A_(s, t)) + (B_(s, t) * B_(s, t)));
732 }
733 }
734 }
735 return;
736}
737
738} // namespace xtp
739} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:52
container for molecular orbitals
Definition orbitals.h:46
Index getNumberOfAlphaElectrons() const
Definition orbitals.h:93
void setLMOs_energies(const Eigen::VectorXd &energies)
Definition orbitals.h:415
void setLMOs(const Eigen::MatrixXd &matrix)
Definition orbitals.h:412
const tools::EigenSystem & MOs() const
Definition orbitals.h:122
const AOBasis & getDftBasis() const
Definition orbitals.h:208
Eigen::VectorXcd find_complex_roots(const Eigen::VectorXcd &coeff) const
double inner_prod(const Eigen::MatrixXd &A, const Eigen::MatrixXd &B) const
Eigen::MatrixXd MullikenPop_orb_per_atom_
void computePML(Orbitals &orbitals)
Eigen::VectorXd calculate_lmo_energies(const Orbitals &orbitals)
void computePML_UT(Orbitals &orbitals)
Eigen::Vector2d offdiag_penalty_elements(const Eigen::MatrixXd &s_overlap, Index s, Index t)
std::pair< Eigen::MatrixXd, Eigen::VectorXd > sort_lmos(const Eigen::VectorXd &energies)
void update_penalty(Index s, Index t)
double find_smallest_step(const Eigen::VectorXd &coeff) const
void computePML_JS(Orbitals &orbitals)
Eigen::MatrixXcd companion_matrix(const Eigen::VectorXcd &coeff) const
Eigen::VectorXd fit_polynomial(const Eigen::VectorXd &x, const Eigen::VectorXd &y) const
Eigen::MatrixX2d rotateorbitals(const Eigen::MatrixX2d &maxorbs, Index s, Index t)
Eigen::MatrixXd localized_orbitals_
std::vector< Eigen::MatrixXd > setup_pop_matrices(const Eigen::MatrixXd &occ_orbitals)
Eigen::VectorXd pop_per_atom(const Eigen::VectorXd &orbital)
std::vector< Index > numfuncpatom_
std::pair< double, Eigen::MatrixXd > cost_derivative(const Eigen::MatrixXd &W, const std::vector< Eigen::MatrixXd > &Sat_all, const Index nat) const
Eigen::MatrixXd rotate_W(const double step, const Eigen::MatrixXd &W, const Eigen::VectorXcd &eval, const Eigen::MatrixXcd &evec) const
double cost(const Eigen::MatrixXd &W, const std::vector< Eigen::MatrixXd > &Sat_all, const Index nat) const
Timestamp returns the current time as a string Example: cout << TimeStamp()
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
const double Pi
Definition constants.h:36
base class for all analysis tools
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26