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