RDKit
Open-source cheminformatics and machine learning.
Loading...
Searching...
No Matches
MolStandardize/Tautomer.h
Go to the documentation of this file.
1//
2// Copyright (C) 2018-2021 Susan H. Leung and other RDKit contributors
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 <RDGeneral/export.h>
11#ifndef RD_TAUTOMER_H
12#define RD_TAUTOMER_H
13
14#include <boost/function.hpp>
15#include <string>
16#include <utility>
17#include <iterator>
18#include <Catalogs/Catalog.h>
23#include <boost/dynamic_bitset.hpp>
24
25namespace RDKit {
26class ROMol;
27class RWMol;
28
29namespace MolStandardize {
30
31typedef RDCatalog::HierarchCatalog<TautomerCatalogEntry, TautomerCatalogParams,
32 int>
34
36const std::string tautomerScoringVersion = "1.0.0";
37
38//! The SubstructTerm controls how Tautomers are generated
39/// Each Term is defined by a name, smarts pattern and score
40/// For example, the C=O term is defined as
41/// SubstructTerm("C=O", "[#6]=,:[#8]", 2)
42/// This gets a score of +2 for each Carbon doubly or aromatically
43/// Bonded to an Oxygen.
44/// For a list of current definitions, see getDefaultTautomerScoreSubstructs
46 std::string name;
47 std::string smarts;
48 int score;
49 RWMol matcher; // requires assignment
50
51 // Pre-screening support: elements that must be present (empty = no filter)
52 std::vector<int> requiredElements;
53 // Bond-order-agnostic connectivity pattern for pre-screening (may be empty)
54 std::string connectivitySmarts;
56
57 SubstructTerm(std::string aname, std::string asmarts, int ascore,
58 std::vector<int> reqElements = {},
59 std::string connSmarts = "");
60 SubstructTerm(const SubstructTerm &rhs) = default;
61 SubstructTerm &operator=(const SubstructTerm &rhs) = default;
62
63 bool operator==(const SubstructTerm &rhs) const {
64 return name == rhs.name && smarts == rhs.smarts && score == rhs.score;
65 }
66};
67
68//! getDefaultTautomerSubstructs returns the SubstructTerms used in scoring
69/// tautomer forms. See SubstructTerm for details.
70RDKIT_MOLSTANDARDIZE_EXPORT const std::vector<SubstructTerm>
72
73//! Score the rings of the current tautomer
74/// Aromatic rings score 100, all carbon aromatic rings score 250
75/*!
76 \param mol Molcule to score
77 \returns integer score for the molecule's rings
78*/
80
81//! scoreSubstructs scores the molecule based on the substructure definitions
82/*!
83 \param mol Molecule to score
84 \param terms Substruct Terms used for scoring this particular tautomer form
85 \returns integer score for the molecule's substructure terms
86*/
88 const ROMol &mol, const std::vector<SubstructTerm> &terms =
90
91//! Determine which SubstructTerms are potentially relevant for a molecule.
92/// This pre-filters terms in two stages:
93/// 1. Element check: skip terms requiring elements not in the molecule
94/// 2. Connectivity check: skip terms whose bond-order-agnostic pattern
95/// doesn't match (since tautomerization doesn't create/destroy bonds)
96/// Returns indices into the terms vector for relevant terms.
98 const ROMol &mol,
99 const std::vector<SubstructTerm> &terms = getDefaultTautomerScoreSubstructs());
100
101//! Score substructures using only the terms at the specified indices.
102/// Uses specialized matchers for simple patterns (C=O, N=O, P=O, methyl, etc.)
103/// and falls back to VF2 for complex patterns. Use with
104/// getRelevantSubstructTermIndices for efficient scoring of many tautomers
105/// from the same parent molecule.
107 const ROMol &mol, const std::vector<SubstructTerm> &terms,
108 const std::vector<size_t> &relevantIndices);
109//! scoreHeteroHs score the molecules hydrogens
110/// This gives a negative penalty to hydrogens attached to S,P, Se and Te
111/*!
112 \param mol Molecule to score
113 \returns integer score for the molecule hetero hydrogens
114*/
116
117inline int scoreTautomer(const ROMol &mol) {
118 return scoreRings(mol) + scoreSubstructs(mol) + scoreHeteroHs(mol);
119}
120
121//! Create an optimized scoring function for tautomers of a specific molecule.
122/// This pre-filters SubstructTerms based on elements and connectivity present
123/// in the input molecule, avoiding unnecessary substructure searches for
124/// patterns that can never match any tautomer of this molecule.
125/// The returned function captures the filtered term indices and can be passed
126/// to pickCanonical or canonicalize.
127inline boost::function<int(const ROMol &)> makeOptimizedScorer(
128 const ROMol &mol) {
129 auto relevantIndices = getRelevantSubstructTermIndices(mol);
130 // Capture by value since the indices are small and we want the lambda to
131 // outlive this function.
132 return [relevantIndices](const ROMol &taut) {
133 const auto &terms = getDefaultTautomerScoreSubstructs();
134 return scoreRings(taut) +
135 scoreSubstructsFiltered(taut, terms, relevantIndices) +
136 scoreHeteroHs(taut);
137 };
138}
139} // namespace TautomerScoringFunctions
140
147
148class Tautomer {
149 friend class TautomerEnumerator;
150
151 public:
152 Tautomer() : d_numModifiedAtoms(0), d_numModifiedBonds(0), d_done(false) {}
153 // Constructor with just the tautomer - kekulized form will be created lazily
154 Tautomer(ROMOL_SPTR t, size_t a = 0, size_t b = 0)
155 : tautomer(std::move(t)),
156 d_numModifiedAtoms(a),
157 d_numModifiedBonds(b),
158 d_done(false) {}
159 // Legacy constructor for compatibility
160 Tautomer(ROMOL_SPTR t, ROMOL_SPTR k, size_t a = 0, size_t b = 0)
161 : tautomer(std::move(t)),
162 kekulized(std::move(k)),
163 d_numModifiedAtoms(a),
164 d_numModifiedBonds(b),
165 d_done(false) {}
167 mutable ROMOL_SPTR kekulized; // Lazily initialized
168
169 // Get the kekulized form, creating it lazily if needed
170 const ROMOL_SPTR &getKekulized() const {
171 if (!kekulized && tautomer) {
172 kekulized.reset(new RWMol(*tautomer));
173 MolOps::Kekulize(static_cast<RWMol &>(*kekulized), false, true);
174 }
175 return kekulized;
176 }
177
178 private:
179 size_t d_numModifiedAtoms;
180 size_t d_numModifiedBonds;
181 bool d_done;
182};
183
184typedef std::map<std::string, Tautomer> SmilesTautomerMap;
185typedef std::pair<std::string, Tautomer> SmilesTautomerPair;
186
187//! Contains results of tautomer enumeration
189 friend class TautomerEnumerator;
190
191 public:
193 public:
195 typedef std::ptrdiff_t difference_type;
196 typedef const ROMol *pointer;
197 typedef const ROMOL_SPTR &reference;
198 typedef std::bidirectional_iterator_tag iterator_category;
199
200 explicit const_iterator(const SmilesTautomerMap::const_iterator &it)
201 : d_it(it) {}
202 reference operator*() const { return d_it->second.tautomer; }
203 pointer operator->() const { return d_it->second.tautomer.get(); }
204 bool operator==(const const_iterator &other) const {
205 return (d_it == other.d_it);
206 }
207 bool operator!=(const const_iterator &other) const {
208 return !(*this == other);
209 }
211 const_iterator copy(d_it);
212 operator++();
213 return copy;
214 }
216 ++d_it;
217 return *this;
218 }
220 const_iterator copy(d_it);
221 operator--();
222 return copy;
223 }
225 --d_it;
226 return *this;
227 }
228
229 private:
230 SmilesTautomerMap::const_iterator d_it;
231 };
234 : d_tautomers(other.d_tautomers),
235 d_status(other.d_status),
236 d_modifiedAtoms(other.d_modifiedAtoms),
237 d_modifiedBonds(other.d_modifiedBonds) {
238 fillTautomersItVec();
239 }
240 const const_iterator begin() const {
241 return const_iterator(d_tautomers.begin());
242 }
243 const const_iterator end() const { return const_iterator(d_tautomers.end()); }
244 size_t size() const { return d_tautomers.size(); }
245 bool empty() const { return d_tautomers.empty(); }
246 const ROMOL_SPTR &at(size_t pos) const {
247 PRECONDITION(pos < d_tautomers.size(), "index out of bounds");
248 return d_tautomersItVec.at(pos)->second.tautomer;
249 }
250 const ROMOL_SPTR &operator[](size_t pos) const { return at(pos); }
251 const boost::dynamic_bitset<> &modifiedAtoms() const {
252 return d_modifiedAtoms;
253 }
254 const boost::dynamic_bitset<> &modifiedBonds() const {
255 return d_modifiedBonds;
256 }
257 TautomerEnumeratorStatus status() const { return d_status; }
258 std::vector<ROMOL_SPTR> tautomers() const {
259 std::vector<ROMOL_SPTR> tautomerVec;
260 tautomerVec.reserve(d_tautomers.size());
261 std::transform(
262 d_tautomers.begin(), d_tautomers.end(), std::back_inserter(tautomerVec),
263 [](const SmilesTautomerPair &t) { return t.second.tautomer; });
264 return tautomerVec;
265 }
266 std::vector<ROMOL_SPTR> operator()() const { return tautomers(); }
267 std::vector<std::string> smiles() const {
268 std::vector<std::string> smilesVec;
269 smilesVec.reserve(d_tautomers.size());
270 std::transform(d_tautomers.begin(), d_tautomers.end(),
271 std::back_inserter(smilesVec),
272 [](const SmilesTautomerPair &t) { return t.first; });
273 return smilesVec;
274 }
275 const SmilesTautomerMap &smilesTautomerMap() const { return d_tautomers; }
276
277 private:
278 void fillTautomersItVec() {
279 for (auto it = d_tautomers.begin(); it != d_tautomers.end(); ++it) {
280 d_tautomersItVec.push_back(it);
281 }
282 }
283 // the enumerated tautomers
284 SmilesTautomerMap d_tautomers;
285 // internal; vector of iterators into map items to enable random
286 // access to map items by index
287 std::vector<SmilesTautomerMap::const_iterator> d_tautomersItVec;
288 // status of the enumeration: did it complete? did it hit a limit?
289 // was it canceled?
290 TautomerEnumeratorStatus d_status;
291 // bit vector: flags atoms modified by the transforms
292 boost::dynamic_bitset<> d_modifiedAtoms;
293 // bit vector: flags bonds modified by the transforms
294 boost::dynamic_bitset<> d_modifiedBonds;
295};
296
303
305 public:
307 : dp_catalog(tautCat),
308 d_maxTautomers(1000),
309 d_maxTransforms(1000),
310 d_removeSp3Stereo(true),
311 d_removeBondStereo(true),
312 d_removeIsotopicHs(true),
313 d_reassignStereo(true) {}
316 : dp_catalog(other.dp_catalog),
317 d_callback(other.d_callback),
318 d_maxTautomers(other.d_maxTautomers),
319 d_maxTransforms(other.d_maxTransforms),
320 d_removeSp3Stereo(other.d_removeSp3Stereo),
321 d_removeBondStereo(other.d_removeBondStereo),
322 d_removeIsotopicHs(other.d_removeIsotopicHs),
323 d_reassignStereo(other.d_reassignStereo) {}
325 if (this == &other) {
326 return *this;
327 }
328 dp_catalog = other.dp_catalog;
329 d_callback = other.d_callback;
330 d_maxTautomers = other.d_maxTautomers;
331 d_maxTransforms = other.d_maxTransforms;
332 d_removeSp3Stereo = other.d_removeSp3Stereo;
333 d_removeBondStereo = other.d_removeBondStereo;
334 d_removeIsotopicHs = other.d_removeIsotopicHs;
335 d_reassignStereo = other.d_reassignStereo;
336 return *this;
337 }
338 //! \param maxTautomers maximum number of tautomers to be generated
339 void setMaxTautomers(unsigned int maxTautomers) {
340 d_maxTautomers = maxTautomers;
341 }
342 //! \return maximum number of tautomers to be generated
343 unsigned int getMaxTautomers() { return d_maxTautomers; }
344 /*! \param maxTransforms maximum number of transformations to be applied
345 this limit is usually hit earlier than the maxTautomers limit
346 and leads to a more linear scaling of CPU time with increasing
347 number of tautomeric centers (see Sitzmann et al.)
348 */
349 void setMaxTransforms(unsigned int maxTransforms) {
350 d_maxTransforms = maxTransforms;
351 }
352 //! \return maximum number of transformations to be applied
353 unsigned int getMaxTransforms() { return d_maxTransforms; }
354 /*! \param removeSp3Stereo; if set to true, stereochemistry information
355 will be removed from sp3 atoms involved in tautomerism.
356 This means that S-aminoacids will lose their stereochemistry after going
357 through tautomer enumeration because of the amido-imidol tautomerism.
358 This defaults to true in RDKit, false in the workflow described
359 by Sitzmann et al.
360 */
361 void setRemoveSp3Stereo(bool removeSp3Stereo) {
362 d_removeSp3Stereo = removeSp3Stereo;
363 }
364 /*! \return whether stereochemistry information will be removed from
365 sp3 atoms involved in tautomerism
366 */
367 bool getRemoveSp3Stereo() { return d_removeSp3Stereo; }
368 /*! \param removeBondStereo; if set to true, stereochemistry information
369 will be removed from double bonds involved in tautomerism.
370 This means that enols will lose their E/Z stereochemistry after going
371 through tautomer enumeration because of the keto-enolic tautomerism.
372 This defaults to true in RDKit and also in the workflow described
373 by Sitzmann et al.
374 */
375 void setRemoveBondStereo(bool removeBondStereo) {
376 d_removeBondStereo = removeBondStereo;
377 }
378 /*! \return whether stereochemistry information will be removed from
379 double bonds involved in tautomerism
380 */
381 bool getRemoveBondStereo() { return d_removeBondStereo; }
382 /*! \param removeIsotopicHs; if set to true, isotopic Hs
383 will be removed from centers involved in tautomerism.
384 */
385 void setRemoveIsotopicHs(bool removeIsotopicHs) {
386 d_removeIsotopicHs = removeIsotopicHs;
387 }
388 /*! \return whether isotpoic Hs will be removed from
389 centers involved in tautomerism
390 */
391 bool getRemoveIsotopicHs() { return d_removeIsotopicHs; }
392 /*! \param reassignStereo; if set to true, assignStereochemistry
393 will be called on each tautomer generated by the enumerate() method.
394 This defaults to true.
395 */
396 void setReassignStereo(bool reassignStereo) {
397 d_reassignStereo = reassignStereo;
398 }
399 /*! \return whether assignStereochemistry will be called on each
400 tautomer generated by the enumerate() method
401 */
402 bool getReassignStereo() { return d_reassignStereo; }
403 /*! set this to an instance of a class derived from
404 TautomerEnumeratorCallback where operator() is overridden.
405 DO NOT delete the instance as ownership of the pointer is transferred
406 to the TautomerEnumerator
407 */
409 d_callback.reset(callback);
410 }
411 /*! \return pointer to an instance of a class derived from
412 TautomerEnumeratorCallback.
413 DO NOT delete the instance as ownership of the pointer is transferred
414 to the TautomerEnumerator
415 */
416 TautomerEnumeratorCallback *getCallback() const { return d_callback.get(); }
417
418 //! returns a \c TautomerEnumeratorResult structure for the input molecule
419 /*!
420 The enumeration rules are inspired by the publication:
421 M. Sitzmann et al., “Tautomerism in Large Databases.”, JCAMD 24:521 (2010)
422 https://doi.org/10.1007/s10822-010-9346-4
423
424 \param mol: the molecule to be enumerated
425
426 Note: the definitions used here are that the atoms modified during
427 tautomerization are the atoms at the beginning and end of each tautomer
428 transform (the H "donor" and H "acceptor" in the transform) and the bonds
429 modified during transformation are any bonds whose order is changed during
430 the tautomer transform (these are the bonds between the "donor" and the
431 "acceptor")
432
433 */
435
436 //! Deprecated, please use the form returning a \c TautomerEnumeratorResult
437 //! instead
438 [[deprecated(
439 "please use the form returning a TautomerEnumeratorResult "
440 "instead")]] std::vector<ROMOL_SPTR>
441 enumerate(const ROMol &mol, boost::dynamic_bitset<> *modifiedAtoms,
442 boost::dynamic_bitset<> *modifiedBonds = nullptr) const;
443
444 //! returns the canonical tautomer from a \c TautomerEnumeratorResult
446 boost::function<int(const ROMol &mol)> scoreFunc =
448
449 //! returns the canonical tautomer from an iterable of possible tautomers
450 /// When Iterable is TautomerEnumeratorResult we use the other non-templated
451 /// overload for efficiency (TautomerEnumeratorResult already has SMILES so no
452 /// need to recompute them)
453 /// caller owns the pointer
454 template <class Iterable,
455 typename std::enable_if<
456 !std::is_same<Iterable, TautomerEnumeratorResult>::value,
457 int>::type = 0>
458 ROMol *pickCanonical(const Iterable &tautomers,
459 boost::function<int(const ROMol &mol)> scoreFunc =
461 ROMOL_SPTR bestMol;
462 if (tautomers.size() == 1) {
463 bestMol = *tautomers.begin();
464 } else {
465 // Calculate score for each tautomer
466 int bestScore = std::numeric_limits<int>::min();
467 std::string bestSmiles = "";
468 for (const auto &t : tautomers) {
469 auto score = scoreFunc(*t);
470#ifdef VERBOSE_ENUMERATION
471 std::cerr << " " << MolToSmiles(*t) << " " << score << std::endl;
472#endif
473 if (score > bestScore) {
474 bestScore = score;
475 bestSmiles = MolToSmiles(*t);
476 bestMol = t;
477 } else if (score == bestScore) {
478 auto smiles = MolToSmiles(*t);
479 if (smiles < bestSmiles) {
480 bestSmiles = smiles;
481 bestMol = t;
482 }
483 }
484 }
485 }
486 ROMol *res = new ROMol(*bestMol);
487 static const bool cleanIt = true;
488 static const bool force = true;
489 MolOps::assignStereochemistry(*res, cleanIt, force);
490
491 return res;
492 }
493
494 //! returns the canonical tautomer for a molecule
495 /*!
496 Note that the canonical tautomer is very likely not the most stable tautomer
497 for any given conditions. The default scoring rules are designed to produce
498 "reasonable" tautomers, but the primary concern is that the results are
499 canonical: you always get the same canonical tautomer for a molecule
500 regardless of what the input tautomer or atom ordering were.
501
502 The default scoring scheme is inspired by the publication:
503 M. Sitzmann et al., “Tautomerism in Large Databases.”, JCAMD 24:521 (2010)
504 https://doi.org/10.1007/s10822-010-9346-4
505
506 */
507 /// When \p scoreFunc is empty (default), an optimized scorer is created
508 /// that pre-filters substructure patterns once for the input molecule.
509 /// caller owns the pointer
511 const ROMol &mol,
512 boost::function<int(const ROMol &mol)> scoreFunc = {}) const;
514 RWMol &mol, boost::function<int(const ROMol &mol)> scoreFunc = {}) const;
515
516 private:
517 bool setTautomerStereoAndIsoHs(const ROMol &mol, ROMol &taut,
518 const TautomerEnumeratorResult &res) const;
519 std::shared_ptr<TautomerCatalog> dp_catalog;
520 std::shared_ptr<TautomerEnumeratorCallback> d_callback;
521 unsigned int d_maxTautomers;
522 unsigned int d_maxTransforms;
523 bool d_removeSp3Stereo;
524 bool d_removeBondStereo;
525 bool d_removeIsotopicHs;
526 bool d_reassignStereo;
527}; // TautomerEnumerator class
528
529// caller owns the pointer
531 const CleanupParameters &params) {
532 return new TautomerEnumerator(params);
533}
534// caller owns the pointer
540
541} // namespace MolStandardize
542} // namespace RDKit
543
544#endif
#define PRECONDITION(expr, mess)
Definition Invariant.h:108
Defines the CleanupParameters and some convenience functions.
virtual bool operator()(const ROMol &, const TautomerEnumeratorResult &)=0
Contains results of tautomer enumeration.
const boost::dynamic_bitset & modifiedBonds() const
const boost::dynamic_bitset & modifiedAtoms() const
TautomerEnumeratorResult(const TautomerEnumeratorResult &other)
TautomerEnumerator(const CleanupParameters &params=CleanupParameters())
std::vector< ROMOL_SPTR > enumerate(const ROMol &mol, boost::dynamic_bitset<> *modifiedAtoms, boost::dynamic_bitset<> *modifiedBonds=nullptr) const
ROMol * pickCanonical(const TautomerEnumeratorResult &tautRes, boost::function< int(const ROMol &mol)> scoreFunc=TautomerScoringFunctions::scoreTautomer) const
returns the canonical tautomer from a TautomerEnumeratorResult
TautomerEnumeratorCallback * getCallback() const
TautomerEnumeratorResult enumerate(const ROMol &mol) const
returns a TautomerEnumeratorResult structure for the input molecule
TautomerEnumerator & operator=(const TautomerEnumerator &other)
void canonicalizeInPlace(RWMol &mol, boost::function< int(const ROMol &mol)> scoreFunc={}) const
void setCallback(TautomerEnumeratorCallback *callback)
ROMol * canonicalize(const ROMol &mol, boost::function< int(const ROMol &mol)> scoreFunc={}) const
returns the canonical tautomer for a molecule
TautomerEnumerator(const TautomerEnumerator &other)
void setMaxTransforms(unsigned int maxTransforms)
void setMaxTautomers(unsigned int maxTautomers)
ROMol * pickCanonical(const Iterable &tautomers, boost::function< int(const ROMol &mol)> scoreFunc=TautomerScoringFunctions::scoreTautomer) const
Tautomer(ROMOL_SPTR t, ROMOL_SPTR k, size_t a=0, size_t b=0)
Tautomer(ROMOL_SPTR t, size_t a=0, size_t b=0)
const ROMOL_SPTR & getKekulized() const
RWMol is a molecule class that is intended to be edited.
Definition RWMol.h:32
#define RDKIT_MOLSTANDARDIZE_EXPORT
Definition export.h:377
RDKIT_GRAPHMOL_EXPORT void assignStereochemistry(ROMol &mol, bool cleanIt=false, bool force=false, bool flagPossibleStereoCenters=false)
Assign stereochemistry tags to atoms and bonds.
RDKIT_GRAPHMOL_EXPORT void Kekulize(RWMol &mol, bool markAtomsBonds=true, bool canonical=true, unsigned int maxBackTracks=100)
Kekulizes the molecule.
RDKIT_MOLSTANDARDIZE_EXPORT int scoreHeteroHs(const ROMol &mol)
RDKIT_MOLSTANDARDIZE_EXPORT std::vector< size_t > getRelevantSubstructTermIndices(const ROMol &mol, const std::vector< SubstructTerm > &terms=getDefaultTautomerScoreSubstructs())
RDKIT_MOLSTANDARDIZE_EXPORT int scoreSubstructsFiltered(const ROMol &mol, const std::vector< SubstructTerm > &terms, const std::vector< size_t > &relevantIndices)
RDKIT_MOLSTANDARDIZE_EXPORT const std::vector< SubstructTerm > & getDefaultTautomerScoreSubstructs()
RDKIT_MOLSTANDARDIZE_EXPORT int scoreRings(const ROMol &mol)
RDKIT_MOLSTANDARDIZE_EXPORT int scoreSubstructs(const ROMol &mol, const std::vector< SubstructTerm > &terms=getDefaultTautomerScoreSubstructs())
scoreSubstructs scores the molecule based on the substructure definitions
boost::function< int(const ROMol &)> makeOptimizedScorer(const ROMol &mol)
RDKIT_MOLSTANDARDIZE_EXPORT const TautomerTransformDefs defaultTautomerTransformsv1
std::map< std::string, Tautomer > SmilesTautomerMap
TautomerEnumerator * tautomerEnumeratorFromParams(const CleanupParameters &params)
TautomerEnumerator * getV1TautomerEnumerator()
RDCatalog::HierarchCatalog< TautomerCatalogEntry, TautomerCatalogParams, int > TautomerCatalog
std::pair< std::string, Tautomer > SmilesTautomerPair
Std stuff.
RDKIT_SMILESPARSE_EXPORT std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params)
returns canonical SMILES for a molecule
boost::shared_ptr< ROMol > ROMOL_SPTR
SubstructTerm(std::string aname, std::string asmarts, int ascore, std::vector< int > reqElements={}, std::string connSmarts="")
SubstructTerm & operator=(const SubstructTerm &rhs)=default