Coverage Report

Created: 2026-09-14 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/phmodel.cpp
Line
Count
Source
1
/**********************************************************************
2
phmodel.cpp - Read pH rules and assign charges.
3
4
Copyright (C) 1998-2001 by OpenEye Scientific Software, Inc.
5
Some portions Copyright (C) 2001-2005 by Geoffrey R. Hutchison
6
7
This file is part of the Open Babel project.
8
For more information, see <http://openbabel.org/>
9
10
This program is free software; you can redistribute it and/or modify
11
it under the terms of the GNU General Public License as published by
12
the Free Software Foundation version 2 of the License.
13
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
GNU General Public License for more details.
18
***********************************************************************/
19
#include <openbabel/babelconfig.h>
20
21
#include <openbabel/mol.h>
22
#include <openbabel/atom.h>
23
#include <openbabel/bond.h>
24
#include <openbabel/typer.h>
25
#include <openbabel/obiter.h>
26
#include <openbabel/oberror.h>
27
#include <openbabel/phmodel.h>
28
#include <openbabel/obfunctions.h>
29
30
#include <cstdlib>
31
32
// private data header with default parameters
33
#include "phmodeldata.h"
34
35
#ifdef WIN32
36
#pragma warning (disable : 4786)
37
#endif
38
39
using namespace std;
40
41
namespace OpenBabel
42
{
43
44
  // Global OBPhModel for assigning formal charges and hydrogen addition rules
45
  THREAD_LOCAL OBPhModel phmodel;
46
47
  OBPhModel::OBPhModel()
48
1
  {
49
1
    _init = false;
50
1
    _dir = BABEL_DATADIR;
51
1
    _envvar = "BABEL_DATADIR";
52
1
    _filename = "phmodel.txt";
53
1
    _subdir = "data";
54
1
    _dataptr = PhModelData;
55
1
  }
56
57
  OBPhModel::~OBPhModel()
58
1
  {
59
1
    vector<OBChemTsfm*>::iterator k;
60
39
    for (k = _vtsfm.begin();k != _vtsfm.end();++k)
61
38
      delete *k;
62
63
1
    vector<pair<OBSmartsPattern*,vector<double> > >::iterator m;
64
26
    for (m = _vschrg.begin();m != _vschrg.end();++m)
65
25
      delete m->first;
66
1
  }
67
68
  void OBPhModel::ParseLine(const char *buffer)
69
63
  {
70
63
    vector<string> vs;
71
63
    OBSmartsPattern *sp;
72
73
63
    if (buffer[0] == '#')
74
0
      return;
75
76
63
    if (EQn(buffer,"TRANSFORM",7))
77
38
      {
78
38
        tokenize(vs,buffer);
79
38
        if (vs.size() < 5)
80
0
          {
81
0
            obErrorLog.ThrowError(__FUNCTION__, " Could not parse line in phmodel table from phmodel.txt", obInfo);
82
0
            return;
83
0
          }
84
85
38
        OBChemTsfm *tsfm = new OBChemTsfm;
86
38
        if (!tsfm->Init(vs[1],vs[3]))
87
0
          {
88
0
            delete tsfm;
89
0
            tsfm = nullptr;
90
0
            obErrorLog.ThrowError(__FUNCTION__, " Could not parse line in phmodel table from phmodel.txt", obInfo);
91
0
            return;
92
0
          }
93
94
38
        _vtsfm.push_back(tsfm);
95
38
        _vpKa.push_back(atof(vs[4].c_str()));
96
38
      }
97
25
    else if (EQn(buffer,"SEEDCHARGE",10))
98
25
      {
99
25
        tokenize(vs,buffer);
100
25
        if (vs.size() < 2)
101
0
          {
102
0
            obErrorLog.ThrowError(__FUNCTION__, " Could not parse line in phmodel table from phmodel.txt", obInfo);
103
0
            return;
104
0
          }
105
106
25
        sp = new OBSmartsPattern;
107
25
        if (!sp->Init(vs[1]) || (vs.size()-2) != sp->NumAtoms())
108
0
          {
109
0
            delete sp;
110
0
            sp = nullptr;
111
0
            obErrorLog.ThrowError(__FUNCTION__, " Could not parse line in phmodel table from phmodel.txt", obInfo);
112
0
            return;
113
0
          }
114
115
25
        vector<double> vf;
116
25
        vector<string>::iterator i;
117
101
        for (i = vs.begin()+2;i != vs.end();++i)
118
76
          vf.push_back(atof((char*)i->c_str()));
119
120
25
        _vschrg.push_back(pair<OBSmartsPattern*,vector<double> > (sp,vf));
121
25
      }
122
63
  }
123
124
  void OBPhModel::AssignSeedPartialCharge(OBMol &mol)
125
924
  {
126
924
    if (!_init)
127
1
      Init();
128
129
924
    mol.SetPartialChargesPerceived();
130
924
    if (!mol.AutomaticPartialCharge())
131
0
      return;
132
133
924
    vector<pair<OBSmartsPattern*,vector<double> > >::iterator i;
134
24.0k
    for (i = _vschrg.begin(); i != _vschrg.end(); ++i) {
135
23.1k
      std::vector<std::vector<int> > mlist;
136
23.1k
      if (i->first->Match(mol, mlist, OBSmartsPattern::AllUnique))
137
87
      {
138
87
        unsigned int k;
139
87
        vector<vector<int> >::iterator j;
140
141
462
        for (j = mlist.begin(); j != mlist.end(); ++j)
142
1.14k
          for (k = 0; k < j->size(); ++k)
143
772
            mol.GetAtom((*j)[k])->SetPartialCharge(i->second[k]);
144
87
      }
145
23.1k
    }
146
924
  }
147
148
  void OBPhModel::CorrectForPH(OBMol &mol, double pH)
149
0
  {
150
0
    if (!_init)
151
0
      Init();
152
0
    if (mol.IsCorrectedForPH())
153
0
      return;
154
0
    if (mol.GetDimension() > 0 && !mol.AutomaticFormalCharge())
155
0
      return;
156
157
0
    bool hasChainsPerceived = mol.HasChainsPerceived();
158
159
0
    mol.SetCorrectedForPH();
160
161
0
    obErrorLog.ThrowError(__FUNCTION__,
162
0
                          "Ran OpenBabel::CorrectForPH", obAuditMsg);
163
164
0
    mol.DeleteHydrogens();
165
166
0
    for (unsigned int i = 0; i < _vtsfm.size(); ++i) {
167
168
0
      if (_vpKa[i] > 1E+9) {
169
        // always apply when pKa is > 1e+9
170
0
        _vtsfm[i]->Apply(mol);
171
0
      } else {
172
        // 10^(pKa - pH) = [HA] / [A-]
173
        //
174
        // > 1 : [HA] > [A-]
175
        // < 1 : [HA] < [A-]
176
0
        if (_vtsfm[i]->IsAcid()) {
177
          //cout << "IsAcid == " << _vtsfm[i]->IsAcid() << endl;
178
          //cout << "pKa == " << _vpKa[i] << endl;
179
          //cout << "pow(10, _vpKa[i] - pH) == " << pow(10, _vpKa[i] - pH) << endl;
180
0
          if (pow(10, _vpKa[i] - pH) < 1.0) {
181
            //cout << "APPLY!!" << endl;
182
0
            _vtsfm[i]->Apply(mol);
183
0
          }
184
0
        }
185
186
        // 10^(pKa - pH) = [BH+] / [B:]
187
        //
188
        // > 1 : [BH+] > [B:]
189
        // < 1 : [BH+] < [B:]
190
0
        if (_vtsfm[i]->IsBase()) {
191
          //cout << "IsBase == " << _vtsfm[i]->IsBase() << endl;
192
          //cout << "pKa == " << _vpKa[i] << endl;
193
          //cout << "pow(10, _vpKa[i] - pH) == " << pow(10, _vpKa[i] - pH) << endl;
194
0
          if (pow(10, _vpKa[i] - pH) > 1.0) {
195
            //cout << "APPLY!!" << endl;
196
0
            _vtsfm[i]->Apply(mol);
197
0
          }
198
0
        }
199
0
      }
200
0
    }
201
202
0
    if (hasChainsPerceived)
203
0
    {
204
0
      mol.SetChainsPerceived();
205
0
    }
206
0
  }
207
208
209
  // Portions of this documentation adapted from the JOELib docs, written by
210
  // Joerg Wegner
211
  /** \class OBChemTsfm phmodel.h <openbabel/phmodel.h>
212
      \brief SMARTS based structural modification (chemical transformation)
213
214
      Transformation of chemical structures can be used for pH value correction
215
      (i.e. via OBPhModel and OBMol::CorrectForPH()). The OBChemTsfm class
216
      defines SMARTS based TRANSFORM patterns to delete atoms, change atom types,
217
      atom formal charges, and bond types.
218
219
      For storing and converting chemical reaction files, use the OBReaction class.
220
  **/
221
  bool OBChemTsfm::Init(string &bgn,string &end)
222
38
  {
223
38
    if (!_bgn.Init(bgn))
224
0
      return(false);
225
38
    if (!end.empty())
226
38
      if (!_end.Init(end))
227
0
        return(false);
228
229
    //find atoms to be deleted
230
38
    unsigned int i,j;
231
38
    int vb;
232
38
    bool found;
233
308
    for (i = 0;i < _bgn.NumAtoms();++i)
234
270
      if ((vb = _bgn.GetVectorBinding(i)))
235
60
        {
236
60
          found = false;
237
292
          for (j = 0;j < _end.NumAtoms();++j)
238
289
            if (vb == _end.GetVectorBinding(j))
239
57
              {
240
57
                found = true;
241
57
                break;
242
57
              }
243
244
60
          if (!found)
245
3
            _vadel.push_back(i);
246
60
        }
247
248
    //find elements to be changed
249
38
    int ele;
250
308
    for (i = 0;i < _bgn.NumAtoms();++i)
251
      // Allow single-atom transformations without vector bindings
252
270
      if ((vb = _bgn.GetVectorBinding(i)) || _bgn.NumAtoms() == 1)
253
60
        {
254
60
          ele = _bgn.GetAtomicNum(i);
255
415
          for (j = 0;j < _end.NumAtoms();++j)
256
355
            if (vb == _end.GetVectorBinding(j))
257
57
              if (ele != _end.GetAtomicNum(j))
258
0
                {
259
0
                  _vele.push_back(pair<int,int> (i,_end.GetAtomicNum(j)));
260
0
                  break;
261
0
                }
262
60
        }
263
264
    //find charges to modify
265
38
    int chrg;
266
308
    for (i = 0;i < _bgn.NumAtoms();++i)
267
      // Allow single-atom transformations without vector bindings
268
      // PR#2802980.
269
270
      if ((vb = _bgn.GetVectorBinding(i)) || _bgn.NumAtoms() == 1)
270
60
        {
271
60
          chrg = _bgn.GetCharge(i);
272
415
          for (j = 0;j < _end.NumAtoms();++j)
273
355
            if (vb == _end.GetVectorBinding(j))
274
57
              if (chrg != _end.GetCharge(j))
275
45
                _vchrg.push_back(pair<int,int> (i,_end.GetCharge(j)));
276
60
        }
277
278
    //find bonds to be modified
279
    //find bonds to be modified
280
38
    int bsrc,bdst,bord,bvb1,bvb2;
281
38
    int esrc,edst,eord,evb1,evb2;
282
283
278
    for (i = 0;i < _bgn.NumBonds();++i)
284
240
      {
285
240
        _bgn.GetBond(bsrc,bdst,bord,i);
286
240
        bvb1 = _bgn.GetVectorBinding(bsrc);
287
240
        bvb2 = _bgn.GetVectorBinding(bdst);
288
240
        if (!bvb1 || !bvb2)
289
219
          continue;
290
291
44
        for (j = 0;j < _end.NumBonds();++j)
292
41
          {
293
41
            _end.GetBond(esrc,edst,eord,j);
294
41
            evb1 = _end.GetVectorBinding(esrc);
295
41
            evb2 = _end.GetVectorBinding(edst);
296
41
            if ((bvb1 == evb1 && bvb2 == evb2) || (bvb1 == evb2 && bvb2 == evb1))
297
18
              {
298
18
                if (bord == eord)
299
9
                  break; //nothing to modify if bond orders identical
300
9
                _vbond.push_back(pair<pair<int,int>,int> (pair<int,int> (bsrc,bdst),eord));
301
9
                break;
302
18
              }
303
41
          }
304
21
      }
305
306
    //make sure there is some kind of transform to do here
307
38
    if (_vadel.empty() && _vchrg.empty() && _vbond.empty() && _vele.empty())
308
0
      return(false);
309
310
38
    return(true);
311
38
  }
312
313
  bool OBChemTsfm::Apply(OBMol &mol)
314
0
  {
315
0
    if (!_bgn.Match(mol))
316
0
      return(false);
317
0
    mol.BeginModify();
318
0
    vector<vector<int> > mlist = _bgn.GetUMapList();
319
320
0
    obErrorLog.ThrowError(__FUNCTION__,
321
0
                          "Ran OpenBabel::OBChemTransform", obAuditMsg);
322
323
0
    if (!_vchrg.empty()) //modify charges
324
0
      {
325
0
        vector<vector<int> >::iterator i;
326
0
        vector<pair<int,int> >::iterator j;
327
328
0
        for (i = mlist.begin();i != mlist.end();++i)
329
0
          for (j = _vchrg.begin();j != _vchrg.end();++j)
330
0
            if (j->first < (signed)i->size()) { //goof proofing
331
0
              OBAtom *atom = mol.GetAtom((*i)[j->first]);
332
0
              int old_charge = atom->GetFormalCharge();
333
0
              if(j->second != old_charge) {
334
0
                atom->SetFormalCharge(j->second);
335
0
                OBAtomAssignTypicalImplicitHydrogens(atom); //update with new charge info
336
0
              }
337
0
            }
338
0
      }
339
340
0
    if (!_vbond.empty()) //modify bond orders
341
0
      {
342
0
        OBBond *bond;
343
0
        vector<vector<int> >::iterator i;
344
0
        vector<pair<pair<int,int>,int> >::iterator j;
345
0
        for (i = mlist.begin();i != mlist.end();++i)
346
0
          for (j = _vbond.begin();j != _vbond.end();++j)
347
0
            {
348
0
              bond = mol.GetBond((*i)[j->first.first],(*i)[j->first.second]);
349
0
              if (!bond)
350
0
                {
351
0
                  obErrorLog.ThrowError(__FUNCTION__, "unable to find bond", obDebug);
352
0
                  continue;
353
0
                }
354
0
              unsigned int old_bond_order = bond->GetBondOrder();
355
0
              bond->SetBondOrder(j->second);
356
0
              for (int k = 0; k < 2; ++k) {
357
0
                OBAtom* atom = k == 0 ? bond->GetBeginAtom() : bond->GetEndAtom();
358
0
                int new_hcount = static_cast<int>(atom->GetImplicitHCount())
359
0
                                 - (static_cast<int>(j->second) - static_cast<int>(old_bond_order));
360
0
                if (new_hcount < 0)
361
0
                  new_hcount = 0;
362
0
                atom->SetImplicitHCount(new_hcount);
363
0
              }
364
0
            }
365
0
      }
366
367
0
    if (!_vadel.empty() || !_vele.empty()) //delete atoms and change elements
368
0
      {
369
0
        vector<int>::iterator j;
370
0
        vector<vector<int> >::iterator i;
371
372
0
        if (!_vele.empty())
373
0
          {
374
0
            vector<pair<int,int> >::iterator k;
375
0
            for (i = mlist.begin();i != mlist.end();++i)
376
0
              for (k = _vele.begin();k != _vele.end();++k)
377
0
                mol.GetAtom((*i)[k->first])->SetAtomicNum(k->second);
378
0
          }
379
380
        //make sure same atom isn't deleted twice
381
0
        vector<bool> vda;
382
0
        vector<OBAtom*> vdel;
383
0
        vda.resize(mol.NumAtoms()+1,false);
384
0
        for (i = mlist.begin();i != mlist.end();++i)
385
0
          for (j = _vadel.begin();j != _vadel.end();++j)
386
0
            if (!vda[(*i)[*j]])
387
0
              {
388
0
                vda[(*i)[*j]] = true;
389
0
                vdel.push_back(mol.GetAtom((*i)[*j]));
390
0
              }
391
392
0
        vector<OBAtom*>::iterator k;
393
0
        for (k = vdel.begin();k != vdel.end();++k)
394
0
          mol.DeleteAtom((OBAtom*)*k);
395
0
      }
396
397
0
    mol.EndModify();
398
0
    return(true);
399
0
  }
400
401
  bool OBChemTsfm::IsAcid()
402
0
  {
403
0
    if (_bgn.NumAtoms() > _end.NumAtoms())  // O=CO[#1:1] >> O=CO
404
0
      return true;
405
406
0
    for (unsigned int i = 0; i < _end.NumAtoms(); ++i) {
407
0
      if (_end.GetCharge(i) < 0)
408
0
        return true;
409
0
    }
410
411
0
    return false;
412
0
  }
413
414
  bool OBChemTsfm::IsBase()
415
0
  {
416
0
    for (unsigned int i = 0; i < _end.NumAtoms(); ++i) {
417
0
      if (_end.GetCharge(i) > 0)
418
0
        return true;
419
0
    }
420
421
0
    return false;
422
0
  }
423
424
} //namespace OpenBabel
425
426
//! \file phmodel.cpp
427
//! \brief Read pH rules and assign charges.