1 //
2 //  Copyright (C) 2018 Susan H. Leung
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 "Metal.h"
11 #include <GraphMol/SmilesParse/SmilesParse.h>
12 #include <GraphMol/SmilesParse/SmilesWrite.h>
13 #include <GraphMol/RDKitBase.h>
14 #include <GraphMol/Substruct/SubstructMatch.h>
15 
16 #include <GraphMol/RDKitQueries.h>
17 #include <GraphMol/Substruct/SubstructMatch.h>
18 #include <GraphMol/Substruct/SubstructUtils.h>
19 
20 using namespace std;
21 using namespace RDKit;
22 namespace RDKit {
23 class RWMol;
24 class ROMol;
25 
26 namespace MolStandardize {
27 
MetalDisconnector()28 MetalDisconnector::MetalDisconnector()
29     : metal_nof(
30           SmartsToMol("[Li,Na,K,Rb,Cs,Fr,Be,Mg,Ca,Sr,Ba,Ra,Sc,Ti,V,Cr,Mn,Fe,Co,"
31                       "Ni,Cu,Zn,Al,Ga,Y,Zr,Nb,Mo,Tc,Ru,Rh,Pd,Ag,Cd,In,Sn,Hf,Ta,"
32                       "W,Re,Os,Ir,Pt,Au,Hg,Tl,Pb,Bi]~[#7,#8,F]")),
33       metal_non(SmartsToMol(
34           "[Al,Sc,Ti,V,Cr,Mn,Fe,Co,Ni,Cu,Zn,Y,Zr,Nb,Mo,Tc,Ru,Rh,Pd,Ag,Cd,Hf,Ta,"
35           "W,Re,Os,Ir,Pt,Au]~[B,C,Si,P,As,Sb,S,Se,Te,Cl,Br,I,At]")) {
36   BOOST_LOG(rdInfoLog) << "Initializing MetalDisconnector\n";
37 };
38 
MetalDisconnector(const MetalDisconnector & other)39 MetalDisconnector::MetalDisconnector(const MetalDisconnector &other) {
40   metal_nof = other.metal_nof;
41   metal_non = other.metal_non;
42 };
43 
~MetalDisconnector()44 MetalDisconnector::~MetalDisconnector(){};
45 
getMetalNof()46 ROMol *MetalDisconnector::getMetalNof() { return metal_nof.get(); }
47 
getMetalNon()48 ROMol *MetalDisconnector::getMetalNon() { return metal_non.get(); }
49 
setMetalNof(const ROMol & mol)50 void MetalDisconnector::setMetalNof(const ROMol &mol) {
51   this->metal_nof.reset(new ROMol(mol));
52 }
53 
setMetalNon(const ROMol & mol)54 void MetalDisconnector::setMetalNon(const ROMol &mol) {
55   this->metal_non.reset(new ROMol(mol));
56 }
57 
disconnect(const ROMol & mol)58 ROMol *MetalDisconnector::disconnect(const ROMol &mol) {
59   auto *res = new RWMol(mol);
60   MetalDisconnector::disconnect(*res);
61   return static_cast<ROMol *>(res);
62 }
63 
disconnect(RWMol & mol)64 void MetalDisconnector::disconnect(RWMol &mol) {
65   BOOST_LOG(rdInfoLog) << "Running MetalDisconnector\n";
66   std::list<ROMOL_SPTR> metalList = {metal_nof, metal_non};
67   std::map<int, NonMetal> nonMetals;
68   std::map<int, int> metalChargeExcess;
69   for (auto &query : metalList) {
70     std::vector<MatchVectType> matches;
71     SubstructMatch(mol, *query, matches);
72 
73     for (const auto &match : matches) {
74       int metal_idx = match[0].second;
75       metalChargeExcess[metal_idx] = 0;
76       auto metal = mol.getAtomWithIdx(metal_idx);
77       int non_idx = match[1].second;
78       auto nonMetal = mol.getAtomWithIdx(non_idx);
79 
80       Bond *b = mol.getBondBetweenAtoms(metal_idx, non_idx);
81       int order = (b->getBondType() >= Bond::DATIVEONE &&
82                    b->getBondType() <= Bond::DATIVER)
83                       ? 0
84                       : static_cast<int>(b->getBondTypeAsDouble());
85       // disconnecting metal-R bond
86       mol.removeBond(metal_idx, non_idx);
87       // increment the cut bond count for this non-metal atom
88       // and store the metal it was bonded to. We will need this
89       // later to adjust the metal charge
90       auto &value = nonMetals[non_idx];
91       value.cutBonds += order;
92       auto it = std::lower_bound(value.boundMetalIndices.begin(),
93                                  value.boundMetalIndices.end(), metal_idx);
94       if (it == value.boundMetalIndices.end() || *it != metal_idx) {
95         value.boundMetalIndices.insert(it, metal_idx);
96       }
97 
98       BOOST_LOG(rdInfoLog) << "Removed covalent bond between "
99                            << metal->getSymbol() << " and "
100                            << nonMetal->getSymbol() << "\n";
101     }
102     //	std::cout << "After removing bond and charge adjustment: " <<
103     // MolToSmiles(mol) << std::endl;
104   }
105 
106   for (auto it = nonMetals.begin(); it != nonMetals.end(); ++it) {
107     auto a = mol.getAtomWithIdx(it->first);
108     // do not blindly trust the original formal charge as it is often wrong
109     // instead, find out the most appropriate formal charge based
110     // on the allowed element valences and on its current valence/lone electrons
111     // if there are no standard valences we assume the original
112     // valence was correct and the non-metal element was neutral
113     int valenceBeforeCut = a->getTotalValence();
114     int radBeforeCut = a->getNumRadicalElectrons();
115     int fcAfterCut = -it->second.cutBonds;
116     int valenceAfterCut = 0;
117     int loneElectrons = 0;
118     const auto &valens =
119         PeriodicTable::getTable()->getValenceList(a->getAtomicNum());
120     if (!valens.empty() && valens.front() != -1) {
121       for (auto v = valens.begin(); v != valens.end(); ++v) {
122         valenceAfterCut = valenceBeforeCut + radBeforeCut - it->second.cutBonds;
123         if (valenceAfterCut > *v) {
124           if (v + 1 != valens.end()) {
125             continue;
126           }
127           valenceAfterCut = *v;
128           break;
129         }
130         fcAfterCut = valenceAfterCut - *v;
131         // if there were radicals before and now we have
132         // a negative formal charge, then it's a carbene-like
133         // system (e.g., [Me]-[C]=O), or there was something silly
134         // such as [Me]-[S]=X or [Me]-[P](X)(X)X
135         if ((radBeforeCut % 2) && fcAfterCut < 0) {
136           ++fcAfterCut;
137           ++loneElectrons;
138           // no radical doublets on N and higher
139           a->setNumRadicalElectrons(a->getAtomicNum() < 7 ? radBeforeCut + 1
140                                                           : 0);
141         }
142         break;
143       }
144     }
145     // do not put a negative charge on sp2 carbon
146     if (fcAfterCut == -1 &&
147         valenceAfterCut == static_cast<int>(a->getTotalDegree()) + 1) {
148       fcAfterCut = 0;
149     }
150     a->setFormalCharge(fcAfterCut);
151     if (!it->second.cutBonds ||
152         (fcAfterCut == -1 && a->getAtomicNum() == 6 &&
153          valenceAfterCut == static_cast<int>(a->getTotalDegree()) + 2)) {
154       // do not take electrons from the metal if it was a dative bond
155       // (e.g., [C-]#[O+] coordinated to metal)
156       fcAfterCut = 0;
157     }
158     std::sort(it->second.boundMetalIndices.begin(),
159               it->second.boundMetalIndices.end(),
160               [metalChargeExcess](int a, int b) {
161                 return (metalChargeExcess.at(a) < metalChargeExcess.at(b));
162               });
163     fcAfterCut += loneElectrons;
164     while (fcAfterCut < 0) {
165       for (auto i : it->second.boundMetalIndices) {
166         // if the bond was not dative, the non-metal stole electrons
167         // from the metal(s), so we need to take electrons from
168         // once-bonded metal(s)
169         if (fcAfterCut++ >= 0) {
170           break;
171         }
172         ++metalChargeExcess[i];
173       }
174     }
175     a->updatePropertyCache();
176   }
177   // adjust formal charges of metal atoms
178   for (auto it = metalChargeExcess.begin(); it != metalChargeExcess.end();
179        ++it) {
180     auto a = mol.getAtomWithIdx(it->first);
181     auto currentFc = a->getFormalCharge();
182     const auto &valens =
183         PeriodicTable::getTable()->getValenceList(a->getAtomicNum());
184     int fcAfterCut = it->second;
185     if (currentFc > 0) {
186       // if the original formal charge on the metal was positive, we trust it
187       // and add it to the charge excess
188       fcAfterCut += currentFc;
189     }
190     if (!valens.empty() && valens.front() != -1) {
191       for (auto v = valens.begin(); v != valens.end(); ++v) {
192         if (fcAfterCut > *v) {
193           auto next = v + 1;
194           if (next != valens.end() && fcAfterCut >= *v) {
195             continue;
196           }
197           fcAfterCut = *v;
198           break;
199         }
200       }
201     }
202     if (fcAfterCut > currentFc) {
203       a->setFormalCharge(fcAfterCut);
204     }
205     // make sure that radical electrons on metals are 0
206     // and are not added to metals by sanitization
207     // by setting NoImplicit to false
208     a->setNumRadicalElectrons(0);
209     a->setNumExplicitHs(0);
210     a->setNoImplicit(false);
211     a->updatePropertyCache();
212   }
213 }
214 
215 }  // namespace MolStandardize
216 }  // namespace RDKit
217