/src/openbabel/src/formats/groformat.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | Copyright (C) 2010 by Reinis Danne |
3 | | Some portions Copyright (C) 2004 by Chris Morley |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation version 2 of the License. |
8 | | |
9 | | This program is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | ***********************************************************************/ |
14 | | |
15 | | #include <openbabel/babelconfig.h> |
16 | | #include <openbabel/obmolecformat.h> |
17 | | #include <openbabel/mol.h> |
18 | | #include <openbabel/atom.h> |
19 | | #include <openbabel/elements.h> |
20 | | #include <openbabel/generic.h> |
21 | | #include <openbabel/obiter.h> |
22 | | |
23 | | |
24 | | #include <sstream> |
25 | | #include <iomanip> |
26 | | |
27 | | using namespace std; |
28 | | namespace OpenBabel |
29 | | { |
30 | | |
31 | | class GROFormat : public OBMoleculeFormat |
32 | | { |
33 | | public: |
34 | | //Register this format type ID in the constructor |
35 | | GROFormat() |
36 | 12 | { |
37 | | /* GRO is the file extension and is case insensitive. A MIME type can be |
38 | | added as an optional third parameter. |
39 | | Multiple file extensions can be registered by adding extra statements.*/ |
40 | 12 | OBConversion::RegisterFormat("gro",this); |
41 | | |
42 | | /* If there are any format specific options they should be registered here |
43 | | so that the commandline interface works properly. |
44 | | The first parameter is the option name. If it is a single letter it can |
45 | | be concatinated with other single letter options. For output options it |
46 | | can be multicharcter and is then written as --optionname on the command |
47 | | line. The third parameter is the number of parameters the option takes. |
48 | | Currently this is either 1 or 0 and if it is 0 can be omitted for output |
49 | | options. The parameter is always text and needs to be parsed to extract |
50 | | a number. |
51 | | |
52 | | Options can apply when writing - 4th parameter is |
53 | | OBConversion::OUTOPTIONS or can be omitted as shown. A single letter |
54 | | output option is preceded by -x on the command line. |
55 | | Or options can apply to the input format - the 4th parameter is |
56 | | OBConversion::INOPTIONS. They are then preceded by -a on the command |
57 | | line. |
58 | | |
59 | | Each option letter may be reused in other formats, but within the same |
60 | | group, INOPTIONS or OUTOPTIONS, must take the same number of parameters |
61 | | (0 or 1). There will be an error message when OpenBabel runs if there |
62 | | are conflicts between formats. A list of formats currently used (which |
63 | | may not be comprehensive) is in docs/options.html. |
64 | | */ |
65 | 12 | OBConversion::RegisterOptionParam("f", this, 1); |
66 | 12 | OBConversion::RegisterOptionParam("n", this); |
67 | 12 | OBConversion::RegisterOptionParam("s", this, 0, OBConversion::INOPTIONS); |
68 | | |
69 | 12 | } |
70 | | |
71 | | /* The first line of the description should be a brief identifier, <40 chars, |
72 | | because it is used in dropdown lists, etc. in some user interfaces. |
73 | | The rest is optional. |
74 | | |
75 | | Describe any format specific options here. This text is parsed to provide |
76 | | checkboxes, etc for the GUI (for details click the control menu), |
77 | | so please try to keep to a similar form. |
78 | | |
79 | | Write options are the most common, and the "Write" is optional. |
80 | | The option f takes a text parameter, so that it is essential that the |
81 | | option is registered in the constructor of the class. |
82 | | Finish the options with a blank line as shown, if there are more than one |
83 | | group of options, or if there are further comments after them. |
84 | | */ |
85 | | const char* Description() override // required |
86 | 0 | { |
87 | 0 | return |
88 | 0 | "GRO format\n" |
89 | 0 | "This is GRO file format as used in Gromacs.\n" |
90 | 0 | "Right now there is only limited support for element perception. It works " |
91 | 0 | "for \nelements with one letter symbols if the atomtype starts with the " |
92 | 0 | "same letter.\n\n" |
93 | |
|
94 | 0 | "Read Options e.g. -as\n" |
95 | 0 | " s Consider single bonds only\n" |
96 | 0 | " b Disable bonding entierly\n" |
97 | 0 | ; |
98 | 0 | } |
99 | | |
100 | | //Optional URL where the file format is specified |
101 | | const char* SpecificationURL() override |
102 | 0 | { |
103 | 0 | return "https://manual.gromacs.org/documentation/current/reference-manual/file-formats.html#gro"; |
104 | 0 | } |
105 | | |
106 | | /* This optional function is for formats which can contain more than one |
107 | | molecule. It is used to quickly position the input stream after the nth |
108 | | molecule without requiring to convert and discard all the n molecules. |
109 | | See obconversion.cpp for details.*/ |
110 | | int SkipObjects(int n, OBConversion* pConv) override |
111 | 0 | { |
112 | 0 | string line = ""; |
113 | 0 | int natoms = 0; |
114 | 0 | int nlines = 0; |
115 | |
|
116 | 0 | if (n == 0) |
117 | 0 | n++; |
118 | 0 | istream& ifs = *pConv->GetInStream(); |
119 | 0 | getline(ifs, line); |
120 | 0 | ifs >> natoms; |
121 | 0 | if (natoms <= 0 || natoms >= 100000000) |
122 | 0 | return -1; |
123 | 0 | nlines = (natoms+3)*n; |
124 | 0 | while (ifs && --nlines) { |
125 | 0 | getline(ifs, line); |
126 | 0 | } |
127 | |
|
128 | 0 | return ifs.good() ? 1 : -1; |
129 | 0 | } |
130 | | |
131 | | //////////////////////////////////////////////////// |
132 | | /// Declarations for the "API" interface functions. Definitions are below |
133 | | bool ReadMolecule(OBBase* pOb, OBConversion* pConv) override; |
134 | | bool WriteMolecule(OBBase* pOb, OBConversion* pConv) override; |
135 | | }; |
136 | | //////////////////////////////////////////////////// |
137 | | //Make an instance of the format class |
138 | | GROFormat theGROFormat; |
139 | | |
140 | | ///////////////////////////////////////////////////////////////// |
141 | | |
142 | | bool GROFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv) |
143 | 0 | { |
144 | 0 | OBMol* pmol = pOb->CastAndClear<OBMol>(); |
145 | 0 | if (pmol == nullptr) |
146 | 0 | return false; |
147 | | |
148 | 0 | istream& ifs = *pConv->GetInStream(); |
149 | |
|
150 | 0 | pmol->BeginModify(); |
151 | | |
152 | | /** Parse the input stream and use the OpenBabel API to populate the OBMol **/ |
153 | |
|
154 | 0 | char buffer[BUFF_SIZE]; |
155 | 0 | string line = ""; |
156 | 0 | stringstream errorMsg; |
157 | |
|
158 | 0 | int natoms = 0; |
159 | 0 | string title = ""; |
160 | 0 | long int resid = 0; // 5 |
161 | 0 | string resname = ""; //5 |
162 | 0 | string atomtype = ""; //5 |
163 | | //long int atomid = 0; //5 |
164 | 0 | double x = 0.0; // 8.3 |
165 | 0 | double y = 0.0; // 8.3 |
166 | 0 | double z = 0.0; // 8.3 |
167 | 0 | double vx = 0.0; // 8.4 |
168 | 0 | double vy = 0.0; // 8.4 |
169 | 0 | double vz = 0.0; // 8.4 |
170 | 0 | string tempstr = ""; |
171 | 0 | long int residx = 0; |
172 | 0 | OBAtom* atom; |
173 | 0 | OBResidue* res = nullptr; |
174 | 0 | OBVectorData* velocity; |
175 | |
|
176 | 0 | if (!ifs || ifs.peek() == EOF) { |
177 | 0 | return false; // Trying to read past end of the file |
178 | 0 | } |
179 | | |
180 | 0 | if (!ifs.getline(buffer, BUFF_SIZE)) { |
181 | 0 | errorMsg << "Problems reading a GRO file: " |
182 | 0 | << "Cannot read the first line!"; |
183 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
184 | 0 | return false; |
185 | 0 | } |
186 | | |
187 | | // Get the title |
188 | 0 | title.assign(buffer); |
189 | 0 | if (title.size() < 1) { |
190 | 0 | title = pConv->GetTitle(); |
191 | 0 | pmol->SetTitle(title); |
192 | 0 | } else { |
193 | 0 | pmol->SetTitle(title); |
194 | 0 | } |
195 | |
|
196 | 0 | if (!ifs.getline(buffer, BUFF_SIZE)) { |
197 | 0 | errorMsg << "Problems reading a GRO file: " |
198 | 0 | << "Cannot read the second line!"; |
199 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
200 | 0 | return false; |
201 | 0 | } |
202 | | |
203 | | // Get the number of atoms |
204 | 0 | stringstream(buffer) >> natoms; |
205 | 0 | if (natoms < 1 || natoms >= 100000000) { |
206 | 0 | errorMsg << "Problems reading a GRO file: " |
207 | 0 | << "There are no atoms in the file or the second line is" |
208 | 0 | << " incorrectly written."; |
209 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
210 | 0 | return false; |
211 | 0 | } |
212 | 0 | pmol->ReserveAtoms(natoms); |
213 | | |
214 | | // Read all atom records |
215 | 0 | for (int i=1; i<=natoms; i++) { |
216 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) { |
217 | 0 | errorMsg << "Problems reading a GRO file: " |
218 | 0 | << "Could not read line #" << i+2 << ", file error." << endl |
219 | 0 | << " According to the second line, there should be " << natoms |
220 | 0 | << " atoms, and therefore " << natoms+3 << " lines in the file."; |
221 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
222 | 0 | return false; |
223 | 0 | } |
224 | | |
225 | 0 | line = buffer; |
226 | | |
227 | | // Get atom |
228 | 0 | atom = pmol->NewAtom(); |
229 | |
|
230 | 0 | tempstr.assign(line,0,5); |
231 | 0 | stringstream(tempstr) >> resid; |
232 | |
|
233 | 0 | resname.assign(line,5,5); |
234 | 0 | Trim(resname); |
235 | |
|
236 | 0 | atomtype.assign(line,10,5); |
237 | 0 | Trim(atomtype); |
238 | | |
239 | | // Not used, OB assigns its own indizes |
240 | | //tempstr.assign(line,15,5); |
241 | | //stringstream(tempstr) >> atomid; |
242 | |
|
243 | 0 | tempstr.assign(line,20,8); |
244 | 0 | stringstream(tempstr) >> x; |
245 | |
|
246 | 0 | tempstr.assign(line,28,8); |
247 | 0 | stringstream(tempstr) >> y; |
248 | |
|
249 | 0 | tempstr.assign(line,36,8); |
250 | 0 | stringstream(tempstr) >> z; |
251 | |
|
252 | 0 | if (line.size() > 44) { |
253 | 0 | tempstr.assign(line,44,8); |
254 | 0 | stringstream(tempstr) >> vx; |
255 | |
|
256 | 0 | tempstr.assign(line,52,8); |
257 | 0 | stringstream(tempstr) >> vy; |
258 | |
|
259 | 0 | tempstr.assign(line,60,8); |
260 | 0 | stringstream(tempstr) >> vz; |
261 | |
|
262 | 0 | velocity = new OBVectorData(); |
263 | 0 | velocity->SetData(vx, vy, vz); |
264 | 0 | velocity->SetAttribute("Velocity"); |
265 | 0 | velocity->SetOrigin(fileformatInput); |
266 | 0 | atom->SetData(velocity); |
267 | 0 | } |
268 | | |
269 | | // OB translates this and, e.g., OW1 turns into O3 |
270 | | // Type conversion should be done explicitly if that needs to be |
271 | | // controlled. |
272 | 0 | atom->SetType(atomtype); |
273 | | |
274 | | // Set coordinates of the atom, multiply by 10 to convert from nm to |
275 | | // angstrom |
276 | 0 | atom->SetVector(x*10, y*10, z*10); |
277 | |
|
278 | 0 | if (resid == residx) { |
279 | | // Add atom to an existing residue |
280 | 0 | if (res == nullptr) { |
281 | 0 | res = pmol->NewResidue(); |
282 | 0 | res->SetName(resname); |
283 | 0 | res->SetNum(resid); |
284 | 0 | } |
285 | 0 | res->AddAtom(atom); |
286 | 0 | } else { |
287 | | // Create new residue and use that |
288 | 0 | res = pmol->NewResidue(); |
289 | 0 | res->SetName(resname); |
290 | 0 | res->SetNum(resid); |
291 | 0 | res->AddAtom(atom); |
292 | 0 | residx = resid; |
293 | 0 | } |
294 | | |
295 | | // Atom type has to be set in residues as AtomID |
296 | 0 | res->SetAtomID(atom, atomtype); |
297 | | |
298 | | // For determining the element this doesn't work: |
299 | | // atom->SetAtomicNum(OBElements::GetAtomicNum(atom->GetType())); |
300 | | // |
301 | | // So the element simbol should be found while reading in the file. It |
302 | | // could be possible to provide an option for external atomtype<->element |
303 | | // simbol map. Now such functionality is not implemented. |
304 | | |
305 | | // TODO: Make element perception optional and improve it |
306 | 0 | string element = ""; |
307 | 0 | if (atomtype[0] == 'C') { |
308 | 0 | if (atomtype.find_first_of("aloru",1) == 1) { |
309 | 0 | element.assign(atomtype,0,2); |
310 | 0 | } else { |
311 | 0 | element.assign(atomtype,0,1); |
312 | 0 | } |
313 | 0 | } else if (atomtype[0] == 'N') { |
314 | 0 | if (atomtype.find_first_of("abei",1) == 1) { |
315 | 0 | element.assign(atomtype,0,2); |
316 | 0 | } else { |
317 | 0 | element.assign(atomtype,0,1); |
318 | 0 | } |
319 | 0 | } else { |
320 | 0 | element.assign(atomtype,0,1); |
321 | 0 | } |
322 | 0 | atom->SetAtomicNum(OBElements::GetAtomicNum(element.data())); |
323 | 0 | } |
324 | | |
325 | | // Get periodic box |
326 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) { |
327 | 0 | errorMsg << "Problems reading a GRO file: " |
328 | 0 | << "Could not read box vectors!" << endl; |
329 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
330 | 0 | return false; |
331 | 0 | } |
332 | | |
333 | 0 | double v1x=0.0, v2y=0.0, v3z=0.0; |
334 | 0 | double v1y=0.0, v1z=0.0, v2x=0.0; |
335 | 0 | double v2z=0.0, v3x=0.0, v3y=0.0; |
336 | |
|
337 | 0 | stringstream ss(buffer); |
338 | 0 | if (!ss) { |
339 | 0 | errorMsg << "Problems reading a GRO file: " |
340 | 0 | << "Could not read box vectors!" << endl; |
341 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
342 | 0 | return false; |
343 | 0 | } |
344 | | |
345 | 0 | ss >> v1x >> v2y >> v3z; |
346 | 0 | if (ss) { |
347 | 0 | ss >> v1y >> v1z >> v2x; |
348 | 0 | ss >> v2z >> v3x >> v3y; |
349 | 0 | } |
350 | |
|
351 | 0 | if (!(v1x == 0.0 && v2y == 0.0 && v3z == 0.0 && |
352 | 0 | v1y == 0.0 && v1z == 0.0 && v2x == 0.0 && |
353 | 0 | v2z == 0.0 && v3x == 0.0 && v3y == 0.0)) { |
354 | | // Set box vectors and convert nm to angstroms |
355 | 0 | const vector3 v1(v1x*10,v1y*10,v1z*10); |
356 | 0 | const vector3 v2(v2x*10,v2y*10,v2z*10); |
357 | 0 | const vector3 v3(v3x*10,v3y*10,v3z*10); |
358 | |
|
359 | 0 | OBUnitCell* cell = new OBUnitCell(); |
360 | 0 | cell->SetData(v1, v2, v3); |
361 | 0 | pmol->SetData(cell); |
362 | 0 | } |
363 | |
|
364 | 0 | pmol->EndModify(); |
365 | | |
366 | | // Input options -as and -ab |
367 | 0 | if (!pConv->IsOption("b", OBConversion::INOPTIONS)) { |
368 | 0 | pmol->ConnectTheDots(); |
369 | 0 | if (!pConv->IsOption("s", OBConversion::INOPTIONS)) { |
370 | 0 | pmol->PerceiveBondOrders(); |
371 | 0 | } |
372 | 0 | } |
373 | 0 | pmol->SetChainsPerceived(); |
374 | | |
375 | | /* For multi-molecule formats, leave the input stream at the start of the |
376 | | next molecule, ready for this routine to be called again. |
377 | | |
378 | | * Return true if ok. Returning false means discard the OBMol and stop |
379 | | converting, unless the -e option is set. With a multi-molecule inputstream |
380 | | this will skip the current molecule and continue with the next, if |
381 | | SkipObjects() has been defined. If it has not, and continuation after |
382 | | errors is still required, it is necessary to leave the input stream at the |
383 | | beginning of next object when returning false;*/ |
384 | 0 | return true; |
385 | 0 | } |
386 | | |
387 | | //////////////////////////////////////////////////////////////// |
388 | | |
389 | | bool GROFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv) |
390 | 2.03k | { |
391 | 2.03k | OBMol* pmol = dynamic_cast<OBMol*>(pOb); |
392 | 2.03k | if (pmol == nullptr) |
393 | 0 | return false; // Stop converting |
394 | | |
395 | 2.03k | ostream& ofs = *pConv->GetOutStream(); |
396 | | |
397 | | /** Write the representation of the OBMol molecule to the output stream **/ |
398 | | |
399 | 2.03k | OBVectorData* vector; |
400 | 2.03k | vector3 v; |
401 | 2.03k | OBResidue* res = nullptr; |
402 | 2.03k | string tempstr = ""; |
403 | 2.03k | long int atIdx = 0; |
404 | 2.03k | long int resIdx = 0; |
405 | | |
406 | 2.03k | ofs << pmol->GetTitle() << endl; |
407 | 2.03k | ofs << pmol->NumAtoms() << endl; |
408 | 2.03k | ofs.setf(ios::fixed); |
409 | | |
410 | 2.03k | FOR_ATOMS_OF_MOL(atom, pmol) { |
411 | | // CVE-2022-42885: atoms read from formats without residue |
412 | | // information return a null OBResidue. Synthesize defaults |
413 | | // rather than dereferencing the null pointer. |
414 | 0 | res = atom->GetResidue(); |
415 | 0 | resIdx = (res != nullptr) ? res->GetNum() : 1; |
416 | | // Check if residue index excedes the field width and should be wrapped |
417 | 0 | if (resIdx > 99999) { |
418 | 0 | ofs << setw(5) << resIdx - 100000*int(resIdx/100000); |
419 | 0 | } else { |
420 | 0 | ofs << setw(5) << resIdx; |
421 | 0 | } |
422 | 0 | ofs << setw(5) << left |
423 | 0 | << ((res != nullptr) ? res->GetName() : string("UNK")); |
424 | | // Remove whitespace from AtomID left by other formats |
425 | 0 | tempstr = (res != nullptr) ? res->GetAtomID(&(*atom)) |
426 | 0 | : string(atom->GetType()); |
427 | 0 | ofs << setw(5) << right << Trim(tempstr); |
428 | 0 | atIdx = atom->GetIdx(); |
429 | | // Check if atom index excedes the field width and should be wrapped |
430 | 0 | if (atIdx > 99999) { |
431 | 0 | ofs << setw(5) << atIdx - 100000*int(atIdx/100000); |
432 | 0 | } else { |
433 | 0 | ofs << setw(5) << atIdx; |
434 | 0 | } |
435 | 0 | ofs.precision(3); |
436 | | // OpenBabel uses angstrom, so converting to nm by dividing by 10 |
437 | 0 | ofs << setw(8) << atom->x()/10 |
438 | 0 | << setw(8) << atom->y()/10 |
439 | 0 | << setw(8) << atom->z()/10; |
440 | 0 | if (atom->GetData("Velocity")) { |
441 | 0 | vector = (OBVectorData*) atom->GetData("Velocity"); |
442 | 0 | v = vector->GetData(); |
443 | 0 | ofs.precision(4); |
444 | 0 | ofs << setw(8) << v.x() |
445 | 0 | << setw(8) << v.y() |
446 | 0 | << setw(8) << v.z(); |
447 | 0 | } |
448 | 0 | ofs << endl; |
449 | 0 | } |
450 | | |
451 | | // On the last line of the file goes periodic box specification |
452 | 2.03k | if (pmol->HasData(OBGenericDataType::UnitCell)) { |
453 | 0 | OBUnitCell* cell = (OBUnitCell*) pmol->GetData(OBGenericDataType::UnitCell); |
454 | 0 | matrix3x3 m = cell->GetCellMatrix(); |
455 | 0 | vector3 v1 = m.GetRow(0); |
456 | 0 | vector3 v2 = m.GetRow(1); |
457 | 0 | vector3 v3 = m.GetRow(2); |
458 | | |
459 | | // Gromacs itself uses precision of 5, so it should be fine |
460 | 0 | ofs.precision(5); |
461 | 0 | ofs << " " << v1.x()/10 |
462 | 0 | << " " << v2.y()/10 |
463 | 0 | << " " << v3.z()/10; |
464 | | |
465 | | // If there is any non-zero value among others, then write them all |
466 | 0 | const double TRESHOLD = 1.0e-8; |
467 | 0 | if (fabs(v1.y()) > TRESHOLD || fabs(v1.z()) > TRESHOLD || |
468 | 0 | fabs(v2.x()) > TRESHOLD || fabs(v2.z()) > TRESHOLD || |
469 | 0 | fabs(v3.x()) > TRESHOLD || fabs(v3.y()) > TRESHOLD) { |
470 | 0 | ofs << " " << v1.y()/10 |
471 | 0 | << " " << v1.z()/10 |
472 | 0 | << " " << v2.x()/10 |
473 | 0 | << " " << v2.z()/10 |
474 | 0 | << " " << v3.x()/10 |
475 | 0 | << " " << v3.y()/10; |
476 | 0 | } |
477 | 2.03k | } else { |
478 | | // Set to zero if there is no box data in the molecule |
479 | 2.03k | ofs << " 0.00000 0.00000 0.00000"; |
480 | 2.03k | } |
481 | 2.03k | ofs << endl; |
482 | | |
483 | 2.03k | return true; |
484 | 2.03k | } |
485 | | |
486 | | } //namespace OpenBabel |
487 | | |