1 /******************************************************************************
2 
3   This source file is part of the Avogadro project.
4 
5   Copyright 2013 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 "gromacsformat.h"
18 
19 #include <avogadro/core/avogadrocore.h>
20 
21 #include <avogadro/core/atom.h>
22 #include <avogadro/core/elements.h>
23 #include <avogadro/core/matrix.h>
24 #include <avogadro/core/molecule.h>
25 #include <avogadro/core/residue.h>
26 #include <avogadro/core/unitcell.h>
27 #include <avogadro/core/utilities.h>
28 
29 #include <iostream>
30 
31 #include <string>
32 #include <utility>
33 
34 namespace Avogadro {
35 namespace Io {
36 
37 using Core::Atom;
38 using Core::Elements;
39 using Core::lexicalCast;
40 using Core::Molecule;
41 using Core::Residue;
42 using Core::split;
43 using Core::trimmed;
44 using Core::UnitCell;
45 
46 using std::getline;
47 using std::map;
48 using std::string;
49 using std::vector;
50 
GromacsFormat()51 GromacsFormat::GromacsFormat() {}
52 
~GromacsFormat()53 GromacsFormat::~GromacsFormat() {}
54 
fileExtensions() const55 std::vector<std::string> GromacsFormat::fileExtensions() const
56 {
57   return std::vector<std::string>(1, std::string("gro"));
58 }
59 
mimeTypes() const60 std::vector<std::string> GromacsFormat::mimeTypes() const
61 {
62   return std::vector<std::string>(1, std::string("chemical/x-gro"));
63 }
64 
read(std::istream & in,Molecule & molecule)65 bool GromacsFormat::read(std::istream& in, Molecule& molecule)
66 {
67   string buffer;
68   string value;
69   Residue* r;
70   size_t currentResidueId = 0;
71 
72   // Title
73   getline(in, buffer);
74   if (!buffer.empty())
75     molecule.setData("name", trimmed(buffer));
76 
77   // Atom count
78   getline(in, buffer);
79   buffer = trimmed(buffer);
80   bool ok;
81   size_t numAtoms = lexicalCast<size_t>(buffer, ok);
82   if (buffer.empty() || !ok) {
83     appendError("Number of atoms (line 2) invalid.");
84     return false;
85   }
86 
87   // read atom info:
88   typedef map<string, unsigned char> AtomTypeMap;
89   AtomTypeMap atomTypes;
90   unsigned char customElementCounter = CustomElementMin;
91   Vector3 pos;
92   while (numAtoms-- > 0) {
93     getline(in, buffer);
94     // Figure out the distance between decimal points, implement support for
95     // variable precision as specified:
96     // "any number of decimal places, the format will then be n+5 positions with
97     // n decimal places (n+1 for velocities) in stead of 8 with 3 (with 4 for
98     // velocities)".
99     size_t decimal1 = buffer.find(".", 20);
100     size_t decimal2 = string::npos;
101     int decimalSep = 0;
102     if (decimal1 != string::npos)
103       decimal2 = buffer.find(".", decimal1 + 1);
104     if (decimal2 != string::npos)
105       decimalSep = decimal2 - decimal1;
106     if (decimalSep == 0) {
107       appendError("Decimal separation of 0 found in atom positions: " + buffer);
108       return false;
109     }
110 
111     if (buffer.size() < static_cast<size_t>(20 + 3 * decimalSep)) {
112       appendError("Error reading atom specification -- line too short: " +
113                   buffer);
114       return false;
115     }
116 
117     // Format of buffer is: (all indices start at 1, variable dp throws this).
118     // Offset:  0 format: %5i   value: Residue number
119     // Offset:  5 format: %-5s  value: Residue name
120     // Offset: 10 format: %5s   value: Atom name
121     // Offset: 15 format: %5i   value: Atom number
122     // Offset: 20 format: %8.3f value: x coordinate (nm)
123     // Offset: 28 format: %8.3f value: y coordinate (nm)
124     // Offset: 36 format: %8.3f value: z coordinate (nm)
125     // Offset: 44 format: %8.4f value: x velocity (nm/ps, a.k.a. km/s)
126     // Offset: 52 format: %8.4f value: y velocity (nm/ps, a.k.a. km/s)
127     // Offset: 60 format: %8.4f value: z velocity (nm/ps, a.k.a. km/s)
128 
129     size_t residueId = lexicalCast<size_t>(buffer.substr(0, 5), ok);
130     if (!ok) {
131       appendError("Failed to parse residue sequence number: " +
132                   buffer.substr(0, 5));
133       return false;
134     }
135 
136     if (residueId != currentResidueId) {
137       currentResidueId = residueId;
138 
139       string residueName = lexicalCast<string>(buffer.substr(5, 5), ok);
140       if (!ok) {
141         appendError("Failed to parse residue name: " + buffer.substr(5, 5));
142         return false;
143       }
144 
145       // gro files do not have a chain ID. So we use a makeshift dummy ID
146       char dummyChainId = '0';
147       r = &molecule.addResidue(residueName, currentResidueId, dummyChainId);
148     }
149 
150     // Atom name:
151     value = trimmed(buffer.substr(10, 5));
152     Atom atom;
153     int atomicNum = r->getAtomicNumber(value);
154     if (atomicNum) {
155       atom = molecule.addAtom(atomicNum);
156     } else {
157       unsigned char atomicNumFromSymbol =
158         Elements::atomicNumberFromSymbol(value);
159       if (atomicNumFromSymbol != 255) {
160         atom = molecule.addAtom(atomicNumFromSymbol);
161       } else {
162         AtomTypeMap::const_iterator it = atomTypes.find(value);
163         if (it == atomTypes.end()) {
164           atomTypes.insert(std::make_pair(value, customElementCounter++));
165           it = atomTypes.find(value);
166           if (customElementCounter > CustomElementMax) {
167             appendError("Custom element type limit exceeded.");
168             return false;
169           }
170         }
171         atom = molecule.addAtom(it->second);
172       }
173     }
174 
175     // Coords
176     for (int i = 0; i < 3; ++i) {
177       value = trimmed(buffer.substr(20 + i * decimalSep, decimalSep));
178       pos[i] = lexicalCast<Real>(value, ok);
179       if (!ok || value.empty()) {
180         appendError(
181           "Error reading atom specification -- invalid coordinate: '" + buffer +
182           "' (bad coord: '" + value + "')");
183         return false;
184       }
185     }
186     atom.setPosition3d(pos * static_cast<Real>(10.0)); // nm --> Angstrom
187     if (r) {
188       r->addResidueAtom(value, atom);
189     }
190   }
191 
192   // Set the custom element map if needed:
193   if (!atomTypes.empty()) {
194     Molecule::CustomElementMap elementMap;
195     for (AtomTypeMap::const_iterator it = atomTypes.begin(),
196                                      itEnd = atomTypes.end();
197          it != itEnd; ++it) {
198       elementMap.insert(std::make_pair(it->second, it->first));
199     }
200     molecule.setCustomElementMap(elementMap);
201   }
202 
203   // Box description:
204   // v1(x) v2(y) v3(z) [v1(y) v1(z) v2(x) v2(z) v3(x) v3(y)]
205   // The last six values may be omitted, set all non-specified values to 0.
206   // v1(y) == v1(z) == v2(z) == 0 always.
207   getline(in, buffer);
208   vector<string> tokens(split(buffer, ' ', true));
209   if (tokens.size() > 0) {
210     if (tokens.size() != 3 && tokens.size() != 9) {
211       appendError("Invalid box specification -- need either 3 or 9 values: '" +
212                   buffer + "'");
213       return false;
214     }
215 
216     // Index arrays for parsing loop:
217     const int rows[] = { 0, 1, 2, 1, 2, 0, 2, 0, 1 };
218     const int cols[] = { 0, 1, 2, 0, 0, 1, 1, 2, 2 };
219 
220     Matrix3 cellMatrix = Matrix3::Zero();
221     for (size_t i = 0; i < tokens.size(); ++i) {
222       cellMatrix(rows[i], cols[i]) = lexicalCast<Real>(tokens[i], ok);
223       if (!ok || tokens[i].empty()) {
224         appendError("Invalid box specification -- bad value: '" + tokens[i] +
225                     "'");
226         return false;
227       }
228     }
229 
230     UnitCell* cell = new UnitCell;
231     cell->setCellMatrix(cellMatrix * static_cast<Real>(10)); // nm --> Angstrom
232     molecule.setUnitCell(cell);
233   }
234 
235   return true;
236 }
237 
write(std::ostream &,const Core::Molecule &)238 bool GromacsFormat::write(std::ostream&, const Core::Molecule&)
239 {
240   return false;
241 }
242 
243 } // namespace Io
244 } // namespace Avogadro
245