Coverage Report

Created: 2025-03-20 06:14

/src/openbabel/src/formats/moldenformat.cpp
Line
Count
Source (jump to first uncovered line)
1
//
2
// Molekel - Molecular Visualization Program
3
// Copyright (C) 2006, 2007 Swiss National Supercomputing Centre (CSCS)
4
// Some portions Copyright (C) 2009 Michael Banck
5
//
6
// This program is free software; you can redistribute it and/or
7
// modify it under the terms of the GNU General Public License
8
// as published by the Free Software Foundation; either version 2
9
// of the License, or (at your option) any later version.
10
11
// This program is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
// GNU General Public License for more details.
15
//
16
// You should have received a copy of the GNU General Public License
17
// along with this program; if not, write to the Free Software
18
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
// MA  02110-1301, USA.
20
//
21
// $Author$
22
// $Date$
23
// $Revision$
24
//
25
26
// STD
27
#include <fstream>
28
#include <string>
29
#include <vector>
30
#include <sstream>
31
#include <cstring>
32
#include <cstdlib>
33
// reference: http://www.cmbi.ru.nl/molden/molden_format.html
34
35
#include <openbabel/obconversion.h>
36
#include <openbabel/obmolecformat.h>
37
#include <openbabel/mol.h>
38
#include <openbabel/atom.h>
39
#include <openbabel/elements.h>
40
#include <openbabel/generic.h>
41
#include <openbabel/obiter.h>
42
43
44
0
#define BOHR_TO_ANGSTROM 0.529177249
45
0
#define ANGSTROM_TO_BOHR 1.889725989
46
47
using namespace std;
48
49
namespace OpenBabel
50
{
51
52
/// Molden input reader: reads atoms from [Atoms] section of Molden input file.
53
class OBMoldenFormat : public OpenBabel::OBMoleculeFormat
54
{
55
public:
56
    /// Constructor: register 'molden' format.
57
    OBMoldenFormat()
58
6
    {
59
6
        OBConversion::RegisterFormat( "molden", this );
60
6
        OBConversion::RegisterFormat( "mold", this );
61
6
        OBConversion::RegisterFormat( "molf", this );
62
6
    }
63
64
    /// Return description.
65
    const char* Description() override  // required
66
0
    {
67
0
        return
68
0
        "Molden format\n"
69
0
        "Read Options e.g. -as\n"
70
0
        "  b no bonds\n"
71
0
        "  s no multiple bonds\n\n";
72
0
    }
73
74
    /// Return a specification url, not really a specification since
75
    /// I couldn't find it but close enough.
76
    const char* SpecificationURL() override
77
0
    {
78
0
        return "http://www.cmbi.ru.nl/molden/molden_format.html";
79
0
    }
80
81
    /// Return MIME type, NULL in this case.
82
0
    const char* GetMIMEType() override { return nullptr; }
83
84
      /// Return read/write flag.
85
    unsigned int Flags() override
86
127
    {
87
127
        return READONEONLY | WRITEONEONLY ;
88
127
    }
89
90
    /// Skip to object: used for multi-object file formats.
91
0
    int SkipObjects(int n, OpenBabel::OBConversion* pConv) override { return 0; }
92
93
    /// Read.
94
    bool ReadMolecule(OpenBabel::OBBase* pOb, OpenBabel::OBConversion* pConv) override;
95
96
    /// Write.
97
    bool WriteMolecule(OpenBabel::OBBase*, OpenBabel::OBConversion*) override;
98
};
99
100
//------------------------------------------------------------------------------
101
102
// Global variable used to register Molden format.
103
OBMoldenFormat moldenFormat__;
104
105
//------------------------------------------------------------------------------
106
107
108
//==============================================================================
109
110
//------------------------------------------------------------------------------
111
bool OBMoldenFormat::ReadMolecule( OBBase* pOb, OBConversion* pConv )
112
32
{
113
32
    OBMol* pmol = dynamic_cast< OBMol* >(pOb);
114
32
    if (pmol == nullptr) return false;
115
116
32
    istream& ifs = *pConv->GetInStream();
117
118
    //Vibrational data
119
32
    std::vector< std::vector< vector3 > > Lx;
120
32
    std::vector<double> Frequencies, Intensities;
121
122
32
    std::vector< std::vector< vector3 > > conformers; // multiple geometries
123
32
    std::vector< std::vector< vector3 > > forces;
124
32
    std::vector<double> energies;
125
126
32
    pmol->BeginModify();
127
32
    pmol->SetDimension( 3 );
128
32
    string lineBuffer;
129
130
32.1k
    while( getline( ifs, lineBuffer ) )
131
32.1k
      {
132
32.1k
        if( lineBuffer.find( "[Atoms]" ) != string::npos ||
133
32.1k
            lineBuffer.find( "[ATOMS]" ) != string::npos ) {
134
0
          unsigned int ecpLines = 0;
135
0
          double factor = 1.; // Angstrom
136
0
          if( lineBuffer.find( "AU" ) != string::npos ) factor = BOHR_TO_ANGSTROM; // Bohr
137
0
          while( getline( ifs, lineBuffer ) )
138
0
            {
139
0
              if( lineBuffer == "" ) continue;
140
0
              if( lineBuffer.find( "[" ) != string::npos ) break;
141
0
              istringstream is( lineBuffer );
142
0
              string atomName;
143
0
              int atomId;
144
0
              int atomicNumber;
145
0
              int valenceCharge;
146
0
              double x, y, z;
147
0
              is >> atomName >> atomId >> valenceCharge >> x >> y >> z;
148
0
              OBAtom* atom = pmol->NewAtom();
149
0
              if( !atom ) break;
150
0
              atomicNumber = OBElements::GetAtomicNum(atomName.c_str());
151
0
              atom->SetAtomicNum( atomicNumber );
152
0
              atom->SetVector( x * factor, y * factor, z * factor );
153
0
              if (atomicNumber-valenceCharge!=0){
154
0
                OBPairData* ecpData = new OBPairData();
155
0
                ecpData->SetAttribute("ecp");
156
0
                std::ostringstream os;
157
0
                os << atomicNumber-valenceCharge;
158
0
                ecpData->SetValue(os.str());
159
0
                atom->SetData(ecpData);
160
0
                ++ecpLines;
161
0
              }
162
0
            }
163
0
          if (ecpLines!=0){
164
0
              cerr << "WARNING: element number given in 3rd column does not agree with element name on " << ecpLines << " lines." << endl
165
0
                   << "         Difference between expected nuclear charge and given element number saved to atom property 'ecp'." << endl;
166
0
          }
167
0
        } // "[Atoms]" || "[ATOMS]"
168
32.1k
        if ( lineBuffer.find( "[GEOMETRIES] (XYZ)" ) != string::npos ) {
169
0
          while( getline( ifs, lineBuffer ) ) {
170
0
              if( lineBuffer == "" ) continue;
171
0
              if( lineBuffer.find( "[" ) != string::npos ) break;
172
173
              // should give us a number of atoms (i.e., this is an XYZ-format file)
174
0
              unsigned int natoms;
175
0
              bool createAtoms = false;
176
177
0
              if (sscanf(lineBuffer.c_str(), "%d", &natoms) == 0 || !natoms) {
178
0
                obErrorLog.ThrowError(__FUNCTION__,
179
0
                                      "Problems reading an XYZ geometry: The first line must contain the number of atoms.", obWarning);
180
//                return(false);
181
0
              }
182
0
              if (pmol->NumAtoms() != 0 && pmol->NumAtoms() != natoms) {
183
0
                obErrorLog.ThrowError(__FUNCTION__,
184
0
                                      "Problems reading an XYZ geometry: The first line must contain the number of atoms.", obWarning);
185
//                return(false);
186
0
              } else if (pmol->NumAtoms() == 0) {
187
0
                createAtoms = true;
188
0
              }
189
190
              // next line should be the energy
191
0
              double energy;
192
0
              getline( ifs, lineBuffer );
193
0
              energy = atof(lineBuffer.c_str());
194
0
              if (fabs(energy) < 1.0e-8 ) {
195
0
                obErrorLog.ThrowError(__FUNCTION__,
196
0
                                      "Problems reading an XYZ geometry: The second line should contain the energy.", obWarning);
197
0
              }
198
0
              energies.push_back(energy);
199
200
0
              vector<vector3> coordinates;
201
0
              vector<string> vs;
202
0
              for (unsigned int a = 0; a < natoms; ++a) {
203
0
                if (!getline(ifs, lineBuffer) )
204
0
                  break;
205
0
                tokenize(vs, lineBuffer);
206
0
                if (vs.size() != 4)
207
0
                  break;
208
209
0
                double x, y, z;
210
0
                x = atof(vs[1].c_str());
211
0
                y = atof(vs[2].c_str());
212
0
                z = atof(vs[3].c_str());
213
0
                vector3 point(x, y, z);
214
0
                coordinates.push_back(point);
215
216
0
                if (createAtoms) {
217
0
                  int atomicNum = OBElements::GetAtomicNum(vs[0].c_str());
218
                  //set atomic number, or '0' if the atom type is not recognized
219
0
                  if (atomicNum == 0) {
220
                    // Sometimes people call this an XYZ file, but it's actually Unichem
221
                    // i.e., the first column is the atomic number, not a symbol
222
                    // so we'll try to convert this to an element number
223
0
                    atomicNum = atoi(vs[0].c_str());
224
0
                  }
225
226
0
                  OBAtom* atom = pmol->NewAtom();
227
0
                  if( !atom ) break;
228
0
                  atom->SetAtomicNum( atomicNum );
229
0
                  atom->SetVector( x, y, z );
230
0
                } // end creating atoms
231
232
0
              } // end reading this set of coords
233
0
              conformers.push_back(coordinates);
234
0
          } // end GEOM block
235
236
0
        }
237
238
32.1k
        if( lineBuffer.find( "[FREQ]" ) != string::npos ) {
239
8.75k
          while( getline( ifs, lineBuffer ) )
240
8.75k
            {
241
8.75k
              if( lineBuffer == "" ) continue;
242
4.95k
              if( lineBuffer.find( "[" ) != string::npos ) break;
243
4.94k
              istringstream is( lineBuffer );
244
4.94k
              double freq;
245
4.94k
              is >> freq;
246
4.94k
              Frequencies.push_back( freq );
247
4.94k
            }
248
9
        } // "[FREQ]"
249
32.1k
        if( lineBuffer.find( "[INT]" ) != string::npos ) {
250
0
          while( getline( ifs, lineBuffer ) )
251
0
            {
252
0
              if( lineBuffer == "" ) continue;
253
0
              if( lineBuffer.find( "[" ) != string::npos ) break;
254
0
              istringstream is( lineBuffer );
255
0
              double intens;
256
0
              is >> intens;
257
0
              Intensities.push_back( intens );
258
0
            }
259
0
        } // "[INT]"
260
32.1k
        if( lineBuffer.find( "[FR-COORD]" ) != string::npos ) {
261
0
          if (pmol->NumAtoms() == 0) {
262
            // No atoms yet, probably there is no [ATOMS] section
263
            // in the file.
264
0
            while ( getline( ifs, lineBuffer ) )
265
0
              {
266
0
                if( lineBuffer == "" ) continue;
267
0
                if( lineBuffer.find( "[" ) != string::npos ) break;
268
0
                string atomName;
269
0
                double x, y, z;
270
0
                istringstream is( lineBuffer );
271
0
                is >> atomName >> x >> y >> z;
272
0
                OBAtom* atom = pmol->NewAtom();
273
0
                if( !atom ) break;
274
0
                atom->SetAtomicNum( OBElements::GetAtomicNum(atomName.c_str()));
275
                // Vibrational equilibrium geometry is mandated to be
276
                // in Bohr.
277
0
                atom->SetVector( x * BOHR_TO_ANGSTROM,
278
0
                                 y * BOHR_TO_ANGSTROM,
279
0
                                 z * BOHR_TO_ANGSTROM);
280
0
             }
281
0
           }
282
0
         } // "[FR-COORD]"
283
32.1k
        if( lineBuffer.find( "[FR-NORM-COORD]" ) != string::npos ) {
284
0
          getline( ifs, lineBuffer );
285
0
          vector<string> vs;
286
0
          while( ifs && lineBuffer.find( "ibration") != string::npos )
287
0
            {
288
0
              vector<vector3> vib;
289
0
              getline( ifs, lineBuffer );
290
0
              tokenize(vs, lineBuffer);
291
0
              while( ifs && vs.size() == 3)
292
0
                {
293
0
                  istringstream is( lineBuffer );
294
0
                  double x, y, z;
295
0
                  is >> x >> y >> z;
296
0
                  vib.push_back( vector3( x, y, z ) );
297
0
                  getline( ifs, lineBuffer );
298
0
                  tokenize(vs, lineBuffer);
299
0
                }
300
0
              Lx.push_back( vib );
301
0
           } // while
302
0
        } // "[FR-NORM-COORD]"
303
32.1k
      } // while
304
305
32
    if ( pmol->NumAtoms() == 0 ) {
306
32
      pmol->EndModify();
307
32
      return false;
308
32
    }
309
310
    // Attach vibrational data, if there is any, to molecule
311
0
    if(Frequencies.size()>0)
312
0
    {
313
0
      for (unsigned int i = 0; i < Frequencies.size(); i++) {
314
0
        if (fabs(Frequencies[i]) < 10.) {
315
          // skip translational and rotational modes
316
0
          Frequencies.erase( Frequencies.begin() + i );
317
0
          if (Intensities.size() > i) Intensities.erase( Intensities.begin() + i );
318
0
          Lx.erase( Lx.begin() + i );
319
0
          i--;  // compensate for the vibration which just got cut out
320
0
        }
321
0
      }
322
0
      OBVibrationData* vd = new OBVibrationData;
323
0
      vd->SetData(Lx, Frequencies, Intensities);
324
0
      pmol->SetData(vd);
325
0
    }
326
327
0
    if (energies.size() > 0)
328
0
      pmol->SetEnergies(energies);
329
330
0
    if (conformers.size() > 0) {
331
0
      for (unsigned int i = 0; i < conformers.size(); ++i) {
332
0
        double *confCoord = new double [3*pmol->NumAtoms()];
333
0
        vector<vector3> coordinates = conformers[i];
334
0
        if (coordinates.size() != pmol->NumAtoms())
335
0
          cerr << " Wrong number of coordinates! " << endl;
336
0
        for (unsigned int a = 0; a < coordinates.size(); ++a) {
337
0
          confCoord[3*a] = coordinates[a].x();
338
0
          confCoord[3*a+1] = coordinates[a].y();
339
0
          confCoord[3*a+2] = coordinates[a].z();
340
0
        } // finished atoms
341
0
        pmol->AddConformer(confCoord);
342
0
      } // finished iteration through conformers
343
0
      pmol->SetConformer(pmol->NumConformers());
344
0
    }
345
346
0
    if( !pConv->IsOption( "b", OBConversion::INOPTIONS ) ) pmol->ConnectTheDots();
347
0
    if (!pConv->IsOption( "s", OBConversion::INOPTIONS )
348
0
        && !pConv->IsOption( "b", OBConversion::INOPTIONS ) )
349
0
    {
350
0
        pmol->PerceiveBondOrders();
351
0
    }
352
0
    pmol->EndModify();
353
354
0
    return true;
355
32
}
356
357
bool OBMoldenFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
358
1
{
359
1
    OBMol* pmol = dynamic_cast<OBMol*>(pOb);
360
1
    if (pmol == nullptr)
361
0
      return false;
362
363
    //Define some references so we can use the old parameter names
364
1
    ostream &ofs = *pConv->GetOutStream();
365
1
    OBMol &mol = *pmol;
366
367
1
    char buffer[BUFF_SIZE];
368
1
    int i = 1;
369
370
1
    ofs << "[Molden Format]" << endl;
371
1
    ofs << "[Atoms] Angs" << endl;
372
373
1
    FOR_ATOMS_OF_MOL(atom, mol)
374
557k
      {
375
557k
        snprintf(buffer, BUFF_SIZE, "%2s%6d%3d%13.6f%13.6f%13.6f\n",
376
557k
                OBElements::GetSymbol(atom->GetAtomicNum()),
377
557k
    i++,
378
557k
                atom->GetAtomicNum(),
379
557k
                atom->GetX(),
380
557k
                atom->GetY(),
381
557k
                atom->GetZ());
382
557k
        ofs << buffer;
383
557k
      }
384
385
1
    OBVibrationData *vib = (OBVibrationData *) mol.GetData(OBGenericDataType::VibrationData);
386
1
    if (vib && vib->GetNumberOfFrequencies() > 0) {
387
0
      ofs << "[FREQ]" << endl;
388
0
      vector<double> frequencies = vib->GetFrequencies();
389
0
      vector<double> intensities = vib->GetIntensities();
390
0
      for (unsigned int i = 0; i < vib->GetNumberOfFrequencies(); i++) {
391
0
  snprintf(buffer, BUFF_SIZE, "%10.4f\n", frequencies[i]);
392
0
        ofs << buffer;
393
0
      }
394
0
      if (intensities.size() > 0) {
395
0
        ofs << "[INT]" << endl;
396
0
  for (unsigned int i = 0; i < vib->GetNumberOfFrequencies(); i++) {
397
0
    snprintf(buffer, BUFF_SIZE, "%10.4f\n", intensities[i]);
398
0
    ofs << buffer;
399
0
        }
400
0
      }
401
0
      ofs << "[FR-COORD]" << endl;
402
0
      FOR_ATOMS_OF_MOL(atom, mol)
403
0
        {
404
0
          snprintf(buffer, BUFF_SIZE, "%2s%13.6f%13.6f%13.6f\n",
405
0
                  OBElements::GetSymbol(atom->GetAtomicNum()),
406
0
                  atom->GetX()*ANGSTROM_TO_BOHR,
407
0
                  atom->GetY()*ANGSTROM_TO_BOHR,
408
0
                  atom->GetZ()*ANGSTROM_TO_BOHR);
409
0
          ofs << buffer;
410
0
        }
411
0
      ofs << "[FR-NORM-COORD]" << endl;
412
0
      for (unsigned int mode = 0; mode < vib->GetNumberOfFrequencies(); mode++) {
413
0
  snprintf(buffer, BUFF_SIZE, "vibration%6d\n", mode+1);
414
0
  ofs << buffer;
415
0
        vector<vector3> lx = vib->GetLx()[mode];
416
0
  for (unsigned int i = 0; i < mol.NumAtoms(); i++) {
417
0
    vector3 disp = lx[i];
418
0
    snprintf(buffer, BUFF_SIZE, "%12.6f%13.6f%13.6f\n",
419
0
      disp[0], disp[1], disp[2]);
420
0
    ofs << buffer;
421
0
  }
422
0
      }
423
0
    } // vib
424
1
    return(true);
425
1
}
426
427
}