n2p2 - A neural network potential package
Structure.cpp
Go to the documentation of this file.
1// n2p2 - A neural network potential package
2// Copyright (C) 2018 Andreas Singraber (University of Vienna)
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17#include "Kspace.h"
18#include "Structure.h"
19#include "Vec3D.h"
20#include "utility.h"
21#include <Eigen/Dense> // MatrixXd, VectorXd
22#include <algorithm> // std::max
23#include <cmath> // fabs, erf
24#include <cstdlib> // atof
25#include <limits> // std::numeric_limits
26#include <stdexcept> // std::runtime_error
27#include <string> // std::getline
28#include <iostream>
29
30using namespace std;
31using namespace nnp;
32using namespace Eigen;
33
34
35Structure::Structure() :
36 isPeriodic (false ),
37 isTriclinic (false ),
38 hasNeighborList (false ),
39 NeighborListIsSorted (false ),
40 hasSymmetryFunctions (false ),
41 hasSymmetryFunctionDerivatives(false ),
42 index (0 ),
43 numAtoms (0 ),
44 numElements (0 ),
45 numElementsPresent (0 ),
46 energy (0.0 ),
47 energyRef (0.0 ),
48 energyShort (0.0 ),
49 energyElec (0.0 ),
50 hasCharges (false ),
51 charge (0.0 ),
52 chargeRef (0.0 ),
53 volume (0.0 ),
54 maxCutoffRadiusOverall (0.0 ),
55 sampleType (ST_UNKNOWN),
56 comment ("" ),
57 hasAMatrix (false )
58{
59 for (size_t i = 0; i < 3; i++)
60 {
61 pbc[i] = 0;
62 box[i][0] = 0.0;
63 box[i][1] = 0.0;
64 box[i][2] = 0.0;
65 invbox[i][0] = 0.0;
66 invbox[i][1] = 0.0;
67 invbox[i][2] = 0.0;
68 }
69}
70
71void Structure::setElementMap(ElementMap const& elementMap)
72{
73 this->elementMap = elementMap;
76
77 return;
78}
79
80void Structure::addAtom(Atom const& atom, string const& element)
81{
82 atoms.push_back(Atom());
83 atoms.back() = atom;
84 // The number of elements may have changed.
85 atoms.back().numNeighborsPerElement.resize(elementMap.size(), 0);
86 atoms.back().clearNeighborList();
87 atoms.back().index = numAtoms;
88 atoms.back().indexStructure = index;
89 atoms.back().element = elementMap[element];
90 atoms.back().numSymmetryFunctions = 0;
91 numAtoms++;
93
94 return;
95}
96
97void Structure::readFromFile(string const fileName)
98{
99 ifstream file;
100
101 file.open(fileName.c_str());
102 if (!file.is_open())
103 {
104 throw runtime_error("ERROR: Could not open file: \"" + fileName
105 + "\".\n");
106 }
107 readFromFile(file);
108 file.close();
109
110 return;
111}
112
113void Structure::readFromFile(ifstream& file)
114{
115 string line;
116 vector<string> lines;
117 vector<string> splitLine;
118
119 // read first line, should be keyword "begin".
120 getline(file, line);
121 lines.push_back(line);
122 splitLine = split(reduce(line));
123 if (splitLine.at(0) != "begin")
124 {
125 throw runtime_error("ERROR: Unexpected file content, expected"
126 " \"begin\" keyword.\n");
127 }
128
129 while (getline(file, line))
130 {
131 lines.push_back(line);
132 splitLine = split(reduce(line));
133 if (splitLine.at(0) == "end") break;
134 }
135
136 readFromLines(lines);
137
138 return;
139}
140
141
142void Structure::readFromLines(vector<string> const& lines)
143{
144 size_t iBoxVector = 0;
145 vector<string> splitLine;
146
147 // read first line, should be keyword "begin".
148 splitLine = split(reduce(lines.at(0)));
149 if (splitLine.at(0) != "begin")
150 {
151 throw runtime_error("ERROR: Unexpected line content, expected"
152 " \"begin\" keyword.\n");
153 }
154
155 for (vector<string>::const_iterator line = lines.begin();
156 line != lines.end(); ++line)
157 {
158 splitLine = split(reduce(*line));
159 if (splitLine.at(0) == "begin")
160 {
161 if (splitLine.size() > 1)
162 {
163 for (vector<string>::const_iterator word =
164 splitLine.begin() + 1; word != splitLine.end(); ++word)
165 {
166 if (*word == "set=train") sampleType = ST_TRAINING;
167 else if (*word == "set=test") sampleType = ST_TEST;
168 else
169 {
170 throw runtime_error("ERROR: Unknown keyword in "
171 "structure specification, check "
172 "\"begin\" arguments.\n");
173 }
174 }
175 }
176 }
177 else if (splitLine.at(0) == "comment")
178 {
179 size_t position = line->find("comment");
180 string tmpLine = *line;
181 comment = tmpLine.erase(position, splitLine.at(0).length() + 1);
182 }
183 else if (splitLine.at(0) == "lattice")
184 {
185 if (iBoxVector > 2)
186 {
187 throw runtime_error("ERROR: Too many box vectors.\n");
188 }
189 box[iBoxVector][0] = atof(splitLine.at(1).c_str());
190 box[iBoxVector][1] = atof(splitLine.at(2).c_str());
191 box[iBoxVector][2] = atof(splitLine.at(3).c_str());
192 iBoxVector++;
193 if (iBoxVector == 3)
194 {
195 isPeriodic = true;
196 if (box[0][1] > numeric_limits<double>::min() ||
197 box[0][2] > numeric_limits<double>::min() ||
198 box[1][0] > numeric_limits<double>::min() ||
199 box[1][2] > numeric_limits<double>::min() ||
200 box[2][0] > numeric_limits<double>::min() ||
201 box[2][1] > numeric_limits<double>::min())
202 {
203 isTriclinic = true;
204 }
207 }
208 }
209 else if (splitLine.at(0) == "atom")
210 {
211 atoms.push_back(Atom());
212 atoms.back().index = numAtoms;
213 atoms.back().indexStructure = index;
214 atoms.back().tag = numAtoms; // Implicit conversion!
215 atoms.back().r[0] = atof(splitLine.at(1).c_str());
216 atoms.back().r[1] = atof(splitLine.at(2).c_str());
217 atoms.back().r[2] = atof(splitLine.at(3).c_str());
218 atoms.back().element = elementMap[splitLine.at(4)];
219 atoms.back().chargeRef = atof(splitLine.at(5).c_str());
220 atoms.back().fRef[0] = atof(splitLine.at(7).c_str());
221 atoms.back().fRef[1] = atof(splitLine.at(8).c_str());
222 atoms.back().fRef[2] = atof(splitLine.at(9).c_str());
223 atoms.back().numNeighborsPerElement.resize(numElements, 0);
224 numAtoms++;
225 numAtomsPerElement[elementMap[splitLine.at(4)]]++;
226 }
227 else if (splitLine.at(0) == "energy")
228 {
229 energyRef = atof(splitLine[1].c_str());
230 }
231 else if (splitLine.at(0) == "charge")
232 {
233 chargeRef = atof(splitLine[1].c_str());
234 }
235 else if (splitLine.at(0) == "end")
236 {
237 if (!(iBoxVector == 0 || iBoxVector == 3))
238 {
239 throw runtime_error("ERROR: Strange number of box vectors.\n");
240 }
241 if (isPeriodic &&
242 abs(chargeRef) > 10*std::numeric_limits<double>::epsilon())
243 {
244 throw runtime_error("ERROR: In structure with index "
245 + to_string(index)
246 + "; if PBCs are applied, the\n"
247 "simulation cell has to be neutrally "
248 "charged.\n");
249 }
250 break;
251 }
252 else
253 {
254 throw runtime_error("ERROR: Unexpected file content, "
255 "unknown keyword \"" + splitLine.at(0) +
256 "\".\n");
257 }
258 }
259
260 for (size_t i = 0; i < numElements; i++)
261 {
262 if (numAtomsPerElement[i] > 0)
263 {
265 }
266 }
267
268 if (isPeriodic) remap();
269
270 return;
271}
272
274 EwaldSetup& ewaldSetup,
275 double rcutScreen,
276 double maxCutoffRadius)
277{
278 maxCutoffRadiusOverall = max(rcutScreen, maxCutoffRadius);
279 if (isPeriodic)
280 {
283 ewaldSetup.params.rCut);
284 }
285}
286
288 double cutoffRadius,
289 bool sortByDistance)
290{
291 cutoffRadius = max( cutoffRadius, maxCutoffRadiusOverall );
292 //cout << "CUTOFF: " << cutoffRadius << "\n";
293
294 if (isPeriodic)
295 {
296 calculatePbcCopies(cutoffRadius, pbc);
297
298 // Use square of cutoffRadius (faster).
299 cutoffRadius *= cutoffRadius;
300
301#ifdef _OPENMP
302 #pragma omp parallel for
303#endif
304 for (size_t i = 0; i < numAtoms; i++)
305 {
306 // Count atom i as unique neighbor.
307 atoms[i].neighborsUnique.push_back(i);
308 atoms[i].numNeighborsUnique++;
309 for (size_t j = 0; j < numAtoms; j++)
310 {
311 for (int bc0 = -pbc[0]; bc0 <= pbc[0]; bc0++)
312 {
313 for (int bc1 = -pbc[1]; bc1 <= pbc[1]; bc1++)
314 {
315 for (int bc2 = -pbc[2]; bc2 <= pbc[2]; bc2++)
316 {
317 if (!(i == j && bc0 == 0 && bc1 == 0 && bc2 == 0))
318 {
319 Vec3D dr = atoms[i].r - atoms[j].r
320 + bc0 * box[0]
321 + bc1 * box[1]
322 + bc2 * box[2];
323 if (dr.norm2() <= cutoffRadius)
324 {
325 atoms[i].neighbors.
326 push_back(Atom::Neighbor());
327 atoms[i].neighbors.
328 back().index = j;
329 atoms[i].neighbors.
330 back().tag = j; // Implicit conversion!
331 atoms[i].neighbors.
332 back().element = atoms[j].element;
333 atoms[i].neighbors.
334 back().d = dr.norm();
335 atoms[i].neighbors.
336 back().dr = dr;
337 atoms[i].numNeighborsPerElement[
338 atoms[j].element]++;
339 atoms[i].numNeighbors++;
340 // Count atom j only once as unique
341 // neighbor.
342 if (atoms[i].neighborsUnique.back() != j &&
343 i != j)
344 {
345 atoms[i].neighborsUnique.push_back(j);
346 atoms[i].numNeighborsUnique++;
347 }
348 }
349 }
350 }
351 }
352 }
353 }
354 atoms[i].hasNeighborList = true;
355 }
356 }
357 else
358 {
359 // Use square of cutoffRadius (faster).
360 cutoffRadius *= cutoffRadius;
361
362#ifdef _OPENMP
363 #pragma omp parallel for
364#endif
365 for (size_t i = 0; i < numAtoms; i++)
366 {
367 // Count atom i as unique neighbor.
368 atoms[i].neighborsUnique.push_back(i);
369 atoms[i].numNeighborsUnique++;
370 for (size_t j = 0; j < numAtoms; j++)
371 {
372 if (i != j)
373 {
374 Vec3D dr = atoms[i].r - atoms[j].r;
375 if (dr.norm2() <= cutoffRadius)
376 {
377 atoms[i].neighbors.push_back(Atom::Neighbor());
378 atoms[i].neighbors.back().index = j;
379 atoms[i].neighbors.back().tag = j; // Impl. conv.!
380 atoms[i].neighbors.back().element = atoms[j].element;
381 atoms[i].neighbors.back().d = dr.norm();
382 atoms[i].neighbors.back().dr = dr;
383 atoms[i].numNeighborsPerElement[atoms[j].element]++;
384 atoms[i].numNeighbors++;
385 atoms[i].neighborsUnique.push_back(j);
386 atoms[i].numNeighborsUnique++;
387 }
388 }
389 }
390 atoms[i].hasNeighborList = true;
391 }
392 }
393
394 hasNeighborList = true;
395
396 if (sortByDistance) sortNeighborList();
397
398 return;
399}
400
401void Structure::calculateNeighborList(double cutoffRadius,
402 std::vector<
403 std::vector<double>>& cutoffs)
404{
405 calculateNeighborList(cutoffRadius, true);
406 setupNeighborCutoffMap(cutoffs);
407}
408
410{
411#ifdef _OPENMP
412 #pragma omp parallel for
413#endif
414 for (size_t i = 0; i < numAtoms; ++i)
415 {
416 sort(atoms[i].neighbors.begin(), atoms[i].neighbors.end());
417 //TODO: maybe sort neighborsUnique too?
418 atoms[i].NeighborListIsSorted = true;
419 }
421}
422
424 vector<double>> cutoffs)
425{
427 throw runtime_error("NeighborCutoffs map needs a sorted neighbor list");
428
429 for ( auto& elementCutoffs : cutoffs )
430 {
431 if ( maxCutoffRadiusOverall > 0 &&
432 !vectorContains(elementCutoffs, maxCutoffRadiusOverall))
433 {
434 elementCutoffs.push_back(maxCutoffRadiusOverall);
435 }
436
437 if ( !is_sorted(elementCutoffs.begin(), elementCutoffs.end()) )
438 {
439 sort(elementCutoffs.begin(), elementCutoffs.end());
440 }
441 }
442
443 // Loop over all atoms in this structure and create its neighborCutoffs map
444 for( auto& a : atoms )
445 {
446 size_t const eIndex = a.element;
447 vector<double> const& elementCutoffs = cutoffs.at(eIndex);
448 size_t in = 0;
449 size_t ic = 0;
450
451 while( in < a.numNeighbors && ic < elementCutoffs.size() )
452 {
453 Atom::Neighbor const& n = a.neighbors[in];
454 // If neighbor distance is higher than current "desired cutoff"
455 // then add neighbor cutoff.
456 // Attention: a step in the neighbor list could jump over multiple
457 // params -> don't increment neighbor index
458 if( n.d > elementCutoffs[ic] )
459 {
460 a.neighborCutoffs.emplace( elementCutoffs.at(ic), in );
461 ++ic;
462 }
463 else if ( in == a.numNeighbors - 1 )
464 {
465 a.neighborCutoffs.emplace( elementCutoffs.at(ic), a.numNeighbors );
466 ++ic;
467 }
468 else ++in;
469 }
470 }
471}
472
474{
475 size_t maxNumNeighbors = 0;
476
477 for(vector<Atom>::const_iterator it = atoms.begin();
478 it != atoms.end(); ++it)
479 {
480 maxNumNeighbors = max(maxNumNeighbors, it->numNeighbors);
481 }
482
483 return maxNumNeighbors;
484}
485
486void Structure::calculatePbcCopies(double cutoffRadius, int (&pbc)[3])
487{
488 Vec3D axb;
489 Vec3D axc;
490 Vec3D bxc;
491
492 axb = box[0].cross(box[1]).normalize();
493 axc = box[0].cross(box[2]).normalize();
494 bxc = box[1].cross(box[2]).normalize();
495
496 double proja = fabs(box[0] * bxc);
497 double projb = fabs(box[1] * axc);
498 double projc = fabs(box[2] * axb);
499
500 pbc[0] = 0;
501 pbc[1] = 0;
502 pbc[2] = 0;
503 while (pbc[0] * proja <= cutoffRadius)
504 {
505 pbc[0]++;
506 }
507 while (pbc[1] * projb <= cutoffRadius)
508 {
509 pbc[1]++;
510 }
511 while (pbc[2] * projc <= cutoffRadius)
512 {
513 pbc[2]++;
514 }
515
516 return;
517}
518
520{
521 double invdet = box[0][0] * box[1][1] * box[2][2]
522 + box[1][0] * box[2][1] * box[0][2]
523 + box[2][0] * box[0][1] * box[1][2]
524 - box[2][0] * box[1][1] * box[0][2]
525 - box[0][0] * box[2][1] * box[1][2]
526 - box[1][0] * box[0][1] * box[2][2];
527 invdet = 1.0 / invdet;
528
529 invbox[0][0] = box[1][1] * box[2][2] - box[2][1] * box[1][2];
530 invbox[0][1] = box[2][1] * box[0][2] - box[0][1] * box[2][2];
531 invbox[0][2] = box[0][1] * box[1][2] - box[1][1] * box[0][2];
532 invbox[0] *= invdet;
533
534 invbox[1][0] = box[2][0] * box[1][2] - box[1][0] * box[2][2];
535 invbox[1][1] = box[0][0] * box[2][2] - box[2][0] * box[0][2];
536 invbox[1][2] = box[1][0] * box[0][2] - box[0][0] * box[1][2];
537 invbox[1] *= invdet;
538
539 invbox[2][0] = box[1][0] * box[2][1] - box[2][0] * box[1][1];
540 invbox[2][1] = box[2][0] * box[0][1] - box[0][0] * box[2][1];
541 invbox[2][2] = box[0][0] * box[1][1] - box[1][0] * box[0][1];
542 invbox[2] *= invdet;
543
544 return;
545}
546
547// TODO: Not needed anymore, should we keep it?
549{
550 Vec3D axb;
551 Vec3D axc;
552 Vec3D bxc;
553
554 axb = box[0].cross(box[1]).normalize();
555 axc = box[0].cross(box[2]).normalize();
556 bxc = box[1].cross(box[2]).normalize();
557
558 double proj[3];
559 proj[0] = fabs(box[0] * bxc);
560 proj[1] = fabs(box[1] * axc);
561 proj[2] = fabs(box[2] * axb);
562
563 double minProj = *min_element(proj, proj+3);
564 return (cutoffRadius < minProj / 2.0);
565}
566
568{
569 Vec3D ds = invbox * dr;
570 Vec3D dsNINT;
571
572 for (size_t i=0; i<3; ++i)
573 {
574 dsNINT[i] = round(ds[i]);
575 }
576 Vec3D drMin = box * (ds - dsNINT);
577
578 return drMin;
579}
580
582{
583 volume = fabs(box[0] * (box[1].cross(box[2])));
584
585 return;
586}
587
589 EwaldSetup& ewaldSetup,
590 VectorXd hardness,
591 MatrixXd gammaSqrt2,
592 VectorXd sigmaSqrtPi,
593 ScreeningFunction const& fs,
594 double const fourPiEps,
595 ErfcBuf& erfcBuf)
596{
597 A.resize(numAtoms + 1, numAtoms + 1);
598 A.setZero();
599 VectorXd b(numAtoms + 1);
600 VectorXd hardnessJ(numAtoms);
601 VectorXd Q;
602 erfcBuf.reset(atoms, 2);
603
604#ifdef _OPENMP
605#pragma omp parallel
606 {
607#endif
608 if (isPeriodic)
609 {
610#ifdef _OPENMP
611#pragma omp single
612 {
613#endif
615 {
616 throw runtime_error("ERROR: Neighbor list needs to "
617 "be sorted for Ewald summation!\n");
618 }
619
620 // Should always happen already before neighborlist construction.
621 //ewaldSetup.calculateParameters(volume, numAtoms);
622#ifdef _OPENMP
623 }
624#endif
625 KspaceGrid grid;
626 grid.setup(box, ewaldSetup);
627 double const rcutReal = ewaldSetup.params.rCut;
628 double const sqrt2eta = sqrt(2.0) * ewaldSetup.params.eta;
629
630#ifdef _OPENMP
631 #pragma omp for schedule(dynamic)
632#endif
633 for (size_t i = 0; i < numAtoms; ++i)
634 {
635 Atom const &ai = atoms.at(i);
636 size_t const ei = ai.element;
637
638 // diagonal including self interaction
639 A(i, i) += hardness(ei)
640 + (1.0 / sigmaSqrtPi(ei) - 2 / (sqrt2eta * sqrt(M_PI)))
641 / fourPiEps;
642
643 hardnessJ(i) = hardness(ei);
644 b(i) = -ai.chi;
645
646 // real part
647 size_t const numNeighbors = ai.getStoredMinNumNeighbors(rcutReal);
648 for (size_t k = 0; k < numNeighbors; ++k)
649 {
650 auto const &n = ai.neighbors[k];
651 size_t j = n.tag;
652 if (j < i) continue;
653
654 double const rij = n.d;
655 size_t const ej = n.element;
656
657 double erfcSqrt2Eta = erfcBuf.getf(i, k, 0, rij / sqrt2eta);
658 double erfcGammaSqrt2 =
659 erfcBuf.getf(i, k, 1, rij / gammaSqrt2(ei, ej));
660
661 A(i, j) += (erfcSqrt2Eta - erfcGammaSqrt2) / (rij * fourPiEps);
662 }
663
664 // reciprocal part
665 //for (size_t j = i + 1; j < numAtoms; ++j)
666 for (size_t j = i; j < numAtoms; ++j)
667 {
668 Atom const &aj = atoms.at(j);
669 for (auto const &gv: grid.kvectors)
670 {
671 // Multiply by 2 because our grid is only a half-sphere
672 // Vec3D const dr = applyMinimumImageConvention(ai.r - aj.r);
673 Vec3D const dr = ai.r - aj.r;
674 A(i, j) += 2 * gv.coeff * cos(gv.k * dr) / fourPiEps;
675 //A(i, j) += 2 * gv.coeff * cos(gv.k * (ai.r - aj.r));
676 }
677 A(j, i) = A(i, j);
678 }
679 }
680 }
681 else
682 {
683#ifdef _OPENMP
684#pragma omp for schedule(dynamic)
685#endif
686 for (size_t i = 0; i < numAtoms; ++i)
687 {
688 Atom const &ai = atoms.at(i);
689 size_t const ei = ai.element;
690
691 A(i, i) = hardness(ei)
692 + 1.0 / (sigmaSqrtPi(ei) * fourPiEps);
693 hardnessJ(i) = hardness(ei);
694 b(i) = -ai.chi;
695 for (size_t j = i + 1; j < numAtoms; ++j)
696 {
697 Atom const &aj = atoms.at(j);
698 size_t const ej = aj.element;
699 double const rij = (ai.r - aj.r).norm();
700
701 A(i, j) = erf(rij / gammaSqrt2(ei, ej)) / (rij * fourPiEps);
702 A(j, i) = A(i, j);
703
704 }
705 }
706 }
707#ifdef _OPENMP
708#pragma omp single
709 {
710#endif
711 A.col(numAtoms).setOnes();
712 A.row(numAtoms).setOnes();
713 A(numAtoms, numAtoms) = 0.0;
714 hasAMatrix = true;
715 b(numAtoms) = chargeRef;
716#ifdef _OPENMP
717 }
718#endif
719 //TODO: sometimes only recalculation of A matrix is needed, because
720 // Qs are stored.
721 Q = A.colPivHouseholderQr().solve(b);
722#ifdef _OPENMP
723 #pragma omp for nowait
724#endif
725 for (size_t i = 0; i < numAtoms; ++i)
726 {
727 atoms.at(i).charge = Q(i);
728 }
729#ifdef _OPENMP
730 } // end of parallel region
731#endif
732
733 lambda = Q(numAtoms);
734 hasCharges = true;
735 double error = (A * Q - b).norm() / b.norm();
736
737 // We need matrix E not A, which only differ by the hardness terms along the diagonal
738 energyElec = 0.5 * Q.head(numAtoms).transpose()
739 * (A.topLeftCorner(numAtoms, numAtoms) -
740 MatrixXd(hardnessJ.asDiagonal())) * Q.head(numAtoms);
741 energyElec += calculateScreeningEnergy(gammaSqrt2, sigmaSqrtPi, fs, fourPiEps);
742
743 return error;
744}
745
747 MatrixXd gammaSqrt2,
748 VectorXd sigmaSqrtPi,
749 ScreeningFunction const& fs,
750 double const fourPiEps)
751
752{
753 double energyScreen = 0;
754 double const rcutScreen = fs.getOuter();
755
756#ifdef _OPENMP
757 #pragma omp parallel
758 {
759 double localEnergyScreen = 0;
760#endif
761 if (isPeriodic)
762 {
763#ifdef _OPENMP
764 #pragma omp for schedule(dynamic)
765#endif
766 for (size_t i = 0; i < numAtoms; ++i)
767 {
768 Atom const& ai = atoms.at(i);
769 size_t const ei = ai.element;
770 double const Qi = ai.charge;
771#ifdef _OPENMP
772 localEnergyScreen += -Qi * Qi
773 / (2 * sigmaSqrtPi(ei) * fourPiEps);
774#else
775 energyScreen += -Qi * Qi
776 / (2 * sigmaSqrtPi(ei) * fourPiEps);
777#endif
778
779 size_t const numNeighbors = ai.getStoredMinNumNeighbors(rcutScreen);
780 for (size_t k = 0; k < numNeighbors; ++k)
781 {
782 auto const& n = ai.neighbors[k];
783 size_t const j = n.tag;
784 if (j < i) continue;
785 double const rij = n.d;
786 size_t const ej = n.element;
787 //TODO: Maybe add charge to neighbor class?
788 double const Qj = atoms.at(j).charge;
789#ifdef _OPENMP
790 localEnergyScreen += Qi * Qj * erf(rij / gammaSqrt2(ei, ej))
791 * (fs.f(rij) - 1) / (rij * fourPiEps);
792#else
793 energyScreen += Qi * Qj * erf(rij / gammaSqrt2(ei, ej))
794 * (fs.f(rij) - 1) / (rij * fourPiEps);
795#endif
796
797 }
798 }
799 }
800 else
801 {
802#ifdef _OPENMP
803 #pragma omp for schedule(dynamic)
804#endif
805 for (size_t i = 0; i < numAtoms; ++i)
806 {
807 Atom const& ai = atoms.at(i);
808 size_t const ei = ai.element;
809 double const Qi = ai.charge;
810#ifdef _OPENMP
811 localEnergyScreen += -Qi * Qi
812 / (2 * sigmaSqrtPi(ei) * fourPiEps);
813#else
814 energyScreen += -Qi * Qi
815 / (2 * sigmaSqrtPi(ei) * fourPiEps);
816#endif
817
818 for (size_t j = i + 1; j < numAtoms; ++j)
819 {
820 Atom const& aj = atoms.at(j);
821 double const Qj = aj.charge;
822 double const rij = (ai.r - aj.r).norm();
823 if ( rij < rcutScreen )
824 {
825#ifdef _OPENMP
826 localEnergyScreen += Qi * Qj * A(i, j) * (fs.f(rij) - 1);
827#else
828 energyScreen += Qi * Qj * A(i, j) * (fs.f(rij) - 1);
829#endif
830 }
831 }
832 }
833 }
834#ifdef _OPENMP
835 #pragma omp atomic
836 energyScreen += localEnergyScreen;
837 }
838#endif
839 //cout << "energyScreen = " << energyScreen << endl;
840 return energyScreen;
841}
842
843
845 EwaldSetup& ewaldSetup,
846 MatrixXd gammaSqrt2,
847 double const fourPiEps,
848 ErfcBuf& erfcBuf)
849{
850
851#ifdef _OPENMP
852 vector<Vec3D> dAdrQDiag(numAtoms);
853 #pragma omp parallel
854 {
855
856 #pragma omp declare reduction(vec_Vec3D_plus : std::vector<Vec3D> : \
857 std::transform(omp_out.begin(), omp_out.end(), omp_in.begin(), omp_out.begin(), std::plus<Vec3D>())) \
858 initializer(omp_priv = decltype(omp_orig)(omp_orig.size()))
859 #pragma omp for
860#endif
861 // TODO: This initialization loop could probably be avoid, maybe use
862 // default constructor?
863 for (size_t i = 0; i < numAtoms; ++i)
864 {
865 Atom &ai = atoms.at(i);
866 // dAdrQ(numAtoms+1,:) entries are zero
867 ai.dAdrQ.resize(numAtoms + 1);
868 }
869
870 if (isPeriodic)
871 {
872 // TODO: We need same Kspace grid as in calculateScreeningEnergy, should
873 // we cache it for reuse? Note that we can't calculate dAdrQ already in
874 // the loops of calculateElectrostaticEnergy because at this point we don't
875 // have the charges.
876
877 KspaceGrid grid;
878 grid.setup(box, ewaldSetup);
879 double rcutReal = ewaldSetup.params.rCut;
880 double const sqrt2eta = sqrt(2.0) * ewaldSetup.params.eta;
881
882#ifdef _OPENMP
883 // This way of parallelization slightly changes the result because of
884 // numerical errors.
885 #pragma omp for reduction(vec_Vec3D_plus : dAdrQDiag) schedule(dynamic)
886#endif
887 for (size_t i = 0; i < numAtoms; ++i)
888 {
889 Atom &ai = atoms.at(i);
890 size_t const ei = ai.element;
891 double const Qi = ai.charge;
892
893 // real part
894 size_t const numNeighbors = ai.getStoredMinNumNeighbors(rcutReal);
895 for (size_t k = 0; k < numNeighbors; ++k)
896 {
897 auto const &n = ai.neighbors[k];
898 size_t j = n.tag;
899 //if (j < i) continue;
900 if (j <= i) continue;
901
902 double const rij = n.d;
903 Atom &aj = atoms.at(j);
904 size_t const ej = aj.element;
905 double const Qj = aj.charge;
906
907 double erfcSqrt2Eta = erfcBuf.getf(i, k, 0, rij / sqrt2eta);
908 double erfcGammaSqrt2 =
909 erfcBuf.getf(i, k, 1, rij / gammaSqrt2(ei, ej));
910
911 Vec3D dAijdri;
912 dAijdri = n.dr / pow(rij, 2)
913 * (2 / sqrt(M_PI) * (-exp(-pow(rij / sqrt2eta, 2))
914 / sqrt2eta + exp(-pow(rij / gammaSqrt2(ei, ej), 2))
915 / gammaSqrt2(ei, ej)) - 1 / rij
916 * (erfcSqrt2Eta - erfcGammaSqrt2));
917 dAijdri /= fourPiEps;
918 // Make use of symmetry: dA_{ij}/dr_i = dA_{ji}/dr_i
919 // = -dA_{ji}/dr_j = -dA_{ij}/dr_j
920 ai.dAdrQ[i] += dAijdri * Qj;
921 ai.dAdrQ[j] += dAijdri * Qi;
922 aj.dAdrQ[i] -= dAijdri * Qj;
923#ifdef _OPENMP
924 dAdrQDiag[j] -= dAijdri * Qi;
925#else
926 aj.dAdrQ[j] -= dAijdri * Qi;
927#endif
928 }
929
930 // reciprocal part
931 for (size_t j = i + 1; j < numAtoms; ++j)
932 {
933 Atom &aj = atoms.at(j);
934 double const Qj = aj.charge;
935 Vec3D dAijdri;
936 for (auto const &gv: grid.kvectors)
937 {
938 // Multiply by 2 because our grid is only a half-sphere
939 //Vec3D const dr = applyMinimumImageConvention(ai.r - aj.r);
940 Vec3D const dr = ai.r - aj.r;
941 dAijdri -= 2 * gv.coeff * sin(gv.k * dr) * gv.k;
942 }
943 dAijdri /= fourPiEps;
944
945 ai.dAdrQ[i] += dAijdri * Qj;
946 ai.dAdrQ[j] += dAijdri * Qi;
947 aj.dAdrQ[i] -= dAijdri * Qj;
948#ifdef _OPENMP
949 dAdrQDiag[j] -= dAijdri * Qi;
950#else
951 aj.dAdrQ[j] -= dAijdri * Qi;
952#endif
953 }
954 }
955
956 } else
957 {
958#ifdef _OPENMP
959 #pragma omp for reduction(vec_Vec3D_plus : dAdrQDiag) schedule(dynamic)
960#endif
961 for (size_t i = 0; i < numAtoms; ++i)
962 {
963 Atom &ai = atoms.at(i);
964 size_t const ei = ai.element;
965 double const Qi = ai.charge;
966
967 for (size_t j = i + 1; j < numAtoms; ++j)
968 {
969 Atom &aj = atoms.at(j);
970 size_t const ej = aj.element;
971 double const Qj = aj.charge;
972
973 double rij = (ai.r - aj.r).norm();
974 Vec3D dAijdri;
975 dAijdri = (ai.r - aj.r) / pow(rij, 2)
976 * (2 / (sqrt(M_PI) * gammaSqrt2(ei, ej))
977 * exp(-pow(rij / gammaSqrt2(ei, ej), 2))
978 - erf(rij / gammaSqrt2(ei, ej)) / rij);
979 dAijdri /= fourPiEps;
980 // Make use of symmetry: dA_{ij}/dr_i = dA_{ji}/dr_i
981 // = -dA_{ji}/dr_j = -dA_{ij}/dr_j
982 ai.dAdrQ[i] += dAijdri * Qj;
983 ai.dAdrQ[j] = dAijdri * Qi;
984 aj.dAdrQ[i] = -dAijdri * Qj;
985#ifdef _OPENMP
986 dAdrQDiag[j] -= dAijdri * Qi;
987#else
988 aj.dAdrQ[j] -= dAijdri * Qi;
989#endif
990 }
991 }
992 }
993#ifdef _OPENMP
994 #pragma omp for
995 for (size_t i = 0; i < numAtoms; ++i)
996 {
997 Atom& ai = atoms[i];
998 ai.dAdrQ[i] += dAdrQDiag[i];
999 }
1000 }
1001#endif
1002 return;
1003}
1004
1005void Structure::calculateDQdChi(vector<Eigen::VectorXd> &dQdChi)
1006{
1007 dQdChi.clear();
1008 dQdChi.reserve(numAtoms);
1009 for (size_t i = 0; i < numAtoms; ++i)
1010 {
1011 // Including Lagrange multiplier equation.
1012 VectorXd b(numAtoms+1);
1013 b.setZero();
1014 b(i) = -1.;
1015 dQdChi.push_back(A.colPivHouseholderQr().solve(b).head(numAtoms));
1016 }
1017 return;
1018}
1019
1020void Structure::calculateDQdJ(vector<Eigen::VectorXd> &dQdJ)
1021{
1022 dQdJ.clear();
1023 dQdJ.reserve(numElements);
1024 for (size_t i = 0; i < numElements; ++i)
1025 {
1026 // Including Lagrange multiplier equation.
1027 VectorXd b(numAtoms+1);
1028 b.setZero();
1029 for (size_t j = 0; j < numAtoms; ++j)
1030 {
1031 Atom const &aj = atoms.at(j);
1032 if (i == aj.element) b(j) = -aj.charge;
1033 }
1034 dQdJ.push_back(A.colPivHouseholderQr().solve(b).head(numAtoms));
1035 }
1036 return;
1037}
1038
1039void Structure::calculateDQdr( vector<size_t> const& atomIndices,
1040 vector<size_t> const& compIndices,
1041 double const maxCutoffRadius,
1042 vector<Element> const& elements)
1043{
1044 if (atomIndices.size() != compIndices.size())
1045 throw runtime_error("ERROR: In calculation of dQ/dr both atom index and"
1046 " component index must be specified.");
1047 for (size_t i = 0; i < atomIndices.size(); ++i)
1048 {
1049 Atom& a = atoms.at(atomIndices[i]);
1050 if ( a.dAdrQ.size() == 0 )
1051 throw runtime_error("ERROR: dAdrQ needs to be calculated before "
1052 "calculating dQdr");
1053 a.dQdr.resize(numAtoms);
1054
1055 // b stores (-dChidr - dAdrQ), last element for charge conservation.
1056 VectorXd b(numAtoms + 1);
1057 b.setZero();
1058 for (size_t j = 0; j < numAtoms; ++j)
1059 {
1060 Atom const& aj = atoms.at(j);
1061
1062#ifndef N2P2_FULL_SFD_MEMORY
1063 vector<vector<size_t> > const *const tableFull
1064 = &(elements.at(aj.element).getSymmetryFunctionTable());
1065#else
1066 vector<vector<size_t> > const *const tableFull = nullptr;
1067#endif
1068 b(j) -= aj.calculateDChidr(atomIndices[i],
1069 maxCutoffRadius,
1070 tableFull)[compIndices[i]];
1071 b(j) -= a.dAdrQ.at(j)[compIndices[i]];
1072 }
1073 VectorXd dQdr = A.colPivHouseholderQr().solve(b).head(numAtoms);
1074 for (size_t j = 0; j < numAtoms; ++j)
1075 {
1076 a.dQdr.at(j)[compIndices[i]] = dQdr(j);
1077 }
1078 }
1079 return;
1080}
1081
1082
1084 Eigen::VectorXd hardness,
1085 Eigen::MatrixXd gammaSqrt2,
1086 VectorXd sigmaSqrtPi,
1087 ScreeningFunction const& fs,
1088 double const fourPiEps)
1089{
1090 // Reset in case structure is used again (e.g. during training)
1091 for (Atom &ai : atoms)
1092 {
1093 ai.pEelecpr = Vec3D{};
1094 ai.dEelecdQ = 0.0;
1095 }
1096
1097 double rcutScreen = fs.getOuter();
1098 for (size_t i = 0; i < numAtoms; ++i)
1099 {
1100 Atom& ai = atoms.at(i);
1101 size_t const ei = ai.element;
1102 double const Qi = ai.charge;
1103
1104 // TODO: This loop could be reduced by making use of symmetry, j>=i or
1105 // so
1106 for (size_t j = 0; j < numAtoms; ++j)
1107 {
1108 Atom& aj = atoms.at(j);
1109 double const Qj = aj.charge;
1110
1111 ai.pEelecpr += 0.5 * Qj * ai.dAdrQ[j];
1112
1113 // Diagonal terms contain self-interaction --> screened
1114 if (i != j) ai.dEelecdQ += Qj * A(i,j);
1115 else if (isPeriodic)
1116 {
1117 ai.dEelecdQ += Qi * (A(i,i) - hardness(ei)
1118 - 1 / (sigmaSqrtPi(ei) * fourPiEps));
1119 }
1120 }
1121
1122 if (isPeriodic)
1123 {
1124 for (auto const& ajN : ai.neighbors)
1125 {
1126 size_t j = ajN.tag;
1127 Atom& aj = atoms.at(j);
1128 if (j < i) continue;
1129 double const rij = ajN.d;
1130 if (rij >= rcutScreen) break;
1131
1132 size_t const ej = aj.element;
1133 double const Qj = atoms.at(j).charge;
1134
1135 double erfRij = erf(rij / gammaSqrt2(ei,ej));
1136 double fsRij = fs.f(rij);
1137
1138 // corrections due to screening
1139 Vec3D Tij = Qi * Qj * ajN.dr / pow(rij,2)
1140 * (2 / (sqrt(M_PI) * gammaSqrt2(ei,ej))
1141 * exp(- pow(rij / gammaSqrt2(ei,ej),2))
1142 * (fsRij - 1) + erfRij * fs.df(rij) - erfRij
1143 * (fsRij - 1) / rij);
1144 Tij /= fourPiEps;
1145 ai.pEelecpr += Tij;
1146 aj.pEelecpr -= Tij;
1147
1148 double Sij = erfRij * (fsRij - 1) / rij;
1149 Sij /= fourPiEps;
1150 ai.dEelecdQ += Qj * Sij;
1151 aj.dEelecdQ += Qi * Sij;
1152 }
1153 }
1154 else
1155 {
1156 for (size_t j = i + 1; j < numAtoms; ++j)
1157 {
1158 Atom& aj = atoms.at(j);
1159 double const rij = (ai.r - aj.r).norm();
1160 if (rij >= rcutScreen) continue;
1161
1162 size_t const ej = aj.element;
1163 double const Qj = atoms.at(j).charge;
1164
1165 double erfRij = erf(rij / gammaSqrt2(ei,ej));
1166 double fsRij = fs.f(rij);
1167
1168 // corrections due to screening
1169 Vec3D Tij = Qi * Qj * (ai.r - aj.r) / pow(rij,2)
1170 * (2 / (sqrt(M_PI) * gammaSqrt2(ei,ej))
1171 * exp(- pow(rij / gammaSqrt2(ei,ej),2))
1172 * (fsRij - 1) + erfRij * fs.df(rij) - erfRij
1173 * (fsRij - 1) / rij);
1174 Tij /= fourPiEps;
1175 ai.pEelecpr += Tij;
1176 aj.pEelecpr -= Tij;
1177
1178 double Sij = erfRij * (fsRij - 1) / rij;
1179 Sij /= fourPiEps;
1180 ai.dEelecdQ += Qj * Sij;
1181 aj.dEelecdQ += Qi * Sij;
1182 }
1183 }
1184 }
1185 return;
1186}
1187
1189{
1190 VectorXd dEdQ(numAtoms+1);
1191 for (size_t i = 0; i < numAtoms; ++i)
1192 {
1193 Atom const& ai = atoms.at(i);
1194 dEdQ(i) = ai.dEelecdQ + ai.dEdG.back();
1195 }
1196 dEdQ(numAtoms) = 0;
1197 VectorXd const lambdaTotal = A.colPivHouseholderQr().solve(-dEdQ);
1198 return lambdaTotal;
1199}
1200
1202{
1203 VectorXd dEelecdQ(numAtoms+1);
1204 for (size_t i = 0; i < numAtoms; ++i)
1205 {
1206 Atom const& ai = atoms.at(i);
1207 dEelecdQ(i) = ai.dEelecdQ;
1208 }
1209 dEelecdQ(numAtoms) = 0;
1210 VectorXd const lambdaElec = A.colPivHouseholderQr().solve(-dEelecdQ);
1211 return lambdaElec;
1212}
1213
1215{
1216 for (vector<Atom>::iterator it = atoms.begin(); it != atoms.end(); ++it)
1217 {
1218 remap((*it));
1219 }
1220 return;
1221}
1222
1224{
1225 Vec3D f = atom.r[0] * invbox[0]
1226 + atom.r[1] * invbox[1]
1227 + atom.r[2] * invbox[2];
1228
1229 // Quick and dirty... there may be a more elegant way!
1230 if (f[0] > 1.0) f[0] -= (int)f[0];
1231 if (f[1] > 1.0) f[1] -= (int)f[1];
1232 if (f[2] > 1.0) f[2] -= (int)f[2];
1233
1234 if (f[0] < 0.0) f[0] += 1.0 - (int)f[0];
1235 if (f[1] < 0.0) f[1] += 1.0 - (int)f[1];
1236 if (f[2] < 0.0) f[2] += 1.0 - (int)f[2];
1237
1238 if (f[0] == 1.0) f[0] = 0.0;
1239 if (f[1] == 1.0) f[1] = 0.0;
1240 if (f[2] == 1.0) f[2] = 0.0;
1241
1242 atom.r = f[0] * box[0]
1243 + f[1] * box[1]
1244 + f[2] * box[2];
1245
1246 return;
1247}
1248
1249void Structure::toNormalizedUnits(double meanEnergy,
1250 double convEnergy,
1251 double convLength,
1252 double convCharge)
1253{
1254 if (isPeriodic)
1255 {
1256 box[0] *= convLength;
1257 box[1] *= convLength;
1258 box[2] *= convLength;
1259 invbox[0] /= convLength;
1260 invbox[1] /= convLength;
1261 invbox[2] /= convLength;
1262 }
1263
1264 energyRef = (energyRef - numAtoms * meanEnergy) * convEnergy;
1265 energy = (energy - numAtoms * meanEnergy) * convEnergy;
1266 chargeRef *= convCharge;
1267 charge *= convCharge;
1268 volume *= convLength * convLength * convLength;
1269
1270 for (vector<Atom>::iterator it = atoms.begin(); it != atoms.end(); ++it)
1271 {
1272 it->toNormalizedUnits(convEnergy, convLength, convCharge);
1273 }
1274
1275 return;
1276}
1277
1278void Structure::toPhysicalUnits(double meanEnergy,
1279 double convEnergy,
1280 double convLength,
1281 double convCharge)
1282{
1283 if (isPeriodic)
1284 {
1285 box[0] /= convLength;
1286 box[1] /= convLength;
1287 box[2] /= convLength;
1288 invbox[0] *= convLength;
1289 invbox[1] *= convLength;
1290 invbox[2] *= convLength;
1291 }
1292
1293 energyRef = energyRef / convEnergy + numAtoms * meanEnergy;
1294 energy = energy / convEnergy + numAtoms * meanEnergy;
1295 chargeRef /= convCharge;
1296 charge /= convCharge;
1297 volume /= convLength * convLength * convLength;
1298
1299 for (vector<Atom>::iterator it = atoms.begin(); it != atoms.end(); ++it)
1300 {
1301 it->toPhysicalUnits(convEnergy, convLength, convCharge);
1302 }
1303
1304 return;
1305}
1306
1307void Structure::freeAtoms(bool all, double const maxCutoffRadius)
1308{
1309 for (vector<Atom>::iterator it = atoms.begin(); it != atoms.end(); ++it)
1310 {
1311 it->free(all, maxCutoffRadius);
1312 }
1313 if (all) hasSymmetryFunctions = false;
1315
1316 return;
1317}
1318
1320{
1321 isPeriodic = false ;
1322 isTriclinic = false ;
1323 hasNeighborList = false ;
1324 hasSymmetryFunctions = false ;
1326 index = 0 ;
1327 numAtoms = 0 ;
1328 numElementsPresent = 0 ;
1329 energy = 0.0 ;
1330 energyRef = 0.0 ;
1331 charge = 0.0 ;
1332 chargeRef = 0.0 ;
1333 volume = 0.0 ;
1335 comment = "" ;
1336
1337 for (size_t i = 0; i < 3; ++i)
1338 {
1339 pbc[i] = 0;
1340 for (size_t j = 0; j < 3; ++j)
1341 {
1342 box[i][j] = 0.0;
1343 invbox[i][j] = 0.0;
1344 }
1345 }
1346
1348 numAtomsPerElement.clear();
1350 atoms.clear();
1351 vector<Atom>(atoms).swap(atoms);
1352
1353 return;
1354}
1355
1357{
1358 for (size_t i = 0; i < numAtoms; i++)
1359 {
1360 Atom& a = atoms.at(i);
1361 // This may have changed if atoms are added via addAtoms().
1364 }
1365 hasNeighborList = false;
1366 hasSymmetryFunctions = false;
1368
1369 return;
1370}
1371
1373{
1374 A.resize(0,0);
1375 hasAMatrix = false;
1376 for (auto& a : atoms)
1377 {
1378 vector<Vec3D>().swap(a.dAdrQ);
1379 if (clearDQdr)
1380 {
1381 vector<Vec3D>().swap(a.dQdr);
1382 }
1383 }
1384}
1385
1386void Structure::updateError(string const& property,
1387 map<string, double>& error,
1388 size_t& count) const
1389{
1390 if (property == "energy")
1391 {
1392 count++;
1393 double diff = energyRef - energy;
1394 error.at("RMSEpa") += diff * diff / (numAtoms * numAtoms);
1395 error.at("RMSE") += diff * diff;
1396 diff = fabs(diff);
1397 error.at("MAEpa") += diff / numAtoms;
1398 error.at("MAE") += diff;
1399 }
1400 else if (property == "force" || property == "charge")
1401 {
1402 for (vector<Atom>::const_iterator it = atoms.begin();
1403 it != atoms.end(); ++it)
1404 {
1405 it->updateError(property, error, count);
1406 }
1407 }
1408 else
1409 {
1410 throw runtime_error("ERROR: Unknown property for error update.\n");
1411 }
1412
1413 return;
1414}
1415
1417{
1418 return strpr("%10zu %16.8E %16.8E\n",
1419 index,
1421 energy / numAtoms);
1422}
1423
1424vector<string> Structure::getForcesLines() const
1425{
1426 vector<string> v;
1427 for (vector<Atom>::const_iterator it = atoms.begin();
1428 it != atoms.end(); ++it)
1429 {
1430 vector<string> va = it->getForcesLines();
1431 v.insert(v.end(), va.begin(), va.end());
1432 }
1433
1434 return v;
1435}
1436
1437vector<string> Structure::getChargesLines() const
1438{
1439 vector<string> v;
1440 for (vector<Atom>::const_iterator it = atoms.begin();
1441 it != atoms.end(); ++it)
1442 {
1443 v.push_back(it->getChargeLine());
1444 }
1445
1446 return v;
1447}
1448
1449void Structure::writeToFile(string const fileName,
1450 bool const ref,
1451 bool const append) const
1452{
1453 ofstream file;
1454
1455 if (append)
1456 {
1457 file.open(fileName.c_str(), ofstream::app);
1458 }
1459 else
1460 {
1461 file.open(fileName.c_str());
1462 }
1463 if (!file.is_open())
1464 {
1465 throw runtime_error("ERROR: Could not open file: \"" + fileName
1466 + "\".\n");
1467 }
1468 writeToFile(&file, ref );
1469 file.close();
1470
1471 return;
1472}
1473
1474void Structure::writeToFile(ofstream* const& file, bool const ref) const
1475{
1476 if (!file->is_open())
1477 {
1478 runtime_error("ERROR: Cannot write to file, file is not open.\n");
1479 }
1480
1481 (*file) << "begin\n";
1482 (*file) << strpr("comment %s\n", comment.c_str());
1483 if (isPeriodic)
1484 {
1485 for (size_t i = 0; i < 3; ++i)
1486 {
1487 (*file) << strpr("lattice %24.16E %24.16E %24.16E\n",
1488 box[i][0], box[i][1], box[i][2]);
1489 }
1490 }
1491 for (vector<Atom>::const_iterator it = atoms.begin();
1492 it != atoms.end(); ++it)
1493 {
1494 if (ref)
1495 {
1496 (*file) << strpr("atom %24.16E %24.16E %24.16E %2s %24.16E %24.16E"
1497 " %24.16E %24.16E %24.16E\n",
1498 it->r[0],
1499 it->r[1],
1500 it->r[2],
1501 elementMap[it->element].c_str(),
1502 it->chargeRef,
1503 0.0,
1504 it->fRef[0],
1505 it->fRef[1],
1506 it->fRef[2]);
1507 }
1508 else
1509 {
1510 (*file) << strpr("atom %24.16E %24.16E %24.16E %2s %24.16E %24.16E"
1511 " %24.16E %24.16E %24.16E\n",
1512 it->r[0],
1513 it->r[1],
1514 it->r[2],
1515 elementMap[it->element].c_str(),
1516 it->charge,
1517 0.0,
1518 it->f[0],
1519 it->f[1],
1520 it->f[2]);
1521
1522 }
1523 }
1524 if (ref) (*file) << strpr("energy %24.16E\n", energyRef);
1525 else (*file) << strpr("energy %24.16E\n", energy);
1526 if (ref) (*file) << strpr("charge %24.16E\n", chargeRef);
1527 else (*file) << strpr("charge %24.16E\n", charge);
1528 (*file) << strpr("end\n");
1529
1530 return;
1531}
1532
1533void Structure::writeToFileXyz(ofstream* const& file) const
1534{
1535 if (!file->is_open())
1536 {
1537 runtime_error("ERROR: Could not write to file.\n");
1538 }
1539
1540 (*file) << strpr("%d\n", numAtoms);
1541 if (isPeriodic)
1542 {
1543 (*file) << "Lattice=\"";
1544 (*file) << strpr("%24.16E %24.16E %24.16E " ,
1545 box[0][0], box[0][1], box[0][2]);
1546 (*file) << strpr("%24.16E %24.16E %24.16E " ,
1547 box[1][0], box[1][1], box[1][2]);
1548 (*file) << strpr("%24.16E %24.16E %24.16E\"\n",
1549 box[2][0], box[2][1], box[2][2]);
1550 }
1551 else
1552 {
1553 (*file) << "\n";
1554 }
1555 for (vector<Atom>::const_iterator it = atoms.begin();
1556 it != atoms.end(); ++it)
1557 {
1558 (*file) << strpr("%-2s %24.16E %24.16E %24.16E\n",
1559 elementMap[it->element].c_str(),
1560 it->r[0],
1561 it->r[1],
1562 it->r[2]);
1563 }
1564
1565 return;
1566}
1567
1568void Structure::writeToFilePoscar(ofstream* const& file) const
1569{
1571
1572 return;
1573}
1574
1575void Structure::writeToFilePoscar(ofstream* const& file,
1576 string const elements) const
1577{
1578 if (!file->is_open())
1579 {
1580 runtime_error("ERROR: Could not write to file.\n");
1581 }
1582
1583 vector<string> ve = split(elements);
1584 vector<size_t> elementOrder;
1585 for (size_t i = 0; i < ve.size(); ++i)
1586 {
1587 elementOrder.push_back(elementMap[ve.at(i)]);
1588 }
1589 if (elementOrder.size() != elementMap.size())
1590 {
1591 runtime_error("ERROR: Inconsistent element declaration.\n");
1592 }
1593
1594 (*file) << strpr("%s, ", comment.c_str());
1595 (*file) << strpr("ATOM=%s", elementMap[elementOrder.at(0)].c_str());
1596 for (size_t i = 1; i < elementOrder.size(); ++i)
1597 {
1598 (*file) << strpr(" %s", elementMap[elementOrder.at(i)].c_str());
1599 }
1600 (*file) << "\n";
1601 (*file) << "1.0\n";
1602 if (isPeriodic)
1603 {
1604 (*file) << strpr("%24.16E %24.16E %24.16E\n",
1605 box[0][0], box[0][1], box[0][2]);
1606 (*file) << strpr("%24.16E %24.16E %24.16E\n",
1607 box[1][0], box[1][1], box[1][2]);
1608 (*file) << strpr("%24.16E %24.16E %24.16E\n",
1609 box[2][0], box[2][1], box[2][2]);
1610 }
1611 else
1612 {
1613 runtime_error("ERROR: Writing non-periodic structure to POSCAR file "
1614 "is not implemented.\n");
1615 }
1616 (*file) << strpr("%d", numAtomsPerElement.at(elementOrder.at(0)));
1617 for (size_t i = 1; i < numAtomsPerElement.size(); ++i)
1618 {
1619 (*file) << strpr(" %d", numAtomsPerElement.at(elementOrder.at(i)));
1620 }
1621 (*file) << "\n";
1622 (*file) << "Cartesian\n";
1623 for (size_t i = 0; i < elementOrder.size(); ++i)
1624 {
1625 for (vector<Atom>::const_iterator it = atoms.begin();
1626 it != atoms.end(); ++it)
1627 {
1628 if (it->element == elementOrder.at(i))
1629 {
1630 (*file) << strpr("%24.16E %24.16E %24.16E\n",
1631 it->r[0],
1632 it->r[1],
1633 it->r[2]);
1634 }
1635 }
1636 }
1637
1638 return;
1639}
1640
1641vector<string> Structure::info() const
1642{
1643 vector<string> v;
1644
1645 v.push_back(strpr("********************************\n"));
1646 v.push_back(strpr("STRUCTURE \n"));
1647 v.push_back(strpr("********************************\n"));
1648 vector<string> vm = elementMap.info();
1649 v.insert(v.end(), vm.begin(), vm.end());
1650 v.push_back(strpr("index : %d\n", index));
1651 v.push_back(strpr("isPeriodic : %d\n", isPeriodic ));
1652 v.push_back(strpr("isTriclinic : %d\n", isTriclinic ));
1653 v.push_back(strpr("hasNeighborList : %d\n", hasNeighborList ));
1654 v.push_back(strpr("hasSymmetryFunctions : %d\n", hasSymmetryFunctions));
1655 v.push_back(strpr("hasSymmetryFunctionDerivatives : %d\n", hasSymmetryFunctionDerivatives));
1656 v.push_back(strpr("numAtoms : %d\n", numAtoms ));
1657 v.push_back(strpr("numElements : %d\n", numElements ));
1658 v.push_back(strpr("numElementsPresent : %d\n", numElementsPresent));
1659 v.push_back(strpr("pbc : %d %d %d\n", pbc[0], pbc[1], pbc[2]));
1660 v.push_back(strpr("energy : %16.8E\n", energy ));
1661 v.push_back(strpr("energyRef : %16.8E\n", energyRef ));
1662 v.push_back(strpr("charge : %16.8E\n", charge ));
1663 v.push_back(strpr("chargeRef : %16.8E\n", chargeRef ));
1664 v.push_back(strpr("volume : %16.8E\n", volume ));
1665 v.push_back(strpr("sampleType : %d\n", (int)sampleType));
1666 v.push_back(strpr("comment : %s\n", comment.c_str() ));
1667 v.push_back(strpr("box[0] : %16.8E %16.8E %16.8E\n", box[0][0], box[0][1], box[0][2]));
1668 v.push_back(strpr("box[1] : %16.8E %16.8E %16.8E\n", box[1][0], box[1][1], box[1][2]));
1669 v.push_back(strpr("box[2] : %16.8E %16.8E %16.8E\n", box[2][0], box[2][1], box[2][2]));
1670 v.push_back(strpr("invbox[0] : %16.8E %16.8E %16.8E\n", invbox[0][0], invbox[0][1], invbox[0][2]));
1671 v.push_back(strpr("invbox[1] : %16.8E %16.8E %16.8E\n", invbox[1][0], invbox[1][1], invbox[1][2]));
1672 v.push_back(strpr("invbox[2] : %16.8E %16.8E %16.8E\n", invbox[2][0], invbox[2][1], invbox[2][2]));
1673 v.push_back(strpr("--------------------------------\n"));
1674 v.push_back(strpr("numAtomsPerElement [*] : %d\n", numAtomsPerElement.size()));
1675 v.push_back(strpr("--------------------------------\n"));
1676 for (size_t i = 0; i < numAtomsPerElement.size(); ++i)
1677 {
1678 v.push_back(strpr("%29d : %d\n", i, numAtomsPerElement.at(i)));
1679 }
1680 v.push_back(strpr("--------------------------------\n"));
1681 v.push_back(strpr("--------------------------------\n"));
1682 v.push_back(strpr("atoms [*] : %d\n", atoms.size()));
1683 v.push_back(strpr("--------------------------------\n"));
1684 for (size_t i = 0; i < atoms.size(); ++i)
1685 {
1686 v.push_back(strpr("%29d :\n", i));
1687 vector<string> va = atoms[i].info();
1688 v.insert(v.end(), va.begin(), va.end());
1689 }
1690 v.push_back(strpr("--------------------------------\n"));
1691 v.push_back(strpr("********************************\n"));
1692
1693 return v;
1694}
Contains element map.
Definition: ElementMap.h:30
std::vector< std::string > info() const
Get map information as a vector of strings.
Definition: ElementMap.cpp:125
std::size_t size() const
Get element map size.
Definition: ElementMap.h:140
std::string getElementsString() const
Get sorted list of elements in one string (space separated).
Definition: ElementMap.cpp:56
Setup data for Ewald summation.
Definition: EwaldSetup.h:37
EwaldParameters params
Definition: EwaldSetup.h:39
void calculateParameters(double const newVolume, size_t const newNumAtoms)
Compute eta, rCut and kCut.
Definition: EwaldSetup.cpp:88
std::vector< Kvector > kvectors
Vector containing all k-vectors.
Definition: Kspace.h:61
void setup(Vec3D box[3], EwaldSetup &ewaldSetup)
Set up reciprocal box vectors and eta.
Definition: Kspace.cpp:45
A screening functions for use with electrostatics.
double getOuter() const
Getter for outer.
double df(double r) const
Derivative of screening function .
double f(double r) const
Screening function .
Definition: Atom.h:29
string strpr(const char *format,...)
String version of printf function.
Definition: utility.cpp:90
bool vectorContains(std::vector< T > const &stdVec, T value)
Test if vector contains specified value.
Definition: utility.h:166
vector< string > split(string const &input, char delimiter)
Split string at each delimiter.
Definition: utility.cpp:33
string reduce(string const &line, string const &whitespace, string const &fill)
Replace multiple whitespaces with fill.
Definition: utility.cpp:60
Struct to store information on neighbor atoms.
Definition: Atom.h:36
double d
Distance to neighbor atom.
Definition: Atom.h:44
Storage for a single atom.
Definition: Atom.h:33
std::vector< Neighbor > neighbors
Neighbor array (maximum number defined in macros.h.
Definition: Atom.h:170
Vec3D r
Cartesian coordinates.
Definition: Atom.h:125
std::vector< double > dEdG
Derivative of atomic energy with respect to symmetry functions.
Definition: Atom.h:149
Vec3D calculateDChidr(size_t const atomIndexOfR, double const maxCutoffRadius, std::vector< std::vector< size_t > > const *const tableFull=nullptr) const
Calculate dChi/dr of this atom's Chi with respect to the coordinates of the given atom.
Definition: Atom.cpp:403
double charge
Atomic charge determined by neural network.
Definition: Atom.h:121
std::size_t getStoredMinNumNeighbors(double const cutoffRadius) const
Return needed number of neighbors for a given cutoff radius from neighborCutoffs map.
Definition: Atom.cpp:329
double chi
Atomic electronegativity determined by neural network.
Definition: Atom.h:119
void clearNeighborList()
Clear neighbor list.
Definition: Atom.cpp:285
Vec3D pEelecpr
Partial derivative of electrostatic energy with respect to this atom's coordinates.
Definition: Atom.h:134
std::vector< Vec3D > dAdrQ
If dQdr has been calculated for respective components.
Definition: Atom.h:168
std::size_t element
Element index of this atom.
Definition: Atom.h:107
std::vector< Vec3D > dQdr
Derivative of charges with respect to this atom's coordinates.
Definition: Atom.h:163
double dEelecdQ
Derivative of electrostatic energy with respect to this atom's charge.
Definition: Atom.h:117
std::vector< std::size_t > numNeighborsPerElement
Number of neighbors per element.
Definition: Atom.h:138
Helper class to store previously calculated values of erfc() that are needed during the charge equili...
Definition: ErfcBuf.h:17
double getf(size_t const atomIndex, size_t const neighIndex, size_t const valIndex, double const x)
Either returns already stored value erfc(x) or calculates it if not yet stored.
Definition: ErfcBuf.cpp:21
void reset(std::vector< Atom > const &atoms, size_t const valuesPerPair)
Resizes and resets the storage array to fit the current structure.
Definition: ErfcBuf.cpp:10
double rCut
Cutoff in real space.
Definition: IEwaldTrunc.h:42
double eta
Width of the gaussian screening charges.
Definition: IEwaldTrunc.h:40
void freeAtoms(bool all, double const maxCutoffRadius=0.0)
Free symmetry function memory for all atoms, see free() in Atom class.
Definition: Structure.cpp:1307
void calculateVolume()
Calculate volume from box vectors.
Definition: Structure.cpp:581
@ ST_TRAINING
Structure is part of the training set.
Definition: Structure.h:49
@ ST_UNKNOWN
Sample type not assigned yet.
Definition: Structure.h:46
@ ST_TEST
Structure is part of the test set.
Definition: Structure.h:55
Vec3D invbox[3]
Inverse simulation box vectors.
Definition: Structure.h:114
void toPhysicalUnits(double meanEnergy, double convEnergy, double convLength, double convCharge)
Switch to physical units, shift energy and change energy, length and charge unit.
Definition: Structure.cpp:1278
Vec3D box[3]
Simulation box vectors.
Definition: Structure.h:112
bool isTriclinic
If the simulation box is triclinic.
Definition: Structure.h:63
std::size_t getMaxNumNeighbors() const
Find maximum number of neighbors.
Definition: Structure.cpp:473
void setupNeighborCutoffMap(std::vector< std::vector< double > > cutoffs)
Set up a neighbor cut-off map which gives the index (value) of the last needed neighbor corresponding...
Definition: Structure.cpp:423
void sortNeighborList()
Sort all neighbor lists of this structure with respect to the distance.
Definition: Structure.cpp:409
Eigen::VectorXd const calculateForceLambdaElec() const
Calculate lambda_elec vector which is needed for the electrostatic force calculation in 4G NN.
Definition: Structure.cpp:1201
std::vector< std::size_t > numAtomsPerElement
Number of atoms of each element in this structure.
Definition: Structure.h:120
double lambda
Lagrange multiplier used for charge equilibration.
Definition: Structure.h:106
void calculateMaxCutoffRadiusOverall(EwaldSetup &ewaldSetup, double rcutScreen, double maxCutoffRadius)
Calculate maximal cut-off if cut-off of screening and real part Ewald summation are also considered.
Definition: Structure.cpp:273
bool canMinimumImageConventionBeApplied(double cutoffRadius)
Check if cut-off radius is small enough to apply minimum image convention.
Definition: Structure.cpp:548
void writeToFile(std::string const fileName="output.data", bool const ref=true, bool const append=false) const
Write configuration to file.
Definition: Structure.cpp:1449
void calculateDQdChi(std::vector< Eigen::VectorXd > &dQdChi)
Calculates derivative of the charges with respect to electronegativities.
Definition: Structure.cpp:1005
std::string comment
Structure comment.
Definition: Structure.h:110
bool isPeriodic
If periodic boundary conditions apply.
Definition: Structure.h:61
double maxCutoffRadiusOverall
Maximum cut-off radius with respect to symmetry functions, screening function and Ewald summation.
Definition: Structure.h:103
double charge
Charge determined by neural network potential.
Definition: Structure.h:96
double calculateElectrostaticEnergy(EwaldSetup &ewaldSetup, Eigen::VectorXd hardness, Eigen::MatrixXd gammaSqrt2, Eigen::VectorXd sigmaSqrtPi, ScreeningFunction const &fs, double const fourPiEps, ErfcBuf &erfcBuf)
Compute electrostatic energy with global charge equilibration.
Definition: Structure.cpp:588
bool NeighborListIsSorted
If the neighbor list has been sorted by distance.
Definition: Structure.h:67
ElementMap elementMap
Copy of element map provided as constructor argument.
Definition: Structure.h:59
void setElementMap(ElementMap const &elementMap)
Set element map of structure.
Definition: Structure.cpp:71
std::size_t index
Index number of this structure.
Definition: Structure.h:73
void remap()
Translate all atoms back into box if outside.
Definition: Structure.cpp:1214
std::vector< std::string > getChargesLines() const
Get reference and NN charges for all atoms.
Definition: Structure.cpp:1437
double chargeRef
Reference charge.
Definition: Structure.h:98
SampleType sampleType
Sample type (training or test set).
Definition: Structure.h:108
void calculateDQdJ(std::vector< Eigen::VectorXd > &dQdJ)
Calculates derivative of the charges with respect to atomic hardness.
Definition: Structure.cpp:1020
bool hasAMatrix
If A matrix of this structure is currently stored.
Definition: Structure.h:118
void addAtom(Atom const &atom, std::string const &element)
Add a single atom to structure.
Definition: Structure.cpp:80
void calculateInverseBox()
Calculate inverse box.
Definition: Structure.cpp:519
void writeToFilePoscar(std::ofstream *const &file) const
Write configuration to POSCAR file.
Definition: Structure.cpp:1568
void writeToFileXyz(std::ofstream *const &file) const
Write configuration to xyz file.
Definition: Structure.cpp:1533
bool hasSymmetryFunctionDerivatives
If symmetry function derivatives are saved for each atom.
Definition: Structure.h:71
void calculateDQdr(std::vector< size_t > const &atomsIndices, std::vector< size_t > const &compIndices, double const maxCutoffRadius, std::vector< Element > const &elements)
Calculates derivative of the charges with respect to the atom's position.
Definition: Structure.cpp:1039
Eigen::MatrixXd A
Global charge equilibration matrix A'.
Definition: Structure.h:116
void calculatePbcCopies(double cutoffRadius, int(&pbc)[3])
Calculate required PBC copies.
Definition: Structure.cpp:486
Eigen::VectorXd const calculateForceLambdaTotal() const
Calculate lambda_total vector which is needed for the total force calculation in 4G NN.
Definition: Structure.cpp:1188
std::string getEnergyLine() const
Get reference and NN energy.
Definition: Structure.cpp:1416
void clearElectrostatics(bool clearDQdr=false)
Clear A-matrix, dAdrQ and optionally dQdr.
Definition: Structure.cpp:1372
double energyElec
Electrostatics part of the potential energy predicted by NNP.
Definition: Structure.h:91
double energy
Potential energy determined by neural network.
Definition: Structure.h:83
void readFromFile(std::string const fileName="input.data")
Read configuration from file.
Definition: Structure.cpp:97
double energyRef
Reference potential energy.
Definition: Structure.h:85
int pbc[3]
Number of PBC images necessary in each direction for max cut-off.
Definition: Structure.h:81
void toNormalizedUnits(double meanEnergy, double convEnergy, double convLength, double convCharge)
Normalize structure, shift energy and change energy, length and charge unit.
Definition: Structure.cpp:1249
void reset()
Reset everything but elementMap.
Definition: Structure.cpp:1319
std::vector< std::string > getForcesLines() const
Get reference and NN forces for all atoms.
Definition: Structure.cpp:1424
double volume
Simulation box volume.
Definition: Structure.h:100
void calculateElectrostaticEnergyDerivatives(Eigen::VectorXd hardness, Eigen::MatrixXd gammaSqrt2, Eigen::VectorXd sigmaSqrtPi, ScreeningFunction const &fs, double const fourPiEps)
Calculates partial derivatives of electrostatic Energy with respect to atom's coordinates and charges...
Definition: Structure.cpp:1083
Vec3D applyMinimumImageConvention(Vec3D const &dr)
Calculate distance between two atoms in the minimum image convention.
Definition: Structure.cpp:567
void calculateNeighborList(double cutoffRadius, bool sortByDistance=false)
Calculate neighbor list for all atoms.
Definition: Structure.cpp:287
std::size_t numAtoms
Total number of atoms present in this structure.
Definition: Structure.h:75
std::size_t numElements
Global number of elements (all structures).
Definition: Structure.h:77
void updateError(std::string const &property, std::map< std::string, double > &error, std::size_t &count) const
Update property error metrics with this structure.
Definition: Structure.cpp:1386
void calculateDAdrQ(EwaldSetup &ewaldSetup, Eigen::MatrixXd gammaSqrt2, double const fourPiEps, ErfcBuf &erfcBuf)
Calculates derivative of A-matrix with respect to the atoms positions and contract it with Q.
Definition: Structure.cpp:844
std::vector< Atom > atoms
Vector of all atoms in this structure.
Definition: Structure.h:122
std::size_t numElementsPresent
Number of elements present in this structure.
Definition: Structure.h:79
void readFromLines(std::vector< std::string > const &lines)
Read configuration from lines.
Definition: Structure.cpp:142
double calculateScreeningEnergy(Eigen::MatrixXd gammaSqrt2, Eigen::VectorXd sigmaSqrtPi, ScreeningFunction const &fs, double const fourPiEps)
Calculate screening energy which needs to be added (!) to the electrostatic energy in order to remove...
Definition: Structure.cpp:746
bool hasNeighborList
If the neighbor list has been calculated.
Definition: Structure.h:65
void clearNeighborList()
Clear neighbor list of all atoms.
Definition: Structure.cpp:1356
std::vector< std::string > info() const
Get structure information as a vector of strings.
Definition: Structure.cpp:1641
bool hasCharges
If all charges of this structure have been calculated (and stay the same, e.g.
Definition: Structure.h:94
bool hasSymmetryFunctions
If symmetry function values are saved for each atom.
Definition: Structure.h:69
Vector in 3 dimensional real space.
Definition: Vec3D.h:30
double norm2() const
Calculate square of norm of vector.
Definition: Vec3D.h:299
Vec3D & normalize()
Normalize vector, norm equals 1.0 afterwards.
Definition: Vec3D.h:309
Vec3D cross(Vec3D const &v) const
Cross product, argument vector is second in product.
Definition: Vec3D.h:319
double norm() const
Calculate norm of vector.
Definition: Vec3D.h:294