1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2016 Kitware, Inc.
6 
7   This source code is released under the New BSD License, (the "License").
8 
9   Unless required by applicable law or agreed to in writing, software
10   distributed under the License is distributed on an "AS IS" BASIS,
11   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   See the License for the specific language governing permissions and
13   limitations under the License.
14 
15 ******************************************************************************/
16 
17 #include "vaspformat.h"
18 
19 #include <avogadro/core/elements.h> // for atomicNumberFromSymbol()
20 #include <avogadro/core/matrix.h>   // for matrix3
21 #include <avogadro/core/molecule.h>
22 #include <avogadro/core/unitcell.h>
23 #include <avogadro/core/utilities.h> // for split(), trimmed(), lexicalCast()
24 #include <avogadro/core/vector.h>    // for Vector3
25 
26 #include <algorithm> // for std::count()
27 #include <iomanip>
28 #include <iostream>
29 
30 namespace Avogadro {
31 namespace Io {
32 
33 using std::getline;
34 using std::map;
35 using std::string;
36 using std::vector;
37 
38 using Core::Array;
39 using Core::Atom;
40 using Core::Elements;
41 using Core::lexicalCast;
42 using Core::Molecule;
43 using Core::split;
44 using Core::trimmed;
45 using Core::UnitCell;
46 
PoscarFormat()47 PoscarFormat::PoscarFormat() {}
48 
~PoscarFormat()49 PoscarFormat::~PoscarFormat() {}
50 
read(std::istream & inStream,Core::Molecule & mol)51 bool PoscarFormat::read(std::istream& inStream, Core::Molecule& mol)
52 {
53   size_t numLines = std::count(std::istreambuf_iterator<char>(inStream),
54                                std::istreambuf_iterator<char>(), '\n');
55 
56   // There must be at least 7 "\n"'s to have a minimum crystal (including 1
57   // atom)
58   if (numLines < 7) {
59     appendError("Error: POSCAR file is 7 or fewer lines long");
60     return false;
61   }
62 
63   // We have to go back to the beginning if we are going to read again
64   inStream.clear();
65   inStream.seekg(0, std::ios::beg);
66 
67   // We'll use these throughout
68   bool ok;
69   string line;
70   vector<string> stringSplit;
71 
72   // First line is comment line
73   getline(inStream, line);
74   line = trimmed(line);
75   string title = " ";
76   if (!line.empty())
77     title = line;
78 
79   // Next line is scaling factor
80   getline(inStream, line);
81   double scalingFactor = lexicalCast<double>(line, ok);
82 
83   if (!ok) {
84     appendError("Error: Could not convert scaling factor to double in POSCAR");
85     return false;
86   }
87 
88   Matrix3 cellMat;
89 
90   // Next comes the matrix
91   for (size_t i = 0; i < 3; ++i) {
92     getline(inStream, line);
93     stringSplit = split(line, ' ');
94     // If this is not three, then there is some kind of error in the line
95     if (stringSplit.size() != 3) {
96       appendError("Error reading lattice vectors in POSCAR");
97       return false;
98     }
99     // UnitCell expects a matrix of this form
100     cellMat(0, i) = lexicalCast<double>(stringSplit.at(0)) * scalingFactor;
101     cellMat(1, i) = lexicalCast<double>(stringSplit.at(1)) * scalingFactor;
102     cellMat(2, i) = lexicalCast<double>(stringSplit.at(2)) * scalingFactor;
103   }
104 
105   // Sometimes, atomic symbols go here.
106   getline(inStream, line);
107   stringSplit = split(line, ' ');
108 
109   if (stringSplit.empty()) {
110     appendError("Error reading numbers of atom types in POSCAR");
111     return false;
112   }
113 
114   // Try a lexical cast here. If it fails, assume we have an atomic symbols list
115   lexicalCast<unsigned int>(trimmed(stringSplit.at(0)), ok);
116   vector<string> symbolsList;
117   vector<unsigned char> atomicNumbers;
118 
119   if (!ok) {
120     // Assume atomic symbols are here and store them
121     symbolsList = split(line, ' ');
122     // Store atomic nums
123     for (size_t i = 0; i < symbolsList.size(); ++i)
124       atomicNumbers.push_back(Elements::atomicNumberFromSymbol(symbolsList[i]));
125     // This next one should be atom types
126     getline(inStream, line);
127   }
128   // If the atomic symbols aren't here, try to find them in the title
129   // In Vasp 4.x, symbols are in the title like so: " O4H2 <restOfTitle>"
130   else {
131     stringSplit = split(title, ' ');
132     if (stringSplit.size() != 0) {
133       string trimmedFormula = trimmed(stringSplit.at(0));
134       // Let's replace all numbers with spaces
135       for (size_t i = 0; i < trimmedFormula.size(); ++i) {
136         if (isdigit(trimmedFormula.at(i)))
137           trimmedFormula[i] = ' ';
138       }
139       // Now get the symbols with a simple space split
140       symbolsList = split(trimmedFormula, ' ');
141       for (size_t i = 0; i < symbolsList.size(); ++i)
142         atomicNumbers.push_back(
143           Elements::atomicNumberFromSymbol(symbolsList.at(i)));
144     }
145   }
146 
147   stringSplit = split(line, ' ');
148   vector<unsigned int> atomCounts;
149   for (size_t i = 0; i < stringSplit.size(); ++i) {
150     unsigned int atomCount = lexicalCast<unsigned int>(stringSplit.at(i));
151     atomCounts.push_back(atomCount);
152   }
153 
154   // If we never filled up the atomic numbers, fill them up
155   // now with "1, 2, 3..."
156   if (atomicNumbers.size() == 0)
157     for (size_t i = 1; i <= atomCounts.size(); ++i)
158       atomicNumbers.push_back(i);
159 
160   if (atomicNumbers.size() != atomCounts.size()) {
161     appendError("Error: numSymbols and numTypes are not equal in POSCAR!");
162     return false;
163   }
164 
165   // Starts with either [Ss]elective dynamics, [KkCc]artesian, or
166   // other for fractional coords.
167   getline(inStream, line);
168   line = trimmed(line);
169 
170   // If selective dynamics, get the next line
171   if (line.empty() || line.at(0) == 'S' || line.at(0) == 's')
172     getline(inStream, line);
173 
174   line = trimmed(line);
175   if (line.empty()) {
176     appendError("Error determining Direct or Cartesian in POSCAR");
177     return false;
178   }
179 
180   bool cart;
181   // Check if we're using cartesian or fractional coordinates:
182   if (line.at(0) == 'K' || line.at(0) == 'k' || line.at(0) == 'C' ||
183       line.at(0) == 'c') {
184     cart = true;
185   }
186   // Assume direct if one of these was not found
187   else {
188     cart = false;
189   }
190 
191   vector<Vector3> atoms;
192   for (size_t i = 0; i < atomCounts.size(); ++i) {
193     for (size_t j = 0; j < atomCounts.at(i); ++j) {
194       getline(inStream, line);
195       stringSplit = split(line, ' ');
196       // This may be greater than 3 with selective dynamics
197       if (stringSplit.size() < 3) {
198         appendError("Error reading atomic coordinates in POSCAR");
199         return false;
200       }
201       Vector3 tmpAtom(lexicalCast<double>(stringSplit.at(0)),
202                       lexicalCast<double>(stringSplit.at(1)),
203                       lexicalCast<double>(stringSplit.at(2)));
204       atoms.push_back(tmpAtom);
205     }
206   }
207 
208   // Let's make a unit cell
209   UnitCell* cell = new UnitCell(cellMat);
210 
211   // If our atomic coordinates are fractional, convert them to Cartesian
212   if (!cart) {
213     for (size_t i = 0; i < atoms.size(); ++i)
214       atoms[i] = cell->toCartesian(atoms.at(i));
215   }
216   // If they're cartesian, we just need to apply the scaling factor
217   else {
218     for (size_t i = 0; i < atoms.size(); ++i)
219       atoms[i] *= scalingFactor;
220   }
221 
222   // If we made it this far, the read was a success!
223   // Delete the current molecule. Add the new title and unit cell
224   mol.clearAtoms();
225   mol.setData("name", title);
226   mol.setUnitCell(cell);
227 
228   // Now add the atoms
229   size_t k = 0;
230   for (size_t i = 0; i < atomCounts.size(); ++i) {
231     unsigned char atomicNum = atomicNumbers.at(i);
232     for (size_t j = 0; j < atomCounts.at(i); ++j) {
233       Atom newAtom = mol.addAtom(atomicNum);
234       newAtom.setPosition3d(atoms.at(k));
235       ++k;
236     }
237   }
238 
239   return true;
240 }
241 
write(std::ostream & outStream,const Core::Molecule & mol)242 bool PoscarFormat::write(std::ostream& outStream, const Core::Molecule& mol)
243 {
244   // Title
245   if (mol.data("name").toString().length())
246     outStream << mol.data("name").toString() << std::endl;
247   else
248     outStream << "POSCAR" << std::endl;
249 
250   // Scaling factor
251   outStream << " 1.00000000" << std::endl;
252 
253   // 3x3 matrix. Transpose is needed to orient the matrix correctly.
254   const Matrix3& mat = mol.unitCell()->cellMatrix().transpose();
255   for (size_t i = 0; i < 3; ++i) {
256     for (size_t j = 0; j < 3; ++j) {
257       outStream << "   " << std::setw(10) << std::right << std::fixed
258                 << std::setprecision(8) << mat(i, j);
259     }
260     outStream << std::endl;
261   }
262 
263   // Adapted from chemkit:
264   // A map of atomic symbols to their quantity.
265   Array<unsigned char> atomicNumbers = mol.atomicNumbers();
266   std::map<unsigned char, size_t> composition;
267   for (Array<unsigned char>::const_iterator it = atomicNumbers.begin(),
268                                             itEnd = atomicNumbers.end();
269        it != itEnd; ++it) {
270     composition[*it]++;
271   }
272 
273   // Atom symbols
274   std::map<unsigned char, size_t>::iterator iter = composition.begin();
275   while (iter != composition.end()) {
276     outStream << "   " << Elements::symbol(iter->first);
277     ++iter;
278   }
279   outStream << std::endl;
280 
281   // Numbers of each type
282   iter = composition.begin();
283   while (iter != composition.end()) {
284     outStream << "   " << iter->second;
285     ++iter;
286   }
287   outStream << std::endl;
288 
289   // Direct or cartesian?
290   outStream << "Direct" << std::endl;
291 
292   // Final section is atomic coordinates
293   size_t numAtoms = mol.atomCount();
294   // We need to make sure we that group the atomic numbers together.
295   // The outer loop is for grouping them.
296   iter = composition.begin();
297   while (iter != composition.end()) {
298     unsigned char currentAtomicNum = iter->first;
299     for (size_t i = 0; i < numAtoms; ++i) {
300       // We need to group atomic numbers together. If this one is not
301       // the current atomic number, skip over it.
302       if (atomicNumbers.at(i) != currentAtomicNum)
303         continue;
304 
305       Atom atom = mol.atom(i);
306       if (!atom.isValid()) {
307         appendError("Internal error: Atom invalid.");
308         return false;
309       }
310       Vector3 fracCoords = mol.unitCell()->toFractional(atom.position3d());
311       outStream << "  " << std::setw(10) << std::right << std::fixed
312                 << std::setprecision(8) << fracCoords.x() << "  "
313                 << std::setw(10) << std::right << std::fixed
314                 << std::setprecision(8) << fracCoords.y() << "  "
315                 << std::setw(10) << std::right << std::fixed
316                 << std::setprecision(8) << fracCoords.z() << "\n";
317     }
318     ++iter;
319   }
320 
321   return true;
322 }
323 
fileExtensions() const324 std::vector<std::string> PoscarFormat::fileExtensions() const
325 {
326   std::vector<std::string> ext;
327   ext.push_back("POSCAR");
328   return ext;
329 }
330 
mimeTypes() const331 std::vector<std::string> PoscarFormat::mimeTypes() const
332 {
333   std::vector<std::string> mime;
334   mime.push_back("N/A");
335   return mime;
336 }
337 
OutcarFormat()338 OutcarFormat::OutcarFormat() {}
339 
~OutcarFormat()340 OutcarFormat::~OutcarFormat() {}
341 
read(std::istream & inStream,Core::Molecule & mol)342 bool OutcarFormat::read(std::istream& inStream, Core::Molecule& mol)
343 {
344   std::string buffer, dashedStr, positionStr, latticeStr;
345   positionStr = " POSITION";
346   latticeStr = "  Lattice vectors:";
347   dashedStr = " -----------";
348   std::vector<std::string> stringSplit;
349   int coordSet = 0, natoms = 0;
350   Array<Vector3> positions;
351   Vector3 ax1, ax2, ax3;
352   bool ax1Set = false, ax2Set = false, ax3Set = false;
353 
354   typedef map<string, unsigned char> AtomTypeMap;
355   AtomTypeMap atomTypes;
356   unsigned char customElementCounter = CustomElementMin;
357 
358   while (getline(inStream, buffer)) {
359     // Checks whether the buffer object contains the lattice vectors keyword
360     if (strncmp(buffer.c_str(), latticeStr.c_str(), latticeStr.size()) == 0) {
361       // Checks whether lattice vectors have been already set. Reason being that
362       // only the first occurrence denotes the true lattice vectors, and the
363       // ones following these are vectors of the primitive cell.
364       if (!(ax1Set && ax2Set && ax3Set)) {
365         getline(inStream, buffer);
366         for (int i = 0; i < 3; ++i) {
367           getline(inStream, buffer);
368           stringSplit = split(buffer, ' ');
369           if (stringSplit[0] == "A1") {
370             ax1 = Vector3(lexicalCast<double>(stringSplit.at(3).substr(
371                             0, stringSplit.at(3).size() - 1)),
372                           lexicalCast<double>(stringSplit.at(4).substr(
373                             0, stringSplit.at(4).size() - 1)),
374                           lexicalCast<double>(stringSplit.at(5).substr(
375                             0, stringSplit.at(5).size() - 1)));
376             ax1Set = true;
377           } else if (stringSplit[0] == "A2") {
378             ax2 = Vector3(lexicalCast<double>(stringSplit.at(3).substr(
379                             0, stringSplit.at(3).size() - 1)),
380                           lexicalCast<double>(stringSplit.at(4).substr(
381                             0, stringSplit.at(4).size() - 1)),
382                           lexicalCast<double>(stringSplit.at(5).substr(
383                             0, stringSplit.at(5).size() - 1)));
384             ax2Set = true;
385           } else if (stringSplit[0] == "A3") {
386             ax3 = Vector3(lexicalCast<double>(stringSplit.at(3).substr(
387                             0, stringSplit.at(3).size() - 1)),
388                           lexicalCast<double>(stringSplit.at(4).substr(
389                             0, stringSplit.at(4).size() - 1)),
390                           lexicalCast<double>(stringSplit.at(5).substr(
391                             0, stringSplit.at(5).size() - 1)));
392             ax3Set = true;
393           }
394         }
395         // Checks whether all the three axis vectors have been read
396         if (ax1Set && ax2Set && ax3Set) {
397           mol.setUnitCell(new UnitCell(ax1, ax2, ax3));
398         }
399       }
400     }
401 
402     // Checks whether the buffer object contains the POSITION keyword
403     else if (strncmp(buffer.c_str(), positionStr.c_str(), positionStr.size()) ==
404              0) {
405       getline(inStream, buffer);
406       // Double checks whether the succeeding line is a sequence of dashes
407       if (strncmp(buffer.c_str(), dashedStr.c_str(), dashedStr.size()) == 0) {
408         // natoms is not known, so the loop proceeds till the bottom dashed line
409         // is encountered
410         while (true) {
411           getline(inStream, buffer);
412           // Condition for encountering dashed line
413           if (strncmp(buffer.c_str(), dashedStr.c_str(), dashedStr.size()) ==
414               0) {
415             if (coordSet == 0) {
416               mol.setCoordinate3d(mol.atomPositions3d(), coordSet++);
417               positions.reserve(natoms);
418             } else {
419               mol.setCoordinate3d(positions, coordSet++);
420               positions.clear();
421             }
422             break;
423           }
424           // Parsing the coordinates
425           stringSplit = split(buffer, ' ');
426           Vector3 tmpAtom(lexicalCast<double>(stringSplit.at(0)),
427                           lexicalCast<double>(stringSplit.at(1)),
428                           lexicalCast<double>(stringSplit.at(2)));
429           if (coordSet == 0) {
430             AtomTypeMap::const_iterator it;
431             atomTypes.insert(
432               std::make_pair(std::to_string(natoms), customElementCounter++));
433             it = atomTypes.find(std::to_string(natoms));
434             // if (customElementCounter > CustomElementMax) {
435             //   appendError("Custom element type limit exceeded.");
436             //   return false;
437             // }
438             Atom newAtom = mol.addAtom(it->second);
439             newAtom.setPosition3d(tmpAtom);
440             natoms++;
441           } else {
442             positions.push_back(tmpAtom);
443           }
444         }
445       }
446     }
447   }
448 
449   // Set the custom element map if needed:
450   if (!atomTypes.empty()) {
451     Molecule::CustomElementMap elementMap;
452     for (AtomTypeMap::const_iterator it = atomTypes.begin(),
453                                      itEnd = atomTypes.end();
454          it != itEnd; ++it) {
455       elementMap.insert(std::make_pair(it->second, "Atom " + it->first));
456     }
457     mol.setCustomElementMap(elementMap);
458   }
459 
460   return true;
461 }
462 
write(std::ostream & outStream,const Core::Molecule & mol)463 bool OutcarFormat::write(std::ostream& outStream, const Core::Molecule& mol)
464 {
465   return false;
466 }
467 
fileExtensions() const468 std::vector<std::string> OutcarFormat::fileExtensions() const
469 {
470   std::vector<std::string> ext;
471   ext.push_back("OUTCAR");
472   return ext;
473 }
474 
mimeTypes() const475 std::vector<std::string> OutcarFormat::mimeTypes() const
476 {
477   std::vector<std::string> mime;
478   mime.push_back("N/A");
479   return mime;
480 }
481 
482 } // end Io namespace
483 } // end Avogadro namespace
484