36bool loadColumnCheckpoint(
const std::string &path,
int size,
int &nextCol,
38 std::ifstream in(path);
44 in >> tag >> fileSize >> nextCol;
45 if (!in || tag !=
"eon_hess_ckpt" || fileSize != size || nextCol < 0 ||
50 for (
int i = 0; i < size; ++i) {
51 for (
int j = 0; j < size; ++j) {
63bool saveColumnCheckpoint(
const std::string &path,
int size,
int nextCol,
65 std::ofstream out(path);
69 out <<
"eon_hess_ckpt " << size <<
" " << nextCol <<
"\n";
71 for (
int i = 0; i < size; ++i) {
72 for (
int j = 0; j < size; ++j) {
73 out << H(i, j) << (j + 1 == size ?
'\n' :
' ');
76 return static_cast<bool>(out);
89 if ((
matter != matterIn) || (
atoms.size() != atomsIn.size()) ||
103 if ((
matter != matterIn) || (
atoms.size() != atomsIn.size()) ||
119struct MobileColoring {
120 std::vector<int> color;
122 std::vector<std::vector<int>> closed;
125MobileColoring buildMobileColoring(
const Matter &matter,
const VectorXi &atoms,
128 const int nAtoms =
static_cast<int>(matter.numberOfAtoms());
129 const int nMobile =
static_cast<int>(atoms.rows());
130 if (nAtoms <= 0 || nMobile <= 0 || !(cutoff > 0.0) ||
131 !std::isfinite(cutoff)) {
134 const Matrix3d cell = matter.getCell();
135 if (!(std::abs(cell.determinant()) > 1e-18)) {
138 for (
int a = 0; a < nMobile; ++a) {
139 const long idx = atoms(a);
140 if (idx < 0 || idx >= nAtoms) {
145 const AtomMatrix &pos = matter.getPositions();
150 opt.return_distances =
false;
151 opt.return_vectors =
false;
152 opt.periodic = {matter.getPeriodic(), matter.getPeriodic(),
153 matter.getPeriodic()};
155 nl.compute(pos.data(),
static_cast<std::size_t
>(nAtoms), cell.data(), opt);
156 }
catch (
const std::exception &) {
160 std::vector<std::vector<int>> nbs(
static_cast<std::size_t
>(nAtoms));
161 for (std::size_t p = 0; p < nl.size(); ++p) {
162 const int i =
static_cast<int>(nl.i(p));
163 const int j =
static_cast<int>(nl.j(p));
164 if (i == j || i < 0 || j < 0 || i >= nAtoms || j >= nAtoms) {
167 nbs[
static_cast<std::size_t
>(i)].push_back(j);
169 for (
auto &row : nbs) {
170 std::sort(row.begin(), row.end());
171 row.erase(std::unique(row.begin(), row.end()), row.end());
174 std::vector<int> local(
static_cast<std::size_t
>(nAtoms), -1);
175 for (
int a = 0; a < nMobile; ++a) {
176 local[
static_cast<std::size_t
>(atoms(a))] = a;
179 out.closed.assign(
static_cast<std::size_t
>(nMobile), {});
180 for (
int ia = 0; ia < nMobile; ++ia) {
181 auto &nbhd = out.closed[
static_cast<std::size_t
>(ia)];
183 const int g =
static_cast<int>(atoms(ia));
184 for (
int nb : nbs[
static_cast<std::size_t
>(g)]) {
185 const int lb = local[
static_cast<std::size_t
>(nb)];
190 std::sort(nbhd.begin(), nbhd.end());
191 nbhd.erase(std::unique(nbhd.begin(), nbhd.end()), nbhd.end());
196 std::vector<std::vector<int>> touchers(
static_cast<std::size_t
>(nMobile));
197 for (
int ia = 0; ia < nMobile; ++ia) {
198 for (
int k : out.closed[
static_cast<std::size_t
>(ia)]) {
199 touchers[
static_cast<std::size_t
>(k)].push_back(ia);
202 std::vector<std::vector<int>> adj(
static_cast<std::size_t
>(nMobile));
203 for (
int k = 0; k < nMobile; ++k) {
204 const auto &t = touchers[
static_cast<std::size_t
>(k)];
205 for (
size_t a = 0; a < t.size(); ++a) {
206 for (
size_t b = a + 1; b < t.size(); ++b) {
207 adj[
static_cast<std::size_t
>(t[a])].push_back(t[b]);
208 adj[
static_cast<std::size_t
>(t[b])].push_back(t[a]);
212 for (
auto &row : adj) {
213 std::sort(row.begin(), row.end());
214 row.erase(std::unique(row.begin(), row.end()), row.end());
221 const VectorXi &atoms,
int col,
int atomI,
223 double denom,
const std::vector<int> &owner,
int ia) {
224 const int size =
static_cast<int>(atoms.rows()) * 3;
225 const double massI = matter.getMass(atomI);
226 for (
int j = 0; j < size; ++j) {
227 const long atomJ = atoms(j / 3);
229 if (owner[
static_cast<std::size_t
>(j / 3)] == ia) {
230 dF = forceA(atomJ, j % 3) - forceB(atomJ, j % 3);
232 hessian(col, j) = -dF / denom;
233 const double effMass = std::sqrt(matter.getMass(atomJ) * massI);
242 const int n =
static_cast<int>(adj.size());
243 std::vector<int> color(
static_cast<std::size_t
>(n), -1);
244 std::vector<int> used(
static_cast<std::size_t
>(std::max(n, 0)), 0);
246 for (
int v = 0; v < n; ++v) {
248 for (
int u : adj[
static_cast<std::size_t
>(v)]) {
249 if (u < 0 || u >= n || u == v) {
252 const int cu = color[
static_cast<std::size_t
>(u)];
253 if (cu >= 0 && cu < n) {
254 used[
static_cast<std::size_t
>(cu)] = epoch;
258 while (c < n && used[
static_cast<std::size_t
>(c)] == epoch) {
261 color[
static_cast<std::size_t
>(v)] = c;
267 const VectorXi &atoms,
double cutoff) {
268 return buildMobileColoring(matter, atoms, cutoff).color;
272 int nAtoms =
matter->numberOfAtoms();
274 int size =
static_cast<int>(
atoms.rows()) * 3;
275 QUILL_LOG_DEBUG(
log,
"[Hessian] Hessian size: {}\n", size);
281 for (
int a = 0; a <
atoms.rows(); ++a) {
282 const long idx =
atoms(a);
283 if (idx < 0 || idx >= nAtoms) {
285 "[Hessian] atom index {} out of range [0, {}) at list "
286 "entry {}; aborting FD Hessian",
292 double dr =
parameters.main_options().finiteDifference;
293 if (!(dr > 0.0) || !std::isfinite(dr)) {
294 QUILL_LOG_ERROR(
log,
"[Hessian] invalid finiteDifference dr={}\n", dr);
299 const std::string &ckptPath =
parameters.hessian_options().checkpoint_path;
307 bool anyFixed =
false;
308 for (
int i = 0; i < nAtoms; ++i) {
309 if (
matter->getFixed(i)) {
314 const bool netCoupled =
315 parameters.main_options().removeNetForce && nAtoms > 1 && !anyFixed;
317 if (
matter->getPotential()) {
318 cutoff =
matter->getPotential()->finiteCutoff();
320 if (!netCoupled && ckptPath.empty() && cutoff > 0.0 &&
321 std::isfinite(cutoff)) {
330 if (ckptPath.empty() &&
matter->getPotential() &&
331 matter->getPotential()->supportsBatchEvaluation() && size > 1) {
338 const int size =
static_cast<int>(
atoms.rows()) * 3;
344 if (!force0.allFinite()) {
345 QUILL_LOG_ERROR(
log,
"[Hessian] non-finite forces at undisplaced geometry; "
346 "aborting FD Hessian");
351 std::vector<double> steps{1.0};
353 steps.push_back(-1.0);
356 steps.push_back(2.0);
357 steps.push_back(-2.0);
359 const int perColumn =
static_cast<int>(steps.size());
360 const long nAtoms =
matter->numberOfAtoms();
361 const VectorXi nrs =
matter->getAtomicNrs();
363 matter->getPeriodic() ?
matter->getCell() : Matrix3d::Zero().eval();
366 constexpr int kChunkColumns = 32;
367 std::vector<Matter> displaced;
368 for (
int c0 = 0; c0 < size; c0 += kChunkColumns) {
369 const int c1 = std::min(size, c0 + kChunkColumns);
370 const long n =
static_cast<long>(c1 - c0) * perColumn;
371 displaced.assign(
static_cast<size_t>(n), base);
372 std::vector<const double *> posPtr, boxPtr;
373 std::vector<const int *> nrsPtr;
374 std::vector<double *> frcPtr;
375 for (
int i = c0; i < c1; ++i) {
376 for (
int k = 0; k < perColumn; ++k) {
377 Matter &m = displaced[
static_cast<size_t>((i - c0) * perColumn + k)];
379 p(
atoms(i / 3), i % 3) += steps[
static_cast<size_t>(k)] * dr;
383 for (
auto &m : displaced) {
384 posPtr.push_back(m.getPositions().data());
385 nrsPtr.push_back(nrs.data());
386 frcPtr.push_back(m.forcesData());
387 boxPtr.push_back(box.data());
389 std::vector<double> energies(
static_cast<size_t>(n)),
390 variances(
static_cast<size_t>(n));
391 pot->forceBatch(n, nAtoms, posPtr.data(), nrsPtr.data(), frcPtr.data(),
392 energies.data(), variances.data(), boxPtr.data());
393 for (
long j = 0; j < n; ++j) {
394 displaced[
static_cast<size_t>(j)].setComputedPotential(
395 energies[
static_cast<size_t>(j)], variances[
static_cast<size_t>(j)]);
397 for (
int i = c0; i < c1; ++i) {
398 auto forces = [&](
int k) ->
const AtomMatrix & {
399 return displaced[
static_cast<size_t>((i - c0) * perColumn + k)]
403 const AtomMatrix &fMinus = perColumn > 1 ? forces(1) : force0;
404 const AtomMatrix &fPlus2 = perColumn > 2 ? forces(2) : force0;
405 const AtomMatrix &fMinus2 = perColumn > 3 ? forces(3) : force0;
406 if (!fPlus.allFinite() || !fMinus.allFinite() || !fPlus2.allFinite() ||
407 !fMinus2.allFinite()) {
409 "[Hessian] non-finite forces for FD column {}; "
410 "aborting FD Hessian",
416 for (
int j = 0; j < size; j++) {
417 const double effMass = std::sqrt(
matter->getMass(
atoms(j / 3)) *
428 const int nAtoms =
static_cast<int>(
matter->numberOfAtoms());
429 const int nMobile =
static_cast<int>(
atoms.rows());
430 const int size = nMobile * 3;
431 const MobileColoring coloring = buildMobileColoring(*
matter,
atoms, cutoff);
432 if (
static_cast<int>(coloring.color.size()) != nMobile ||
433 static_cast<int>(coloring.closed.size()) != nMobile) {
438 for (
int c : coloring.color) {
442 nColors = std::max(nColors, c + 1);
448 "[Hessian] cutoff coloring: {} colors for {} mobile atoms\n",
451 std::vector<std::vector<int>> members(
static_cast<std::size_t
>(nColors));
452 for (
int ia = 0; ia < nMobile; ++ia) {
453 members[
static_cast<std::size_t
>(
454 coloring.color[
static_cast<std::size_t
>(ia)])]
457 std::vector<std::vector<int>> owner(
458 static_cast<std::size_t
>(nColors),
459 std::vector<int>(
static_cast<std::size_t
>(nMobile), -1));
460 for (
int c = 0; c < nColors; ++c) {
461 for (
int ia : members[
static_cast<std::size_t
>(c)]) {
462 for (
int k : coloring.closed[
static_cast<std::size_t
>(ia)]) {
464 owner[
static_cast<std::size_t
>(c)][
static_cast<std::size_t
>(k)];
465 if (slot >= 0 && slot != ia) {
477 if (!force0.allFinite()) {
478 QUILL_LOG_ERROR(
log,
"[Hessian] non-finite forces at undisplaced geometry; "
479 "aborting FD Hessian");
483 for (
int dir = 0; dir < 3; ++dir) {
484 for (
int c = 0; c < nColors; ++c) {
485 const auto &group = members[
static_cast<std::size_t
>(c)];
489 auto shifted = [&](
double scale,
const char *side) ->
AtomMatrix {
490 posDisplace.setZero();
491 for (
int ia : group) {
492 posDisplace(
atoms(ia), dir) = scale * dr;
496 if (!force.allFinite()) {
498 "[Hessian] non-finite forces for color {} dir {} "
499 "({}); aborting FD Hessian",
505 const AtomMatrix forcePlus = shifted(1.0,
"+");
506 if (forcePlus.size() == 0) {
513 forceMinus = shifted(-1.0,
"-");
514 if (forceMinus.size() == 0) {
519 forcePlus2 = shifted(2.0,
"+2");
520 if (forcePlus2.size() == 0) {
523 forceMinus2 = shifted(-2.0,
"-2");
524 if (forceMinus2.size() == 0) {
529 scheme, dr, force0, forcePlus, forceMinus, forcePlus2, forceMinus2);
530 const AtomMatrix zero = AtomMatrix::Zero(nAtoms, 3);
531 for (
int ia : group) {
532 const int col = ia * 3 + dir;
534 static_cast<int>(
atoms(ia)), slope, zero, 1.0,
535 owner[
static_cast<std::size_t
>(c)], ia);
543 const int nAtoms =
static_cast<int>(
matter->numberOfAtoms());
544 const int size =
static_cast<int>(
atoms.rows()) * 3;
545 const std::string &ckptPath =
parameters.hessian_options().checkpoint_path;
546 const bool wantResume =
547 parameters.hessian_options().resume && !ckptPath.empty();
556 if (wantResume && loadColumnCheckpoint(ckptPath, size, startCol,
hessian)) {
557 QUILL_LOG_DEBUG(
log,
"[Hessian] resume from column {} / {}\n", startCol,
566 if (!force0.allFinite()) {
567 QUILL_LOG_ERROR(
log,
"[Hessian] non-finite forces at undisplaced geometry; "
568 "aborting FD Hessian");
572 for (
int i = startCol; i < size; i++) {
573 posDisplace.setZero();
574 posDisplace(
atoms(i / 3), i % 3) = dr;
576 posTemp = pos + posDisplace;
579 if (!forcePlus.allFinite()) {
581 "[Hessian] non-finite forces for FD column {} (+); "
582 "aborting FD Hessian",
588 posTemp = pos - posDisplace;
591 if (!forceMinus.allFinite()) {
593 "[Hessian] non-finite forces for FD column {} (-); "
594 "aborting FD Hessian",
602 posDisplace(
atoms(i / 3), i % 3) = 2.0 * dr;
605 if (!forcePlus2.allFinite()) {
607 "[Hessian] non-finite forces for FD column {} (+2); "
608 "aborting FD Hessian",
614 if (!forceMinus2.allFinite()) {
616 "[Hessian] non-finite forces for FD column {} (-2); "
617 "aborting FD Hessian",
624 scheme, dr, force0, forcePlus, forceMinus, forcePlus2, forceMinus2);
625 for (
int j = 0; j < size; j++) {
627 const double effMass = std::sqrt(
matter->getMass(
atoms(j / 3)) *
632 if (!ckptPath.empty()) {
634 saveColumnCheckpoint(ckptPath, size, i + 1,
hessian);
641 const std::string &ckptPath =
parameters.hessian_options().checkpoint_path;
644 for (
int i = 0; i < size; i++) {
645 for (
int j = 0; j < i; j++) {
652 QUILL_LOG_ERROR(
log,
"[Hessian] non-finite entries after FD assembly; "
653 "aborting eigen solve");
658 QUILL_LOG_DEBUG(
log,
"[Hessian] writing hessian\n");
663 std::ofstream hessfile(
"hessian.dat", std::ios::out | std::ios::trunc);
665 std::remove(
"hessian.dat");
667 hessfile.open(
"hessian.dat", std::ios::out | std::ios::trunc);
670 QUILL_LOG_ERROR(
log,
"[Hessian] failed to open hessian.dat");
676 QUILL_LOG_ERROR(
log,
"[Hessian] failed to write hessian.dat");
682 if (!ckptPath.empty()) {
683 std::remove(ckptPath.c_str());
688 QUILL_LOG_DEBUG(
log,
"[Hessian] calculating eigen values of the hessian\n");
691 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
692 ColMajorXd hessianCol =
hessian;
693 const bool withModes =
parameters.hessian_options().write_modes;
694 Eigen::SelfAdjointEigenSolver<ColMajorXd> es(
696 withModes ? Eigen::ComputeEigenvectors : Eigen::EigenvaluesOnly);
698 QUILL_LOG_DEBUG(
log,
"[Hessian] eigenvalue problem took {:.4e} seconds\n",
700 if (es.info() != Eigen::Success) {
702 "[Hessian] SelfAdjointEigenSolver failed (info={}); "
704 static_cast<int>(es.info()));
707 freqs = es.eigenvalues();
708 if (!
freqs.allFinite()) {
709 QUILL_LOG_ERROR(
log,
"[Hessian] non-finite eigenvalues; aborting");
713 modes = es.eigenvectors();
722 QUILL_LOG_DEBUG(
log,
"[Hessian] removing zero frequency modes");
723 int size =
freqs.size();
724 if (size != 3 *
matter->numberOfAtoms()) {
728 newfreqs.resize(size);
730 for (
int i = 0; i < size; i++) {
731 if (std::abs(
freqs(i)) >
parameters.hessian_options().zero_freq_value) {
732 newfreqs(i - nremoved) =
freqs(i);
739 QUILL_LOG_WARNING(
log,
740 "[Hessian] found {} trivial eigenmodes; a free cluster "
741 "has 6 (5 if linear), a periodic cell 3, and a "
742 "structure with fixed atoms none",
745 return newfreqs.head(size - nremoved);
749 const Eigen::Ref<const VectorXd> &mode) {
751 std::vector<double> out(
static_cast<size_t>(3 * n), 0.0);
752 if (mode.size() != 3 * atoms.size()) {
753 throw std::invalid_argument(
"cartesianMode: mode length is not 3 x atoms");
756 for (
long j = 0; j < mode.size(); ++j) {
757 const long atom = atoms(j / 3);
758 const double mass = matter.
getMass(atom);
760 throw std::invalid_argument(
"cartesianMode: atom without a mass");
762 const double x = mode(j) / std::sqrt(mass);
763 out[
static_cast<size_t>(3 * atom + j % 3)] = x;
767 const double scale = 1.0 / std::sqrt(norm2);
768 for (
double &x : out) {
776 const VectorXd &eigenvalues,
const MatrixXd &modes,
777 const std::string &path) {
778 if (modes.cols() != eigenvalues.size() || modes.rows() != 3 * atoms.size()) {
781 for (
long k = 0; k < eigenvalues.size(); ++k) {
782 const double lambda = eigenvalues(k);
787 meta.
scalars = {{
"mode_eigenvalue", lambda},
799 if (fixedAtoms > 0) {
802 return removed == 3 || removed == 5 || removed == 6;
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Hessian(const Parameters ¶ms, Matter *matter)
MatrixXd getHessian(Matter *matterIn, const VectorXi &atomsIn)
bool calculateSerial(double dr, FdScheme scheme)
VectorXd removeZeroFreqs(const VectorXd &freqs)
bool calculateBatched(double dr, FdScheme scheme)
const Parameters & parameters
bool finalizeHessian(int size)
bool calculateColored(double cutoff, double dr, FdScheme scheme)
VectorXd getFreqs(Matter *matterIn, const VectorXi &atomsIn)
void setPositions(const AtomMatrix &pos)
long int numberOfAtoms() const
double getMass(long int atom) const
const AtomMatrix & getForces() const
io::IoStatus matter2con(std::string filename, bool append=false, const io::ConFrameMetadata *metadata=nullptr)
RAII wrapper around vesin_neighbors for Matter-style boxes (double[9] row-major 3×3 cell,...
void getTime(double *real, double *user, double *sys)
constexpr bool io_ok(IoStatus s) noexcept
constexpr double safe_div(double num, double denom, double fallback=0.0)
constexpr double kHbar
hbar in eV^0.5 amu^0.5 Angstrom, correctly rounded from the exact SI values (h = 6....
RAII resource manager for the ARTn C library with global synchronization.
FdScheme
Real finite-difference scheme for the assembled Hessian and for Lanczos/Davidson Hessian-vector produ...
M fdForceDerivative(FdScheme scheme, double dr, const M &f0, const M &fPlus, const M &fMinus, const M &fPlus2, const M &fMinus2)
Derivative of a sampled force map along one real step of length dr.
constexpr double kEvToWavenumber
1 eV in cm^-1, e / (h c) from the exact SI values.
FdScheme parseFdScheme(std::string_view scheme)
bool writeNormalModes(Matter &matter, const VectorXi &atoms, const VectorXd &eigenvalues, const MatrixXd &modes, const std::string &path)
Writes one frame of matter per mode to path, the mode as the displacements section and mode_eigenvalu...
bool trivialModeCountIsPhysical(long removed, long fixedAtoms)
Whether removed zero-frequency modes are what the structure's symmetries give: 6 for a free cluster (...
std::vector< int > greedyColorCutoffGraph(const std::vector< std::vector< int > > &adj)
Greedy coloring of an undirected graph.
std::vector< int > colorMobileCutoffGraph(const Matter &matter, const VectorXi &atoms, double cutoff)
Conflict graph on atoms: two mobile atoms share an edge when their closed cutoff neighborhoods inters...
std::vector< double > cartesianMode(const Matter &matter, const VectorXi &atoms, const Eigen::Ref< const VectorXd > &mode)
Cartesian displacement of every atom along one mass-weighted mode over the mobile degrees of freedom ...