22#include <boost/filesystem.hpp>
23#include <boost/format.hpp>
57void CanonicalizeOrbitalPhases(Eigen::MatrixXd& coeffs) {
58 constexpr double tol = 1
e-14;
60 for (
Index col = 0; col < coeffs.cols(); ++col) {
61 Eigen::Index pivot = 0;
62 const double maxabs = coeffs.col(col).cwiseAbs().maxCoeff(&pivot);
68 if (coeffs(pivot, col) < 0.0) {
69 coeffs.col(col) *= -1.0;
74void CanonicalizeOrbitalPhases(tools::EigenSystem& mos) {
75 CanonicalizeOrbitalPhases(mos.eigenvectors());
105 const std::string key_xtpdft =
"xtpdft";
108 if (options.
exists(
".auxbasisset")) {
115 options.
get(key_xtpdft +
".fock_matrix_reset").
as<
Index>();
117 if (options.
exists(
".ecp")) {
121 if (options.
exists(key_xtpdft +
".force_uks_path")) {
125 if (options.
exists(key_xtpdft +
".compute_forces")) {
140 throw std::runtime_error(
141 "compute_forces=true was requested, but the libint2 this was "
142 "built against does not support derivative integrals for one "
143 "or more operator categories it needs (one-body, the two-center "
144 "Coulomb metric, or three-center RI -- see "
145 "libint2_derivative_calls.cc's own compile guards for exactly "
146 "which). Many pre-packaged libint2 builds (Homebrew, Ubuntu "
147 "apt, etc.) do not enable derivative-integral support for all "
148 "of these by default; rebuild libint2 with "
149 "--enable-1body/--enable-eri2/--enable-eri3 to use this "
165 throw std::runtime_error(
166 "compute_forces=true was requested together with an ECP ('" +
168 "'), but analytic nuclear forces do not yet include the ECP "
169 "contribution to the force (d(V_ECP)/dR) -- computing forces "
170 "in this configuration would silently omit that term rather "
171 "than fail visibly. Either drop the ECP or do not request "
172 "compute_forces until this is implemented.");
175 if (options.
exists(key_xtpdft +
".cdft.enabled")) {
188 std::string indices_str =
189 options.
get(key_xtpdft +
".cdft.indices").
as<std::string>();
190 if (indices_str.empty()) {
191 throw std::runtime_error(
192 "cdft.enabled=true was requested, but cdft.indices is empty -- "
193 "specify which atoms (0-based, e.g. '1 3 13:17', same syntax "
194 "already used for diabatization.xml's own fragment indices) "
195 "make up the constrained fragment.");
200 options.
get(key_xtpdft +
".cdft.charge").
as<
double>();
202 options.
get(key_xtpdft +
".cdft.initial_lambda").
as<
double>();
204 options.
get(key_xtpdft +
".cdft.max_iterations").
as<
Index>();
206 options.
get(key_xtpdft +
".cdft.population_tolerance").
as<
double>();
208 options.
get(key_xtpdft +
".cdft.guess_strategy").
as<std::string>();
225 throw std::runtime_error(
226 "initial_guess=dimer_guess requires both dimer_guess_orbA and "
227 "dimer_guess_orbB to be set to real monomer .orb file paths.");
231 grid_name_ = options.
get(key_xtpdft +
".integration_grid").
as<std::string>();
234 if (options.
exists(key_xtpdft +
".externaldensity")) {
237 options.
get(key_xtpdft +
".externaldensity.orbfile").
as<std::string>();
238 gridquality_ = options.
get(key_xtpdft +
".externaldensity.gridquality")
241 options.
get(key_xtpdft +
".externaldensity.state").
as<std::string>();
244 if (options.
exists(
".externalfield")) {
250 options.
get(key_xtpdft +
".convergence.energy").
as<
double>();
252 options.
get(key_xtpdft +
".convergence.error").
as<
double>();
254 options.
get(key_xtpdft +
".convergence.max_iterations").
as<
Index>();
257 options.
get(key_xtpdft +
".convergence.method").
as<std::string>();
258 if (method ==
"DIIS") {
260 }
else if (method ==
"mixing") {
268 options.
get(key_xtpdft +
".convergence.mixing").
as<
double>();
272 options.
get(key_xtpdft +
".convergence.mixing_max").
as<
double>();
274 options.
get(key_xtpdft +
".convergence.levelshift").
as<
double>();
276 options.
get(key_xtpdft +
".convergence.levelshift_end").
as<
double>();
281 options.
get(key_xtpdft +
".convergence.mixing_end").
as<
double>();
283 options.
get(key_xtpdft +
".convergence.DIIS_maxout").
as<
bool>();
285 options.
get(key_xtpdft +
".convergence.DIIS_length").
as<
Index>();
287 options.
get(key_xtpdft +
".convergence.DIIS_start").
as<
double>();
289 options.
get(key_xtpdft +
".convergence.ADIIS_start").
as<
double>();
291 options.
get(key_xtpdft +
".convergence.davidson_max_iter").
as<
Index>();
293 key_xtpdft +
".convergence.energy_reset", 1.0);
295 key_xtpdft +
".overlap_tolerance", 1
e-8);
297 key_xtpdft +
".ri_pair_threshold", 1
e-10);
299 if (options.
exists(key_xtpdft +
".dft_in_dft.activeatoms")) {
301 options.
get(key_xtpdft +
".dft_in_dft.activeatoms").
as<std::string>();
303 options.
get(key_xtpdft +
".dft_in_dft.threshold").
as<
double>();
305 options.
get(key_xtpdft +
".dft_in_dft.levelshift").
as<
double>();
307 options.
get(key_xtpdft +
".dft_in_dft.truncate_basis").
as<
bool>();
310 options.
get(key_xtpdft +
".dft_in_dft.truncation_threshold")
317 XTP_LOG(level, *
pLog_) <<
" Orbital energies: " << std::flush;
318 XTP_LOG(level, *
pLog_) <<
" index occupation energy(Hartree) " << std::flush;
320 for (
Index i = 0; i < MOEnergies.size(); ++i) {
328 XTP_LOG(level, *
pLog_) << (boost::format(
" %1$5d %2$1d %3$+1.10f") %
329 i % occupancy % MOEnergies(i))
337 const Eigen::VectorXd& beta_energies,
339 XTP_LOG(level, *
pLog_) <<
" UKS orbital energies:" << std::flush;
340 XTP_LOG(level, *
pLog_) <<
" index occ eps_a(Ha) eps_b(Ha)"
344 std::max<Index>(alpha_energies.size(), beta_energies.size());
346 for (
Index i = 0; i < nrows; ++i) {
350 std::string occ =
"0";
351 if (occ_a && occ_b) {
359 std::string eps_a =
" -";
360 std::string eps_b =
" -";
362 if (i < alpha_energies.size()) {
363 eps_a = (boost::format(
"%+1.10f") % alpha_energies(i)).str();
365 if (i < beta_energies.size()) {
366 eps_b = (boost::format(
"%+1.10f") % beta_energies(i)).str();
370 " %1$5d %2$1s %3$15s %4$15s") %
371 i % occ % eps_a % eps_b)
379 " alpha HOMO-LUMO gap: %+1.10f Ha") %
388 " beta HOMO-LUMO gap: %+1.10f Ha") %
400 <<
TimeStamp() <<
" Electric Dipole is[e*bohr]:\n\t\t dx=" << result[0]
401 <<
"\n\t\t dy=" << result[1] <<
"\n\t\t dz=" << result[2] << std::flush;
435 Orbitals& orb,
const Eigen::MatrixXd& Dmat,
440 <<
" Skipping force calculation: RI-J gradient (DFTGradient::"
441 "RIJGradient) only implements the RI path, but this SCF ran "
442 "without an auxiliary basis (conventional 4-center ERIs)."
450 <<
" Skipping force calculation: the libint2 this was built "
451 "against does not support derivative integrals for one or "
452 "more operator categories it needs. Many pre-packaged "
453 "libint2 builds (Homebrew, Ubuntu apt, etc.) do not enable "
454 "this by default -- rebuild libint2 with "
455 "--enable-1body/--enable-eri2/--enable-eri3 to use analytic "
476 <<
"') was used for this SCF, but analytic nuclear forces do "
477 "not yet include the ECP contribution to the force "
478 "(d(V_ECP)/dR) -- computing forces in this configuration "
479 "would silently omit that term rather than fail visibly."
488 << natoms <<
" atoms)" << std::flush;
502 <<
" Computing one-electron (kinetic + nuclear "
503 "attraction) derivatives"
506 std::vector<AOMatrixDerivative> dVne =
508 Eigen::MatrixXd eone_grad = Eigen::MatrixXd::Zero(natoms, 3);
509 for (
Index a = 0; a < natoms; ++a) {
510 for (
Index xyz = 0; xyz < 3; ++xyz) {
511 eone_grad(a, xyz) = Dmat.cwiseProduct(dT[a][xyz] + dVne[a][xyz]).sum();
515 <<
TimeStamp() <<
" One-electron derivatives done" << std::flush;
546 Eigen::MatrixXd W = 2.0 * C_occ * eps_occ.asDiagonal() * C_occ.transpose();
549 <<
TimeStamp() <<
" Computing overlap (Pulay) derivatives"
552 Eigen::MatrixXd overlap_pulay_grad = Eigen::MatrixXd::Zero(natoms, 3);
553 for (
Index a = 0; a < natoms; ++a) {
554 for (
Index xyz = 0; xyz < 3; ++xyz) {
555 overlap_pulay_grad(a, xyz) = -W.cwiseProduct(dS[a][xyz]).sum();
559 <<
TimeStamp() <<
" Overlap derivatives done" << std::flush;
562 <<
TimeStamp() <<
" Computing RI-J (Coulomb) gradient" << std::flush;
563 Eigen::MatrixXd rij_term =
566 <<
TimeStamp() <<
" RI-J gradient done" << std::flush;
569 <<
TimeStamp() <<
" Computing XC grid (Pulay + weight) gradient terms"
575 <<
TimeStamp() <<
" XC grid gradient terms done" << std::flush;
577 Eigen::MatrixXd grad = nucrep_term + eone_grad + overlap_pulay_grad +
578 rij_term + pulay_term + weight_term;
601 <<
TimeStamp() <<
" Computing RI-K (exact exchange) gradient"
605 <<
TimeStamp() <<
" RI-K gradient done" << std::flush;
613 Eigen::Vector3d sum = grad.colwise().sum();
614 if (sum.cwiseAbs().maxCoeff() > 1
e-4) {
617 <<
" WARNING: computed forces do not sum to zero across atoms "
618 "(translational invariance check failed, max component="
619 << sum.cwiseAbs().maxCoeff()
620 <<
") -- treat these forces with "
632 Eigen::MatrixXd force = -grad;
636 <<
TimeStamp() <<
" Computed and stored ground-state nuclear forces."
645 for (
Index a = 0; a < natoms; ++a) {
647 (boost::format(
" %1$s"
648 " %2$+1.6f %3$+1.6f %4$+1.6f") %
649 mol[a].getElement() % force(a, 0) % force(a, 1) % force(a, 2))
684 Eigen::MatrixXd C_alpha_occ = MOs_alpha.
eigenvectors().leftCols(n_occ_alpha);
685 Eigen::MatrixXd C_beta_occ = MOs_beta.
eigenvectors().leftCols(n_occ_beta);
686 Eigen::VectorXd eps_alpha_occ = MOs_alpha.
eigenvalues().head(n_occ_alpha);
687 Eigen::VectorXd eps_beta_occ = MOs_beta.
eigenvalues().head(n_occ_beta);
689 C_alpha_occ * eps_alpha_occ.asDiagonal() * C_alpha_occ.transpose() +
690 C_beta_occ * eps_beta_occ.asDiagonal() * C_beta_occ.transpose();
694 Eigen::MatrixXd overlap_pulay_grad = Eigen::MatrixXd::Zero(natoms, 3);
695 for (
Index a = 0; a < natoms; ++a) {
696 for (
Index xyz = 0; xyz < 3; ++xyz) {
697 overlap_pulay_grad(a, xyz) = -W.cwiseProduct(dS[a][xyz]).sum();
700 return overlap_pulay_grad;
708 const Eigen::MatrixXd D_total = Dspin.
total();
717 std::vector<AOMatrixDerivative> dVne =
719 Eigen::MatrixXd eone_grad = Eigen::MatrixXd::Zero(natoms, 3);
720 for (
Index a = 0; a < natoms; ++a) {
721 for (
Index xyz = 0; xyz < 3; ++xyz) {
722 eone_grad(a, xyz) = D_total.cwiseProduct(dT[a][xyz] + dVne[a][xyz]).sum();
726 Eigen::MatrixXd overlap_pulay_grad =
729 Eigen::MatrixXd grad =
745 Eigen::MatrixXd C_alpha_occ =
747 Eigen::MatrixXd C_beta_occ =
763 <<
" Skipping UKS force calculation: RI-J gradient only "
764 "implements the RI path, but this SCF ran without an "
773 <<
" Skipping UKS force calculation: the libint2 this was "
774 "built against does not support derivative integrals for "
775 "one or more operator categories it needs. Many "
776 "pre-packaged libint2 builds (Homebrew, Ubuntu apt, etc.) "
777 "do not enable this by default -- rebuild libint2 with "
778 "--enable-1body/--enable-eri2/--enable-eri3 to use "
790 <<
TimeStamp() <<
" Skipping UKS force calculation: an ECP ('"
792 <<
"') was used for this SCF, but analytic nuclear forces do "
793 "not yet include the ECP contribution to the force "
794 "(d(V_ECP)/dR) -- computing forces in this configuration "
795 "would silently omit that term rather than fail visibly."
800 Eigen::MatrixXd grad =
816 Eigen::Vector3d sum = grad.colwise().sum();
817 if (sum.cwiseAbs().maxCoeff() > 1
e-4) {
820 <<
" WARNING: computed UKS forces do not sum to zero across "
821 "atoms (translational invariance check failed, max "
823 << sum.cwiseAbs().maxCoeff()
824 <<
") -- treat these forces with "
833 Eigen::MatrixXd force = -grad;
837 <<
TimeStamp() <<
" Computed and stored ground-state UKS nuclear forces."
843 for (
Index a = 0; a < force.rows(); ++a) {
844 std::string output = (boost::format(
" %1$s"
845 " %2$+1.6f %3$+1.6f %4$+1.6f") %
846 mol_for_print[a].getElement() % force(a, 0) %
847 force(a, 1) % force(a, 2))
862 const Eigen::MatrixXd& MOCoeff,
const Eigen::MatrixXd& Dmat,
863 double error)
const {
865 std::array<Eigen::MatrixXd, 2> result;
867 auto t =
timings_.Measure(
"J (RI)");
868 result[0] =
ERIs_.CalculateERIs_3c(Dmat);
871 auto t =
timings_.Measure(
"K (RI, from density matrix)");
872 result[1] =
ERIs_.CalculateEXX_3c(Eigen::MatrixXd::Zero(0, 0), Dmat);
874 auto t =
timings_.Measure(
"K (RI, from occupied MOs)");
876 result[1] =
ERIs_.CalculateEXX_3c(occblock, Dmat);
880 auto t =
timings_.Measure(
"J+K (4c)");
881 return ERIs_.CalculateERIs_EXX_4c(Dmat, error);
888 double error)
const {
890 auto t =
timings_.Measure(
"J (RI)");
891 return ERIs_.CalculateERIs_3c(Dmat);
893 auto t =
timings_.Measure(
"J (4c)");
894 return ERIs_.CalculateERIs_4c(Dmat, error);
899 const double gb = 1024.0 * 1024.0 * 1024.0;
900 const double n = double(
dftbasis_.AOBasisSize());
907 const double naux = double(
ERIs_.AuxSize());
909 const double tensor =
910 naux * double(
ERIs_.StoredPairs()) *
sizeof(double) / gb;
911 const double kept = double(
ERIs_.StoredPairs()) /
912 double(std::max<Index>(1,
ERIs_.AllPairs()));
915 const double scratch = threads * 4 * n * n *
sizeof(double) / gb;
917 <<
TimeStamp() <<
" RI: " <<
ERIs_.AuxSize() <<
" aux functions ("
918 <<
ERIs_.Removedfunctions()
919 <<
" removed from the metric); stored 3c tensor "
920 << std::setprecision(3) << tensor <<
" GB (" << 100.0 * kept
922 <<
"); J/K scratch up to about " << scratch <<
" GB" << std::flush;
928 <<
" Resident memory after setup: " << std::setprecision(3) << rss
929 <<
" GB" << std::flush;
947 Eigen::MatrixXd Dmat = [&]() {
948 auto t =
timings_.Measure(
"guess: atomic densities");
956 <<
TimeStamp() <<
" Filled DFT Vxc matrix " << std::flush;
961 std::array<Eigen::MatrixXd, 2> both =
978 <<
TimeStamp() <<
" Peak resident memory of this process so far: "
979 << std::setprecision(3) << peak <<
" GB" << std::flush;
1008 bool converged =
RunCDFT(orb, constraint);
1011 if (converged && original_compute_forces) {
1027 <<
" CDFT converged -- computing the ordinary DFT force once, "
1028 "for the final, converged density only"
1059 std::map<std::string, Eigen::MatrixXd> reference_densities =
1069 std::vector<HirshfeldPartition::AtomicReference> atoms =
1073 std::array<Eigen::MatrixXd, 2> Dspin =
1079 Eigen::MatrixXd density_total = Dspin[0] + Dspin[1];
1081 Eigen::MatrixXd cdft_gradient_correction =
1084 cdft_gradient_correction +=
1086 atoms, atom_index, density_total, orb.
QMAtoms(), full_dftbasis,
1096 constraint.
lambda * cdft_gradient_correction);
1102 const std::string previous_basis =
1116 <<
" Starting from the orbitals of the previous QM/MM iteration"
1120 <<
TimeStamp() <<
" Previous orbitals not usable as guess (" << reason
1127 auto t =
timings_.Measure(
"setup: XC grid");
1132 bool success =
false;
1137 <<
" Forcing closed-shell singlet through UKS development path."
1150 const std::string& previous_basis,
1151 std::string& reason)
const {
1159 reason =
"basis size differs";
1162 if (!previous_basis.empty() && previous_basis != orb.
getDFTbasisName()) {
1163 reason =
"basis set differs";
1168 reason =
"electron count differs";
1201 double lambda_lo = constraint.
lambda - 0.1;
1202 double lambda_hi = constraint.
lambda + 0.1;
1204 auto EvaluateMismatch = [&](
double lambda) ->
double {
1207 <<
TimeStamp() <<
" CDFT: starting inner SCF at lambda=" << lambda
1209 bool scf_converged =
EvaluateUKS(orb, H0, vxcpotential);
1210 if (!scf_converged) {
1211 throw std::runtime_error(
1212 "RunCDFT: inner SCF did not converge at lambda=" +
1213 std::to_string(lambda));
1226 std::array<Eigen::MatrixXd, 2> Dspin =
1237 double mismatch_lo = EvaluateMismatch(lambda_lo);
1238 double mismatch_hi = EvaluateMismatch(lambda_hi);
1240 Index bracket_attempts = 0;
1241 constexpr Index kMaxBracketAttempts = 10;
1242 while (mismatch_lo * mismatch_hi > 0.0 &&
1243 bracket_attempts < kMaxBracketAttempts) {
1244 double width = lambda_hi - lambda_lo;
1245 lambda_lo -= 0.5 * width;
1246 lambda_hi += 0.5 * width;
1247 mismatch_lo = EvaluateMismatch(lambda_lo);
1248 mismatch_hi = EvaluateMismatch(lambda_hi);
1251 if (mismatch_lo * mismatch_hi > 0.0) {
1254 <<
" RunCDFT: could not bracket a root for the population "
1256 << kMaxBracketAttempts
1257 <<
" bracket-expansion attempts -- the target population may "
1258 "be unreachable for this system, or the initial "
1259 "lambda guess may be far from the actual root."
1268 double lambda_mid = 0.5 * (lambda_lo + lambda_hi);
1269 double mismatch_mid = EvaluateMismatch(lambda_mid);
1272 <<
TimeStamp() <<
" CDFT outer iteration " << outer_iter + 1 <<
" of "
1274 <<
" population mismatch=" << mismatch_mid << std::flush;
1277 constraint.
lambda = lambda_mid;
1280 <<
TimeStamp() <<
" CDFT converged after " << outer_iter + 1
1281 <<
" outer iterations, lambda=" << lambda_mid << std::flush;
1285 if (mismatch_mid * mismatch_lo < 0.0) {
1286 lambda_hi = lambda_mid;
1287 mismatch_hi = mismatch_mid;
1289 lambda_lo = lambda_mid;
1290 mismatch_lo = mismatch_mid;
1293 }
catch (
const std::runtime_error&) {
1301 <<
" RunCDFT: outer bisection loop did not converge "
1304 constraint.
lambda = 0.5 * (lambda_lo + lambda_hi);
1325 <<
TimeStamp() <<
" Reading guess from orbitals object/file"
1350 throw std::runtime_error(
1351 "initial_guess=dimer_guess: this is a restricted (closed-shell) "
1352 "calculation, but at least one monomer .orb file is open-shell "
1353 "or unrestricted. Use force_uks_path to run the dimer "
1354 "unrestricted with this guess.");
1357 throw std::runtime_error(
1358 "initial_guess=dimer_guess: the monomers have " +
1360 " electrons in total, but this calculation has " +
1362 ". Check the monomer charges against the dimer charge.");
1364 MOs = dimer_guess_orb.
MOs();
1367 throw std::runtime_error(
"Initial guess method not known/implemented");
1373 Eigen::MatrixXd Dmat = spin_dmat.
total();
1376 <<
TimeStamp() <<
" Guess Matrix gives N=" << std::setprecision(9)
1377 << Dmat.cwiseProduct(
dftAOoverlap_.Matrix()).sum() <<
" electrons."
1381 <<
TimeStamp() <<
" STARTING SCF cycle" << std::flush;
1383 <<
" ----------------------------------------------"
1384 "----------------------------"
1387 Eigen::MatrixXd J = Eigen::MatrixXd::Zero(Dmat.rows(), Dmat.cols());
1390 K = Eigen::MatrixXd::Zero(Dmat.rows(), Dmat.cols());
1393 double start_incremental_F_threshold = 1
e-4;
1395 start_incremental_F_threshold = 0.0;
1412 <<
TimeStamp() <<
" Filled DFT Vxc matrix " << std::flush;
1415 double Eone = Dmat.cwiseProduct(H0.
matrix()).sum();
1416 double Etwo = e_vxc.
energy();
1424 double integral_error =
1432 Etwo += 0.5 * Dmat.cwiseProduct(J).sum();
1435 exx = 0.25 *
ScaHFX_ * Dmat.cwiseProduct(K).sum();
1437 <<
TimeStamp() <<
" Filled F+K matrix " << std::flush;
1441 <<
TimeStamp() <<
" Filled F matrix " << std::flush;
1443 Etwo += 0.5 * Dmat.cwiseProduct(J).sum();
1447 double totenergy = Eone + H0.
energy() + Etwo;
1450 << std::setprecision(12) << Eone << std::flush;
1452 << std::setprecision(12) << Etwo << std::flush;
1454 <<
TimeStamp() << std::setprecision(12) <<
" Local Exc contribution "
1455 << e_vxc.
energy() << std::flush;
1459 <<
" Non local Ex contribution " << exx << std::flush;
1462 <<
TimeStamp() <<
" Total Energy " << std::setprecision(12) << totenergy
1466 auto t =
timings_.Measure(
"DIIS/ADIIS + diagonalisation");
1485 <<
TimeStamp() <<
" Total Energy has converged to "
1487 <<
"[Ha] after " << this_iter + 1
1488 <<
" iterations. DIIS error is converged up to "
1491 <<
TimeStamp() <<
" Final Single Point Energy "
1492 << std::setprecision(12) << totenergy <<
" Ha" << std::flush;
1494 <<
" Final Local Exc contribution "
1495 << e_vxc.
energy() <<
" Ha" << std::flush;
1498 <<
" Final Non Local Ex contribution "
1499 << exx <<
" Ha" << std::flush;
1504 Index nuclear_charge = 0;
1506 nuclear_charge += atom.getNuccharge();
1519 auto t =
timings_.Measure(
"forces");
1525 }
else if (this_iter ==
max_iter_ - 1) {
1527 <<
TimeStamp() <<
" DFT calculation has not converged after "
1529 <<
" iterations. Use more iterations or another convergence "
1530 "acceleration scheme."
1567 conv_uks.
Configure(opt_alpha, opt_beta);
1573 <<
TimeStamp() <<
" Reading UKS guess from orbitals object/file"
1576 MOs_alpha = orb.
MOs();
1585 <<
" Orbital file has no beta MOs, using alpha guess for beta."
1587 MOs_beta = MOs_alpha;
1592 <<
" Building UKS guess from two monomer .orb files (dimer_guess)"
1595 MOs_alpha = dimer_guess_orb.
MOs();
1597 MOs_beta = dimer_guess_orb.
MOs_beta();
1614 throw std::runtime_error(
"Initial guess method not known/implemented");
1627 <<
TimeStamp() <<
" UKS guess gives Nalpha="
1634 <<
TimeStamp() <<
" STARTING UKS SCF cycle" << std::flush;
1636 <<
" ------------------------------------------------------------"
1644 Eigen::MatrixXd H_alpha = H0.
matrix();
1645 Eigen::MatrixXd H_beta = H0.
matrix();
1649 const Eigen::MatrixXd D_total = Dspin.
total();
1651 double E_one = Dspin.
alpha.cwiseProduct(H0.
matrix()).sum() +
1654 double E_coul = 0.0;
1658 double integral_error = std::min(conv_uks.
getDIIsError() * 1
e-5, 1
e-5);
1661 std::array<Eigen::MatrixXd, 2> both_alpha =
CalcERIs_EXX(
1662 Eigen::MatrixXd::Zero(0, 0), Dspin.
alpha, integral_error);
1663 std::array<Eigen::MatrixXd, 2> both_beta =
1666 Eigen::MatrixXd J = both_alpha[0] + both_beta[0];
1667 Eigen::MatrixXd K_alpha = both_alpha[1];
1668 Eigen::MatrixXd K_beta = both_beta[1];
1670 H_alpha += J +
ScaHFX_ * K_alpha;
1671 H_beta += J +
ScaHFX_ * K_beta;
1673 E_coul = 0.5 * D_total.cwiseProduct(J).sum();
1675 (Dspin.
alpha.cwiseProduct(K_alpha).sum() +
1676 Dspin.
beta.cwiseProduct(K_beta).sum());
1678 Eigen::MatrixXd J =
CalcERIs(D_total, integral_error);
1681 E_coul = 0.5 * D_total.cwiseProduct(J).sum();
1688 H_alpha += vxc.vxc_alpha;
1689 H_beta += vxc.vxc_beta;
1692 double totenergy = H0.
energy() + E_one + E_coul + E_xc + E_exx;
1711 H_alpha += (c.lambda * c.spin_alpha_coefficient) * c.weight_matrix;
1712 H_beta += (c.lambda * c.spin_beta_coefficient) * c.weight_matrix;
1714 c.spin_alpha_coefficient *
1715 Dspin.
alpha.cwiseProduct(c.weight_matrix).sum() +
1716 c.spin_beta_coefficient *
1717 Dspin.
beta.cwiseProduct(c.weight_matrix).sum();
1718 totenergy += c.lambda * (population - c.target_population);
1723 << std::setprecision(12) << E_one << std::flush;
1725 << std::setprecision(12) << E_coul << std::flush;
1727 << std::setprecision(12) << E_xc << std::flush;
1730 <<
TimeStamp() <<
" EXX contribution " << std::setprecision(12)
1731 << E_exx << std::flush;
1734 <<
TimeStamp() <<
" Total Energy " << std::setprecision(12) << totenergy
1749 [
this, &H0, &vxcpotential](
1750 const Eigen::MatrixXd& alpha_new,
1755 constexpr double kIntegralError = 1
e-8;
1757 std::array<Eigen::MatrixXd, 2> both_alpha_new =
CalcERIs_EXX(
1758 Eigen::MatrixXd::Zero(0, 0), alpha_new, kIntegralError);
1759 std::array<Eigen::MatrixXd, 2> both_beta_new =
CalcERIs_EXX(
1760 Eigen::MatrixXd::Zero(0, 0), beta_new, kIntegralError);
1761 Eigen::MatrixXd J_new = both_alpha_new[0] + both_beta_new[0];
1765 Eigen::MatrixXd D_total_new = alpha_new + beta_new;
1766 Eigen::MatrixXd J_new =
CalcERIs(D_total_new, kIntegralError);
1767 H_new.
alpha += J_new;
1768 H_new.
beta += J_new;
1770 auto vxc_new = [&]() {
1774 H_new.
alpha += vxc_new.vxc_alpha;
1775 H_new.
beta += vxc_new.vxc_beta;
1780 auto t =
timings_.Measure(
"DIIS/ADIIS + diagonalisation");
1781 Dspin = conv_uks.
Iterate(Dspin, Hspin, MOs_alpha, MOs_beta, totenergy);
1784 MOs_beta = MOs_alpha;
1802 Index nuclear_charge = 0;
1804 nuclear_charge += atom.getNuccharge();
1807 CanonicalizeOrbitalPhases(MOs_alpha);
1808 CanonicalizeOrbitalPhases(MOs_beta);
1811 orb.
MOs() = MOs_alpha;
1822 <<
TimeStamp() <<
" UKS converged after " << this_iter + 1
1823 <<
" iterations. Delta E=" << conv_uks.
getDeltaE()
1824 <<
" DIIS error=" << conv_uks.
getDIIsError() << std::flush;
1827 <<
TimeStamp() <<
" Final Single Point Energy "
1828 << std::setprecision(12) << totenergy <<
" Ha" << std::flush;
1830 <<
TimeStamp() << std::setprecision(12) <<
" Final XC contribution "
1831 << E_xc <<
" Ha" << std::flush;
1835 <<
" Final EXX contribution " << E_exx <<
" Ha" << std::flush;
1846 auto t =
timings_.Measure(
"forces");
1856 <<
TimeStamp() <<
" UKS calculation has not converged after "
1857 <<
max_iter_ <<
" iterations." << std::flush;
1875 std::make_unique<DFTTimings::Scope>(
timings_,
"setup: one-electron H0");
1881 <<
TimeStamp() <<
" Filled DFT Kinetic energy matrix ." << std::flush;
1886 <<
TimeStamp() <<
" Filled DFT nuclear potential matrix." << std::flush;
1888 Eigen::MatrixXd H0 = dftAOkinetic.
Matrix() + dftAOESP.
Matrix();
1890 <<
TimeStamp() <<
" Constructed independent particle hamiltonian "
1894 << std::setprecision(9) << E0 << std::flush;
1901 <<
TimeStamp() <<
" Filled DFT ECP matrix" << std::flush;
1907 <<
" External sites" << std::flush;
1908 bool has_quadrupoles = std::any_of(
1910 [](
const std::unique_ptr<StaticSite>& s) { return s->getRank() == 2; });
1911 std::string header =
1912 " Name Coordinates[a0] charge[e] dipole[e*a0] ";
1913 if (has_quadrupoles) {
1914 header +=
" quadrupole[e*a0^2]";
1919 for (
const std::unique_ptr<StaticSite>& site : *
externalsites_) {
1920 if (counter == limit) {
1923 std::string output =
1924 (boost::format(
" %1$s"
1925 " %2$+1.4f %3$+1.4f %4$+1.4f"
1927 site->getElement() % site->getPos()[0] % site->getPos()[1] %
1928 site->getPos()[2] % site->getCharge())
1930 const Eigen::Vector3d& dipole = site->getDipole();
1931 output += (boost::format(
" %1$+1.4f %2$+1.4f %3$+1.4f") % dipole[0] %
1932 dipole[1] % dipole[2])
1934 if (site->getRank() > 1) {
1935 Eigen::VectorXd quadrupole = site->Q().tail<5>();
1937 (boost::format(
" %1$+1.4f %2$+1.4f %3$+1.4f %4$+1.4f %5$+1.4f") %
1938 quadrupole[0] % quadrupole[1] % quadrupole[2] % quadrupole[3] %
1945 if (counter == limit) {
1948 <<
" sites not displayed)\n"
1952 auto t =
timings_.Measure(
"setup: external multipoles");
1956 <<
TimeStamp() <<
" Nuclei-external site interaction energy "
1957 << std::setprecision(9) << ext_multipoles.
energy() << std::flush;
1958 E0 += ext_multipoles.
energy();
1959 H0 += ext_multipoles.
matrix();
1966 E0 += extdensity_result.
energy();
1968 <<
TimeStamp() <<
" Nuclei-external density interaction energy "
1969 << std::setprecision(9) << extdensity_result.
energy() << std::flush;
1970 H0 += extdensity_result.
matrix();
1976 <<
TimeStamp() <<
" Integrating external electric field with F[Hrt]="
1983 <<
TimeStamp() <<
" Integrating external Ewald Potential" << std::flush;
1984 auto t =
timings_.Measure(
"setup: Ewald potential on grid");
2000 throw std::runtime_error(
2001 "DFTEngine: the external Ewald potential grid has " +
2003 " boxes but this molecule's own grid has " +
2005 ". The potential was evaluated on a different grid than the one "
2006 "being integrated over.");
2012 const std::vector<double>& source =
2014 if (
Index(source.size()) != box.
size()) {
2015 throw std::runtime_error(
"DFTEngine: box " + std::to_string(i) +
2016 " of the external Ewald "
2017 "potential grid holds " +
2018 std::to_string(source.size()) +
2019 " values for " + std::to_string(box.
size()) +
2027 std::string ewald_key;
2033 for (
double v : ewaldgrid[i].getPotentialValues()) {
2039 std::ostringstream key;
2041 << points <<
"|" << sum <<
"|" << sum2;
2042 ewald_key = key.str();
2049 <<
" Reusing the Ewald potential matrix of the previous run"
2053 const Eigen::MatrixXd ewald_matrix =
2066 <<
TimeStamp() <<
" Nuclei-external Ewald potential energy "
2075 std::ostringstream key;
2080 for (
const AOShell& shell : *basis) {
2081 key << static_cast<int>(shell.getL()) <<
"," << shell.getSize() <<
","
2082 << shell.getPos().x() <<
"," << shell.getPos().y() <<
","
2083 << shell.getPos().z() <<
";";
2096 <<
TimeStamp() <<
" SCF thresholds for this run: Delta E "
2098 <<
" (set by the caller)" << std::flush;
2103 ERIs_.AuxSize() == 0) {
2115 auto overlap_timer =
2116 std::make_unique<DFTTimings::Scope>(
timings_,
"setup: overlap, S^-1/2");
2119 <<
TimeStamp() <<
" Filled DFT Overlap matrix." << std::flush;
2131 overlap_timer.reset();
2141 <<
" Reusing the RI integrals of the previous run (same basis sets "
2146 auto ri_start = DFTTimings::Clock::now();
2154 std::chrono::duration<double>(DFTTimings::Clock::now() - ri_start)
2156 timings_.Add(
"setup: RI metric V^-1/2",
ERIs_.MetricSeconds());
2157 timings_.Add(
"setup: RI 3c integrals", ri_seconds -
ERIs_.MetricSeconds());
2159 <<
TimeStamp() <<
" Inverted AUX Coulomb matrix, removed "
2160 <<
ERIs_.Removedfunctions() <<
" functions from aux basis"
2164 <<
" Setup invariant parts of Electron Repulsion integrals "
2168 <<
TimeStamp() <<
" Calculating 4c diagonals. " << std::flush;
2169 auto t =
timings_.Measure(
"setup: 4c Schwarz screening");
2172 <<
TimeStamp() <<
" Calculated 4c diagonals. " << std::flush;
2192class SphericalAtomSCF {
2194 explicit SphericalAtomSCF(
const AOBasis& basis) : n_(basis.AOBasisSize()) {
2195 for (
const AOShell& shell : basis) {
2196 const Index l =
static_cast<Index>(shell.getL());
2198 while (g <
Index(l_.size()) && l_[g] != l) {
2201 if (g ==
Index(l_.size())) {
2203 starts_.emplace_back();
2205 starts_[g].push_back(shell.getStartIndex());
2209 Index Groups()
const {
return Index(l_.size()); }
2212 Eigen::MatrixXd Reduce(
const Eigen::MatrixXd& full,
Index g)
const {
2213 const std::vector<Index>& st = starts_[g];
2214 const Index size = 2 * l_[g] + 1;
2215 Eigen::MatrixXd red(st.size(), st.size());
2216 for (
Index i = 0; i <
Index(st.size()); ++i) {
2217 for (
Index j = 0; j <
Index(st.size()); ++j) {
2218 red(i, j) = full.block(st[i], st[j], size, size).trace() / double(size);
2226 Eigen::MatrixXd Expand(
const std::vector<Eigen::MatrixXd>& red)
const {
2227 Eigen::MatrixXd full = Eigen::MatrixXd::Zero(n_, n_);
2228 for (
Index g = 0; g < Groups(); ++g) {
2229 const std::vector<Index>& st = starts_[g];
2230 const Index size = 2 * l_[g] + 1;
2231 for (
Index i = 0; i <
Index(st.size()); ++i) {
2232 for (
Index j = 0; j <
Index(st.size()); ++j) {
2233 full.block(st[i], st[j], size, size)
2235 .setConstant(red[g](i, j));
2244 std::vector<Eigen::MatrixXd> Density(
2245 const std::vector<Eigen::MatrixXd>& fock,
2246 const std::vector<Eigen::MatrixXd>& overlap,
2247 const std::vector<Eigen::VectorXd>& occupation)
const {
2248 std::vector<Eigen::MatrixXd> dens(Groups());
2249 for (
Index g = 0; g < Groups(); ++g) {
2250 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es(fock[g],
2252 const Index nocc = occupation[g].size();
2253 const Eigen::MatrixXd c = es.eigenvectors().leftCols(nocc);
2254 dens[g] = c * (occupation[g] / double(2 * l_[g] + 1)).asDiagonal() *
2262 std::vector<Eigen::VectorXd> AufbauOccupation(
2263 const std::vector<Eigen::MatrixXd>& fock,
2264 const std::vector<Eigen::MatrixXd>& overlap,
double nelectrons)
const {
2270 std::vector<Level> levels;
2271 std::vector<Eigen::VectorXd> occ(Groups());
2272 for (
Index g = 0; g < Groups(); ++g) {
2273 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es(
2274 fock[g], overlap[g], Eigen::EigenvaluesOnly);
2275 occ[g] = Eigen::VectorXd::Zero(es.eigenvalues().size());
2276 for (
Index k = 0; k < es.eigenvalues().size(); ++k) {
2277 levels.push_back({es.eigenvalues()(k), g, k});
2281 levels.begin(), levels.end(),
2282 [](
const Level& a,
const Level& b) { return a.energy < b.energy; });
2283 double left = nelectrons;
2284 for (
const Level& lev : levels) {
2285 const double take = std::clamp(left, 0.0,
double(2 * l_[lev.group] + 1));
2286 occ[lev.group](lev.index) = take;
2295 bool OccupationFromSubshells(
2296 const std::vector<std::array<double, 3>>& subshells,
2297 const std::vector<Eigen::MatrixXd>& overlap,
2298 std::vector<Eigen::VectorXd>& occ)
const {
2299 occ.assign(Groups(), Eigen::VectorXd());
2300 for (
Index g = 0; g < Groups(); ++g) {
2301 occ[g] = Eigen::VectorXd::Zero(overlap[g].rows());
2303 for (
const auto& sub : subshells) {
2307 while (g < Groups() && l_[g] != l) {
2310 if (g == Groups() || k >= occ[g].size()) {
2313 occ[g](k) += sub[2];
2320 std::vector<Index> l_;
2321 std::vector<std::vector<Index>> starts_;
2327 explicit SimpleDIIS(
Index maxhist) : maxhist_(maxhist) {}
2329 Eigen::VectorXd Extrapolate(
const Eigen::VectorXd& fock,
2330 const Eigen::VectorXd& error) {
2331 focks_.push_back(fock);
2332 errors_.push_back(error);
2333 if (
Index(focks_.size()) > maxhist_) {
2334 focks_.erase(focks_.begin());
2335 errors_.erase(errors_.begin());
2341 Eigen::MatrixXd b = Eigen::MatrixXd::Zero(m + 1, m + 1);
2342 for (
Index i = 0; i < m; ++i) {
2343 for (
Index j = 0; j <= i; ++j) {
2344 b(i, j) = b(j, i) = errors_[i].dot(errors_[j]);
2346 b(i, m) = b(m, i) = -1.0;
2352 const double scale = b.topLeftCorner(m, m).diagonal().maxCoeff();
2354 b.topLeftCorner(m, m) /= scale;
2356 Eigen::VectorXd rhs = Eigen::VectorXd::Zero(m + 1);
2358 const Eigen::VectorXd
x = b.colPivHouseholderQr().solve(rhs);
2359 if (!
x.allFinite()) {
2362 Eigen::VectorXd result = Eigen::VectorXd::Zero(fock.size());
2363 for (
Index i = 0; i < m; ++i) {
2364 result +=
x(i) * focks_[i];
2371 std::vector<Eigen::VectorXd> focks_;
2372 std::vector<Eigen::VectorXd> errors_;
2384std::vector<std::array<double, 4>> ValenceConfiguration(
Index z,
Index ncore,
2392 std::vector<Sub> subs;
2394 for (
Index sum = 1; left > 0; ++sum) {
2395 for (
Index l = (sum - 1) / 2; l >= 0 && left > 0; --l) {
2396 const Index take = std::min(left, 2 * (2 * l + 1));
2397 subs.push_back({sum - l, l, double(take)});
2401 std::stable_sort(subs.begin(), subs.end(), [](
const Sub& a,
const Sub& b) {
2402 return a.n < b.n || (a.n == b.n && a.l < b.l);
2405 std::size_t first = 0;
2406 while (first < subs.size() && removed < ncore) {
2407 removed +=
Index(subs[first].electrons);
2410 if (removed != ncore) {
2413 std::vector<std::array<double, 4>> result;
2414 for (std::size_t i = first; i < subs.size(); ++i) {
2416 for (std::size_t j = first; j < i; ++j) {
2417 k += (subs[j].l == subs[i].l) ? 1 : 0;
2419 result.push_back({double(subs[i].l), double(k), 0.5 * subs[i].electrons,
2420 0.5 * subs[i].electrons});
2423 double excess = 0.5 * double(nalpha - nbeta);
2424 for (
auto it = result.rbegin(); it != result.rend() && excess > 0.0; ++it) {
2425 const double capacity = double(2 *
Index((*it)[0]) + 1);
2426 const double move = std::min(excess, capacity - (*it)[2]);
2427 if (move > 0.0 && (*it)[3] >= move) {
2433 if (excess > 1
e-12) {
2466std::optional<std::pair<Index, Index>> HundsRuleAlphaBetaElectrons(
2467 Index nuclear_charge) {
2468 switch (nuclear_charge) {
2470 return std::make_pair(1, 0);
2472 return std::make_pair(1, 1);
2474 return std::make_pair(2, 1);
2476 return std::make_pair(2, 2);
2478 return std::make_pair(3, 2);
2480 return std::make_pair(4, 2);
2482 return std::make_pair(5, 2);
2484 return std::make_pair(5, 3);
2486 return std::make_pair(5, 4);
2488 return std::make_pair(5, 5);
2490 return std::make_pair(6, 5);
2492 return std::make_pair(6, 6);
2494 return std::make_pair(7, 6);
2496 return std::make_pair(8, 6);
2498 return std::make_pair(9, 6);
2500 return std::make_pair(9, 7);
2502 return std::make_pair(9, 8);
2504 return std::make_pair(9, 9);
2506 return std::make_pair(10, 9);
2508 return std::make_pair(10, 10);
2511 return std::make_pair(16, 15);
2513 return std::make_pair(17, 15);
2515 return std::make_pair(18, 15);
2517 return std::make_pair(18, 16);
2519 return std::make_pair(18, 17);
2521 return std::make_pair(18, 18);
2524 return std::make_pair(25, 24);
2526 return std::make_pair(26, 24);
2528 return std::make_pair(27, 24);
2530 return std::make_pair(27, 25);
2532 return std::make_pair(27, 26);
2534 return std::make_pair(27, 27);
2536 return std::nullopt;
2542 const QMAtom& uniqueAtom,
bool use_hunds_rule_occupation)
const {
2554 dftbasis.
Fill(basisset, atom);
2564 ecp.
Fill(ecps, atom);
2571 const Index ncore = z - atom[0].getNuccharge();
2572 const Index numofelectrons = atom[0].getNuccharge();
2580 if (use_hunds_rule_occupation) {
2581 auto hunds_rule = HundsRuleAlphaBetaElectrons(z);
2582 if (hunds_rule.has_value()) {
2584 alpha_e = hunds_rule->first - ncore / 2;
2585 beta_e = hunds_rule->second - ncore / 2;
2589 <<
" No Hund's-rule ground-state occupation table "
2590 "entry for nuclear charge "
2592 <<
" (d/f-block elements are not covered -- see "
2593 "HundsRuleAlphaBetaElectrons's own comment for why) -- "
2594 "falling back to the simpler, parity-based alpha/beta split."
2596 use_hunds_rule_occupation =
false;
2599 if (!use_hunds_rule_occupation) {
2600 if ((numofelectrons % 2) != 0) {
2601 alpha_e = numofelectrons / 2 + numofelectrons % 2;
2602 beta_e = numofelectrons / 2;
2604 alpha_e = numofelectrons / 2;
2615 dftAOoverlap.
Fill(dftbasis);
2616 dftAOkinetic.
Fill(dftbasis);
2621 Eigen::MatrixXd H0 = dftAOkinetic.
Matrix() + dftAOESP.
Matrix();
2629 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es(
2630 H0, dftAOoverlap.
Matrix());
2631 const Eigen::VectorXd c = es.eigenvectors().col(0);
2632 return c * c.transpose();
2637 const SphericalAtomSCF sph(dftbasis);
2638 const Index ngroups = sph.Groups();
2639 using Radial = std::vector<Eigen::MatrixXd>;
2640 Radial S_red(ngroups);
2641 Radial H0_red(ngroups);
2643 Radial X_red(ngroups);
2644 for (
Index g = 0; g < ngroups; ++g) {
2645 S_red[g] = sph.Reduce(dftAOoverlap.
Matrix(), g);
2646 H0_red[g] = sph.Reduce(H0, g);
2647 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(S_red[g]);
2648 X_red[g] = es.eigenvectors() *
2649 es.eigenvalues().cwiseInverse().cwiseSqrt().asDiagonal() *
2650 es.eigenvectors().transpose();
2655 Radial Da, Db, Fa, Fb;
2656 double energy = 0.0;
2660 const double scahfx =
2662 auto Build = [&](
const Radial& Da,
const Radial& Db) {
2663 const Eigen::MatrixXd D_alpha = sph.Expand(Da);
2664 const Eigen::MatrixXd D_beta = sph.Expand(Db);
2665 const Eigen::MatrixXd D_total = D_alpha + D_beta;
2666 Eigen::MatrixXd H_alpha = H0;
2667 Eigen::MatrixXd H_beta = H0;
2670 std::array<Eigen::MatrixXd, 2> both_alpha =
2672 std::array<Eigen::MatrixXd, 2> both_beta =
2674 const Eigen::MatrixXd hartree = both_alpha[0] + both_beta[0];
2675 H_alpha += hartree + scahfx * both_alpha[1];
2676 H_beta += hartree + scahfx * both_beta[1];
2677 e_two = 0.5 * D_total.cwiseProduct(hartree).sum() +
2679 (both_alpha[1].cwiseProduct(D_alpha).sum() +
2680 both_beta[1].cwiseProduct(D_beta).sum());
2682 const Eigen::MatrixXd hartree =
2686 e_two = 0.5 * D_total.cwiseProduct(hartree).sum();
2690 H_beta += vxc.vxc_beta;
2694 st.energy = D_total.cwiseProduct(H0).sum() + e_two + vxc.energy;
2695 st.Fa.resize(ngroups);
2696 st.Fb.resize(ngroups);
2697 for (
Index g = 0; g < ngroups; ++g) {
2698 st.Fa[g] = sph.Reduce(H_alpha, g);
2699 st.Fb[g] = sph.Reduce(H_beta, g);
2706 std::vector<Eigen::VectorXd> occ_alpha;
2707 std::vector<Eigen::VectorXd> occ_beta;
2708 bool fixed_occupation =
false;
2710 const std::vector<std::array<double, 4>> config =
2711 ValenceConfiguration(z, ncore, alpha_e, beta_e);
2712 std::vector<std::array<double, 3>> alpha_subshells;
2713 std::vector<std::array<double, 3>> beta_subshells;
2714 for (
const auto& sub : config) {
2715 alpha_subshells.push_back({sub[0], sub[1], sub[2]});
2716 beta_subshells.push_back({sub[0], sub[1], sub[3]});
2720 sph.OccupationFromSubshells(alpha_subshells, S_red, occ_alpha) &&
2721 sph.OccupationFromSubshells(beta_subshells, S_red, occ_beta);
2722 if (!fixed_occupation) {
2724 <<
TimeStamp() <<
" No subshell configuration for "
2726 <<
" in this basis/ECP; atomic SCF uses aufbau occupation"
2730 auto Occupy = [&](
const Radial&
F,
bool alpha) {
2731 if (fixed_occupation) {
2732 return sph.Density(
F, S_red, alpha ? occ_alpha : occ_beta);
2736 sph.AufbauOccupation(
F, S_red,
double(alpha ? alpha_e : beta_e)));
2739 auto Flatten = [&](
const State& st, Eigen::VectorXd& fock,
2740 Eigen::VectorXd& error) {
2742 for (
Index g = 0; g < ngroups; ++g) {
2743 total += 2 * st.Fa[g].size();
2746 error.resize(total);
2748 for (
Index spin = 0; spin < 2; ++spin) {
2749 const Radial&
F = (spin == 0) ? st.Fa : st.Fb;
2750 const Radial&
D = (spin == 0) ? st.Da : st.Db;
2751 for (
Index g = 0; g < ngroups; ++g) {
2752 const Eigen::MatrixXd fds =
F[g] *
D[g] * S_red[g];
2753 const Eigen::MatrixXd err =
2754 X_red[g] * (fds - fds.transpose()) * X_red[g];
2755 const Index size =
F[g].size();
2756 fock.segment(pos, size) =
2757 Eigen::Map<const Eigen::VectorXd>(
F[g].data(), size);
2758 error.segment(pos, size) =
2759 Eigen::Map<const Eigen::VectorXd>(err.data(), size);
2764 auto Unflatten = [&](
const Eigen::VectorXd& fock, Radial& Fa, Radial& Fb) {
2766 for (
Index spin = 0; spin < 2; ++spin) {
2767 Radial&
F = (spin == 0) ? Fa : Fb;
2768 for (
Index g = 0; g < ngroups; ++g) {
2769 const Index rows = S_red[g].rows();
2770 F[g] = Eigen::Map<const Eigen::MatrixXd>(fock.data() + pos, rows, rows);
2780 State cur = Build(Occupy(H0_red,
true), Occupy(H0_red,
false));
2782 const Index maxiter = 100;
2786 const double error_tolerance = std::max(
conv_opt_.error_converged, 1
e-6);
2787 const double energy_tolerance = std::max(
conv_opt_.Econverged, 1
e-8);
2788 double energy_old = cur.energy;
2789 bool converged =
false;
2790 Index this_iter = 0;
2791 for (; this_iter < maxiter; this_iter++) {
2792 Eigen::VectorXd fock;
2793 Eigen::VectorXd error;
2794 Flatten(cur, fock, error);
2795 const double max_error = error.cwiseAbs().maxCoeff();
2797 <<
TimeStamp() <<
" Iter " << this_iter <<
" of " << maxiter <<
" Etot "
2798 << std::setprecision(12) << cur.energy <<
" error " << max_error
2800 if (this_iter > 0 && max_error < error_tolerance &&
2801 std::abs(cur.energy - energy_old) < energy_tolerance) {
2805 energy_old = cur.energy;
2808 Unflatten(diis.Extrapolate(fock, error), Fa, Fb);
2809 cur = Build(Occupy(Fa,
true), Occupy(Fb,
false));
2813 <<
TimeStamp() <<
" Converged after " << this_iter + 1
2814 <<
" iterations, Etot=" << std::setprecision(12) << cur.energy
2818 <<
TimeStamp() <<
" Not converged after " << maxiter
2819 <<
" iterations. Unconverged density." << std::flush;
2821 const Radial& Da = cur.Da;
2822 const Radial& Db = cur.Db;
2824 const Eigen::MatrixXd density = sph.Expand(Da) + sph.Expand(Db);
2827 <<
" gives N=" << std::setprecision(9)
2828 << density.cwiseProduct(dftAOoverlap.
Matrix()).sum() <<
" electrons."
2837 <<
TimeStamp() <<
" Scanning molecule of size " << mol.
size()
2838 <<
" for unique elements" << std::flush;
2840 for (
auto element : elements) {
2841 uniqueelements.
push_back(
QMAtom(0, element, Eigen::Vector3d::Zero()));
2845 <<
" unique elements found" << std::flush;
2846 std::vector<Eigen::MatrixXd> uniqueatom_guesses;
2847 for (
QMAtom& unique_atom : uniqueelements) {
2849 <<
TimeStamp() <<
" Calculating atom density for "
2850 << unique_atom.getElement() << std::flush;
2852 uniqueatom_guesses.push_back(dmat_unrestricted);
2855 Eigen::MatrixXd guess =
2858 for (
const QMAtom& atom : mol) {
2860 for (; index < uniqueelements.
size(); index++) {
2861 if (atom.getElement() == uniqueelements[index].getElement()) {
2865 Eigen::MatrixXd& dmat_unrestricted = uniqueatom_guesses[index];
2866 guess.block(start, start, dmat_unrestricted.rows(),
2867 dmat_unrestricted.cols()) = dmat_unrestricted;
2868 start += dmat_unrestricted.rows();
2874std::map<std::string, Eigen::MatrixXd>
2878 <<
TimeStamp() <<
" Scanning molecule of size " << mol.
size()
2879 <<
" for unique elements (Hirshfeld reference densities)" << std::flush;
2881 std::map<std::string, Eigen::MatrixXd> reference_densities;
2882 for (
const std::string& element : elements) {
2883 QMAtom unique_atom(0, element, Eigen::Vector3d::Zero());
2885 <<
TimeStamp() <<
" Calculating Hirshfeld reference density for "
2886 << element << std::flush;
2893 return reference_densities;
2898 std::map<std::string, Eigen::MatrixXd> reference_densities =
2905 full_dftbasis.
Fill(basisset, mol);
2911 std::vector<HirshfeldPartition::AtomicReference> atoms =
2913 reference_densities);
2918 double neutral_reference_population = 0.0;
2920 if (atom_index < 0 || atom_index >=
static_cast<Index>(mol.
size())) {
2921 throw std::runtime_error(
2922 "BuildCDFTConstraint: cdft.indices contains atom index " +
2923 std::to_string(atom_index) +
", but this molecule only has " +
2924 std::to_string(mol.
size()) +
2925 " atoms (0-based indexing -- valid range is 0.." +
2926 std::to_string(mol.
size() - 1) +
").");
2936 atoms, atom_index, full_dftbasis, grid);
2937 neutral_reference_population +=
2938 static_cast<double>(mol[atom_index].getNuccharge());
2949 <<
" atom(s), neutral reference population="
2950 << neutral_reference_population
2965 throw std::runtime_error(
2966 (boost::format(
"Basisset Name in guess orb file "
2967 "and in dftengine option file differ %1% vs %2%") %
2975 "Orbital file has no basisset information,"
2976 "using it as a guess might work or not for calculation with "
3001 throw std::runtime_error(
3002 (boost::format(
"ECPs in orb file: %1% and options %2% differ") %
3009 throw std::runtime_error(
3010 (boost::format(
"Number of electrons in guess orb file "
3011 "and in dftengine differ: "
3012 "alpha %1% vs %2%, beta %3% vs %4%.") %
3018 throw std::runtime_error(
3019 (boost::format(
"Number of levels in guess orb file: "
3020 "%1% and in dftengine: %2% differ.") %
3040 <<
TimeStamp() <<
" Using MKL overload for Eigen " << std::flush;
3044 <<
" Using native Eigen implementation, no BLAS overload "
3049 for (
const QMAtom& atom : mol) {
3051 std::string output = (boost::format(
" %1$s"
3052 " %2$+1.4f %3$+1.4f %4$+1.4f") %
3053 atom.getElement() % pos[0] % pos[1] % pos[2])
3064 <<
dftbasis_.AOBasisSize() <<
" functions" << std::flush;
3072 <<
auxbasis_.AOBasisSize() <<
" functions" << std::flush;
3080 std::vector<std::string> results =
ecp_.Fill(ecpbasisset, mol);
3082 <<
TimeStamp() <<
" Filled ECP Basis" << std::flush;
3083 if (results.size() > 0) {
3084 std::string message =
"";
3085 for (
const std::string& element : results) {
3086 message +=
" " + element;
3089 <<
TimeStamp() <<
" Found no ECPs for elements" << message
3100 Index nuclear_charge = 0;
3101 for (
const QMAtom& atom : mol) {
3102 nuclear_charge += atom.getNuccharge();
3108 if (multiplicity < 1) {
3109 throw std::runtime_error(
"Spin multiplicity must be >= 1.");
3112 if (numofelectrons >= 0) {
3118 Index spin_excess = multiplicity - 1;
3121 throw std::runtime_error(
"Computed a negative number of electrons.");
3125 throw std::runtime_error(
3126 "Spin multiplicity incompatible with total number of electrons.");
3130 throw std::runtime_error(
3131 "Charge and spin multiplicity imply non-integer alpha/beta "
3143 <<
" (charge=" << target_charge <<
", multiplicity=" << multiplicity
3167 <<
"\t\t " <<
" with " << grid.
getGridSize() <<
" points"
3168 <<
" divided into " << grid.
getBoxesSize() <<
" boxes" << std::flush;
3173 double E_nucnuc = 0.0;
3175 for (
Index i = 0; i < mol.
size(); i++) {
3176 const Eigen::Vector3d& r1 = mol[i].
getPos();
3177 double charge1 = double(mol[i].getNuccharge());
3178 for (
Index j = 0; j < i; j++) {
3179 const Eigen::Vector3d& r2 = mol[j].
getPos();
3180 double charge2 = double(mol[j].getNuccharge());
3181 E_nucnuc += charge1 * charge2 / (r1 - r2).norm();
3198 const Eigen::MatrixXd& dmat,
const AOBasis& dftbasis)
const {
3199 Eigen::MatrixXd avdmat = Eigen::MatrixXd::Zero(dmat.rows(), dmat.cols());
3200 for (
const AOShell& shellrow : dftbasis) {
3201 for (
const AOShell& shellcol : dftbasis) {
3202 if (shellrow.getL() != shellcol.getL()) {
3205 const Index size = shellrow.getNumFunc();
3206 const double diagavg = dmat.block(shellrow.getStartIndex(),
3207 shellcol.getStartIndex(), size, size)
3211 .block(shellrow.getStartIndex(), shellcol.getStartIndex(), size, size)
3213 .setConstant(diagavg);
3221 const std::vector<std::unique_ptr<StaticSite>>& multipoles)
const {
3223 if (multipoles.size() == 0) {
3229 for (
const QMAtom& atom : mol) {
3231 for (
const std::unique_ptr<StaticSite>& site : multipoles) {
3232 if ((site->getPos() - nucleus.
getPos()).norm() < 1
e-7) {
3234 <<
" External site sits on nucleus, "
3235 "interaction between them is ignored."
3270 Q.segment<3>(1) += site->getInducedDipole();
3271 Index rank = site->getRank();
3272 if (rank < 1 && Q.segment<3>(1).norm() > 1
e-12) {
3275 StaticSite effective(site->getId(), site->getElement(), site->getPos());
3289 Eigen::MatrixXd result =
3291 for (
Index i = 0; i < 3; i++) {
3299 const std::vector<std::unique_ptr<StaticSite>>& multipoles)
const {
3308 <<
TimeStamp() <<
" Filled DFT external multipole potential matrix"
3318 Eigen::MatrixXd sites(
Index(multipoles.size()), 13);
3319 for (
Index i = 0; i <
Index(multipoles.size()); ++i) {
3321 sites.block<1, 3>(i, 0) = site.
getPos().transpose();
3322 sites(i, 3) = double(site.
getRank());
3323 sites.block<1, 9>(i, 4) = site.
Q().transpose();
3328 setup_cache_->multipole_sites.rows() == sites.rows() &&
3333 <<
" Reusing the permanent multipole potential matrix of the previous "
3345 <<
TimeStamp() <<
" Filled DFT permanent multipole potential matrix"
3349 const bool has_induced =
3350 std::any_of(multipoles.begin(), multipoles.end(),
3351 [](
const std::unique_ptr<StaticSite>& site) {
3352 return site->getInducedDipole().norm() > 1e-12;
3359 <<
TimeStamp() <<
" Filled DFT induced dipole potential matrix"
3379 <<
TimeStamp() <<
" Calculated external density" << std::flush;
3382 <<
TimeStamp() <<
" Calculated potential from electron density"
3387 double nuc_energy = 0.0;
3388 for (
const QMAtom& atom : mol) {
3392 const double dist = (atom.getPos() - extatom.getPos()).norm();
3394 double(atom.getNuccharge()) * double(extatom.getNuccharge()) / dist;
3398 <<
TimeStamp() <<
" Calculated potential from nuclei" << std::flush;
3400 <<
TimeStamp() <<
" Electrostatic: " << nuc_energy << std::flush;
3405 const Eigen::MatrixXd& GuessMOs)
const {
3406 Eigen::MatrixXd nonortho =
3408 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(nonortho);
3409 Eigen::MatrixXd result = GuessMOs * es.operatorInverseSqrt();
3422 Eigen::VectorXd eps = Eigen::VectorXd::Zero(nao);
3426 int l =
static_cast<int>(shell.getL());
3427 Index start = shell.getStartIndex();
3428 Index nfunc = shell.getNumFunc();
3430 const QMAtom& atom = mol[shell.getAtomIndex()];
3431 const std::string& element = atom.
getElement();
3436 for (
Index i = 0; i < nfunc; ++i) {
3447 const Index nao =
S.rows();
3449 Eigen::MatrixXd
H = Eigen::MatrixXd::Zero(nao, nao);
3450 constexpr double K = 1.75;
3452 for (
Index mu = 0; mu < nao; ++mu) {
3453 H(mu, mu) = eps(mu);
3454 for (
Index nu = 0; nu < mu; ++nu) {
3455 double hij = K *
S(mu, nu) * 0.5 * (eps(mu) + eps(nu));
3467 <<
TimeStamp() <<
" Building Extended Huckel guess" << std::flush;
3472 <<
TimeStamp() <<
" Solving EHT generalized eigenproblem" << std::flush;
3490 std::array<Eigen::MatrixXd, 2> both =
3518 if (nA + nB != dimer_mol.
size()) {
3519 throw std::runtime_error(
3520 "BuildDimerGuessFromMonomerFiles: monomer A (" + std::to_string(nA) +
3521 " atoms) + monomer B (" + std::to_string(nB) +
3522 " atoms) does not equal this calculation's own molecule (" +
3523 std::to_string(dimer_mol.
size()) +
3524 " atoms) -- wrong monomer file(s), or this calculation's molecule "
3525 "is not simply the concatenation of these two monomers.");
3527 for (
Index i = 0; i < nA; ++i) {
3528 if (atomsA[i].getElement() != dimer_mol[i].getElement()) {
3529 throw std::runtime_error(
3530 "BuildDimerGuessFromMonomerFiles: monomer A's own atom " +
3531 std::to_string(i) +
" (" + atomsA[i].getElement() +
3532 ") does not match this calculation's own atom " + std::to_string(i) +
3533 " (" + dimer_mol[i].getElement() +
3534 ") -- dimer_guess assumes monomer A occupies exactly the first "
3535 "N_A atoms of this calculation's molecule, in the same order.");
3538 for (
Index i = 0; i < nB; ++i) {
3539 if (atomsB[i].getElement() != dimer_mol[nA + i].getElement()) {
3540 throw std::runtime_error(
3541 "BuildDimerGuessFromMonomerFiles: monomer B's own atom " +
3542 std::to_string(i) +
" (" + atomsB[i].getElement() +
3543 ") does not match this calculation's own atom " +
3544 std::to_string(nA + i) +
" (" + dimer_mol[nA + i].getElement() +
3545 ") -- dimer_guess assumes monomer B occupies exactly the "
3546 "remaining atoms of this calculation's molecule (after monomer "
3547 "A's own N_A atoms), in the same order.");
3560 constexpr double kGeometryToleranceBohr = 1
e-3;
3561 auto CheckInternalGeometry = [&](
const QMMolecule& monomer_atoms,
3562 Index offset_in_dimer,
3563 const std::string& label) {
3565 for (
Index i = 0; i < n; ++i) {
3566 for (
Index j = i + 1; j < n; ++j) {
3567 double monomer_distance =
3568 (monomer_atoms[i].
getPos() - monomer_atoms[j].getPos()).norm();
3569 double dimer_distance = (dimer_mol[offset_in_dimer + i].
getPos() -
3570 dimer_mol[offset_in_dimer + j].getPos())
3572 double diff = std::abs(monomer_distance - dimer_distance);
3573 if (diff > kGeometryToleranceBohr) {
3574 throw std::runtime_error(
3575 "BuildDimerGuessFromMonomerFiles: " + label +
3576 "'s own internal geometry does not match this calculation's "
3577 "molecule -- distance between its own atoms " +
3578 std::to_string(i) +
" and " + std::to_string(j) +
" is " +
3579 std::to_string(monomer_distance) +
3580 " Bohr in the monomer file, but " +
3581 std::to_string(dimer_distance) +
3582 " Bohr in this calculation's own molecule (difference " +
3583 std::to_string(diff) +
" Bohr, tolerance " +
3584 std::to_string(kGeometryToleranceBohr) +
3585 " Bohr). This is checked as an INTERNAL, translation/"
3586 "rotation-invariant distance specifically because the "
3587 "monomer's absolute position/orientation is expected to "
3588 "differ between its own standalone optimization and its "
3589 "placement in the dimer -- only its internal geometry "
3590 "should still match.");
3595 CheckInternalGeometry(atomsA, 0,
"Monomer A");
3596 CheckInternalGeometry(atomsB, nA,
"Monomer B");
3600 auto MaxDeviationFromTranslation = [&](
const QMMolecule& monomer_atoms,
3601 Index offset_in_dimer) {
3602 Eigen::Vector3d shift =
3603 dimer_mol[offset_in_dimer].
getPos() - monomer_atoms[0].
getPos();
3604 double max_dev = 0.0;
3605 for (
Index i = 0; i < monomer_atoms.
size(); ++i) {
3606 double dev = (dimer_mol[offset_in_dimer + i].
getPos() -
3607 monomer_atoms[i].getPos() - shift)
3609 max_dev = std::max(max_dev, dev);
3613 auto WarnIfRotated = [&](
const QMMolecule& monomer_atoms,
3614 Index offset_in_dimer,
const std::string& label) {
3615 double dev = MaxDeviationFromTranslation(monomer_atoms, offset_in_dimer);
3616 if (dev > kGeometryToleranceBohr) {
3619 <<
" is rotated with respect to its .orb file (max deviation " << dev
3620 <<
" bohr after translation). Its MO coefficients are not "
3621 "rotated, so the dimer guess will be poor."
3625 WarnIfRotated(atomsA, 0,
"Monomer A");
3626 WarnIfRotated(atomsB, nA,
"Monomer B");
3635 dimer_guess.
QMAtoms() = dimer_mol;
Container to hold Basisfunctions for all atoms.
Index AOBasisSize() const
void Fill(const BasisSet &bs, const QMMolecule &atoms)
void setCenter(const Eigen::Vector3d &r)
const std::array< Eigen::MatrixXd, 3 > & Matrix() const
void Fill(const AOBasis &aobasis) final
void FillPotential(const AOBasis &aobasis, const ECPAOBasis &ecp)
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
void FillPotential(const AOBasis &aobasis, const QMMolecule &atoms)
@ Permanent
permanent moments only (charge, dipole, quadrupole)
@ Induced
induced dipoles only; sites without one are skipped
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
const Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > & Matrix() const
void push_back(const T &atom)
std::vector< std::string > FindUniqueElements() const
const Eigen::Vector3d & getPos() const
void Load(const std::string &name)
Eigen::MatrixXd CalcERIs(const Eigen::MatrixXd &Dmat, double error) const
Build the Coulomb matrix contribution from the current density matrix.
Eigen::MatrixXd ComputeOverlapPulayGradientUKS(const QMMolecule &mol, const tools::EigenSystem &MOs_alpha, const tools::EigenSystem &MOs_beta) const
std::string auxbasis_name_
Index num_alpha_electrons_
tools::EigenSystem ModelPotentialGuess(const Mat_p_Energy &H0, const QMMolecule &mol, const Vxc_Potential< Vxc_Grid > &vxcpotential) const
Mat_p_Energy IntegrateExternalMultipoles(const QMMolecule &mol, const std::vector< std::unique_ptr< StaticSite > > &multipoles) const
tools::EigenSystem IndependentElectronGuess(const Mat_p_Energy &H0) const
Generate an initial guess by diagonalizing the core Hamiltonian only.
void setSCFToleranceFloor(double energy, double error)
Index num_beta_electrons_
Orbitals BuildDimerGuessFromMonomerFiles(const QMMolecule &dimer_mol) const
double cdft_population_tolerance_
void ComputeAndStoreForces(Orbitals &orb, const Eigen::MatrixXd &Dmat, const Vxc_Potential< Vxc_Grid > &vxcpotential) const
bool EvaluateClosedShell(Orbitals &orb, const Mat_p_Energy &H0, const Vxc_Potential< Vxc_Grid > &vxcpotential)
Run the restricted closed-shell SCF loop and store the converged result.
void PrintMOsUKS(const Eigen::VectorXd &alpha_energies, const Eigen::VectorXd &beta_energies, Log::Level level) const
Print separate alpha and beta orbital energies for a UKS calculation.
void ReportDimensionsAndMemory() const
Basis dimensions and the memory of the large intermediates.
std::string dftbasis_name_
DFTSetupCache * setup_cache_
bool EvaluateUKS(Orbitals &orb, const Mat_p_Energy &H0, const Vxc_Potential< Vxc_Grid > &vxcpotential)
double truncation_threshold_
Eigen::MatrixXd RunAtomicDFT_unrestricted(const QMAtom &uniqueAtom, bool use_hunds_rule_occupation=false) const
Mat_p_Energy IntegrateExternalDensity(const QMMolecule &mol, const Orbitals &extdensity) const
void Prepare(Orbitals &orb, Index numofelectrons=-1)
Vxc_Grid external_ewaldgrid_
std::string initial_guess_
Eigen::MatrixXd AtomicGuess(const QMMolecule &mol) const
Build an atomic-density based initial guess in the AO basis.
bool EvaluateAndTime(Orbitals &orb)
Evaluate() without the timing report.
Eigen::MatrixXd BuildEHTHamiltonian(const QMMolecule &mol) const
Build the extended-Hückel Hamiltonian for the current molecule.
Eigen::MatrixXd IntegrateExternalField(const QMMolecule &mol) const
Integrate a homogeneous external electric field into the AO basis.
double ri_pair_threshold_
Eigen::Vector3d extfield_
double overlap_tolerance_
std::array< Eigen::MatrixXd, 2 > CalcERIs_EXX(const Eigen::MatrixXd &MOCoeff, const Eigen::MatrixXd &Dmat, double error) const
HirshfeldPartition::Constraint BuildCDFTConstraint(const QMMolecule &mol, const CDFTConstraintSpec &spec) const
void Initialize(tools::Property &options)
Read DFT, grid, and SCF settings from the user options tree.
std::string xc_functional_name_
void CalcElDipole(const Orbitals &orb) const
Evaluate and print the electronic dipole moment from the final density.
double ExternalRepulsion(const QMMolecule &mol, const std::vector< std::unique_ptr< StaticSite > > &multipoles) const
ConvergenceAcc::options conv_opt_
void SetupInvariantMatrices()
Precompute AO matrices that remain unchanged throughout the SCF procedure.
tools::EigenSystem ExtendedHuckelDFTGuess(const Mat_p_Energy &H0, const QMMolecule &mol, const Vxc_Potential< Vxc_Grid > &vxcpotential) const
std::map< std::string, Eigen::MatrixXd > ComputeHirshfeldReferenceDensities(const QMMolecule &mol) const
Index max_cdft_iterations_
std::string active_atoms_as_string_
void ComputeAndStoreForcesUKS(Orbitals &orb, const UKSConvergenceAcc::SpinDensity &Dspin, const tools::EigenSystem &MOs_alpha, const tools::EigenSystem &MOs_beta, const Vxc_Potential< Vxc_Grid > &vxcpotential) const
double ewald_nuclear_energy_
void ConfigOrbfile(Orbitals &orb)
Propagate basis-set, XC, and metadata settings into the orbital container.
Eigen::VectorXd BuildEHTOrbitalEnergies(const QMMolecule &mol) const
Build orbital energies used in the extended-Hückel starting guess.
bool integrate_ext_density_
std::vector< HirshfeldPartition::Constraint > constraints_
bool UsableAsWarmStart(const Orbitals &orb, const std::string &previous_basis, std::string &reason) const
void PrintMOs(const Eigen::VectorXd &MOEnergies, Log::Level level)
Print a one-spin list of orbital energies and occupations to the logger.
Mat_p_Energy SetupH0(const QMMolecule &mol) const
Assemble the one-electron core Hamiltonian for the current molecule.
tools::EigenSystem ExtendedHuckelGuess(const QMMolecule &mol) const
Eigen::MatrixXd OrthogonalizeGuess(const Eigen::MatrixXd &GuessMOs) const
Orthonormalize an initial MO guess with respect to the AO overlap matrix.
Eigen::MatrixXd SphericalAverageShells(const Eigen::MatrixXd &dmat, const AOBasis &dftbasis) const
bool Evaluate(Orbitals &orb)
CDFTConstraintSpec cdft_constraint_spec_
Eigen::MatrixXd ComputeNonXCGradientUKS(const QMMolecule &mol, const UKSConvergenceAcc::SpinDensity &Dspin, const tools::EigenSystem &MOs_alpha, const tools::EigenSystem &MOs_beta) const
bool RunCDFT(Orbitals &orb, HirshfeldPartition::Constraint &constraint)
std::string dimer_guess_orbB_name_
std::vector< std::unique_ptr< StaticSite > > * externalsites_
Vxc_Potential< Vxc_Grid > SetupVxc(const QMMolecule &mol)
std::string RISetupKey() const
bool integrate_ext_field_
double NuclearRepulsion(const QMMolecule &mol) const
Compute the classical nucleus-nucleus repulsion energy.
ConvergenceAcc conv_accelerator_
std::string dimer_guess_orbA_name_
static Eigen::MatrixXd RIKGradient(const Eigen::MatrixXd &occ_mo_coeffs, const AOBasis &auxbasis, const AOBasis &dftbasis)
static Eigen::MatrixXd NuclearRepulsionDerivative(const QMMolecule &mol)
static Eigen::MatrixXd RIJGradient(const Eigen::MatrixXd &density, const AOBasis &auxbasis, const AOBasis &dftbasis)
static double ResidentMemoryGB(bool peak)
double IntegratePotential(const Eigen::Vector3d &rvector) const
double IntegrateDensity(const Eigen::MatrixXd &density_matrix)
Container to hold ECPs for all atoms.
std::vector< std::string > Fill(const ECPBasisSet &bs, QMMolecule &atoms)
void Load(const std::string &name)
Takes a density matrix and and an auxiliary basis set and calculates the electron repulsion integrals...
std::array< Eigen::MatrixXd, 2 > CalculateERIs_EXX_4c(const Eigen::MatrixXd &DMAT, double error) const
void Initialize_4c(const AOBasis &dftbasis)
Eigen::MatrixXd CalculateERIs_4c(const Eigen::MatrixXd &DMAT, double error) const
Mat_p_Energy IntegrateEwald(Index basissize) const
double GetWithFallback(const std::string &element, int l, int *used_l=nullptr) const
std::vector< double > & getPotentialValues()
static std::vector< AtomicReference > BuildAtomicReferences(const QMMolecule &mol, const std::string &basisset_name, const std::map< std::string, Eigen::MatrixXd > &reference_densities)
static Eigen::MatrixXd BuildWeightMatrix(const std::vector< AtomicReference > &atoms, Index target_atom_index, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
static Eigen::MatrixXd ComputeCDFTForceContribution(const std::vector< AtomicReference > &atoms, Index target_atom_index, const Eigen::MatrixXd &density_matrix, const QMMolecule &mol, const AOBasis &full_dftbasis, const Vxc_Grid &grid)
void UpdateDmats(const Eigen::MatrixXd &dmat, double DiisError, Index Iteration)
void Configure(const Eigen::MatrixXd &dmat)
void resetMatrices(Eigen::MatrixXd &J, Eigen::MatrixXd &K, const Eigen::MatrixXd &dmat)
void Start(Index iteration, double DiisError)
void UpdateCriteria(double DiisError, Index Iteration)
const Eigen::MatrixXd & getDmat_diff() const
std::vector< Index > CreateIndexVector(const std::string &Ids) const
Eigen::MatrixXd & matrix()
Container for molecular orbitals and derived one-particle data.
void setScaHFX(double ScaHFX)
Store the fraction of exact exchange associated with the functional.
Index getCharge() const
Return the stored total charge.
Index getSpin() const
Return the stored spin multiplicity.
bool hasForces() const
Report whether nuclear forces have been stored.
const tools::EigenSystem & MOs_beta() const
Return read-only access to beta-spin molecular orbitals.
bool hasMOs() const
Report whether alpha/restricted molecular orbitals are available.
std::array< Eigen::MatrixXd, 2 > DensityMatrixGroundStateSpinResolved() const
Index getNumberOfAlphaElectrons() const
Return the stored number of alpha electrons.
void setNumberOfAlphaElectrons(Index electrons)
Store the total number of alpha electrons.
Eigen::MatrixXd DensityMatrixFull(const QMState &state) const
void PrepareDimerGuessMixedSpin(const Orbitals &orbitalsA, const Orbitals &orbitalsB)
Guess for a dimer of two monomers with independently ARBITRARY charge and spin, built by combining ea...
void SetupAuxBasis(std::string aux_basis_name)
void setNumberOfBetaElectrons(Index electrons)
Store the total number of beta electrons.
void setForces(const Eigen::MatrixXd &forces)
void setECPName(const std::string &ECP)
Store the effective core potential label.
void setXCGrid(std::string grid)
Store the numerical XC grid quality label.
void setNumberOfOccupiedLevels(Index occupied_levels)
Index getBasisSetSize() const
Return the number of AO basis functions in the DFT basis.
void setQMEnergy(double qmenergy)
Store the total DFT energy.
bool hasECPName() const
Report whether an effective core potential label has been stored.
const tools::EigenSystem & MOs() const
Return read-only access to alpha/restricted molecular orbitals.
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
void ReadFromCpt(const std::string &filename)
Read the orbital container from a checkpoint file on disk.
void setNumberOfOccupiedLevelsBeta(Index occupied_levels_beta)
Store the number of occupied beta-spin orbitals.
void setChargeAndSpin(Index charge, Index spin)
bool hasDFTbasisName() const
Report whether a DFT basis-set name has been stored.
const std::string & getECPName() const
Return the effective core potential label.
const std::string & getDFTbasisName() const
Return the DFT basis-set name.
bool hasBetaMOs() const
Report whether beta-spin molecular orbitals are available.
void SetupDftBasis(std::string basis_name)
Build and attach the DFT AO basis from the stored molecular geometry.
const Eigen::MatrixXd & getForces() const
Return the stored nuclear forces (Natoms x 3, Hartree/Bohr).
Eigen::Vector3d CalcElDipole(const QMState &state) const
Compute the electronic dipole moment associated with a state density.
Index getNumberOfBetaElectrons() const
Return the stored number of beta electrons.
const AOBasis & getDftBasis() const
Return the DFT AO basis, throwing if it has not been initialized.
void setXCFunctionalName(std::string functionalname)
Index getElementNumber() const
const std::string & getElement() const
Identifier for QMstates. Strings like S1 are converted into enum +zero indexed int.
Class to represent Atom/Site in electrostatic.
void setMultipole(const Vector9d &multipole, Index rank)
const Eigen::Vector3d & getPos() const
const Vector9d & Q() const
Timestamp returns the current time as a string Example: cout << TimeStamp().
SpinDensity DensityMatrix(const tools::EigenSystem &MOs_alpha, const tools::EigenSystem &MOs_beta) const
void setOverlap(AOOverlap &S, double etol)
void setLogger(Logger *log)
void Configure(const options &opt_alpha, const options &opt_beta)
SpinDensity Iterate(const SpinDensity &dmat, SpinFock &H, tools::EigenSystem &MOs_alpha, tools::EigenSystem &MOs_beta, double totE)
double getDIIsError() const
void setCoupledFockBuilder(const CoupledFockBuilder &builder)
Index getGridSize() const
void GridSetup(const std::string &type, const QMMolecule &atoms, const AOBasis &basis)
Index getBoxesSize() const
Eigen::MatrixXd GridWeightGradient(const Eigen::MatrixXd &density_matrix, const QMMolecule &atoms) const
Eigen::MatrixXd PulayGradient(const Eigen::MatrixXd &density_matrix, const AOBasis &dftbasis) const
Eigen::MatrixXd PulayGradientUKS(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta, const AOBasis &dftbasis) const
Mat_p_Energy IntegrateVXC(const Eigen::MatrixXd &density_matrix) const
void setXCfunctional(const std::string &functional)
static double getExactExchange(const std::string &functional)
Eigen::MatrixXd GridWeightGradientUKS(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta, const QMMolecule &atoms) const
SpinResult IntegrateVXCSpin(const Eigen::MatrixXd &dmat_alpha, const Eigen::MatrixXd &dmat_beta) const
Mediates interaction between polar and static sites.
double CalcStaticEnergy_site(const StaticSite &site1, const StaticSite &site2) const
#define XTP_LOG(level, log)
Charge transport classes.
bool XTP_HAS_MKL_OVERLOAD()
std::vector< AOMatrixDerivative > ComputeNuclearAttractionDerivatives(const AOBasis &aobasis, const QMMolecule &mol)
std::vector< AOMatrixDerivative > ComputeKineticDerivatives(const AOBasis &aobasis)
std::vector< AOMatrixDerivative > ComputeOverlapDerivatives(const AOBasis &aobasis)
std::array< Eigen::MatrixXd, 3 > AOMatrixDerivative
bool HasLibint2DerivativeSupport()
Provides a means for comparing floating point numbers.
Spin-resolved density matrices returned for open-shell SCF updates.
Eigen::MatrixXd total() const
Return the total density P = P^alpha + P^beta.
std::vector< Index > atom_indices
Eigen::MatrixXd weight_matrix
double spin_alpha_coefficient
double spin_beta_coefficient
Eigen::MatrixXd total() const
Eigen::MatrixXd vxc_alpha
Eigen::Matrix< double, 9, 1 > Vector9d