Coverage Report

Created: 2026-06-23 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rdkit/Code/GraphMol/AdjustQuery.cpp
Line
Count
Source
1
//
2
//  Copyright (c) 2015-2020 Greg Landrum
3
//
4
//   @@ All Rights Reserved @@
5
//  This file is part of the RDKit.
6
//  The contents are covered by the terms of the BSD license
7
//  which is included in the file license.txt, found at the root
8
//  of the RDKit source tree.
9
//
10
#include <GraphMol/GraphMol.h>
11
#include <GraphMol/QueryAtom.h>
12
#include <GraphMol/QueryBond.h>
13
#include <GraphMol/MolOps.h>
14
#include <GraphMol/QueryOps.h>
15
#include <GraphMol/AtomIterators.h>
16
#include <GraphMol/BondIterators.h>
17
18
#include <RDGeneral/BoostStartInclude.h>
19
#include <boost/property_tree/ptree.hpp>
20
#include <boost/property_tree/json_parser.hpp>
21
#include <boost/algorithm/string.hpp>
22
#include <boost/tokenizer.hpp>
23
#include <RDGeneral/BoostEndInclude.h>
24
25
#include <vector>
26
#include <algorithm>
27
28
namespace RDKit {
29
namespace {
30
0
bool isMapped(const Atom *atom) {
31
0
  return atom->hasProp(common_properties::molAtomMapNumber);
32
0
}
33
}  // namespace
34
35
namespace MolOps {
36
37
namespace {
38
typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
39
40
0
unsigned int parseWhichString(const std::string &txt) {
41
0
  unsigned int res = MolOps::ADJUST_IGNORENONE;
42
0
  boost::char_separator<char> sep("|");
43
0
  tokenizer tokens(txt, sep);
44
0
  for (const auto &token : tokens) {
45
0
    if (token == "IGNORENONE") {
46
0
      res |= MolOps::ADJUST_IGNORENONE;
47
0
    } else if (token == "IGNORERINGS") {
48
0
      res |= MolOps::ADJUST_IGNORERINGS;
49
0
    } else if (token == "IGNORECHAINS") {
50
0
      res |= MolOps::ADJUST_IGNORECHAINS;
51
0
    } else if (token == "IGNOREDUMMIES") {
52
0
      res |= MolOps::ADJUST_IGNOREDUMMIES;
53
0
    } else if (token == "IGNORENONDUMMIES") {
54
0
      res |= MolOps::ADJUST_IGNORENONDUMMIES;
55
0
    } else if (token == "IGNOREALL") {
56
0
      res |= MolOps::ADJUST_IGNOREALL;
57
0
    } else {
58
0
      std::string msg = "Unknown flag value: '" + token + "'. Flags ignored.";
59
0
      throw ValueErrorException(msg);
60
0
    }
61
0
  }
62
0
  return res;
63
0
}
64
constexpr const char *conjugatedOrAromatic = "_conjugatedOrAromatic";
65
0
void adjustConjugatedFiveRings(RWMol &mol) {
66
  /*
67
   The idea here is to allow conjugated five-rings to match either aromatic or
68
   aliphatic rings
69
70
   five-rings which contain at least 3 conjugated bonds have all of their
71
   non-query bonds replaced with a SINGLE|DOUBLE|AROMATIC query.
72
73
   */
74
75
0
  std::vector<Bond::BondType> bondTypesToModify = {
76
0
      Bond::BondType::SINGLE, Bond::BondType::DOUBLE, Bond::BondType::AROMATIC};
77
0
  if (!mol.getRingInfo()->isSymmSssr()) {
78
0
    MolOps::symmetrizeSSSR(mol);
79
0
  }
80
0
  for (auto ring : mol.getRingInfo()->bondRings()) {
81
    // only consider 5-rings with at least 3 conjugated bonds
82
0
    if (ring.size() != 5) {
83
0
      continue;
84
0
    }
85
0
    unsigned int nconj = 0;
86
0
    for (auto bi : ring) {
87
0
      const auto bond = mol.getBondWithIdx(bi);
88
0
      if (bond->getIsConjugated()) {
89
0
        ++nconj;
90
0
        if (nconj >= 3) {
91
0
          break;
92
0
        }
93
0
      }
94
0
    }
95
0
    if (nconj < 3) {
96
0
      continue;
97
0
    }
98
    // now make the adjustments
99
0
    QueryBond qb;
100
0
    qb.setQuery(makeSingleOrDoubleOrAromaticBondQuery());
101
0
    for (auto bi : ring) {
102
0
      const auto bond = mol.getBondWithIdx(bi);
103
0
      bond->getBeginAtom()->setProp(conjugatedOrAromatic, 1, true);
104
0
      bond->getEndAtom()->setProp(conjugatedOrAromatic, 1, true);
105
0
      if (std::find(bondTypesToModify.begin(), bondTypesToModify.end(),
106
0
                    bond->getBondType()) != bondTypesToModify.end()) {
107
0
        if (bond->hasQuery()) {
108
0
          BOOST_LOG(rdWarningLog)
109
0
              << "adjustConjugatedFiveRings: replacing a bond "
110
0
                 "that already has a query"
111
0
              << std::endl;
112
0
        }
113
0
        mol.replaceBond(bi, &qb);
114
0
      }
115
0
    }
116
0
  }
117
0
}
118
119
0
bool isAromaticOrConjugated(const Atom &atom) {
120
0
  return atom.getIsAromatic() || atom.hasProp(conjugatedOrAromatic);
121
0
}
122
void adjustSingleBondsFromAromaticAtoms(RWMol &mol, bool toDegreeOneNeighbors,
123
0
                                        bool betweenAromaticAtoms) {
124
  /*
125
  The idea here is to allow single bonds coming from aromatic atoms to match
126
  aromatic bonds under particular circumstances. The conditions are:
127
128
  1. toDegreeOneNeighbors: [D1]-[a] -> [D1]-,:[a]
129
  2. betweenAromaticAtoms: [a]-[a] -> [a]-,:[a]
130
131
  */
132
0
  if (!toDegreeOneNeighbors && !betweenAromaticAtoms) {
133
0
    return;
134
0
  }
135
0
  QueryBond qb;
136
0
  qb.setQuery(makeSingleOrAromaticBondQuery());
137
0
  if (!mol.getRingInfo()->isSymmSssr()) {
138
0
    MolOps::symmetrizeSSSR(mol);
139
0
  }
140
0
  for (auto bond : mol.bonds()) {
141
0
    const auto bAt = bond->getBeginAtom();
142
0
    const auto eAt = bond->getEndAtom();
143
0
    if (!bond->hasQuery() && bond->getBondType() == Bond::BondType::SINGLE) {
144
0
      auto bAtIsAromatic = isAromaticOrConjugated(*bAt);
145
0
      auto eAtIsAromatic = isAromaticOrConjugated(*eAt);
146
147
0
      if (toDegreeOneNeighbors && (bAtIsAromatic ^ eAtIsAromatic)) {
148
0
        if ((bAtIsAromatic && eAt->getDegree() == 1) ||
149
0
            (eAtIsAromatic && bAt->getDegree() == 1)) {
150
0
          mol.replaceBond(bond->getIdx(), &qb);
151
0
        }
152
0
      } else if (betweenAromaticAtoms && bAtIsAromatic && eAtIsAromatic) {
153
0
        mol.replaceBond(bond->getIdx(), &qb);
154
0
      }
155
0
    }
156
0
  }
157
0
}
158
159
0
void setMDLAromaticity(RWMol &mol) {
160
  /*
161
  The idea here is to make aromatic 5-rings that contain an "A" atom in the CTAB
162
  match both aromatic and aliphatic rings.
163
  Schematically, this converts the ring from:
164
     ["A"]1:c:c:c:c:1
165
  to:
166
     ["A"]1-,:c=,:c-,:c=,:c-,:1
167
  Note that "A" is an A atom from a CTAB, not a SMARTS aliphatic query
168
169
  */
170
171
  // it would be simpler to use the substructure matcher for this, but we can't
172
  // use SubstructMatch in the core GraphMol lib
173
0
  if (!mol.getRingInfo()->isSymmSssr()) {
174
0
    MolOps::symmetrizeSSSR(mol);
175
0
  }
176
0
  for (auto ring : mol.getRingInfo()->atomRings()) {
177
0
    if (ring.size() != 5) {
178
0
      continue;
179
0
    }
180
0
    bool keepIt = true;
181
0
    size_t dummy = ring.size() + 1;
182
0
    for (size_t i = 0; i < ring.size(); ++i) {
183
0
      auto ai = ring[i];
184
0
      const auto atom = mol.getAtomWithIdx(ai);
185
0
      if (!atom->getIsAromatic()) {
186
        // we only do fully aromatic rings:
187
0
        keepIt = false;
188
0
        break;
189
0
      } else if (atom->getAtomicNum() == 0 && atom->hasQuery() &&
190
0
                 atom->getQuery()->getTypeLabel() == "A") {
191
0
        if (dummy >= ring.size()) {
192
0
          dummy = i;
193
0
        } else {
194
          // second dummy encountered, we won't do this ring.
195
0
          keepIt = false;
196
0
          break;
197
0
        }
198
0
      } else if (atom->getAtomicNum() != 6) {
199
        // we only do rings consisting solely of C and *
200
0
        keepIt = false;
201
0
        break;
202
0
      }
203
      // we can't handle rings that have query bonds already:
204
0
      auto oidx = ring[4];
205
0
      if (i > 0) {
206
0
        oidx = ring[i - 1];
207
0
      }
208
0
      auto bond = mol.getBondBetweenAtoms(ring[i], oidx);
209
0
      ASSERT_INVARIANT(bond, "expected bond not found");
210
0
      if (bond->hasQuery()) {
211
0
        keepIt = false;
212
0
        break;
213
0
      }
214
0
    }
215
0
    if (keepIt && dummy < ring.size()) {
216
      // we think about the 5-ring in three layers:
217
      //   layer 0: the dummy
218
      //   layer 1: the two atoms connected to the dummy
219
      //   layer 2: the two atoms not connected to the dummy
220
0
      auto l0 = ring[dummy];
221
0
      std::vector<int> l1;
222
0
      std::vector<int> l2;
223
0
      for (auto ai : ring) {
224
0
        if (ai == l0) {
225
0
          continue;
226
0
        } else if (mol.getBondBetweenAtoms(ai, l0)) {
227
0
          l1.push_back(ai);
228
0
        } else {
229
0
          l2.push_back(ai);
230
0
        }
231
0
      }
232
0
      ASSERT_INVARIANT(l1.size() == 2, "bad layer 1 size");
233
0
      ASSERT_INVARIANT(l2.size() == 2, "bad layer 2 size");
234
235
0
      QueryBond qbSingleAromatic;
236
0
      {
237
0
        BOND_OR_QUERY *q = new BOND_OR_QUERY;
238
0
        q->addChild(QueryBond::QUERYBOND_QUERY::CHILD_TYPE(
239
0
            makeBondOrderEqualsQuery(Bond::SINGLE)));
240
0
        q->addChild(QueryBond::QUERYBOND_QUERY::CHILD_TYPE(
241
0
            makeBondOrderEqualsQuery(Bond::AROMATIC)));
242
0
        q->setDescription("BondOr");
243
0
        qbSingleAromatic.setQuery(q);
244
0
      }
245
0
      QueryBond qbDoubleAromatic;
246
0
      {
247
0
        BOND_OR_QUERY *q = new BOND_OR_QUERY;
248
0
        q->addChild(QueryBond::QUERYBOND_QUERY::CHILD_TYPE(
249
0
            makeBondOrderEqualsQuery(Bond::DOUBLE)));
250
0
        q->addChild(QueryBond::QUERYBOND_QUERY::CHILD_TYPE(
251
0
            makeBondOrderEqualsQuery(Bond::AROMATIC)));
252
0
        q->setDescription("BondOr");
253
0
        qbDoubleAromatic.setQuery(q);
254
0
      }
255
0
      for (auto ai : l1) {
256
        // l0 - l1 bonds:
257
0
        auto bond = mol.getBondBetweenAtoms(ai, l0);
258
0
        ASSERT_INVARIANT(bond, "expected l0-l1 bond not found");
259
0
        mol.replaceBond(bond->getIdx(), &qbSingleAromatic);
260
        // l1 - l2 bonds:
261
0
        bond = mol.getBondBetweenAtoms(ai, l2[0]);
262
0
        if (!bond) {
263
0
          bond = mol.getBondBetweenAtoms(ai, l2[1]);
264
0
        }
265
0
        ASSERT_INVARIANT(bond, "expected l1-l2 bond not found");
266
0
        mol.replaceBond(bond->getIdx(), &qbDoubleAromatic);
267
0
      }
268
      // l2 - l2 bond:
269
0
      auto bond = mol.getBondBetweenAtoms(l2[0], l2[1]);
270
0
      ASSERT_INVARIANT(bond, "expected l2-l2 bond not found");
271
0
      mol.replaceBond(bond->getIdx(), &qbSingleAromatic);
272
0
    }
273
0
  }
274
0
}
275
}  // namespace
276
void parseAdjustQueryParametersFromJSON(MolOps::AdjustQueryParameters &p,
277
0
                                        const std::string &json) {
278
0
  PRECONDITION(!json.empty(), "empty JSON provided");
279
0
  std::istringstream ss;
280
0
  ss.str(json);
281
282
0
  boost::property_tree::ptree pt;
283
0
  boost::property_tree::read_json(ss, pt);
284
0
  p.adjustDegree = pt.get("adjustDegree", p.adjustDegree);
285
0
  p.adjustRingCount = pt.get("adjustRingCount", p.adjustRingCount);
286
0
  p.makeDummiesQueries = pt.get("makeDummiesQueries", p.makeDummiesQueries);
287
0
  p.aromatizeIfPossible = pt.get("aromatizeIfPossible", p.aromatizeIfPossible);
288
0
  p.makeBondsGeneric = pt.get("makeBondsGeneric", p.makeBondsGeneric);
289
0
  p.makeAtomsGeneric = pt.get("makeAtomsGeneric", p.makeAtomsGeneric);
290
0
  p.adjustHeavyDegree = pt.get("adjustHeavyDegree", p.adjustHeavyDegree);
291
0
  p.adjustRingChain = pt.get("adjustRingChain", p.adjustRingChain);
292
0
  p.useStereoCareForBonds =
293
0
      pt.get("useStereoCareForBonds", p.useStereoCareForBonds);
294
0
  p.adjustConjugatedFiveRings =
295
0
      pt.get("adjustConjugatedFiveRings", p.adjustConjugatedFiveRings);
296
0
  p.setMDLFiveRingAromaticity =
297
0
      pt.get("setMDLFiveRingAromaticity", p.setMDLFiveRingAromaticity);
298
0
  p.adjustSingleBondsToDegreeOneNeighbors =
299
0
      pt.get("adjustSingleBondsToDegreeOneNeighbors",
300
0
             p.adjustSingleBondsToDegreeOneNeighbors);
301
0
  p.adjustSingleBondsBetweenAromaticAtoms =
302
0
      pt.get("adjustSingleBondsBetweenAromaticAtoms",
303
0
             p.adjustSingleBondsBetweenAromaticAtoms);
304
305
0
  std::string which;
306
0
  which = boost::to_upper_copy<std::string>(pt.get("adjustDegreeFlags", ""));
307
0
  if (!which.empty()) {
308
0
    p.adjustDegreeFlags = parseWhichString(which);
309
0
  }
310
0
  which =
311
0
      boost::to_upper_copy<std::string>(pt.get("adjustHeavyDegreeFlags", ""));
312
0
  if (!which.empty()) {
313
0
    p.adjustHeavyDegreeFlags = parseWhichString(which);
314
0
  }
315
0
  which = boost::to_upper_copy<std::string>(pt.get("adjustRingCountFlags", ""));
316
0
  if (!which.empty()) {
317
0
    p.adjustRingCountFlags = parseWhichString(which);
318
0
  }
319
0
  which =
320
0
      boost::to_upper_copy<std::string>(pt.get("makeBondsGenericFlags", ""));
321
0
  if (!which.empty()) {
322
0
    p.makeBondsGenericFlags = parseWhichString(which);
323
0
  }
324
0
  which =
325
0
      boost::to_upper_copy<std::string>(pt.get("makeAtomsGenericFlags", ""));
326
0
  if (!which.empty()) {
327
0
    p.makeAtomsGenericFlags = parseWhichString(which);
328
0
  }
329
0
  which = boost::to_upper_copy<std::string>(pt.get("adjustRingChainFlags", ""));
330
0
  if (!which.empty()) {
331
0
    p.adjustRingChainFlags = parseWhichString(which);
332
0
  }
333
0
}  // namespace MolOps
334
335
ROMol *adjustQueryProperties(const ROMol &mol,
336
0
                             const AdjustQueryParameters *params) {
337
0
  auto *res = new RWMol(mol);
338
0
  try {
339
0
    adjustQueryProperties(*res, params);
340
0
  } catch (MolSanitizeException &se) {
341
0
    delete res;
342
0
    throw se;
343
0
  }
344
0
  return static_cast<ROMol *>(res);
345
0
}
346
347
0
void adjustQueryProperties(RWMol &mol, const AdjustQueryParameters *inParams) {
348
0
  AdjustQueryParameters params;
349
0
  if (inParams) {
350
0
    params = *inParams;
351
0
  }
352
0
  const auto ringInfo = mol.getRingInfo();
353
354
0
  if (params.aromatizeIfPossible) {
355
0
    unsigned int failed;
356
0
    sanitizeMol(mol, failed, SANITIZE_SYMMRINGS | SANITIZE_SETAROMATICITY);
357
0
  } else {
358
0
    if (!ringInfo->isSymmSssr()) {
359
0
      MolOps::symmetrizeSSSR(mol);
360
0
    }
361
0
  }
362
0
  QueryAtom qaTmpl;
363
0
  QueryBond qbTmpl;
364
365
0
  std::vector<int> origAtomicNums;
366
0
  origAtomicNums.reserve(mol.getNumAtoms());
367
0
  for (const auto atom : mol.atoms()) {
368
0
    origAtomicNums.push_back(atom->getAtomicNum());
369
0
  }
370
371
0
  if (params.makeAtomsGeneric) {
372
0
    for (unsigned int i = 0; i < mol.getNumAtoms(); ++i) {
373
0
      if (!((params.makeAtomsGenericFlags & ADJUST_IGNORECHAINS) &&
374
0
            !ringInfo->numAtomRings(i)) &&
375
0
          !((params.makeAtomsGenericFlags & ADJUST_IGNORERINGS) &&
376
0
            ringInfo->numAtomRings(i)) &&
377
0
          !((params.makeAtomsGenericFlags & ADJUST_IGNOREMAPPED) &&
378
0
            isMapped(mol.getAtomWithIdx(i)))) {
379
0
        qaTmpl.setQuery(makeAtomNullQuery());
380
0
        constexpr bool updateLabel = false;
381
0
        constexpr bool preserveProps = true;
382
0
        mol.replaceAtom(i, &qaTmpl, updateLabel, preserveProps);
383
0
      }
384
0
    }
385
0
  }  // end of makeAtomsGeneric
386
0
  if (params.makeBondsGeneric) {
387
0
    for (unsigned int i = 0; i < mol.getNumBonds(); ++i) {
388
0
      if (!((params.makeBondsGenericFlags & ADJUST_IGNORECHAINS) &&
389
0
            !ringInfo->numBondRings(i)) &&
390
0
          !((params.makeBondsGenericFlags & ADJUST_IGNORERINGS) &&
391
0
            ringInfo->numBondRings(i))) {
392
0
        qbTmpl.setQuery(makeBondNullQuery());
393
0
        constexpr bool preserveProps = true;
394
0
        mol.replaceBond(i, &qbTmpl, preserveProps);
395
0
      }
396
0
    }
397
0
  }  // end of makeBondsGeneric
398
0
  for (unsigned int i = 0; i < mol.getNumAtoms(); ++i) {
399
0
    auto *at = mol.getAtomWithIdx(i);
400
    // pull properties we need from the atom here, once we
401
    // create a query atom they may no longer be valid.
402
0
    auto nRings = ringInfo->numAtomRings(i);
403
0
    auto atomicNum = origAtomicNums[i];
404
0
    if (params.makeDummiesQueries && atomicNum == 0 && !at->hasQuery() &&
405
0
        !at->getIsotope()) {
406
0
      qaTmpl.setQuery(makeAtomNullQuery());
407
0
      constexpr bool updateLabel = false;
408
0
      constexpr bool preserveProps = true;
409
0
      mol.replaceAtom(i, &qaTmpl, updateLabel, preserveProps);
410
0
      at = mol.getAtomWithIdx(i);
411
0
    }  // end of makeDummiesQueries
412
0
    if (params.adjustDegree &&
413
0
        !((params.adjustDegreeFlags & ADJUST_IGNORECHAINS) && !nRings) &&
414
0
        !((params.adjustDegreeFlags & ADJUST_IGNORERINGS) && nRings) &&
415
0
        !((params.adjustDegreeFlags & ADJUST_IGNOREDUMMIES) && !atomicNum) &&
416
0
        !((params.adjustDegreeFlags & ADJUST_IGNORENONDUMMIES) && atomicNum) &&
417
0
        !((params.adjustDegreeFlags & ADJUST_IGNOREMAPPED) && isMapped(at))) {
418
0
      QueryAtom *qa;
419
0
      if (!at->hasQuery()) {
420
0
        QueryAtom atQueryAtom(*at);
421
0
        constexpr bool updateLabel = false;
422
0
        constexpr bool preserveProps = true;
423
0
        mol.replaceAtom(i, &atQueryAtom, updateLabel, preserveProps);
424
0
        qa = static_cast<QueryAtom *>(mol.getAtomWithIdx(i));
425
0
        at = static_cast<Atom *>(qa);
426
0
      } else {
427
0
        qa = static_cast<QueryAtom *>(at);
428
0
      }
429
0
      qa->expandQuery(makeAtomExplicitDegreeQuery(qa->getDegree()));
430
0
    }  // end of adjust degree
431
0
    if (params.adjustHeavyDegree &&
432
0
        !((params.adjustHeavyDegreeFlags & ADJUST_IGNORECHAINS) && !nRings) &&
433
0
        !((params.adjustHeavyDegreeFlags & ADJUST_IGNORERINGS) && nRings) &&
434
0
        !((params.adjustHeavyDegreeFlags & ADJUST_IGNOREDUMMIES) &&
435
0
          !atomicNum) &&
436
0
        !((params.adjustHeavyDegreeFlags & ADJUST_IGNORENONDUMMIES) &&
437
0
          atomicNum) &&
438
0
        !((params.adjustHeavyDegreeFlags & ADJUST_IGNOREMAPPED) &&
439
0
          isMapped(at))) {
440
0
      QueryAtom *qa;
441
0
      if (!at->hasQuery()) {
442
0
        QueryAtom atQueryAtom(*at);
443
0
        constexpr bool updateLabel = false;
444
0
        constexpr bool preserveProps = true;
445
0
        mol.replaceAtom(i, &atQueryAtom, updateLabel, preserveProps);
446
0
        qa = static_cast<QueryAtom *>(mol.getAtomWithIdx(i));
447
0
        at = static_cast<Atom *>(qa);
448
0
      } else {
449
0
        qa = static_cast<QueryAtom *>(at);
450
0
      }
451
0
      qa->expandQuery(makeAtomHeavyAtomDegreeQuery(qa->getTotalDegree() -
452
0
                                                   qa->getTotalNumHs(true)));
453
0
    }  // end of adjust heavy degree
454
0
    if (params.adjustRingCount &&
455
0
        !((params.adjustRingCountFlags & ADJUST_IGNORECHAINS) && !nRings) &&
456
0
        !((params.adjustRingCountFlags & ADJUST_IGNORERINGS) && nRings) &&
457
0
        !((params.adjustRingCountFlags & ADJUST_IGNOREDUMMIES) && !atomicNum) &&
458
0
        !((params.adjustRingCountFlags & ADJUST_IGNORENONDUMMIES) &&
459
0
          atomicNum) &&
460
0
        !((params.adjustRingCountFlags & ADJUST_IGNOREMAPPED) &&
461
0
          isMapped(at))) {
462
0
      QueryAtom *qa;
463
0
      if (!at->hasQuery()) {
464
0
        QueryAtom atQueryAtom(*at);
465
0
        constexpr bool updateLabel = false;
466
0
        constexpr bool preserveProps = true;
467
0
        mol.replaceAtom(i, &atQueryAtom, updateLabel, preserveProps);
468
0
        qa = static_cast<QueryAtom *>(mol.getAtomWithIdx(i));
469
0
        at = static_cast<Atom *>(qa);
470
0
      } else {
471
0
        qa = static_cast<QueryAtom *>(at);
472
0
      }
473
0
      qa->expandQuery(makeAtomInNRingsQuery(nRings));
474
0
    }  // end of adjust ring count
475
0
    if (params.adjustRingChain &&
476
0
        !((params.adjustRingChainFlags & ADJUST_IGNORECHAINS) && !nRings) &&
477
0
        !((params.adjustRingChainFlags & ADJUST_IGNORERINGS) && nRings) &&
478
0
        !((params.adjustRingChainFlags & ADJUST_IGNOREDUMMIES) && !atomicNum) &&
479
0
        !((params.adjustRingChainFlags & ADJUST_IGNORENONDUMMIES) &&
480
0
          atomicNum) &&
481
0
        !((params.adjustRingChainFlags & ADJUST_IGNOREMAPPED) &&
482
0
          isMapped(at))) {
483
0
      QueryAtom *qa;
484
0
      if (!at->hasQuery()) {
485
0
        QueryAtom atQueryAtom(*at);
486
0
        constexpr bool updateLabel = false;
487
0
        constexpr bool preserveProps = true;
488
0
        mol.replaceAtom(i, &atQueryAtom, updateLabel, preserveProps);
489
0
        qa = static_cast<QueryAtom *>(mol.getAtomWithIdx(i));
490
0
        at = static_cast<Atom *>(qa);
491
0
      } else {
492
0
        qa = static_cast<QueryAtom *>(at);
493
0
      }
494
0
      ATOM_EQUALS_QUERY *nq = makeAtomInRingQuery();
495
0
      if (!nRings) {
496
0
        nq->setNegation(true);
497
0
      }
498
0
      qa->expandQuery(nq);
499
0
    }  // end of adjust ring chain
500
0
  }  // end of loop over atoms
501
0
  if (params.useStereoCareForBonds) {
502
0
    for (auto bnd : mol.bonds()) {
503
0
      if (bnd->getBondType() == Bond::BondType::DOUBLE) {
504
0
        if (bnd->getStereo() > Bond::BondStereo::STEREOANY) {
505
0
          bool preserve = false;
506
0
          int val = 0;
507
          // is stereoCare set on the bond or both atoms?
508
0
          if (bnd->getPropIfPresent(common_properties::molStereoCare, val) &&
509
0
              val) {
510
0
            preserve = true;
511
0
          }
512
0
          if (!preserve) {
513
0
            bnd->setStereo(Bond::BondStereo::STEREONONE);
514
0
          }
515
0
        }
516
0
      }
517
0
    }
518
0
  }
519
0
  if (params.setMDLFiveRingAromaticity) {
520
0
    setMDLAromaticity(mol);
521
0
  }
522
0
  if (params.adjustConjugatedFiveRings) {
523
0
    adjustConjugatedFiveRings(mol);
524
0
  }
525
0
  if (params.adjustSingleBondsToDegreeOneNeighbors ||
526
0
      params.adjustSingleBondsBetweenAromaticAtoms) {
527
0
    adjustSingleBondsFromAromaticAtoms(
528
0
        mol, params.adjustSingleBondsToDegreeOneNeighbors,
529
0
        params.adjustSingleBondsBetweenAromaticAtoms);
530
0
  }
531
0
  if (params.setMDLFiveRingAromaticity) {
532
0
    for (auto atom : mol.atoms()) {
533
0
      atom->clearProp(conjugatedOrAromatic);
534
0
    }
535
0
  }
536
0
}
537
}  // namespace MolOps
538
}  // namespace RDKit