Loading...
Searching...
No Matches
Matter.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12#include "eon/Matter.h"
13#include "eon/BaseStructures.h"
14#include "eon/BondBoost.h"
15#include "eon/ForceNorm.h"
17#include "eon/HelperFunctions.h"
18#include "eon/Parameters.h"
20
21#include "eon/EonLogger.h"
22#include <cmath>
23#include <memory>
24#include <span>
25#include <stdexcept>
26#include <string>
27
28namespace eonc {
29
31 AtomMatrix positions{AtomMatrix::Zero(0, 3)};
32 AtomMatrix velocities{AtomMatrix::Zero(0, 3)};
33 mutable AtomMatrix forces{AtomMatrix::Zero(0, 3)};
34 AtomMatrix biasForces{AtomMatrix::Zero(0, 3)};
35 VectorXd masses{VectorXd::Zero(0)};
36 VectorXi atomicNrs{VectorXi::Zero(0)};
37 // Nx3; 1.0 if that axis is fixed, 0.0 if free.
38 AtomMatrix isFixed{AtomMatrix::Zero(0, 3)};
39 // Original atom index from .con column 5.
40 Eigen::Matrix<std::int64_t, Eigen::Dynamic, 1> atomIndex;
41 mutable AtomMatrix freeMask; // Nx3; 1.0 free, 0.0 fixed
42 mutable AtomMatrix maskedForces; // forces with fixed atoms zeroed
43 Matrix3d cell{Matrix3d::Zero()};
44 Matrix3d cellInverse{Matrix3d::Zero()};
45};
46
47Matter::~Matter() = default;
48
49Matter::Matter(std::shared_ptr<Potential> pot, const Parameters &params)
50 : potential{pot},
51 usePeriodicBoundaries{!(pot && pot->requiresIsolatedMoleculeLayout())},
54 forceCalls{0},
55 removeNetForce{params.main_options().removeNetForce},
56 structComp{params.structure_comparison_options()},
57 parameters{&params},
58 nAtoms{0},
59 impl_{std::make_unique<Impl>()},
60 biasPotential{nullptr},
61 energyVariance{0.0},
62 potentialEnergy{0.0} {}
63
64bool Matter::getWriteConForces() const noexcept {
65 return parameters != nullptr && parameters->main_options().writeConForces;
66}
67
68namespace {
69void checkAtom(long nAtoms, long indexAtom, const char *fn) {
70 if (indexAtom < 0 || indexAtom >= nAtoms) {
71 throw std::out_of_range(std::string(fn) + ": atom index out of range");
72 }
73}
74void checkAxis(int axis, const char *fn) {
75 if (axis < 0 || axis > 2) {
76 throw std::out_of_range(std::string(fn) + ": axis out of range");
77 }
78}
79} // namespace
80
81Matter::Matter(const Matter &matter)
82 : impl_{std::make_unique<Impl>()} {
83 operator=(matter);
84}
85
86const Matter &Matter::operator=(const Matter &matter) {
87 if (this == &matter) {
88 return *this;
89 }
90 nAtoms = matter.nAtoms;
92
93 impl_->positions = matter.impl_->positions;
94 impl_->forces = matter.impl_->forces;
95 impl_->masses = matter.impl_->masses;
96 impl_->atomicNrs = matter.impl_->atomicNrs;
97 impl_->isFixed = matter.impl_->isFixed;
98 impl_->atomIndex = matter.impl_->atomIndex;
100 impl_->cell = matter.impl_->cell;
101 impl_->cellInverse = matter.impl_->cellInverse;
102 impl_->velocities = matter.impl_->velocities;
103
105 structComp = matter.structComp;
106 parameters = matter.parameters;
107
110
111 potential = matter.potential;
114 forceCalls = matter.forceCalls;
116 // Both caches describe the forces this object held before the assignment.
117 // resize() above already raises them; state it here alongside the members
118 // this function owns.
119 recomputeFreeMask = true;
121
122 // A BondBoost binds to one Matter (BondBoost.h:32 takes a Matter *), so a
123 // copy cannot share the source's: boosting through it would drive the
124 // original. The copy starts without one and re-establishes it through
125 // setBiasPotential. biasForces is zeroed by the resize above, which is the
126 // state that matches having no bias potential.
127 biasPotential = nullptr;
128
129 headerCon = matter.headerCon;
130 // ConFrame is move-only; copy does not retain movie trajectory.
131 movie_frames_.clear();
132
133 return *this;
134}
135
136Matter::Matter(Matter &&other) noexcept
137 : impl_{std::make_unique<Impl>()} {
138 operator=(std::move(other));
139}
140
141Matter &Matter::operator=(Matter &&other) noexcept {
142 if (this == &other) {
143 return *this;
144 }
145 potential = std::move(other.potential);
146 usePeriodicBoundaries = other.usePeriodicBoundaries;
147 pbcConvention = other.pbcConvention;
148 recomputePotential = other.recomputePotential;
149 forceCalls = other.forceCalls;
150 headerCon = std::move(other.headerCon);
151 removeNetForce = other.removeNetForce;
152 structComp = other.structComp;
153 parameters = other.parameters;
154 nAtoms = other.nAtoms;
155 impl_->positions = std::move(other.impl_->positions);
156 impl_->velocities = std::move(other.impl_->velocities);
157 impl_->forces = std::move(other.impl_->forces);
158 impl_->biasForces = std::move(other.impl_->biasForces);
159 biasPotential = other.biasPotential;
160 other.biasPotential = nullptr;
161 impl_->masses = std::move(other.impl_->masses);
162 impl_->atomicNrs = std::move(other.impl_->atomicNrs);
163 impl_->isFixed = std::move(other.impl_->isFixed);
164 impl_->atomIndex = std::move(other.impl_->atomIndex);
165 fileToMatter = std::move(other.fileToMatter);
166 impl_->freeMask = std::move(other.impl_->freeMask);
167 impl_->maskedForces = std::move(other.impl_->maskedForces);
168 freeIndices = std::move(other.freeIndices);
169 recomputeFreeMask = other.recomputeFreeMask;
170 recomputeMaskedForces = other.recomputeMaskedForces;
171 impl_->cell = std::move(other.impl_->cell);
172 impl_->cellInverse = std::move(other.impl_->cellInverse);
173 energyVariance = other.energyVariance;
174 movie_frames_ = std::move(other.movie_frames_);
175 potentialEnergy = other.potentialEnergy;
176
177 other.nAtoms = 0;
178 other.recomputePotential = true;
179 other.recomputeFreeMask = true;
180 other.recomputeMaskedForces = true;
181 return *this;
182}
183
184bool Matter::compare(const Matter &matter, bool indistinguishable) {
185 if (nAtoms != matter.numberOfAtoms())
186 return false;
187 if (structComp.check_rotation && indistinguishable) {
188 return eonc::geometry::sortedR(*this, matter,
189 structComp.distance_difference);
190 } else if (indistinguishable) {
191 if (this->numberOfFixedAtoms() == 0 and structComp.remove_translation)
193 return eonc::geometry::identical(*this, matter,
194 structComp.distance_difference);
195 } else if (structComp.check_rotation) {
196 return eonc::geometry::rotationMatch(*this, matter,
197 structComp.distance_difference);
198 } else {
199 if (this->numberOfFixedAtoms() == 0 and structComp.remove_translation)
201 return (structComp.distance_difference) > perAtomNorm(matter);
202 }
203}
204
205// Returns the distance to the given matter object.
206double Matter::distanceTo(const Matter &matter) {
207 if (matter.numberOfAtoms() != nAtoms) {
208 throw std::invalid_argument("Matter::distanceTo: size mismatch");
209 }
210 return pbc(impl_->positions - matter.impl_->positions).norm();
211}
212
213// Returns the maximum distance between two atoms in the Matter objects.
214double Matter::perAtomNorm(const Matter &matter) {
215 long i = 0;
216 double max_distance = 0.0;
217
218 if (matter.numberOfAtoms() == nAtoms) {
219 AtomMatrix diff = pbc(impl_->positions - matter.impl_->positions);
220 for (i = 0; i < nAtoms; i++) {
221 max_distance = std::max(diff.row(i).norm(), max_distance);
222 }
223 }
224 return max_distance;
225}
226
227void Matter::resize(const long int length) {
228 if (length < 0) {
229 throw std::invalid_argument("Matter::resize: negative atom count");
230 }
231 // Same-N resize still zeros coordinates. Keep .con column-5 ids and
232 // the file-order map so a later matter2con does not stamp 1..N.
233 const bool keepAtomIds =
234 (length == nAtoms && impl_->atomIndex.size() == length &&
235 fileToMatter.size() == static_cast<size_t>(length));
236 // Zero is a real size: leaving nAtoms at the old value there sends
237 // setMasses and every other nAtoms loop off the end of an empty array.
238 nAtoms = length;
239 impl_->positions.resize(length, 3);
240 impl_->positions.setZero();
241
242 impl_->velocities.resize(length, 3);
243 impl_->velocities.setZero();
244
245 impl_->biasForces.resize(length, 3);
246 impl_->biasForces.setZero();
247
248 impl_->forces.resize(length, 3);
249 impl_->forces.setZero();
250
251 impl_->masses.resize(length);
252 impl_->masses.setZero();
253
254 impl_->atomicNrs.resize(length);
255 impl_->atomicNrs.setZero();
256
257 impl_->isFixed.resize(length, 3);
258 impl_->isFixed.setZero();
259
260 if (!keepAtomIds) {
261 impl_->atomIndex.resize(length);
262 fileToMatter.resize(static_cast<size_t>(length));
263 for (long i = 0; i < length; i++) {
264 impl_->atomIndex(i) = static_cast<std::int64_t>(i); // default: sequential
265 fileToMatter[static_cast<size_t>(i)] = i;
266 }
267 }
268 recomputePotential = true;
270 recomputeFreeMask = true;
271}
272
273long int Matter::numberOfAtoms() const { return (nAtoms); }
274
275Matrix3d Matter::getCell() const { return impl_->cell; }
276
277void Matter::setCell(const Matrix3d &newCell) {
278 impl_->cell = newCell;
279 impl_->cellInverse = impl_->cell.inverse();
280 recomputePotential = true;
282}
283
284double Matter::getPosition(long int indexAtom, int axis) const {
285 checkAtom(nAtoms, indexAtom, "Matter::getPosition");
286 checkAxis(axis, "Matter::getPosition");
287 return impl_->positions(indexAtom, axis);
288}
289
290void Matter::setPosition(long int indexAtom, int axis, double position) {
291 checkAtom(nAtoms, indexAtom, "Matter::setPosition");
292 checkAxis(axis, "Matter::setPosition");
293 impl_->positions(indexAtom, axis) = position;
296 }
297 recomputePotential = true;
299}
300
301void Matter::setVelocity(long int indexAtom, int axis, double vel) {
302 checkAtom(nAtoms, indexAtom, "Matter::setVelocity");
303 checkAxis(axis, "Matter::setVelocity");
304 impl_->velocities(indexAtom, axis) = vel;
305}
306
307// return coordinates of atoms by const reference (zero-copy)
308const AtomMatrix &Matter::getPositions() const { return impl_->positions; }
309// return a modifiable copy of positions
310AtomMatrix Matter::getPositionsCopy() const { return impl_->positions; }
311
312VectorXd Matter::getPositionsV() const {
313 return VectorXd::Map(impl_->positions.data(), 3 * numberOfAtoms());
314}
315
317 getFree(); // ensure freeIndices is up to date
318 AtomMatrix ret(static_cast<long>(freeIndices.size()), 3);
319 for (size_t j = 0; j < freeIndices.size(); j++) {
320 ret.row(static_cast<long>(j)) = impl_->positions.row(freeIndices[j]);
321 }
322 return ret;
323}
324
325VectorXi Matter::getAtomicNrsFree() const {
326 getFree();
327 VectorXi ret(static_cast<Eigen::Index>(freeIndices.size()));
328 for (size_t j = 0; j < freeIndices.size(); j++) {
329 ret[static_cast<Eigen::Index>(j)] = impl_->atomicNrs[freeIndices[j]];
330 }
331 return ret;
332}
333
334bool Matter::relax(bool quiet, bool writeMovie, bool checkpoint,
335 std::string prefixMovie, std::string prefixCheckpoint,
336 bool retainMovieFrames) {
337 if (retainMovieFrames) {
338 movie_frames_.clear();
339 }
341 *this, *parameters, quiet, writeMovie, checkpoint, prefixMovie,
342 prefixCheckpoint, retainMovieFrames ? &movie_frames_ : nullptr);
343}
344
346 return VectorXd::Map(getPositionsFree().data(), 3 * numberOfFreeAtoms());
347}
348
349// update Matter with the new positions of the free atoms given in array 'pos'
351 if (pos.rows() != nAtoms) {
352 throw std::invalid_argument("Matter::setPositions: row count mismatch");
353 }
354 impl_->positions = pos;
357 }
358 recomputePotential = true;
360}
361
362// Same but takes vector instead of n x 3 matrix
363void Matter::setPositionsV(const VectorXd &pos) {
364 setPositions(AtomMatrix::Map(pos.data(), numberOfAtoms(), 3));
365}
366
368 getFree(); // ensure freeIndices is up to date
369 for (size_t j = 0; j < freeIndices.size(); j++) {
370 impl_->positions.row(freeIndices[j]) = pos.row(static_cast<long>(j));
371 }
372 // Optimizers write free-atom coords only (TIP4P/SPCE and any PBC pot).
373 // Match setPositions: wrap the full configuration when PBC is on (#171).
376 }
377 recomputePotential = true;
379}
380
381void Matter::setPositionsFreeV(const VectorXd &pos) {
382 setPositionsFree(AtomMatrix::Map(pos.data(), numberOfFreeAtoms(), 3));
383}
384
386 if (biasPotential != nullptr) {
387 // Evaluate the current bias only. The MD job advances the bond-boost
388 // schedule once per step via BondBoost::advance().
389 biasPotential->boost();
390 }
391 return impl_->biasForces.array() * getFree().array();
392}
393
395 biasPotential = bondBoost;
396}
397
399
401 BondBoost *keep = biasPotential;
402 *this = other;
403 biasPotential = keep;
404}
405
407 impl_->biasForces = bf.array() * getFree().array();
408}
409// Return forces with fixed atoms zeroed (cached).
410// Note: not thread-safe. Concurrent reads on the same Matter instance
411// may race on the mutable maskedForces/recomputeMaskedForces members.
415 // Use the cached freeMask (Nx3, 1.0 for free / 0.0 for fixed) to zero
416 // fixed-atom forces in a single vectorized Eigen operation.
417 impl_->maskedForces = impl_->forces.array() * getFree().array();
418 recomputeMaskedForces = false;
419 }
420 return impl_->maskedForces;
421}
422
425 return impl_->forces;
426}
427
428VectorXd Matter::getForcesV() const {
429 return VectorXd::Map(getForces().data(), 3 * numberOfAtoms());
430}
431
433 AtomMatrix allForces = getForces();
434 getFree(); // ensure freeIndices is up to date (mutable cache)
435 AtomMatrix ret(static_cast<long>(freeIndices.size()), 3);
436 for (size_t j = 0; j < freeIndices.size(); j++) {
437 ret.row(static_cast<long>(j)) = allForces.row(freeIndices[j]);
438 }
439 return ret;
440}
441
442VectorXd Matter::getForcesFreeV() const {
443 AtomMatrix freeForces = getForcesFree();
444 return VectorXd::Map(freeForces.data(), 3 * numberOfFreeAtoms());
445}
446
447// return distance between the atoms with index1 and index2
448double Matter::distance(long index1, long index2) const {
449 checkAtom(nAtoms, index1, "Matter::distance");
450 checkAtom(nAtoms, index2, "Matter::distance");
451 return pbc(impl_->positions.row(index1) - impl_->positions.row(index2))
452 .norm();
453}
454
455// return projected distance between the atoms with index1 and index2 on asix
456// (0-x,1-y,2-z)
457double Matter::pdistance(long index1, long index2, int axis) const {
458 checkAtom(nAtoms, index1, "Matter::pdistance");
459 checkAtom(nAtoms, index2, "Matter::pdistance");
460 checkAxis(axis, "Matter::pdistance");
461 Matrix<double, 1, 3> ret;
462 ret.setZero();
463 ret(0, axis) =
464 impl_->positions(index1, axis) - impl_->positions(index2, axis);
465 ret = pbc(ret);
466 return ret(0, axis);
467}
468
469// return the distance atom with index has moved between the current Matter
470// object and the Matter object passed as argument
471double Matter::distance(const Matter &matter, long index) const {
472 checkAtom(nAtoms, index, "Matter::distance");
473 checkAtom(matter.nAtoms, index, "Matter::distance");
474 return pbc(impl_->positions.row(index) - matter.getPositions().row(index))
475 .norm();
476}
477
478double Matter::getMass(long int indexAtom) const {
479 checkAtom(nAtoms, indexAtom, "Matter::getMass");
480 return (impl_->masses[indexAtom]);
481}
482
483void Matter::setMass(long int indexAtom, double mass) {
484 checkAtom(nAtoms, indexAtom, "Matter::setMass");
485 impl_->masses[indexAtom] = mass;
486}
487
488void Matter::setMasses(const VectorXd &massesIn) {
489 if (massesIn.size() != nAtoms) {
490 throw std::invalid_argument("Matter::setMasses: size mismatch");
491 }
492 impl_->masses = massesIn;
493}
494
495long Matter::getAtomicNr(long int indexAtom) const {
496 return (impl_->atomicNrs[indexAtom]);
497}
498
499void Matter::setAtomicNr(long int indexAtom, long atomicNr) {
500 impl_->atomicNrs[indexAtom] = atomicNr;
501 recomputePotential = true;
503}
504
505int Matter::getFixed(long int indexAtom) const {
506 checkAtom(nAtoms, indexAtom, "Matter::getFixed");
507 return (impl_->isFixed(indexAtom, 0) > 0.5 &&
508 impl_->isFixed(indexAtom, 1) > 0.5 &&
509 impl_->isFixed(indexAtom, 2) > 0.5)
510 ? 1
511 : 0;
512}
513
514int Matter::getFixed(long int indexAtom, int axis) const {
515 checkAtom(nAtoms, indexAtom, "Matter::getFixed");
516 checkAxis(axis, "Matter::getFixed");
517 return impl_->isFixed(indexAtom, axis) > 0.5 ? 1 : 0;
518}
519
520std::array<bool, 3> Matter::getFixedMask(long int indexAtom) const {
521 checkAtom(nAtoms, indexAtom, "Matter::getFixedMask");
522 return {impl_->isFixed(indexAtom, 0) > 0.5,
523 impl_->isFixed(indexAtom, 1) > 0.5,
524 impl_->isFixed(indexAtom, 2) > 0.5};
525}
526
527void Matter::setFixed(long int indexAtom, int isFixed_passed) {
528 checkAtom(nAtoms, indexAtom, "Matter::setFixed");
529 const double v = isFixed_passed ? 1.0 : 0.0;
530 impl_->isFixed(indexAtom, 0) = v;
531 impl_->isFixed(indexAtom, 1) = v;
532 impl_->isFixed(indexAtom, 2) = v;
533 recomputeFreeMask = true;
535}
536
537void Matter::setFixed(long int indexAtom, int axis, int isFixed_passed) {
538 checkAtom(nAtoms, indexAtom, "Matter::setFixed");
539 checkAxis(axis, "Matter::setFixed");
540 impl_->isFixed(indexAtom, axis) = isFixed_passed ? 1.0 : 0.0;
541 recomputeFreeMask = true;
543}
544
545void Matter::setFixedMask(long int indexAtom, std::array<bool, 3> mask) {
546 checkAtom(nAtoms, indexAtom, "Matter::setFixedMask");
547 impl_->isFixed(indexAtom, 0) = mask[0] ? 1.0 : 0.0;
548 impl_->isFixed(indexAtom, 1) = mask[1] ? 1.0 : 0.0;
549 impl_->isFixed(indexAtom, 2) = mask[2] ? 1.0 : 0.0;
550 recomputeFreeMask = true;
552}
553
555 if (nAtoms > 0) {
557 return potentialEnergy;
558 } else
559 return 0.0;
560}
561
563 // 0.5 * sum(mass_i * |v_i,free|^2); a constrained axis does not contribute.
564 AtomMatrix vfree = impl_->velocities.array() * getFree().array();
565 Eigen::VectorXd speed2 = vfree.rowwise().squaredNorm();
566 return 0.5 * (impl_->masses.array() * speed2.array()).sum();
567}
568
572
574 long n = 0;
575 for (long i = 0; i < nAtoms; ++i) {
576 if (getFixed(i)) {
577 ++n;
578 }
579 }
580 return n;
581}
582
584 return nAtoms - numberOfFixedAtoms();
585}
586
587long Matter::getForceCalls() const { return (forceCalls); }
588
590 forceCalls = 0;
591 return;
592}
593
595 if (!potential || !potential->requiresIsolatedMoleculeLayout()) {
596 return;
597 }
599 throw std::runtime_error(
600 "Potential requires an isolated (non-periodic) molecular layout "
601 "(NWChem/ORCA-class). Disable periodic boundaries before optimizing; "
602 "PBC wraps can tear non-centered molecules (issue #188).");
603 }
604}
605
607 if (recomputePotential) {
608 if (!potential) {
609 throw std::runtime_error(
610 "Matter::computePotential called without a potential");
611 }
613 if (potential->isSurrogate()) {
614 // Surrogate potential case: uses free-atom subset interface
615 auto surrogatePotential =
616 static_cast<SurrogatePotential *>(potential.get());
617 auto [freePE, freeForces, vari] = surrogatePotential->get_ef_var(
618 this->getPositionsFree(), this->getAtomicNrsFree(), impl_->cell);
619 this->potentialEnergy = freePE;
620 this->energyVariance = vari;
621 for (long idx{0}, jdx{0}; idx < nAtoms; idx++) {
622 if (!getFixed(idx)) {
623 impl_->forces.row(idx) = freeForces.row(jdx);
624 jdx++;
625 }
626 }
627 } else {
628 // Hot path: call force() directly into member storage.
629 // No intermediate allocation, no tuple, no copy.
630 double var{0};
631 potential->setFixedMask(nAtoms, impl_->isFixed.data());
632 const auto n = static_cast<size_t>(nAtoms);
633 // Isolated molecules still store a box for I/O. Pots that infer PBC
634 // from a non-zero cell (GFN2) must see a zero box here.
635 const Matrix3d force_cell =
636 usePeriodicBoundaries ? impl_->cell : Matrix3d::Zero();
637 potential->force(std::span<const double>(impl_->positions.data(), n * 3),
638 std::span<const int>(impl_->atomicNrs.data(), n),
639 std::span<double>(impl_->forces.data(), n * 3),
640 &potentialEnergy, &var,
641 std::span<const double>(force_cell.data(), 9));
642 potential->forceCallCounter++;
644 }
645 if (!std::isfinite(potentialEnergy) || !impl_->forces.allFinite()) {
646 throw std::runtime_error(
647 "Potential returned non-finite energy or forces");
648 }
650 recomputePotential = false;
651
652 // One free atom: subtracting the mean force is identically zero,
653 // and NEB would then report immediate GOOD.
654 if (impl_->isFixed.maxCoeff() < 0.5 && removeNetForce && nAtoms > 1) {
655 Vector3d tempForce = impl_->forces.colwise().sum() / nAtoms;
656 for (long int i = 0; i < nAtoms; i++) {
657 impl_->forces.row(i) -= tempForce.transpose();
658 }
659 }
660 }
661}
662
663// Transform coordinates into the cell using the selected PBC convention (#176).
664// Legacy: fractional [0,1) via fmod (historical). MinimumImage: fractional
665// [-0.5,0.5) via floor (same MIC as eonc::pbc::apply for differences).
668 // Unset / singular cell (default after Matter construct is Zero) — do not
669 // wipe coordinates; callers set the cell before wrapping makes sense.
670 if (std::abs(impl_->cell.determinant()) < 1e-30) {
671 return;
672 }
673 impl_->positions = eonc::pbc::applyPositions(
674 impl_->positions, impl_->cell, impl_->cellInverse, pbcConvention);
675}
676
677double Matter::maxFreeAtomForce(const AtomMatrix &rows) const {
678 if (rows.rows() != nAtoms) {
679 throw std::invalid_argument(
680 "Matter::maxFreeAtomForce: row count does not match atom count");
681 }
682 return maxFreeAtomForceNorm(rows.data(), impl_->isFixed.data(), nAtoms);
683}
684
685double Matter::maxForce() const {
686 // Ensures that the forces are up to date
688 return maxFreeAtomForce(getForces());
689}
690
691VectorXi Matter::getAtomicNrs() const { return this->impl_->atomicNrs; }
692
693void Matter::setAtomicNrs(const VectorXi &atmnrs) {
694 if (atmnrs.size() != this->nAtoms) {
695 throw std::invalid_argument(
696 "Vector of atomic numbers not equal to the number of atoms");
697 } else {
698 this->impl_->atomicNrs = atmnrs;
699 recomputePotential = true;
701 }
702}
703
705 if (recomputeFreeMask) {
706 impl_->freeMask.resize(nAtoms, 3);
707 impl_->freeMask = 1.0 - impl_->isFixed.array();
708 freeIndices.clear();
709 freeIndices.reserve(static_cast<size_t>(nAtoms));
710 for (long i = 0; i < nAtoms; i++) {
711 if (impl_->freeMask.row(i).sum() > 0.5) {
712 freeIndices.push_back(static_cast<int>(i));
713 }
714 }
715 recomputeFreeMask = false;
716 }
717 return impl_->freeMask;
718}
719
720VectorXd Matter::getFreeV() const {
721 return VectorXd::Map(getFree().data(), 3 * numberOfAtoms());
722}
723
725 return impl_->velocities.array() * getFree().array();
726}
727
729 impl_->velocities = v.array() * getFree().array();
730}
731
733 impl_->forces = f.array() * getFree().array();
734 impl_->maskedForces = impl_->forces;
735 recomputeMaskedForces = false;
736 recomputePotential = false;
737}
738
741 AtomMatrix ret = totF.array() * getFree().array();
742 // Single reciprocal mass computation, replicated across 3 columns
743 auto invMass = impl_->masses.array().inverse();
744 ret.col(0).array() *= invMass;
745 ret.col(1).array() *= invMass;
746 ret.col(2).array() *= invMass;
747 return ret;
748}
749
750Matrix<double, Eigen::Dynamic, 1> Matter::getMasses() const {
751 return impl_->masses;
752}
753
754void Matter::setPotential(std::shared_ptr<Potential> pot) {
755 this->potential = pot;
756 // Molecular QM backends (NWChem/ORCA) must not use PBC wraps (#188). Auto-off
757 // with a hard fail if code later forces PBC while this pot is attached.
758 if (potential && potential->requiresIsolatedMoleculeLayout() &&
760 usePeriodicBoundaries = false;
761 // Use EONC_LOG_* (not bare QUILL_LOG_* with eonc::log::get() as arg): the
762 // quill macros expand logger->… and choke on a call expression as the first
763 // macro argument on some GCC versions (CI: expected primary-expression).
765 "Disabled PBC for isolated-molecule potential (NWChem/ORCA-class); "
766 "re-enabling PBC will throw (issue #188)");
767 }
768 recomputePotential = true;
770}
771
772void Matter::setComputedPotential(double energy, double variance) {
773 potentialEnergy = energy;
774 energyVariance = variance;
775 recomputePotential = false;
777 forceCalls++;
778
779 // Apply the same net force removal as computePotential()
780 if (impl_->isFixed.maxCoeff() < 0.5 && removeNetForce && nAtoms > 1) {
781 Vector3d tempForce = impl_->forces.colwise().sum() / nAtoms;
782 for (long int i = 0; i < nAtoms; i++) {
783 impl_->forces.row(i) -= tempForce.transpose();
784 }
785 }
786}
787
789 return this->potential->forceCallCounter;
790}
791
792double Matter::getEnergyVariance() const { return this->energyVariance; }
793
795 if (!potential || !potential->computesStress()) {
796 throw std::logic_error(
797 "Matter::cauchyStress requires a potential that reports stress");
798 }
799 recomputePotential = true;
801 Matrix3d sigma = potential->cauchyStress();
802 if (!sigma.allFinite()) {
803 throw std::runtime_error("Potential returned a non-finite stress tensor");
804 }
805 return sigma;
806}
807
808std::shared_ptr<Potential> Matter::getPotential() { return this->potential; }
809
812 return diff;
813 }
814 return eonc::pbc::apply(diff, impl_->cell, impl_->cellInverse);
815}
816
817VectorXd Matter::pbcV(const VectorXd &diff) const {
819 return diff;
820 }
821 return eonc::pbc::applyV(diff, impl_->cell, impl_->cellInverse);
822}
823
824double *Matter::forcesData() { return impl_->forces.data(); }
825
826std::int64_t Matter::getAtomIndex(long int atom) const {
827 return impl_->atomIndex(atom);
828}
829
830void Matter::setAtomIndex(long int atom, std::int64_t index) {
831 impl_->atomIndex(atom) = index;
832}
833
834void Matter::restoreFileForces(const AtomMatrix &fileForces, bool trustEnergy,
835 double energy) {
836 impl_->forces = fileForces;
838 if (trustEnergy) {
839 potentialEnergy = energy;
840 energyVariance = 0.0;
841 recomputePotential = false;
842 } else {
843 recomputePotential = true;
844 }
845}
846
847void evaluateTogether(Potential &pot, std::span<Matter *const> systems) {
848 std::vector<Matter *> dirty;
849 for (Matter *m : systems) {
850 if (m != nullptr && m->needsForceUpdate()) {
851 dirty.push_back(m);
852 }
853 }
854 if (dirty.empty()) {
855 return;
856 }
857 if (dirty.size() == 1 || !pot.supportsBatchEvaluation()) {
858 for (Matter *m : dirty) {
859 m->getForcesRaw();
860 }
861 return;
862 }
863 const long n = dirty.size();
864 const long atoms = dirty.front()->numberOfAtoms();
865 std::vector<VectorXi> nrs(n);
866 std::vector<Matrix3d> boxes(n);
867 std::vector<const double *> posPtr, boxPtr;
868 std::vector<const int *> nrsPtr;
869 std::vector<double *> frcPtr;
870 for (long j = 0; j < n; ++j) {
871 Matter *m = dirty[j];
872 if (m->numberOfAtoms() != atoms) {
873 throw std::invalid_argument(
874 "evaluateTogether: systems differ in atom count");
875 }
876 nrs[j] = m->getAtomicNrs();
877 // A non-periodic system hands the potential a zero box, as
878 // computePotential does.
879 boxes[j] = m->getPeriodic() ? m->getCell() : Matrix3d::Zero().eval();
880 }
881 for (long j = 0; j < n; ++j) {
882 posPtr.push_back(dirty[j]->getPositions().data());
883 nrsPtr.push_back(nrs[j].data());
884 frcPtr.push_back(dirty[j]->forcesData());
885 boxPtr.push_back(boxes[j].data());
886 }
887 std::vector<double> energies(n), variances(n);
888 pot.forceBatch(n, atoms, posPtr.data(), nrsPtr.data(), frcPtr.data(),
889 energies.data(), variances.data(), boxPtr.data());
890 for (long j = 0; j < n; ++j) {
891 dirty[j]->setComputedPotential(energies[j], variances[j]);
892 }
893}
894
895} // namespace eonc
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
Functionality relying on the conjugate gradients algorithm.
Definition BondBoost.h:25
double potentialEnergy
Definition Matter.h:376
void applyPeriodicBoundary()
Definition Matter.cpp:666
void setBiasPotential(BondBoost *bondBoost)
Definition Matter.cpp:394
double getKineticEnergy() const
Definition Matter.cpp:562
void resetForceCalls()
Definition Matter.cpp:589
bool getWriteConForces() const noexcept
Parameters.main_options().writeConForces for this Matter, if bound.
Definition Matter.cpp:64
VectorXi getAtomicNrs() const
Definition Matter.cpp:691
bool recomputeFreeMask
Definition Matter.h:372
void setComputedPotential(double energy, double variance)
Set energy/variance from external batched evaluation and mark forces as up-to-date (recomputePotentia...
Definition Matter.cpp:772
void setForces(const AtomMatrix &f)
Definition Matter.cpp:732
void setFixed(long int atom, int isFixed)
Broadcast a whole-atom flag onto all three axes.
Definition Matter.cpp:527
std::shared_ptr< Potential > getPotential()
Definition Matter.cpp:808
BondBoost * getBiasPotential() const
The bias potential added to this Matter's forces, or nullptr.
Definition Matter.cpp:398
double maxFreeAtomForce(const AtomMatrix &rows) const
Max per-atom Euclidean norm of rows.
Definition Matter.cpp:677
void setFixedMask(long int atom, std::array< bool, 3 > mask)
Definition Matter.cpp:545
void setPosition(long int atom, int axis, double position)
Definition Matter.cpp:290
PbcConvention pbcConvention
Definition Matter.h:342
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
bool relax(bool quiet=false, bool writeMovie=false, bool checkpoint=false, std::string prefixMovie=std::string(), std::string prefixCheckpoint=std::string(), bool retainMovieFrames=false)
Definition Matter.cpp:334
double distanceTo(const Matter &matter)
Definition Matter.cpp:206
VectorXd getForcesFreeV() const
Definition Matter.cpp:442
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:350
void setAtomicNr(long int atom, long atomicNr)
Definition Matter.cpp:499
double pdistance(long index1, long index2, int axis) const
Definition Matter.cpp:457
AtomMatrix getForcesFree() const
Definition Matter.cpp:432
const Matter & operator=(const Matter &matter)
Definition Matter.cpp:86
std::vector< readcon::ConFrame > movie_frames_
Definition Matter.h:375
bool compare(const Matter &matter, bool indistinguishable=false)
Definition Matter.cpp:184
void setPositionsV(const VectorXd &pos)
Definition Matter.cpp:363
void setCell(const Matrix3d &newCell)
Definition Matter.cpp:277
std::vector< int > freeIndices
Definition Matter.h:371
AtomMatrix getFree() const
Definition Matter.cpp:704
bool getPeriodic() const noexcept
Definition Matter.h:313
void setVelocity(long int atom, int axis, double velocity)
Definition Matter.cpp:301
void computePotential() const
Definition Matter.cpp:606
void setPotential(std::shared_ptr< Potential > pot)
Definition Matter.cpp:754
void setPositionsFreeV(const VectorXd &pos)
Definition Matter.cpp:381
bool recomputeMaskedForces
Definition Matter.h:373
Matrix3d getCell() const
Definition Matter.cpp:275
const Parameters * parameters
Definition Matter.h:360
bool removeNetForce
Definition Matter.h:357
long nAtoms
Definition Matter.h:361
long int numberOfAtoms() const
Definition Matter.cpp:273
Matrix3d cauchyStress()
Cauchy stress in eV/Angstrom^3.
Definition Matter.cpp:794
AtomMatrix pbc(const AtomMatrix &diff) const
Definition Matter.cpp:810
std::shared_ptr< Potential > potential
Definition Matter.h:337
long getForceCalls() const
Definition Matter.cpp:587
VectorXd getPositionsFreeV() const
Definition Matter.cpp:345
BondBoost * biasPotential
Definition Matter.h:366
void setAtomicNrs(const VectorXi &atmnrs)
Definition Matter.cpp:693
AtomMatrix getVelocities() const
Definition Matter.cpp:724
void setVelocities(const AtomMatrix &v)
Definition Matter.cpp:728
double getMechanicalEnergy() const
Definition Matter.cpp:569
void setAtomIndex(long int atom, std::int64_t index)
Definition Matter.cpp:830
double perAtomNorm(const Matter &matter)
Definition Matter.cpp:214
const AtomMatrix & getForcesRaw() const
Definition Matter.cpp:423
long forceCalls
Definition Matter.h:346
void resize(long int nAtoms)
Definition Matter.cpp:227
VectorXd pbcV(const VectorXd &diff) const
Definition Matter.cpp:817
AtomMatrix getPositionsFree() const
Definition Matter.cpp:316
double getPosition(long int atom, int axis) const
Definition Matter.cpp:284
size_t getPotentialCalls() const
Definition Matter.cpp:788
VectorXi getAtomicNrsFree() const
Definition Matter.cpp:325
void restoreFileForces(const AtomMatrix &fileForces, bool trustEnergy, double energy)
Write .con forces without the fixed-atom mask setForces applies.
Definition Matter.cpp:834
Eigen::Matrix< double, Eigen::Dynamic, 1 > getMasses() const
Definition Matter.cpp:750
long int numberOfFixedAtoms() const
Definition Matter.cpp:573
std::unique_ptr< Impl > impl_
Definition Matter.h:365
double distance(long index1, long index2) const
Definition Matter.cpp:448
double getMass(long int atom) const
Definition Matter.cpp:478
bool recomputePotential
Definition Matter.h:343
void setMasses(const VectorXd &massesIn)
Definition Matter.cpp:488
void setMass(long int atom, double mass)
Definition Matter.cpp:483
double getPotentialEnergy() const
Definition Matter.cpp:554
AtomMatrix getAccelerations()
Definition Matter.cpp:739
double * forcesData()
Mutable access to force storage for batched potential evaluation.
Definition Matter.cpp:824
AtomMatrix getBiasForces()
Definition Matter.cpp:385
std::array< std::string, 5 > headerCon
Definition Matter.h:349
StructureComparisonOptions structComp
Definition Matter.h:358
bool usePeriodicBoundaries
Definition Matter.h:338
long int numberOfFreeAtoms() const
Definition Matter.cpp:583
VectorXd getFreeV() const
Definition Matter.cpp:720
Matter(std::shared_ptr< Potential > pot, const Parameters &params)
Definition Matter.cpp:49
long getAtomicNr(long int atom) const
Definition Matter.cpp:495
double getEnergyVariance() const
Definition Matter.cpp:792
AtomMatrix getPositionsCopy() const
Definition Matter.cpp:310
std::array< bool, 3 > getFixedMask(long int atom) const
Per-axis CON column-4 mask (bit0=x, bit1=y, bit2=z).
Definition Matter.cpp:520
void setBiasForces(const AtomMatrix &bf)
Definition Matter.cpp:406
void setPositionsFree(const AtomMatrix &pos)
Definition Matter.cpp:367
std::vector< long > fileToMatter
Definition Matter.h:367
std::int64_t getAtomIndex(long int atom) const
.con column-5 index (pre-grouping); public for I/O / bindings.
Definition Matter.cpp:826
void assertIsolatedMoleculeLayoutSafe() const
Throw if pot forbids PBC (isolated molecular QM backends, issue #188).
Definition Matter.cpp:594
VectorXd getPositionsV() const
Definition Matter.cpp:312
void assignKeepingBias(const Matter &other)
Copy another structure in and keep this Matter's own bias potential.
Definition Matter.cpp:400
const AtomMatrix & getForces() const
Definition Matter.cpp:412
double energyVariance
Definition Matter.h:374
int getFixed(long int atom) const
1 if every Cartesian axis of the atom is fixed, else 0.
Definition Matter.cpp:505
VectorXd getForcesV() const
Definition Matter.cpp:428
double maxForce(void) const
Definition Matter.cpp:685
void on_force_call(PotType t) noexcept override
static PotRegistry & get() noexcept
Process-lifetime singleton.
std::tuple< double, AtomMatrix, double > get_ef_var(const AtomMatrix pos, const VectorXi atmnrs, const Matrix3d box)
bool rotationMatch(const Matter &m1, const Matter &m2, const double max_diff)
bool identical(const Matter &m1, const Matter &m2, const double distanceDifference)
bool sortedR(const Matter &m1, const Matter &m2, const double distanceDifference)
void translationRemove(Matter &m1, const AtomMatrix r1)
bool relaxMatter(Matter &matter, const Parameters &params, bool quiet=false, bool writeMovie=false, bool checkpoint=false, std::string prefixMovie=std::string(), std::string prefixCheckpoint=std::string(), std::vector< readcon::ConFrame > *outFrames=nullptr)
AtomMatrix apply(const AtomMatrix &diff, const Matrix3d &cell, const Matrix3d &cellInverse)
Definition Matter.h:44
AtomMatrix applyPositions(const AtomMatrix &coords, const Matrix3d &cell, const Matrix3d &cellInverse, PbcConvention convention)
Definition Matter.h:64
VectorXd applyV(const VectorXd &diffVector, const Matrix3d &cell, const Matrix3d &cellInverse)
Definition Matter.h:73
RAII resource manager for the ARTn C library with global synchronization.
void evaluateTogether(Potential &pot, std::span< Matter *const > systems)
Evaluates every system that needs a force update.
Definition Matter.cpp:847
PbcConvention
Definition Matter.h:36
double maxFreeAtomForceNorm(const double *forces, const double *fixed, long nAtoms)
Max Euclidean norm over N x 3 row-major force rows.
AtomMatrix positions
Definition Matter.cpp:31
AtomMatrix isFixed
Definition Matter.cpp:38
Matrix3d cellInverse
Definition Matter.cpp:44
AtomMatrix velocities
Definition Matter.cpp:32
AtomMatrix forces
Definition Matter.cpp:33
AtomMatrix maskedForces
Definition Matter.cpp:42
VectorXd masses
Definition Matter.cpp:35
Eigen::Matrix< std::int64_t, Eigen::Dynamic, 1 > atomIndex
Definition Matter.cpp:40
AtomMatrix freeMask
Definition Matter.cpp:41
VectorXi atomicNrs
Definition Matter.cpp:36
AtomMatrix biasForces
Definition Matter.cpp:34