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