1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2018 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 "lammpsformat.h"
18 
19 #include <avogadro/core/crystaltools.h>
20 #include <avogadro/core/elements.h>
21 #include <avogadro/core/molecule.h>
22 #include <avogadro/core/unitcell.h>
23 #include <avogadro/core/utilities.h>
24 #include <avogadro/core/vector.h>
25 
26 #include <iomanip>
27 #include <istream>
28 #include <ostream>
29 #include <sstream>
30 #include <string>
31 
32 using std::endl;
33 using std::getline;
34 using std::map;
35 using std::string;
36 using std::to_string;
37 using std::vector;
38 
39 namespace Avogadro {
40 namespace Io {
41 
42 using Core::Array;
43 using Core::Atom;
44 using Core::Bond;
45 using Core::CrystalTools;
46 using Core::Elements;
47 using Core::lexicalCast;
48 using Core::Molecule;
49 using Core::split;
50 using Core::trimmed;
51 using Core::UnitCell;
52 
53 #ifndef _WIN32
54 using std::isalpha;
55 #endif
56 
LammpsTrajectoryFormat()57 LammpsTrajectoryFormat::LammpsTrajectoryFormat() {}
58 
~LammpsTrajectoryFormat()59 LammpsTrajectoryFormat::~LammpsTrajectoryFormat() {}
60 
read(std::istream & inStream,Core::Molecule & mol)61 bool LammpsTrajectoryFormat::read(std::istream& inStream, Core::Molecule& mol)
62 {
63   size_t numAtoms = 0, timestep = 0, x_idx = -1, y_idx = -1, z_idx = -1,
64          type_idx = -1, id_idx = -1;
65   double x_min = 0, x_max = 0, y_min = 0, y_max = 0, z_min = 0, z_max = 0,
66          tilt_xy = 0, tilt_xz = 0, tilt_yz = 0, scale_x = 0., scale_y = 0.,
67          scale_z = 0.;
68 
69   string buffer;
70   getline(inStream, buffer); // Finish the first line
71   buffer = trimmed(buffer);
72   if (buffer != "ITEM: TIMESTEP") {
73     appendError("No timestep item found.");
74     return false;
75   }
76   getline(inStream, buffer);
77   if (!buffer.empty()) {
78     timestep = lexicalCast<size_t>(buffer);
79     mol.setTimeStep(timestep, 0);
80   }
81 
82   getline(inStream, buffer);
83   buffer = trimmed(buffer);
84   if (buffer != "ITEM: NUMBER OF ATOMS") {
85     appendError("No number of atoms item found.");
86     return false;
87   }
88   getline(inStream, buffer);
89   if (!buffer.empty())
90     numAtoms = lexicalCast<size_t>(buffer);
91 
92   // If unit cell is triclinic, tilt factors are needed to define the supercell
93   getline(inStream, buffer);
94   if (buffer.find("ITEM: BOX BOUNDS xy xz yz") == 0) {
95     // Read x_min, x_max, tiltfactor_xy
96     getline(inStream, buffer);
97     vector<string> box_bounds_x(split(buffer, ' '));
98     x_min = lexicalCast<double>(box_bounds_x.at(0));
99     x_max = lexicalCast<double>(box_bounds_x.at(1));
100     tilt_xy = lexicalCast<double>(box_bounds_x.at(2));
101     // Read y_min, y_max, tiltfactor_xz
102     getline(inStream, buffer);
103     vector<string> box_bounds_y(split(buffer, ' '));
104     y_min = lexicalCast<double>(box_bounds_y.at(0));
105     y_max = lexicalCast<double>(box_bounds_y.at(1));
106     tilt_xz = lexicalCast<double>(box_bounds_y.at(2));
107     getline(inStream, buffer);
108     // Read z_min, z_max, tiltfactor_yz
109     vector<string> box_bounds_z(split(buffer, ' '));
110     z_min = lexicalCast<double>(box_bounds_z.at(0));
111     z_max = lexicalCast<double>(box_bounds_z.at(1));
112     tilt_yz = lexicalCast<double>(box_bounds_z.at(2));
113 
114     x_min -= std::min(std::min(std::min(tilt_xy, tilt_xz), tilt_xy + tilt_xz),
115                       (double)0);
116     x_max -= std::max(std::max(std::max(tilt_xy, tilt_xz), tilt_xy + tilt_xz),
117                       (double)0);
118     y_min -= std::min(tilt_yz, (double)0);
119     y_max -= std::max(tilt_yz, (double)0);
120   }
121 
122   // Else if unit cell is orthogonal, tilt factors are zero
123   else if (buffer.find("ITEM: BOX BOUNDS") == 0) {
124     // Read x_min, x_max
125     getline(inStream, buffer);
126     vector<string> box_bounds_x(split(buffer, ' '));
127     x_min = lexicalCast<double>(box_bounds_x.at(0));
128     x_max = lexicalCast<double>(box_bounds_x.at(1));
129     // Read y_min, y_max
130     getline(inStream, buffer);
131     vector<string> box_bounds_y(split(buffer, ' '));
132     y_min = lexicalCast<double>(box_bounds_y.at(0));
133     y_max = lexicalCast<double>(box_bounds_y.at(1));
134     // Read z_min, z_max
135     getline(inStream, buffer);
136     vector<string> box_bounds_z(split(buffer, ' '));
137     z_min = lexicalCast<double>(box_bounds_z.at(0));
138     z_max = lexicalCast<double>(box_bounds_z.at(1));
139   }
140 
141   typedef map<string, unsigned char> AtomTypeMap;
142   AtomTypeMap atomTypes;
143   unsigned char customElementCounter = CustomElementMin;
144 
145   // x,y,z stand for the coordinate axes
146   // s stands for scaled coordinates
147   // u stands for unwrapped coordinates
148   // scale_x = 0. if coordinates are cartesian and 1 if fractional (scaled)
149   getline(inStream, buffer);
150   vector<string> labels(split(buffer, ' '));
151   for (size_t i = 0; i < labels.size(); i++) {
152     if (labels[i] == "x" || labels[i] == "xu") {
153       x_idx = i;
154       scale_x = 0.;
155     } else if (labels[i] == "xs" || labels[i] == "xsu") {
156       x_idx = i;
157       scale_x = 1.;
158     } else if (labels[i] == "y" || labels[i] == "yu") {
159       y_idx = i;
160       scale_y = 0.;
161     } else if (labels[i] == "ys" || labels[i] == "ysu") {
162       y_idx = i;
163       scale_y = 1.;
164     } else if (labels[i] == "z" || labels[i] == "zu") {
165       z_idx = i;
166       scale_z = 0.;
167     } else if (labels[i] == "zs" || labels[i] == "zsu") {
168       z_idx = i;
169       scale_z = 1.;
170     } else if (labels[i] == "type")
171       type_idx = i;
172     else if (labels[i] == "id")
173       id_idx = i;
174   }
175 
176   // Parse atoms
177   for (size_t i = 0; i < numAtoms; ++i) {
178     getline(inStream, buffer);
179     vector<string> tokens(split(buffer, ' '));
180 
181     if (tokens.size() < labels.size() - 2) {
182       appendError("Not enough tokens in this line: " + buffer);
183       return false;
184     }
185 
186     unsigned char atomicNum(0);
187     atomicNum = lexicalCast<short int>(tokens[type_idx - 2]);
188 
189     // If parsed coordinates are fractional, the corresponding unscaling is
190     // done. Else the positions are assigned as parsed.
191     Vector3 pos((1 - scale_x) * lexicalCast<double>(tokens[x_idx - 2]) +
192                   scale_x * (x_min + (x_max - x_min) *
193                                        lexicalCast<double>(tokens[x_idx - 2])),
194                 (1 - scale_y) * lexicalCast<double>(tokens[y_idx - 2]) +
195                   scale_y * (y_min + (y_max - y_min) *
196                                        lexicalCast<double>(tokens[y_idx - 2])),
197                 (1 - scale_z) * lexicalCast<double>(tokens[z_idx - 2]) +
198                   scale_z * (z_min + (z_max - z_min) *
199                                        lexicalCast<double>(tokens[z_idx - 2])));
200 
201     AtomTypeMap::const_iterator it = atomTypes.find(to_string(atomicNum));
202     if (it == atomTypes.end()) {
203       atomTypes.insert(
204         std::make_pair(to_string(atomicNum), customElementCounter++));
205       it = atomTypes.find(to_string(atomicNum));
206       if (customElementCounter > CustomElementMax) {
207         appendError("Custom element type limit exceeded.");
208         return false;
209       }
210     }
211     Atom newAtom = mol.addAtom(it->second);
212     newAtom.setPosition3d(pos);
213   }
214 
215   // Set the custom element map if needed:
216   if (!atomTypes.empty()) {
217     Molecule::CustomElementMap elementMap;
218     for (AtomTypeMap::const_iterator it = atomTypes.begin(),
219                                      itEnd = atomTypes.end();
220          it != itEnd; ++it) {
221       elementMap.insert(std::make_pair(it->second, it->first));
222     }
223     mol.setCustomElementMap(elementMap);
224   }
225 
226   // Check that all atoms were handled.
227   if (mol.atomCount() != numAtoms) {
228     std::ostringstream errorStream;
229     errorStream << "Error parsing atom at index " << mol.atomCount()
230                 << " (line " << 10 + mol.atomCount() << ").\n"
231                 << buffer;
232     appendError(errorStream.str());
233     return false;
234   }
235   mol.setCoordinate3d(mol.atomPositions3d(), 0);
236   mol.setUnitCell(new UnitCell(Vector3(x_max - x_min, 0, 0),
237                                Vector3(tilt_xy, y_max - y_min, 0),
238                                Vector3(tilt_xz, tilt_yz, z_max - z_min)));
239 
240   // Do we have an animation?
241   size_t numAtoms2;
242   int coordSet = 1;
243   while (getline(inStream, buffer) && trimmed(buffer) == "ITEM: TIMESTEP") {
244     x_idx = -1;
245     y_idx = -1;
246     z_idx = -1;
247     type_idx = -1;
248     id_idx = -1;
249     x_min = 0;
250     x_max = 0;
251     y_min = 0;
252     y_max = 0;
253     z_min = 0;
254     z_max = 0;
255     tilt_xy = 0;
256     tilt_xz = 0;
257     tilt_yz = 0;
258     scale_x = 0.;
259     scale_y = 0.;
260     scale_z = 0.;
261 
262     getline(inStream, buffer);
263     if (!buffer.empty()) {
264       timestep = lexicalCast<size_t>(buffer);
265       mol.setTimeStep(timestep, coordSet);
266     }
267 
268     getline(inStream, buffer);
269     buffer = trimmed(buffer);
270     if (buffer != "ITEM: NUMBER OF ATOMS") {
271       appendError("No number of atoms item found.");
272       return false;
273     }
274     getline(inStream, buffer);
275     if (!buffer.empty())
276       numAtoms2 = lexicalCast<size_t>(buffer);
277 
278     if (numAtoms2 != numAtoms) {
279       appendError("Number of atoms isn't constant in the trajectory.");
280     }
281 
282     // If unit cell is triclinic, tilt factors are needed to define the
283     // supercell
284     getline(inStream, buffer);
285     if (buffer.find("ITEM: BOX BOUNDS xy xz yz") == 0) {
286       // Read x_min, x_max, tiltfactor_xy
287       getline(inStream, buffer);
288       vector<string> box_bounds_x(split(buffer, ' '));
289       x_min = lexicalCast<double>(box_bounds_x.at(0));
290       x_max = lexicalCast<double>(box_bounds_x.at(1));
291       tilt_xy = lexicalCast<double>(box_bounds_x.at(2));
292       // Read y_min, y_max, tiltfactor_xz
293       getline(inStream, buffer);
294       vector<string> box_bounds_y(split(buffer, ' '));
295       y_min = lexicalCast<double>(box_bounds_y.at(0));
296       y_max = lexicalCast<double>(box_bounds_y.at(1));
297       tilt_xz = lexicalCast<double>(box_bounds_y.at(2));
298       getline(inStream, buffer);
299       // Read z_min, z_max, tiltfactor_yz
300       vector<string> box_bounds_z(split(buffer, ' '));
301       z_min = lexicalCast<double>(box_bounds_z.at(0));
302       z_max = lexicalCast<double>(box_bounds_z.at(1));
303       tilt_yz = lexicalCast<double>(box_bounds_z.at(2));
304 
305       x_min -= std::min(std::min(std::min(tilt_xy, tilt_xz), tilt_xy + tilt_xz),
306                         (double)0);
307       x_max -= std::max(std::max(std::max(tilt_xy, tilt_xz), tilt_xy + tilt_xz),
308                         (double)0);
309       y_min -= std::min(tilt_yz, (double)0);
310       y_max -= std::max(tilt_yz, (double)0);
311     }
312 
313     // Else if unit cell is orthogonal, tilt factors are zero
314     else if (buffer.find("ITEM: BOX BOUNDS") == 0) {
315       // Read x_min, x_max
316       getline(inStream, buffer);
317       vector<string> box_bounds_x(split(buffer, ' '));
318       x_min = lexicalCast<double>(box_bounds_x.at(0));
319       x_max = lexicalCast<double>(box_bounds_x.at(1));
320       // Read y_min, y_max
321       getline(inStream, buffer);
322       vector<string> box_bounds_y(split(buffer, ' '));
323       y_min = lexicalCast<double>(box_bounds_y.at(0));
324       y_max = lexicalCast<double>(box_bounds_y.at(1));
325       // Read z_min, z_max
326       getline(inStream, buffer);
327       vector<string> box_bounds_z(split(buffer, ' '));
328       z_min = lexicalCast<double>(box_bounds_z.at(0));
329       z_max = lexicalCast<double>(box_bounds_z.at(1));
330     }
331 
332     // x,y,z stand for the coordinate axes
333     // s stands for scaled coordinates
334     // u stands for unwrapped coordinates
335     // scale_x = 0. if coordinates are cartesian and 1 if fractional (scaled)
336     getline(inStream, buffer);
337     labels = vector<string>(split(buffer, ' '));
338     for (size_t i = 0; i < labels.size(); ++i) {
339       if (labels[i] == "x" || labels[i] == "xu") {
340         x_idx = i;
341         scale_x = 0.;
342       } else if (labels[i] == "xs" || labels[i] == "xsu") {
343         x_idx = i;
344         scale_x = 1.;
345       } else if (labels[i] == "y" || labels[i] == "yu") {
346         y_idx = i;
347         scale_y = 0.;
348       } else if (labels[i] == "ys" || labels[i] == "ysu") {
349         y_idx = i;
350         scale_y = 1.;
351       } else if (labels[i] == "z" || labels[i] == "zu") {
352         z_idx = i;
353         scale_z = 0.;
354       } else if (labels[i] == "zs" || labels[i] == "zsu") {
355         z_idx = i;
356         scale_z = 1.;
357       } else if (labels[i] == "type")
358         type_idx = i;
359       else if (labels[i] == "id")
360         id_idx = i;
361     }
362 
363     Array<Vector3> positions;
364     positions.reserve(numAtoms);
365 
366     for (size_t i = 0; i < numAtoms; ++i) {
367       getline(inStream, buffer);
368       vector<string> tokens(split(buffer, ' '));
369       if (tokens.size() < 5) {
370         appendError("Not enough tokens in this line: " + buffer);
371         return false;
372       }
373       // If parsed coordinates are fractional, the corresponding unscaling is
374       // done. Else the positions are assigned as parsed.
375       Vector3 pos(
376         (1 - scale_x) * lexicalCast<double>(tokens[x_idx - 2]) +
377           scale_x *
378             (x_min + (x_max - x_min) * lexicalCast<double>(tokens[x_idx - 2])),
379         (1 - scale_y) * lexicalCast<double>(tokens[y_idx - 2]) +
380           scale_y *
381             (y_min + (y_max - y_min) * lexicalCast<double>(tokens[y_idx - 2])),
382         (1 - scale_z) * lexicalCast<double>(tokens[z_idx - 2]) +
383           scale_z *
384             (z_min + (z_max - z_min) * lexicalCast<double>(tokens[z_idx - 2])));
385       positions.push_back(pos);
386     }
387 
388     mol.setCoordinate3d(positions, coordSet++);
389     mol.setUnitCell(new UnitCell(Vector3(x_max - x_min, 0, 0),
390                                  Vector3(tilt_xy, y_max - y_min, 0),
391                                  Vector3(tilt_xz, tilt_yz, z_max - z_min)));
392   }
393 
394   return true;
395 }
396 
write(std::ostream & outStream,const Core::Molecule & mol)397 bool LammpsTrajectoryFormat::write(std::ostream& outStream,
398                                    const Core::Molecule& mol)
399 {
400   return false;
401 }
402 
fileExtensions() const403 std::vector<std::string> LammpsTrajectoryFormat::fileExtensions() const
404 {
405   std::vector<std::string> ext;
406   ext.push_back("dump");
407   return ext;
408 }
409 
mimeTypes() const410 std::vector<std::string> LammpsTrajectoryFormat::mimeTypes() const
411 {
412   std::vector<std::string> mime;
413   mime.push_back("text/lammps");
414   return mime;
415 }
416 
LammpsDataFormat()417 LammpsDataFormat::LammpsDataFormat() {}
418 
~LammpsDataFormat()419 LammpsDataFormat::~LammpsDataFormat() {}
420 
read(std::istream & inStream,Core::Molecule & mol)421 bool LammpsDataFormat::read(std::istream& inStream, Core::Molecule& mol)
422 {
423   return false;
424 }
425 
write(std::ostream & outStream,const Core::Molecule & mol)426 bool LammpsDataFormat::write(std::ostream& outStream, const Core::Molecule& mol)
427 {
428   Core::Molecule mol2(mol);
429   CrystalTools::rotateToStandardOrientation(mol2, CrystalTools::TransformAtoms);
430 
431   // Title
432   if (mol2.data("name").toString().length())
433     outStream << mol2.data("name").toString() << std::endl;
434   else
435     outStream << "LAMMPS data file generated by Avogadro" << std::endl;
436 
437   std::ostringstream massStream, atomStream, bondStream;
438   double xmin, xmax, ymin, ymax, zmin, zmax;
439 
440   size_t numAtoms = mol2.atomCount();
441   outStream << to_string(numAtoms) << " atoms\n";
442 
443   size_t numBonds = mol2.bondCount();
444   outStream << to_string(numBonds) << " bonds\n";
445 
446   // A map of atomic symbols to their quantity.
447   size_t idx = 1;
448   Array<unsigned char> atomicNumbers = mol2.atomicNumbers();
449   std::map<unsigned char, size_t> composition;
450   for (Array<unsigned char>::const_iterator it = atomicNumbers.begin(),
451                                             itEnd = atomicNumbers.end();
452        it != itEnd; ++it) {
453     if (composition.find(*it) == composition.end()) {
454       composition[*it] = idx++;
455     }
456   }
457 
458   outStream << composition.size() << " atom types\n";
459 
460   // Masses
461   massStream << "Masses\n\n";
462   std::map<unsigned char, size_t>::iterator iter = composition.begin();
463   while (iter != composition.end()) {
464     massStream << iter->second << "   " << Elements::mass(iter->first) << "\n";
465     ++iter;
466   }
467   massStream << std::endl << std::endl << std::endl;
468 
469   if (numAtoms) {
470     // Atomic coordinates
471     atomStream << "Atoms\n\n";
472     for (Index i = 0; i < numAtoms; ++i) {
473       Atom atom = mol2.atom(i);
474       if (!atom.isValid()) {
475         appendError("Internal error: Atom invalid.");
476         return false;
477       }
478       Vector3 coords = atom.position3d();
479       if (i == 0) {
480         xmin = coords[0];
481         xmax = coords[0];
482         ymin = coords[1];
483         ymax = coords[1];
484         zmin = coords[2];
485         zmax = coords[2];
486       } else {
487         xmin = std::min(coords[0], xmin);
488         xmax = std::max(coords[0], xmax);
489         ymin = std::min(coords[1], ymin);
490         ymax = std::max(coords[1], ymax);
491         zmin = std::min(coords[2], zmin);
492         zmax = std::max(coords[2], zmax);
493       }
494 
495       char atomline[200];
496       sprintf(atomline, "%-*d %d %10f %10f %10f\n",
497               static_cast<int>(log(numAtoms)) + 1, static_cast<int>(i + 1),
498               static_cast<int>(composition[atomicNumbers[i]]), coords.x(),
499               coords.y(), coords.z());
500       atomStream << atomline;
501     }
502 
503     atomStream << std::endl << std::endl;
504   }
505 
506   if (numBonds) {
507     // Bonds
508     std::map<std::pair<unsigned char, unsigned char>, int> bondIds;
509     int bondItr = 1;
510     bondStream << "Bonds\n\n";
511     for (Index i = 0; i < numBonds; ++i) {
512       char bondline[200];
513       Bond b = mol2.bond(i);
514       if (bondIds.find(std::make_pair(b.atom1().atomicNumber(),
515                                       b.atom2().atomicNumber())) !=
516           bondIds.end()) {
517         sprintf(bondline, "%-*d %7d %7d %7d\n",
518                 static_cast<int>(log(numAtoms) + 1), static_cast<int>(i + 1),
519                 bondIds[std::make_pair(b.atom1().atomicNumber(),
520                                        b.atom2().atomicNumber())],
521                 static_cast<int>(b.atom1().index() + 1),
522                 static_cast<int>(b.atom2().index() + 1));
523         bondStream << bondline;
524       } else if (bondIds.find(std::make_pair(b.atom2().atomicNumber(),
525                                              b.atom1().atomicNumber())) !=
526                  bondIds.end()) {
527         sprintf(bondline, "%-*d %7d %7d %7d\n",
528                 static_cast<int>(log(numAtoms) + 1), static_cast<int>(i + 1),
529                 bondIds[std::make_pair(b.atom1().atomicNumber(),
530                                        b.atom2().atomicNumber())],
531                 static_cast<int>(b.atom2().index() + 1),
532                 static_cast<int>(b.atom1().index() + 1));
533         bondStream << bondline;
534       } else {
535         bondIds.insert(std::make_pair(
536           std::make_pair(b.atom1().atomicNumber(), b.atom2().atomicNumber()),
537           bondItr++));
538         sprintf(bondline, "%-*d %7d %7d %7d\n",
539                 static_cast<int>(log(numAtoms) + 1), static_cast<int>(i + 1),
540                 bondIds[std::make_pair(b.atom1().atomicNumber(),
541                                        b.atom2().atomicNumber())],
542                 static_cast<int>(b.atom1().index() + 1),
543                 static_cast<int>(b.atom2().index() + 1));
544         bondStream << bondline;
545       }
546     }
547   }
548 
549   UnitCell* unitcell = mol2.unitCell();
550   char simBoxBlock[200];
551   if (unitcell) {
552     const Matrix3& mat = unitcell->cellMatrix().transpose();
553     sprintf(simBoxBlock,
554             "%10f %10f xlo xhi\n%10f %10f ylo yhi\n%10f %10f zlo zhi\n%10f "
555             "%10f %10f xy xz yz",
556             0.0, mat(0, 0), 0.0, mat(1, 1), 0.0, mat(2, 2), mat(1, 0),
557             mat(2, 0), mat(2, 1));
558     outStream << simBoxBlock;
559   } else {
560     sprintf(simBoxBlock,
561             "%10f %10f xlo xhi\n%10f %10f ylo yhi\n%10f %10f zlo zhi\n%10f "
562             "%10f %10f xy xz yz",
563             xmin - 0.5, xmax - 0.5, ymin - 0.5, ymax - 0.5, zmin - 0.5,
564             zmax - 0.5, 0.0, 0.0, 0.0);
565     outStream << simBoxBlock;
566   }
567   outStream << std::endl << std::endl << std::endl;
568   outStream << massStream.str();
569   outStream << atomStream.str();
570   outStream << bondStream.str();
571 
572   return true;
573 }
574 
fileExtensions() const575 std::vector<std::string> LammpsDataFormat::fileExtensions() const
576 {
577   std::vector<std::string> ext;
578   ext.push_back("lmpdat");
579   return ext;
580 }
581 
mimeTypes() const582 std::vector<std::string> LammpsDataFormat::mimeTypes() const
583 {
584   std::vector<std::string> mime;
585   mime.push_back("N/A");
586   return mime;
587 }
588 
589 } // end Io namespace
590 } // end Avogadro namespace
591