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