votca 2026-dev
Loading...
Searching...
No Matches
embeddingengine.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 *
18 */
19/*
20 *References- (1) A Simple, Exact Density-Functional-Theory
21 *Embedding Scheme. Frederick R. Manby, Martina Stella, Jason D. Goodpaster, and
22 *Thomas F. Miller Journal of Chemical Theory and Computation 2012 8 (8),
23 *2564-2568 DOI: 10.1021/ct300544e
24 *(2) Taylor A. Barnes, Jason D. Goodpaster, Frederick R. Manby, and Thomas F.
25 *Miller III , "Accurate basis set truncation for wavefunction embedding", J.
26 *Chem. Phys. 139, 024103 (2013) https://doi.org/10.1063/1.4811112
27 *(3) Simon J. Bennie, Martina Stella, Thomas F. Miller III, and Frederick R.
28 *Manby , "Accelerating wavefunction in density-functional-theory embedding by
29 *truncating the active basis set", J. Chem. Phys. 143, 024105 (2015)
30 *https://doi.org/10.1063/1.4923367
31 *(3) Projection-Based Wavefunction-in-DFT Embedding
32 *Sebastian J. R. Lee, Matthew Welborn, Frederick R. Manby, and Thomas F. Miller
33 *III Accounts of Chemical Research 2019 52 (5), 1359-1368
34 *DOI: 10.1021/acs.accounts.8b00672
35 */
36
37// Third party includes
38#include <algorithm>
39#include <boost/format.hpp>
40
41// VOTCA includes
44
45// Local VOTCA includes
49#include "votca/xtp/aomatrix.h"
51#include "votca/xtp/citations.h"
53#include "votca/xtp/dftengine.h"
55#include "votca/xtp/logger.h"
56#include "votca/xtp/mmregion.h"
57#include "votca/xtp/orbitals.h"
59
60namespace votca {
61namespace xtp {
62
64 citations::Cite("Manby:2012");
65 citations::Cite("Sundaram:2024");
66
67 // reading in the orbitals of the full DFT calculation
68 tools::EigenSystem embeddingMOs = orb.MOs();
69
70 // constructing the full electron density matrix
71 const Eigen::MatrixXd FullDensityMatrix = orb.DensityMatrixGroundState();
72 XTP_LOG(Log::error, *pLog_) << std::flush;
74 << TimeStamp() << " Starting Projection based dft-in-dft embedding"
75 << std::flush;
76
77 // reading the localized orbitals and update the occupied orbitals in MOs
79 << TimeStamp() << " Passing localized orbitals as the initial guess"
80 << std::flush;
81 Eigen::MatrixXd LMOs = orb.getLMOs();
82
83 embeddingMOs.eigenvectors().leftCols(orb.getNumberOfAlphaElectrons()) = LMOs;
84
85 // determine the active and inactive electron densities
86 std::vector<Index> activeatoms =
88
90 << "Indices of active atoms selected are: " << active_atoms_as_string_
91 << std::flush;
92 ActiveDensityMatrix DMAT_A(orb, activeatoms, active_threshold_);
93 const Eigen::MatrixXd InitialActiveDensityMatrix = DMAT_A.compute_Dmat_A()[0];
94 Eigen::MatrixXd InitialInactiveMOs = DMAT_A.compute_Dmat_A()[2];
95
97 << TimeStamp() << " Active density formation done" << std::flush;
98
99 Eigen::MatrixXd InactiveDensityMatrix =
100 FullDensityMatrix - InitialActiveDensityMatrix;
101
102 orb.setInactiveDensity(InactiveDensityMatrix);
103
104 // AOoverlap to calculate number of active/inactive electrons
105 AOBasis aobasis = orb.getDftBasis();
106 AOOverlap overlap;
107 overlap.Fill(aobasis);
108
109 Index all_electrons = static_cast<Index>(
110 std::round(FullDensityMatrix.cwiseProduct(overlap.Matrix()).sum()));
111 active_electrons_ = static_cast<Index>(std::round(
112 InitialActiveDensityMatrix.cwiseProduct(overlap.Matrix()).sum()));
113 Index inactive_electrons = static_cast<Index>(
114 std::round(InactiveDensityMatrix.cwiseProduct(overlap.Matrix()).sum()));
115
117 << TimeStamp() << " Total electrons: " << all_electrons << std::flush;
119 << TimeStamp() << " Active electrons: " << active_electrons_
120 << std::flush;
122 << TimeStamp() << " Inactive electrons: " << inactive_electrons
123 << std::flush;
124
125 // check for consistency
126 if ((active_electrons_ + inactive_electrons) != all_electrons) {
127 throw std::runtime_error(
128 " Sum of active and inactive electrons does "
129 "not match full number of electrons!");
130 return false;
131 }
132
133 // setup the DFT engine with the active electrons only
135
136 // setup one-electron part of the Hamiltonian
137 Mat_p_Energy H0 = SetupH0(orb.QMAtoms());
138
139 // energy of the one-electron part of the Hamiltonian
140 const double E0_full = FullDensityMatrix.cwiseProduct(H0.matrix()).sum();
141 const double E0_initial_active =
142 InitialActiveDensityMatrix.cwiseProduct(H0.matrix()).sum();
143 E_nuc_ = H0.energy();
144
145 // setup the Vxc integrator
146 Vxc_Potential<Vxc_Grid> vxcpotential = SetupVxc(orb.QMAtoms());
147 // get the constant XC contributions for the embedding
148 const Mat_p_Energy xc_full = vxcpotential.IntegrateVXC(FullDensityMatrix);
149 const Mat_p_Energy xc_initial_active =
150 vxcpotential.IntegrateVXC(InitialActiveDensityMatrix);
151
152 // get the constant Coulomb matrices for the embedding
153 Eigen::MatrixXd J_full =
154 Eigen::MatrixXd::Zero(FullDensityMatrix.rows(), FullDensityMatrix.cols());
155 Eigen::MatrixXd J_initial_active =
156 Eigen::MatrixXd::Zero(FullDensityMatrix.rows(), FullDensityMatrix.cols());
157 Eigen::MatrixXd K_full =
158 Eigen::MatrixXd::Zero(FullDensityMatrix.rows(), FullDensityMatrix.cols());
159 Eigen::MatrixXd K_initial_active =
160 Eigen::MatrixXd::Zero(FullDensityMatrix.rows(), FullDensityMatrix.cols());
161 if (ScaHFX_ > 0) {
162 std::array<Eigen::MatrixXd, 2> JandK_full =
163 CalcERIs_EXX(embeddingMOs.eigenvectors(), FullDensityMatrix, 1e-12);
164 std::array<Eigen::MatrixXd, 2> JandK_initial_active = CalcERIs_EXX(
165 embeddingMOs.eigenvectors(), InitialActiveDensityMatrix, 1e-12);
166 J_full = JandK_full[0];
167 K_full = 0.5 * ScaHFX_ * JandK_full[1];
168 J_initial_active = JandK_initial_active[0];
169 K_initial_active = 0.5 * ScaHFX_ * JandK_initial_active[1];
170 } else {
171 J_full = CalcERIs(FullDensityMatrix, 1e-12);
172 J_initial_active = CalcERIs(InitialActiveDensityMatrix, 1e-12);
173 }
174 // get the Hartree energies
175 const double E_Hartree_full =
176 0.5 * FullDensityMatrix.cwiseProduct(J_full + K_full).sum();
177 const double E_Hartree_initial_active =
178 0.5 * InitialActiveDensityMatrix
179 .cwiseProduct(J_initial_active + K_initial_active)
180 .sum();
181
182 // Energy of the full reference system
183 Total_E_full_ = E0_full + E_Hartree_full + xc_full.energy() + E_nuc_;
184
186 << TimeStamp()
187 << " Reference total energy full electron system: " << Total_E_full_
188 << " Ha" << std::flush;
189
190 // projection parameter, to be made an option
191 Eigen::MatrixXd ProjectionOperator =
192 overlap.Matrix() * InactiveDensityMatrix * overlap.Matrix();
193
194 // XC and Hartree contribution to the external embedding potential/energy
195 const Eigen::MatrixXd v_embedding = J_full + K_full + xc_full.matrix() -
196 J_initial_active - K_initial_active -
197 xc_initial_active.matrix();
198
199 const double constant_embedding_energy = Total_E_full_ - E0_initial_active -
200 E_Hartree_initial_active -
201 xc_initial_active.energy();
202
204 << TimeStamp() << " Constant energy embedding terms: " << std::flush;
206 << TimeStamp() << " 1) E_DFT[full]: " << Total_E_full_ << std::flush;
208 << TimeStamp() << " 2) E_0[initial active]: " << E0_initial_active
209 << std::flush;
211 << TimeStamp() << " 3) E_H[initial active]: " << E_Hartree_initial_active
212 << std::flush;
213 XTP_LOG(Log::info, *pLog_) << TimeStamp() << " 4) E_XC[initial active]: "
214 << xc_initial_active.energy() << std::flush;
216 << TimeStamp() << " Total 1 -2 -3 -4: " << constant_embedding_energy
217 << std::flush;
218
219 if (truncate_) { // Truncation starts here
220 orb.setCalculationType("Truncated");
221 TruncateBasis(orb, activeatoms, H0, InitialActiveDensityMatrix, v_embedding,
222 InitialInactiveMOs);
223 active_and_border_atoms_ = activeatoms;
224 }
225 // SCF loop if you don't truncate active region
226 else {
227 orb.setCalculationType("Embedded_noTrunc");
228 Eigen::MatrixXd ActiveDensityMatrix = InitialActiveDensityMatrix;
229 conv_accelerator_.StartNewSCF();
230 for (Index this_iter = 0; this_iter < max_iter_; this_iter++) {
231 XTP_LOG(Log::error, *pLog_) << std::flush;
233 << TimeStamp() << " Iteration " << this_iter + 1 << " of "
234 << max_iter_ << std::flush;
235
236 // get the XC contribution for the updated active density matrix
237 Mat_p_Energy xc_active = vxcpotential.IntegrateVXC(ActiveDensityMatrix);
238 double E_xc_active = xc_active.energy();
239
240 // get the Hartree contribution for the updated active density matrix
241 Eigen::MatrixXd J_active = Eigen::MatrixXd::Zero(
243 Eigen::MatrixXd K_active = Eigen::MatrixXd::Zero(
245 if (ScaHFX_ > 0) {
246 std::array<Eigen::MatrixXd, 2> JandK_active = CalcERIs_EXX(
247 embeddingMOs.eigenvectors(), ActiveDensityMatrix, 1e-12);
248 J_active = JandK_active[0];
249 K_active = 0.5 * ScaHFX_ * JandK_active[1];
250
251 } else {
252 J_active = CalcERIs(ActiveDensityMatrix, 1e-12);
253 }
254 double E_Hartree_active =
255 0.5 * ActiveDensityMatrix.cwiseProduct(J_active + K_active).sum();
256
257 // update the active Hamiltonian
258 const Eigen::MatrixXd Level_Shift_Operator =
259 levelshift_ * ProjectionOperator;
260 Eigen::MatrixXd H_active = H0.matrix() + v_embedding +
261 Level_Shift_Operator + xc_active.matrix() +
262 J_active + K_active;
263
264 const double E0_active =
265 ActiveDensityMatrix.cwiseProduct(H0.matrix()).sum();
266 const double E_embedding =
267 (ActiveDensityMatrix - InitialActiveDensityMatrix)
268 .cwiseProduct(v_embedding)
269 .sum();
270 const double E_levelshift =
271 ActiveDensityMatrix.cwiseProduct(Level_Shift_Operator).sum();
272
273 double TotalEnergy = E0_active + E_Hartree_active + E_xc_active +
274 constant_embedding_energy + E_levelshift +
275 E_embedding;
276
278 << TimeStamp() << " Total Energy after embedding: " << TotalEnergy
279 << std::flush;
281 << TimeStamp() << " I) E_0[active]: " << E0_active << std::flush;
283 << TimeStamp() << " II) E_H[active]: " << E_Hartree_active
284 << std::flush;
286 << TimeStamp() << " III) E_xc[active]: " << E_xc_active << std::flush;
288 << TimeStamp() << " IV) E_ext_emb :" << constant_embedding_energy
289 << std::flush;
291 << TimeStamp() << " V) E_corr_emb :" << E_embedding << std::flush;
293 << TimeStamp() << " VI) E_levelshift :" << E_levelshift
294 << std::flush;
296 << TimeStamp() << " Total I + II + III + IV + V +VI :" << TotalEnergy
297 << std::flush;
298
299 // get a new density matrix in the active region
301 << std::endl
302 << "Active electrons in = "
303 << ActiveDensityMatrix.cwiseProduct(overlap.Matrix()).sum()
304 << std::flush;
306 ActiveDensityMatrix, H_active, embeddingMOs, TotalEnergy);
308 << std::endl
309 << "Active electrons out= "
310 << ActiveDensityMatrix.cwiseProduct(overlap.Matrix()).sum()
311 << std::flush;
312
313 PrintMOs(embeddingMOs.eigenvalues(), Log::info);
314
315 if (conv_accelerator_.isConverged()) {
317 << TimeStamp() << " Total embedding energy has converged to "
318 << std::setprecision(9) << conv_accelerator_.getDeltaE()
319 << "[Ha] after " << this_iter + 1
320 << " iterations. DIIS error is converged up to "
321 << conv_accelerator_.getDIIsError() << std::flush;
323 << TimeStamp() << " Final Single Point Energy of embedding "
324 << std::setprecision(12) << TotalEnergy << " Ha" << std::flush;
326 << "The difference between the reference energy and the embedding "
327 "energy is "
328 << Total_E_full_ - TotalEnergy << " Ha" << std::flush;
329
330 if (abs(Total_E_full_ - TotalEnergy) > 1e-3) {
332 << "Warning!! The difference is greater than 1e-03 Ha"
333 << std::flush;
334 }
335
336 PrintMOs(embeddingMOs.eigenvalues(), Log::error);
337 orb.setEmbeddedMOs(embeddingMOs);
339 CalcElDipole(orb);
340 break;
341 } else if (this_iter == max_iter_ - 1) {
343 << TimeStamp()
344 << " DFT calculation for active region has not converged after "
345 << max_iter_
346 << " iterations. Use more iterations or another convergence "
347 "acceleration scheme."
348 << std::flush;
349 return false;
350 }
351 }
352 }
353 return true;
354}
355
357 if (truncate_) {
358 activemol_.ReorderAtomIDs();
359 trunc_orb.QMAtoms() = activemol_;
360 Prepare(trunc_orb, active_electrons_);
361 Vxc_Potential<Vxc_Grid> vxcpotential = SetupVxc(trunc_orb.QMAtoms());
362 ConfigOrbfile(trunc_orb);
363 AOBasis aobasis = trunc_orb.getDftBasis();
364 AOOverlap overlap;
365 overlap.Fill(aobasis);
366
367 const double E0_initial_truncated =
368 InitialActiveDmat_trunc_.cwiseProduct(H0_trunc_).sum();
369 const Mat_p_Energy xc_initial_truncated =
371 Eigen::MatrixXd J_initial_truncated = Eigen::MatrixXd::Zero(
373 Eigen::MatrixXd K_initial_truncated = Eigen::MatrixXd::Zero(
375 tools::EigenSystem MOs_trunc;
376 MOs_trunc.eigenvalues() = Eigen::VectorXd::Zero(H0_trunc_.cols());
377 MOs_trunc.eigenvectors() =
378 Eigen::MatrixXd::Zero(H0_trunc_.rows(), H0_trunc_.cols());
379 if (ScaHFX_ > 0) {
380 std::array<Eigen::MatrixXd, 2> JandK_initial_truncated = CalcERIs_EXX(
381 MOs_trunc.eigenvectors(), InitialActiveDmat_trunc_, 1e-12);
382 J_initial_truncated = JandK_initial_truncated[0];
383 K_initial_truncated = 0.5 * ScaHFX_ * JandK_initial_truncated[1];
384 } else {
385 J_initial_truncated = CalcERIs(InitialActiveDmat_trunc_, 1e-12);
386 }
387 // get the Hartree energies
388 const double E_Hartree_initial_truncated =
390 .cwiseProduct(J_initial_truncated + K_initial_truncated)
391 .sum();
392 const double Initial_truncated_energy = E0_initial_truncated +
393 E_Hartree_initial_truncated +
394 xc_initial_truncated.energy();
396 << "Initial truncated energy = " << Initial_truncated_energy
397 << std::flush;
398
399 Eigen::MatrixXd PurifiedActiveDmat_trunc =
401 Eigen::MatrixXd TruncatedDensityMatrix = PurifiedActiveDmat_trunc;
402
403 conv_accelerator_.StartNewSCF();
404 for (Index this_iter = 0; this_iter < max_iter_; this_iter++) {
405 XTP_LOG(Log::error, *pLog_) << std::flush;
407 << TimeStamp() << " Iteration " << this_iter + 1 << " of "
408 << max_iter_ << std::flush;
409
410 // get the XC contribution for the updated truncated density matrix
411 Mat_p_Energy xc_truncated =
412 vxcpotential.IntegrateVXC(TruncatedDensityMatrix);
413 double E_xc_truncated = xc_truncated.energy();
414
415 // get the Hartree contribution for the updated truncated density matrix
416 Eigen::MatrixXd J_truncated = Eigen::MatrixXd::Zero(
417 TruncatedDensityMatrix.rows(), TruncatedDensityMatrix.cols());
418 Eigen::MatrixXd K_truncated = Eigen::MatrixXd::Zero(
419 TruncatedDensityMatrix.rows(), TruncatedDensityMatrix.cols());
420 if (ScaHFX_ > 0) {
421 std::array<Eigen::MatrixXd, 2> JandK_truncated = CalcERIs_EXX(
422 MOs_trunc.eigenvectors(), TruncatedDensityMatrix, 1e-12);
423 J_truncated = JandK_truncated[0];
424 K_truncated = 0.5 * ScaHFX_ * JandK_truncated[1];
425
426 } else {
427 J_truncated = CalcERIs(TruncatedDensityMatrix, 1e-12);
428 }
429 double E_Hartree_truncated =
430 0.5 *
431 TruncatedDensityMatrix.cwiseProduct(J_truncated + K_truncated).sum();
432 // update the truncated Hamiltonian
433 Eigen::MatrixXd H_truncated =
434 H0_trunc_ + xc_truncated.matrix() + J_truncated + K_truncated;
435
436 double TruncatedTotalEnergy =
437 TruncatedDensityMatrix.cwiseProduct(H0_trunc_).sum() +
438 E_Hartree_truncated + E_xc_truncated + Total_E_full_ -
439 Initial_truncated_energy +
440 ((TruncatedDensityMatrix - InitialActiveDmat_trunc_)
441 .cwiseProduct(v_embedding_trunc_)
442 .sum());
443 // get the new truncated density matrix
445 << "Electrons in = "
446 << TruncatedDensityMatrix.cwiseProduct(overlap.Matrix()).sum()
447 << std::flush;
448 TruncatedDensityMatrix = conv_accelerator_.Iterate(
449 TruncatedDensityMatrix, H_truncated, MOs_trunc, TruncatedTotalEnergy);
451 << "Electrons out = "
452 << TruncatedDensityMatrix.cwiseProduct(overlap.Matrix()).sum()
453 << std::flush;
454
456 << TimeStamp() << " E_trunc (Ha) = "
457 << TruncatedDensityMatrix.cwiseProduct(H0_trunc_).sum() +
458 E_Hartree_truncated + E_xc_truncated
459 << "\n \t \t \t" << " E_fullDFT (Ha) = " << Total_E_full_
460 << "\n \t \t \t"
461 << " E_initial_trunc(Ha) = " << Initial_truncated_energy
462 << "\n \t \t \t" << " E_embedding_correction(Ha) = "
463 << ((TruncatedDensityMatrix - InitialActiveDmat_trunc_)
464 .cwiseProduct(v_embedding_trunc_)
465 .sum())
466 << std::flush;
467
469 << " Truncated Energy of the system: E_trunc + E_fullDFT - "
470 << "\n \t \t" << " E_initial_trunc + E_embedding_correction = "
471 << TruncatedTotalEnergy << std::flush;
472
473 PrintMOs(MOs_trunc.eigenvalues(), Log::info);
474
475 if (conv_accelerator_.isConverged()) {
477 << TimeStamp() << " Total embedding energy has converged to "
478 << std::setprecision(9) << conv_accelerator_.getDeltaE()
479 << "[Ha] after " << this_iter + 1
480 << " iterations. DIIS error is converged up to "
481 << conv_accelerator_.getDIIsError() << std::flush;
483 << TimeStamp() << " Final Single Point Energy of embedding "
484 << std::setprecision(12) << TruncatedTotalEnergy << " Ha"
485 << std::flush;
486
487 PrintMOs(MOs_trunc.eigenvalues(), Log::error);
488 trunc_orb.setEmbeddedMOs(MOs_trunc);
490 break;
491 }
492 }
494 }
495 return true;
496}
497
498void DFTEngine::TruncateBasis(Orbitals& orb, std::vector<Index>& activeatoms,
499 Mat_p_Energy& H0,
500 Eigen::MatrixXd InitialActiveDensityMatrix,
501 Eigen::MatrixXd v_embedding,
502 Eigen::MatrixXd InitialInactiveMOs) {
503 AOBasis aobasis;
504 aobasis = orb.getDftBasis();
505 AOOverlap overlap;
506 overlap.Fill(aobasis);
507 // Mulliken population per basis function on every active atom
508 Eigen::VectorXd DiagonalofDmatA = InitialActiveDensityMatrix.diagonal();
509 Eigen::VectorXd DiagonalofOverlap = overlap.Matrix().diagonal();
510 Eigen::VectorXd MnP = DiagonalofDmatA.cwiseProduct(DiagonalofOverlap);
511
512 // Get a vector containing the number of basis functions per atom
513 const std::vector<Index>& numfuncpatom = aobasis.getFuncPerAtom();
514 numfuncpatom_ = numfuncpatom;
515 Index numofactivebasisfunction = 0;
516 // Store start indices. Will be used later
517 std::vector<Index> start_indices, start_indices_activemolecule;
518 Index start_idx = 0, start_idx_activemolecule = 0;
519 std::vector<Index> borderatoms;
520 for (Index atom_num = 0; atom_num < orb.QMAtoms().size(); atom_num++) {
521 start_indices.push_back(start_idx);
522 /* Condition for basis fn on an atom to be counted: either in active
523 region or MnP of any function > threshold (border atoms) */
524 bool partOfActive =
525 (std::find(activeatoms.begin(), activeatoms.end(),
526 orb.QMAtoms()[atom_num].getId()) != activeatoms.end());
527 if (partOfActive == true) {
528 activemol_.push_back(orb.QMAtoms()[atom_num]);
529 /* if active append the atom to the new molecule. Also increment active
530 basis function size and start indices */
531 start_indices_activemolecule.push_back(start_idx_activemolecule);
532 start_idx_activemolecule += numfuncpatom[atom_num];
533 numofactivebasisfunction += numfuncpatom[atom_num];
534 } else {
535 std::vector<const AOShell*> inactiveshell =
536 aobasis.getShellsofAtom(orb.QMAtoms()[atom_num].getId());
537 bool loop_break = false;
538 for (const AOShell* shell : inactiveshell) {
539 for (Index shell_fn_no = shell->getStartIndex();
540 shell_fn_no < shell->getStartIndex() + shell->getNumFunc();
541 shell_fn_no++) {
542 if (MnP[shell_fn_no] > truncation_threshold_) {
543 activeatoms.push_back(atom_num);
544 borderatoms.push_back(atom_num); // push this index to border
545 // atoms
546 activemol_.push_back(orb.QMAtoms()[atom_num]);
547 loop_break = true; // if any function in the whole atom satisfies
548 // we break loop to check next atom
549 start_indices_activemolecule.push_back(start_idx_activemolecule);
550 start_idx_activemolecule += numfuncpatom[atom_num];
551 numofactivebasisfunction += numfuncpatom[atom_num];
552 break;
553 }
554 }
555 if (loop_break) break;
556 }
557 }
558 start_idx += numfuncpatom[atom_num];
559 }
560
561 // Sort atoms before you cut the Hamiltonian
562 sort(activeatoms.begin(), activeatoms.end());
563 sort(borderatoms.begin(), borderatoms.end());
564 Eigen::MatrixXd InactiveDensityMatrix = orb.getInactiveDensity();
565 XTP_LOG(Log::error, *pLog_) << std::flush;
567 << "Active + Border Molecule Size = " << activeatoms.size() << "\n \t \t "
568 << "Border Molecule Size = " << borderatoms.size() << std::flush;
569 Eigen::MatrixXd ProjectionOperator =
570 overlap.Matrix() * InactiveDensityMatrix * overlap.Matrix();
571
572 Eigen::MatrixXd H_embedding =
573 H0.matrix() + v_embedding + levelshift_ * ProjectionOperator;
574
575 if (borderatoms.size() != 0) {
576 Eigen::MatrixXd BorderMOs;
577 for (Index lmo_index = 0; lmo_index < InitialInactiveMOs.cols();
578 lmo_index++) {
579 double mullikenpop_lmo_borderatoms = 0;
580 for (Index borderatom : borderatoms) {
581 Index start = start_indices[borderatom];
582 Index size = numfuncpatom[borderatom];
583 mullikenpop_lmo_borderatoms +=
584 (InitialInactiveMOs.col(lmo_index) *
585 InitialInactiveMOs.col(lmo_index).transpose() * overlap.Matrix())
586 .diagonal()
587 .segment(start, size)
588 .sum();
589 }
590 /*If more than half of a MO contributes on a border atom include that in
591 * the Border MOs list. Not made an user option. If you want to fix
592 * further for density leak you can reduce this number*/
593 if (mullikenpop_lmo_borderatoms > 0.25) {
594 BorderMOs.conservativeResize(InitialInactiveMOs.rows(),
595 BorderMOs.cols() + 1);
596 BorderMOs.col(BorderMOs.cols() - 1) = InitialInactiveMOs.col(lmo_index);
597 }
598 }
599
600 Eigen::MatrixXd BorderDmat = 2 * BorderMOs * BorderMOs.transpose();
601 Eigen::MatrixXd BorderProjectionOperator =
602 overlap.Matrix() * BorderDmat * overlap.Matrix();
603 Eigen::MatrixXd DistantProjectionOperator =
604 ProjectionOperator - BorderProjectionOperator;
605 // 1e+2 is the suitable projection value for border MOs: see citations
606 H_embedding +=
607 1e+2 * BorderProjectionOperator +
608 levelshift_ * (DistantProjectionOperator - ProjectionOperator);
609 }
610
611 // from here it is time to truncate Hamiltonian H0
612 H0_trunc_ =
613 Eigen::MatrixXd::Zero(numofactivebasisfunction, numofactivebasisfunction);
615 Eigen::MatrixXd::Zero(numofactivebasisfunction, numofactivebasisfunction);
617 Eigen::MatrixXd::Zero(numofactivebasisfunction, numofactivebasisfunction);
618 // Defined needed matrices with basisset_size * basisset_size
619
620 for (Index activeatom1_idx = 0; activeatom1_idx < Index(activeatoms.size());
621 activeatom1_idx++) {
622 Index activeatom1 = activeatoms[activeatom1_idx];
623 for (Index activeatom2_idx = 0; activeatom2_idx < Index(activeatoms.size());
624 activeatom2_idx++) {
625 Index activeatom2 = activeatoms[activeatom2_idx];
626 // Fill the H0, Dmat and v_embedding at the right indices
627 H0_trunc_.block(start_indices_activemolecule[activeatom1_idx],
628 start_indices_activemolecule[activeatom2_idx],
629 numfuncpatom[activeatom1], numfuncpatom[activeatom2]) =
630 H_embedding.block(
631 start_indices[activeatom1], start_indices[activeatom2],
632 numfuncpatom[activeatom1], numfuncpatom[activeatom2]);
634 start_indices_activemolecule[activeatom1_idx],
635 start_indices_activemolecule[activeatom2_idx],
636 numfuncpatom[activeatom1], numfuncpatom[activeatom2]) =
637 InitialActiveDensityMatrix.block(
638 start_indices[activeatom1], start_indices[activeatom2],
639 numfuncpatom[activeatom1], numfuncpatom[activeatom2]);
640 v_embedding_trunc_.block(start_indices_activemolecule[activeatom1_idx],
641 start_indices_activemolecule[activeatom2_idx],
642 numfuncpatom[activeatom1],
643 numfuncpatom[activeatom2]) =
644 v_embedding.block(
645 start_indices[activeatom1], start_indices[activeatom2],
646 numfuncpatom[activeatom1], numfuncpatom[activeatom2]);
647 }
648 }
649}
650
651Eigen::MatrixXd DFTEngine::McWeenyPurification(Eigen::MatrixXd& Dmat_in,
652 AOOverlap& overlap) {
653 Eigen::MatrixXd Ssqrt = overlap.Sqrt();
654 Eigen::MatrixXd InvSsqrt = overlap.Pseudo_InvSqrt(1e-8);
655 Eigen::MatrixXd ModifiedDmat = 0.5 * Ssqrt * Dmat_in * Ssqrt;
656 for (Index iter = 0; iter < 100; iter++) {
657 Eigen::MatrixXd Dmat_new = (3 * ModifiedDmat * ModifiedDmat) -
658 (2 * ModifiedDmat * ModifiedDmat * ModifiedDmat);
659 double IdempotencyError =
660 ((Dmat_new * Dmat_new - Dmat_new) * (Dmat_new * Dmat_new - Dmat_new))
661 .trace();
663 << "Idempotency Error: " << IdempotencyError << std::flush;
664
665 ModifiedDmat = Dmat_new;
666 if (IdempotencyError < 1e-20) break;
667 }
668 Eigen::MatrixXd Dmat_out = 2 * InvSsqrt * ModifiedDmat * InvSsqrt;
669 return 0.5 * (Dmat_out + Dmat_out.transpose());
670}
671
672void DFTEngine::TruncMOsFullBasis(Orbitals& orb, std::vector<Index> activeatoms,
673 std::vector<Index> numfuncpatom) {
674 Eigen::MatrixXd expandtruncorb = orb.getEmbeddedMOs().eigenvectors();
675 Index start_index = 0;
676 Index numofactualactoms = numfuncpatom.size();
677 for (Index atomindex = 0; atomindex < numofactualactoms; atomindex++) {
678 bool partOfActive = (std::find(activeatoms.begin(), activeatoms.end(),
679 atomindex) != activeatoms.end());
680 if (partOfActive == false) {
681 expandtruncorb =
682 InsertZeroRows(expandtruncorb, start_index, numfuncpatom[atomindex]);
683 }
684 start_index += numfuncpatom[atomindex];
685 }
686 orb.setTruncMOsFullBasis(expandtruncorb);
687}
688
689Eigen::MatrixXd DFTEngine::InsertZeroRows(Eigen::MatrixXd MOsMatrix,
690 Index startidx, Index numofzerorows) {
691 Eigen::MatrixXd FinalMatrix =
692 Eigen::MatrixXd::Zero(MOsMatrix.rows() + numofzerorows, MOsMatrix.cols());
693 FinalMatrix.topRows(startidx) = MOsMatrix.topRows(startidx);
694 FinalMatrix.bottomRows(MOsMatrix.rows() - startidx) =
695 MOsMatrix.bottomRows(MOsMatrix.rows() - startidx);
696 return FinalMatrix;
697}
698} // namespace xtp
699} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
const std::vector< const AOShell * > getShellsofAtom(Index AtomId) const
Definition aobasis.cc:63
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd Pseudo_InvSqrt(double etol)
Definition aomatrix.cc:29
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:52
Eigen::MatrixXd Sqrt()
Definition aomatrix.cc:45
std::array< Eigen::MatrixXd, 3 > compute_Dmat_A()
Eigen::MatrixXd CalcERIs(const Eigen::MatrixXd &Dmat, double error) const
Build the Coulomb matrix contribution from the current density matrix.
Definition dftengine.cc:730
bool EvaluateTruncatedActiveRegion(Orbitals &trunc_orb)
Eigen::MatrixXd InitialActiveDmat_trunc_
Definition dftengine.h:583
void TruncMOsFullBasis(Orbitals &orb, std::vector< Index > activeatoms, std::vector< Index > numfuncpatom)
void TruncateBasis(Orbitals &orb, std::vector< Index > &activeatoms, Mat_p_Energy &H0, Eigen::MatrixXd InitialActiveDensityMatrix, Eigen::MatrixXd v_embedding, Eigen::MatrixXd InitialInactiveMOs)
bool EvaluateActiveRegion(Orbitals &orb)
void Prepare(Orbitals &orb, Index numofelectrons=-1)
Eigen::MatrixXd InsertZeroRows(Eigen::MatrixXd MOsMatrix, Index startidx, Index numofzerorows)
Insert zero rows into an MO coefficient matrix at the requested position.
std::array< Eigen::MatrixXd, 2 > CalcERIs_EXX(const Eigen::MatrixXd &MOCoeff, const Eigen::MatrixXd &Dmat, double error) const
Definition dftengine.cc:704
Eigen::MatrixXd H0_trunc_
Definition dftengine.h:582
void CalcElDipole(const Orbitals &orb) const
Evaluate and print the electronic dipole moment from the final density.
Definition dftengine.cc:364
std::string active_atoms_as_string_
Definition dftengine.h:577
void ConfigOrbfile(Orbitals &orb)
Propagate basis-set, XC, and metadata settings into the orbital container.
QMMolecule activemol_
Definition dftengine.h:568
void PrintMOs(const Eigen::VectorXd &MOEnergies, Log::Level level)
Print a one-spin list of orbital energies and occupations to the logger.
Definition dftengine.cc:284
std::vector< Index > numfuncpatom_
Definition dftengine.h:591
std::vector< Index > active_and_border_atoms_
Definition dftengine.h:590
Mat_p_Energy SetupH0(const QMMolecule &mol) const
Assemble the one-electron core Hamiltonian for the current molecule.
Eigen::MatrixXd v_embedding_trunc_
Definition dftengine.h:584
Eigen::MatrixXd McWeenyPurification(Eigen::MatrixXd &Dmat_in, AOOverlap &overlap)
Vxc_Potential< Vxc_Grid > SetupVxc(const QMMolecule &mol)
ConvergenceAcc conv_accelerator_
Definition dftengine.h:538
std::vector< Index > CreateIndexVector(const std::string &Ids) const
Eigen::MatrixXd & matrix()
Definition eigen.h:97
double & energy()
Definition eigen.h:98
Container for molecular orbitals and derived one-particle data.
Definition orbitals.h:47
void setCalculationType(std::string CalcType)
Store the calculation-type tag used by downstream workflows.
Definition orbitals.h:240
void setInactiveDensity(Eigen::MatrixXd inactivedensity)
Store the inactive-region density matrix used in embedding workflows.
Definition orbitals.h:710
void setNumofActiveElectrons(const Index active_electrons)
Store the number of electrons assigned to the active region.
Definition orbitals.h:703
void setTruncMOsFullBasis(const Eigen::MatrixXd &expandedMOs)
Store truncated active-region orbitals expanded back to the full AO basis.
Definition orbitals.h:64
Index getNumberOfAlphaElectrons() const
Return the stored number of alpha electrons.
Definition orbitals.h:159
const Eigen::MatrixXd & getLMOs() const
Return localized molecular orbitals, if available.
Definition orbitals.h:689
const Eigen::MatrixXd & getInactiveDensity() const
Return the inactive-region density matrix used in embedding workflows.
Definition orbitals.h:708
const tools::EigenSystem & MOs() const
Return read-only access to alpha/restricted molecular orbitals.
Definition orbitals.h:192
Eigen::MatrixXd DensityMatrixGroundState() const
Build the ground-state AO density matrix from the stored orbitals.
Definition orbitals.cc:220
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
Definition orbitals.h:262
void setEmbeddedMOs(tools::EigenSystem &system)
Store molecular orbitals obtained from an embedding calculation.
Definition orbitals.h:58
const tools::EigenSystem & getEmbeddedMOs() const
Return molecular orbitals obtained from an embedding calculation.
Definition orbitals.h:61
const AOBasis & getDftBasis() const
Return the DFT AO basis, throwing if it has not been initialized.
Definition orbitals.h:342
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
Mat_p_Energy IntegrateVXC(const Eigen::MatrixXd &density_matrix) const
#define XTP_LOG(level, log)
Definition logger.h:40
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26