42Eigen::VectorXd Weights(
43 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
44 const Eigen::Vector3d& point) {
45 Eigen::VectorXd rho(
Index(atoms.size()));
46 for (
Index j = 0; j < rho.size(); ++j) {
48 atoms[std::size_t(j)].basis, atoms[std::size_t(j)].density, point);
50 const double denominator = rho.sum();
51 constexpr double kNegligibleDenominator = 1.e-12;
52 if (denominator < kNegligibleDenominator) {
53 return Eigen::VectorXd::Zero(rho.size());
55 return rho / denominator;
60 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
64 std::vector<Eigen::MatrixXd> W(std::size_t(n_atoms),
65 Eigen::MatrixXd::Zero(n_ao, n_ao));
66 std::exception_ptr eptr =
nullptr;
67#pragma omp parallel for schedule(guided)
74 const std::vector<Eigen::Vector3d>& points = box.
getGridPoints();
77 Eigen::MatrixXd ao(box.
Matrixsize(), n_points);
78 Eigen::MatrixXd w(n_points, n_atoms);
79 for (
Index p = 0; p < n_points; ++p) {
81 w.row(p) = weights[std::size_t(p)] *
82 Weights(atoms, points[std::size_t(p)]).transpose();
84 std::vector<Eigen::MatrixXd> local(
static_cast<std::size_t
>(n_atoms));
85 for (
Index k = 0; k < n_atoms; ++k) {
86 if (w.col(k).cwiseAbs().maxCoeff() == 0.0) {
89 local[std::size_t(k)] = ao * w.col(k).asDiagonal() * ao.transpose();
93 for (
Index k = 0; k < n_atoms; ++k) {
94 if (local[std::size_t(k)].size() > 0) {
103 eptr = std::current_exception();
109 std::rethrow_exception(eptr);
111 for (Eigen::MatrixXd& m : W) {
112 m = 0.5 * (m + m.transpose()).eval();
118 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
119 const std::vector<Eigen::Vector3d>& positions,
const AOBasis& basis,
120 const Vxc_Grid& grid,
const std::array<Eigen::MatrixXd, 3>& dP_field) {
122 if (
Index(positions.size()) != n_atoms) {
123 throw std::runtime_error(
124 "ChargeResponseFit: positions and Hirshfeld references differ in "
127 for (
const Eigen::MatrixXd& m : dP_field) {
129 throw std::runtime_error(
130 "ChargeResponseFit: density response does not match the basis");
133 std::vector<Eigen::Matrix3d> alpha(std::size_t(n_atoms),
134 Eigen::Matrix3d::Zero());
135 std::exception_ptr eptr =
nullptr;
136#pragma omp parallel for schedule(guided)
143 std::array<Eigen::MatrixXd, 3> dP_box;
144 for (
Index c = 0; c < 3; ++c) {
145 dP_box[std::size_t(c)] =
148 const std::vector<Eigen::Vector3d>& points = box.
getGridPoints();
150 std::vector<Eigen::Matrix3d> local(std::size_t(n_atoms),
151 Eigen::Matrix3d::Zero());
152 for (std::size_t p = 0; p < points.size(); ++p) {
153 const Eigen::VectorXd w = Weights(atoms, points[p]);
154 if (w.cwiseAbs().maxCoeff() == 0.0) {
159 for (
Index c = 0; c < 3; ++c) {
160 rho(c) = ao.dot(dP_box[std::size_t(c)] * ao);
162 for (
Index k = 0; k < n_atoms; ++k) {
163 const Eigen::Vector3d d = points[p] - positions[std::size_t(k)];
164 local[std::size_t(k)] -= (weights[p] * w(k)) * d * rho.transpose();
169 for (
Index k = 0; k < n_atoms; ++k) {
170 alpha[std::size_t(k)] += local[std::size_t(k)];
177 eptr = std::current_exception();
183 std::rethrow_exception(eptr);
189 const std::vector<Eigen::Vector3d>& positions,
190 const std::vector<Eigen::MatrixXd>& W,
191 const std::vector<Eigen::MatrixXd>& dP_potential,
192 const std::array<Eigen::MatrixXd, 3>& dipole,
193 const std::array<Eigen::MatrixXd, 3>& dP_field,
194 const std::vector<Eigen::Matrix3d>& alpha_atoms) {
196 if (
Index(W.size()) != n ||
Index(dP_potential.size()) != n ||
197 Index(alpha_atoms.size()) != n || n < 2) {
198 throw std::runtime_error(
199 "ChargeResponseFit::Analyse: inconsistent number of atoms");
202 Eigen::MatrixXd pi(n, n);
203 for (
Index j = 0; j < n; ++j) {
204 for (
Index k = 0; k < n; ++k) {
206 -W[std::size_t(j)].cwiseProduct(dP_potential[std::size_t(k)]).sum();
209 r.
pi_asymmetry = (pi - pi.transpose()).norm() / pi.norm();
210 const Eigen::MatrixXd projector =
211 Eigen::MatrixXd::Identity(n, n) -
212 Eigen::MatrixXd::Constant(n, n, 1.0 /
double(n));
213 r.
pi = projector * (0.5 * (pi + pi.transpose())) * projector;
214 r.
pi = 0.5 * (r.
pi + r.
pi.transpose()).eval();
215 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(r.
pi,
216 Eigen::EigenvaluesOnly);
219 std::stringstream message;
220 message <<
"ChargeResponseFit: Pi is not positive semidefinite (lowest "
223 <<
"); tighten the SCF convergence or change the step";
224 throw std::runtime_error(message.str());
227 for (
Index a = 0; a < 3; ++a) {
228 for (
Index c = 0; c < 3; ++c) {
230 -dipole[std::size_t(a)].cwiseProduct(dP_field[std::size_t(c)]).sum();
235 Eigen::MatrixX3d X(n, 3);
236 for (
Index k = 0; k < n; ++k) {
237 X.row(k) = positions[std::size_t(k)].transpose();
239 X.rowwise() -= X.colwise().mean();
242 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eres(r.
alpha_res,
243 Eigen::EigenvaluesOnly);
244 if (eres.eigenvalues()(0) <= 0.0) {
245 std::stringstream message;
246 message <<
"ChargeResponseFit: alpha - X^T Pi X is not positive definite "
248 << eres.eigenvalues().transpose()
249 <<
" bohr^3); the charge response over-polarizes";
250 throw std::runtime_error(message.str());
255 for (
Index k = 0; k < n; ++k) {
256 r.
res_weights(k) = std::max(alpha_atoms[std::size_t(k)].trace(), 0.0);
259 throw std::runtime_error(
260 "ChargeResponseFit: no positive Hirshfeld atomic polarizability");
263 for (
Index k = 0; k < n; ++k) {
273 Eigen::Vector3d centre = Eigen::Vector3d::Zero();
274 for (
const QMAtom& atom : mol) {
275 centre += atom.getPos();
278 std::vector<Index> map(
static_cast<std::size_t
>(n), -1);
279 for (
Index k = 0; k < n; ++k) {
280 const Eigen::Vector3d image = 2.0 * centre - mol[k].
getPos();
281 for (
Index j = 0; j < n; ++j) {
282 if ((mol[j].getPos() - image).norm() < tol &&
283 mol[j].getElement() == mol[k].getElement()) {
284 map[std::size_t(k)] = j;
288 if (map[std::size_t(k)] < 0) {
293 Eigen::MatrixXd T = Eigen::MatrixXd::Zero(n_ao, n_ao);
294 for (
Index k = 0; k < n; ++k) {
296 const std::vector<const AOShell*> to =
298 if (from.size() != to.size()) {
301 for (std::size_t s = 0; s < from.size(); ++s) {
302 if (from[s]->getL() != to[s]->getL()) {
305 const double parity = (
Index(from[s]->getL()) % 2 == 0) ? 1.0 : -1.0;
306 for (
Index f = 0; f < from[s]->getNumFunc(); ++f) {
307 T(to[s]->getStartIndex() + f, from[s]->getStartIndex() + f) = parity;
320 throw std::runtime_error(
321 "ChargeResponseFit: closed-shell molecules only (spin 1)");
324 throw std::runtime_error(
"ChargeResponseFit: step must be positive");
327 throw std::runtime_error(
"ChargeResponseFit: tolerance must be positive");
329 const double step = fit.
step;
338 auto scf = [&](
const Eigen::MatrixXd& v,
Orbitals& target) {
347 const bool ok = engine.
Evaluate(target);
350 throw std::runtime_error(
"ChargeResponseFit: SCF did not converge");
355 <<
" Charge-response fit: ground state"
357 scf(Eigen::MatrixXd(), orb);
365 bs.
Load(options.
get(
"basisset").
as<std::string>());
370 const std::vector<HirshfeldPartition::AtomicReference> atoms =
372 const std::vector<Eigen::MatrixXd> W =
WeightMatrices(atoms, basis, grid);
376 Eigen::MatrixXd sum = -overlap.
Matrix();
377 for (
const Eigen::MatrixXd& w : W) {
381 <<
TimeStamp() <<
" Hirshfeld weight matrices: max |sum_k W_k - S| = "
382 << sum.cwiseAbs().maxCoeff() << std::flush;
389 auto response = [&](
const Eigen::MatrixXd& h,
const std::string& label) {
390 Eigen::MatrixXd dP = Eigen::MatrixXd::Zero(h.rows(), h.cols());
391 for (
double sign : {1.0, -1.0}) {
393 <<
TimeStamp() <<
" Perturbation " << label
394 << (sign > 0 ?
" +" :
" -") << step << std::flush;
396 scf(sign * step * h, perturbed);
397 dP += sign * perturbed.DensityMatrixGroundState();
399 return Eigen::MatrixXd(dP / (2.0 * step));
407 <<
" Inversion centre: one atom of each pair is perturbed"
412 std::vector<Index> perturbed;
413 for (
Index k = 0; k < n_atoms; ++k) {
415 perturbed.push_back(k);
418 std::vector<Eigen::MatrixXd> dP_potential(
static_cast<std::size_t
>(n_atoms));
419 std::array<Eigen::MatrixXd, 3> dP_field;
421 std::vector<Eigen::MatrixXd> h;
422 for (
Index k : perturbed) {
423 h.push_back(W[std::size_t(k)]);
425 for (
Index c = 0; c < 3; ++c) {
426 h.push_back(dipole.
Matrix()[std::size_t(c)]);
429 << h.size() <<
" perturbations" << std::flush;
433 const std::vector<Eigen::MatrixXd> dP =
435 for (std::size_t j = 0; j < perturbed.size(); ++j) {
436 dP_potential[std::size_t(perturbed[j])] = dP[j];
438 for (std::size_t c = 0; c < 3; ++c) {
439 dP_field[c] = dP[perturbed.size() + c];
442 for (
Index k : perturbed) {
443 dP_potential[std::size_t(k)] =
444 response(W[std::size_t(k)],
445 "w_" + std::to_string(k) +
" (" + mol[k].getElement() +
")");
447 const std::array<std::string, 3> axes = {
"x",
"y",
"z"};
448 for (
Index c = 0; c < 3; ++c) {
449 dP_field[std::size_t(c)] = response(dipole.
Matrix()[std::size_t(c)],
450 "field " + axes[std::size_t(c)]);
453 for (
Index k = 0; k < n_atoms; ++k) {
455 dP_potential[std::size_t(k)] =
456 inv.
T * dP_potential[std::size_t(inv.
atom_map[std::size_t(k)])] *
461 std::vector<Eigen::Vector3d> positions;
462 for (
const QMAtom& atom : mol) {
463 positions.push_back(atom.getPos());
465 const std::vector<Eigen::Matrix3d> alpha_atoms =
467 return Analyse(positions, W, dP_potential, dipole.
Matrix(), dP_field,
Container to hold Basisfunctions for all atoms.
const std::vector< const AOShell * > getShellsofAtom(Index AtomId) const
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 Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
const Eigen::Vector3d & getPos() const
void Load(const std::string &name)
static Inversion FindInversion(const QMMolecule &mol, const AOBasis &basis, double tol=1e-4)
static std::vector< Eigen::Matrix3d > AtomicPolarizabilities(const std::vector< HirshfeldPartition::AtomicReference > &atoms, const std::vector< Eigen::Vector3d > &positions, const AOBasis &basis, const Vxc_Grid &grid, const std::array< Eigen::MatrixXd, 3 > &dP_field)
static Result Analyse(const std::vector< Eigen::Vector3d > &positions, const std::vector< Eigen::MatrixXd > &W, const std::vector< Eigen::MatrixXd > &dP_potential, const std::array< Eigen::MatrixXd, 3 > &dipole, const std::array< Eigen::MatrixXd, 3 > &dP_field, const std::vector< Eigen::Matrix3d > &alpha_atoms)
static std::vector< Eigen::MatrixXd > WeightMatrices(const std::vector< HirshfeldPartition::AtomicReference > &atoms, const AOBasis &basis, const Vxc_Grid &grid)
static Result Fit(const tools::Property &dftoptions, Orbitals &orb, const FitOptions &fit, Logger &log)
Electronic ground-state via Density-Functional Theory.
void setWarmStart(bool warm_start)
void setLogger(Logger *pLog)
Attach the logger used for SCF progress and diagnostics.
std::vector< HirshfeldPartition::AtomicReference > HirshfeldReferences(const QMMolecule &mol) const
void setExternalPotentialMatrix(const Eigen::MatrixXd &v)
void Initialize(tools::Property &options)
Read DFT, grid, and SCF settings from the user options tree.
const std::string & getGridName() const
Name of the integration grid of the SCF (xtpdft.integration_grid).
std::vector< Eigen::MatrixXd > StaticResponse(const Orbitals &orb, const std::vector< Eigen::MatrixXd > &perturbations, double tolerance=1e-7, Index max_iterations=100)
bool Evaluate(Orbitals &orb)
void setSetupCache(DFTSetupCache *cache)
const std::vector< Eigen::Vector3d > & getGridPoints() const
Eigen::MatrixXd ReadFromBigMatrix(const Eigen::MatrixXd &bigmatrix) const
void AddtoBigMatrix(Eigen::MatrixXd &bigmatrix, const Eigen::MatrixXd &smallmatrix) const
const std::vector< double > & getGridWeights() const
AOShell::AOValues CalcAOValues(const Eigen::Vector3d &point) const
static double EvaluateAtomicDensity(const AOBasis &atom_basis, const Eigen::MatrixXd &reference_density, const Eigen::Vector3d &point)
Logger is used for thread-safe output of messages.
Container for molecular orbitals and derived one-particle data.
Index getSpin() const
Return the stored spin multiplicity.
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
Timestamp returns the current time as a string Example: cout << TimeStamp().
void GridSetup(const std::string &type, const QMMolecule &atoms, const AOBasis &basis)
Index getBoxesSize() const
#define XTP_LOG(level, log)
Charge transport classes.
Provides a means for comparing floating point numbers.
double step
finite differences: strength of each perturbation
double tolerance
relative CPKS residual
std::vector< Index > atom_map
std::vector< Eigen::Matrix3d > alpha_res_atoms
Eigen::Matrix3d alpha_res
std::vector< Eigen::Matrix3d > alpha_atoms
Eigen::VectorXd res_weights
Eigen::VectorXd pi_eigenvalues
Parts of the DFT setup that stay the same between runs on the same molecule with the same basis sets,...