/src/openbabel/src/alias.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | alias.cpp - implementation of an OBGenericData class to hold alias information on atoms |
3 | | Copyright (C) 2008 by Chris Morley |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation version 2 of the License. |
8 | | |
9 | | This program is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | ***********************************************************************/ |
14 | | #include <openbabel/babelconfig.h> |
15 | | #include <cstdlib> |
16 | | #include <memory> |
17 | | #include <sstream> |
18 | | #include <string> |
19 | | #include <openbabel/alias.h> |
20 | | #include <openbabel/obconversion.h> |
21 | | #include <openbabel/op.h> |
22 | | #include <openbabel/mol.h> |
23 | | #include <openbabel/atom.h> |
24 | | #include <openbabel/bond.h> |
25 | | #include <openbabel/builder.h> |
26 | | #include <openbabel/obiter.h> |
27 | | #include <openbabel/parsmart.h> |
28 | | #include <openbabel/mcdlutil.h> |
29 | | #include <openbabel/elements.h> |
30 | | #include <openbabel/generic.h> |
31 | | |
32 | 0 | #define MARK_UNUSED(x) (void)(x) |
33 | | |
34 | | using namespace std; |
35 | | namespace OpenBabel |
36 | | { |
37 | | std::string AliasData::GetAlias(bool rightAligned)const |
38 | 0 | { |
39 | 0 | if(rightAligned) |
40 | 0 | { |
41 | 0 | if(!_right_form.empty()) |
42 | 0 | return _right_form; |
43 | 0 | if(table().find(_alias)!=table().end()) |
44 | 0 | return table().find(_alias)->second.right_form; |
45 | 0 | } |
46 | 0 | return _alias; |
47 | 0 | } |
48 | | |
49 | | bool AliasData::Expand(OBMol& mol, const unsigned int atomindex) |
50 | 236k | { |
51 | | /* |
52 | | Interprets the alias text and adds atom(s) as appropriate to mol. |
53 | | Tries the following in turn until one is successful: |
54 | | 1) If starts with number treat as isotope+element e.g. 2H |
55 | | 2) Looks up alias in superatom.txt e.g. COOH Pr |
56 | | 3) If of the form Rn stored as a * atom with Atom Class data |
57 | | Returns false if none are successful. |
58 | | */ |
59 | | |
60 | | //parse as isotopic atom |
61 | 236k | if(isdigit(_alias[0])) |
62 | 142k | { |
63 | 142k | std::stringstream ss(_alias); |
64 | 142k | int iso; |
65 | 142k | std::string el; |
66 | 142k | ss >> iso >>el; |
67 | 142k | unsigned int elemno = OBElements::GetAtomicNum(el.c_str()); |
68 | 142k | if(elemno > 0) |
69 | 40.9k | { |
70 | 40.9k | OBAtom* pAtom = mol.GetAtom(atomindex); |
71 | 40.9k | if(!pAtom) |
72 | 0 | return false; |
73 | 40.9k | pAtom->SetIsotope(iso); |
74 | 40.9k | pAtom->SetAtomicNum(elemno); |
75 | 40.9k | return true; |
76 | 40.9k | } |
77 | 142k | } |
78 | | |
79 | 196k | if(FromNameLookup(mol, atomindex)) |
80 | 0 | return true; |
81 | | |
82 | | // Rn is stored as an atom with 0 atomic number and atomclass = n |
83 | | // R', R'' etc. are treated as R1, R2 |
84 | | // Note that if the name contains anything after the number it is ignored. |
85 | 196k | if(_alias[0]=='R' && (_alias[1]=='\'' || isdigit(_alias[1]))) |
86 | 3.27k | { |
87 | 3.27k | unsigned int n = 1; |
88 | 3.27k | if(_alias[1]=='\'') |
89 | 110k | while(n<_alias.size()-1 && _alias[n]==_alias[n+1]) n++; |
90 | 379 | else |
91 | 379 | n = atoi(_alias.c_str()+1); |
92 | | |
93 | 3.27k | OBPairInteger *atomclass = new OBPairInteger(); |
94 | 3.27k | atomclass->SetAttribute("Atom Class"); |
95 | 3.27k | atomclass->SetValue(n); |
96 | 3.27k | mol.GetAtom(atomindex)->SetData(atomclass); |
97 | | |
98 | 3.27k | if(atomindex <= mol.NumAtoms()) //needed for Rn aliases in mdlformat |
99 | 3.27k | mol.GetAtom(atomindex)->SetAtomicNum(0); |
100 | | |
101 | 3.27k | _right_form = _alias; |
102 | 3.27k | return true; |
103 | 3.27k | } |
104 | | |
105 | 192k | obErrorLog.ThrowError(__FUNCTION__, "Alias " + _alias + |
106 | 192k | " was not chemically interpreted\n", obWarning, onceOnly); |
107 | 192k | return false; |
108 | 196k | } |
109 | | |
110 | | bool AliasData::FromNameLookup(OBMol& mol, const unsigned int atomindex) |
111 | 196k | { |
112 | | /*Converts an alias name (like COOH) to real chemistry: |
113 | | looks up in a table loaded from superatom.txt; |
114 | | converts the SMILES of the fragment and adds it to the molecule. |
115 | | If the molecule already has atom coordinates, generates coordinates |
116 | | for the new atoms, using builder for 3D and MCDL for 2D. |
117 | | */ |
118 | | |
119 | 196k | OBAtom* XxAtom = mol.GetAtom(atomindex); |
120 | | /* if(XxAtom->GetExplicitDegree()>1) |
121 | | { |
122 | | obErrorLog.ThrowError(__FUNCTION__, _alias + " is multivalent, which is currently not supported.", obWarning); |
123 | | return false; |
124 | | } |
125 | | */ |
126 | 196k | SuperAtomTable::iterator pos = table().find(_alias); |
127 | 196k | if(pos==table().end()) |
128 | 196k | return false; |
129 | | |
130 | 0 | int dimension=0; |
131 | 0 | if(mol.Has3D()) |
132 | 0 | dimension=3; |
133 | 0 | else if(mol.Has2D()) |
134 | 0 | dimension=2; |
135 | 0 | mol.SetDimension(dimension); |
136 | | |
137 | | //Convert SMILES of alias |
138 | 0 | OBConversion conv; |
139 | 0 | OBMol obFrag; |
140 | 0 | obFrag.SetIsPatternStructure(); |
141 | 0 | if(conv.SetInFormat("smi")) |
142 | 0 | { |
143 | 0 | conv.ReadString(&obFrag, '*' + pos->second.smiles);//Add dummy atom to SMILES |
144 | 0 | _right_form = pos->second.right_form; |
145 | 0 | _color = pos->second.color; |
146 | 0 | } |
147 | 0 | obFrag.SetDimension(dimension);//will be same as parent |
148 | | |
149 | | //Find index of *first* atom to which XxAtom is attached (could be NULL) |
150 | 0 | OBBondIterator bi; |
151 | 0 | OBAtom* firstAttachAtom = XxAtom->BeginNbrAtom(bi); |
152 | 0 | unsigned mainAttachIdx = firstAttachAtom ? firstAttachAtom->GetIdx() : 0; |
153 | 0 | unsigned int firstAttachFlags = 0; |
154 | 0 | unsigned int firstAttachOrder = 1; |
155 | 0 | if (firstAttachAtom) { |
156 | 0 | firstAttachFlags = mol.GetBond(XxAtom, firstAttachAtom)->GetFlags(); |
157 | 0 | firstAttachOrder = mol.GetBond(XxAtom, firstAttachAtom)->GetBondOrder(); |
158 | 0 | } |
159 | | //++Make list of other attachments* of XxAtom |
160 | | // (Added later so that the existing bonding of the XXAtom are retained) |
161 | 0 | vector<pair<OBAtom*, unsigned> > otherAttachments; |
162 | 0 | OBAtom* pAttach; |
163 | 0 | while(firstAttachAtom && (pAttach = XxAtom->NextNbrAtom(bi)) ) // extra parentheses to minimize warnings |
164 | 0 | otherAttachments.push_back(make_pair(pAttach, (*bi)->GetBondOrder())); |
165 | | |
166 | | //Copy coords of XxAtom to the first real atom in the fragment |
167 | | //so that the connecting bond is well defined for 2D case |
168 | 0 | obFrag.GetAtom(2)->SetVector( XxAtom->GetVector()); |
169 | | |
170 | | //delete original Xx atom |
171 | 0 | mol.DeleteAtom(XxAtom, false);//delay deletion of the OBAtom object because this is attached to it |
172 | | //Correct indices for the deletion |
173 | 0 | if(atomindex<mainAttachIdx) |
174 | 0 | --mainAttachIdx; |
175 | | |
176 | | //Find the eventual index of first atom in fragment |
177 | 0 | unsigned newFragIdx = mol.NumAtoms()+1; |
178 | | |
179 | | //Give the fragment appropriate coordinates |
180 | 0 | if(dimension==3) |
181 | 0 | { |
182 | 0 | OBBuilder builder; |
183 | |
|
184 | 0 | builder.Build(obFrag); |
185 | 0 | obFrag.DeleteAtom(obFrag.GetAtom(1));//remove dummy atom |
186 | 0 | mol += obFrag; //Combine with main molecule |
187 | 0 | if(mainAttachIdx) { |
188 | 0 | builder.Connect(mol, mainAttachIdx, newFragIdx,XxAtom->GetVector(),firstAttachOrder); |
189 | 0 | } |
190 | 0 | } |
191 | 0 | else // 0D, 2D |
192 | 0 | { |
193 | 0 | obFrag.DeleteAtom(obFrag.GetAtom(1));//remove dummy atom |
194 | 0 | mol += obFrag; //Combine with main molecule and connect |
195 | 0 | if(mainAttachIdx) |
196 | 0 | mol.AddBond(mainAttachIdx, newFragIdx, 1, firstAttachFlags); |
197 | 0 | } |
198 | |
|
199 | 0 | if(dimension==2)//Use MCDL |
200 | 0 | groupRedraw(&mol, mol.NumBonds()-1, newFragIdx, true); |
201 | | |
202 | | //++Add bonds from list to newFragIdx |
203 | 0 | while(!otherAttachments.empty()) |
204 | 0 | { |
205 | 0 | mol.AddBond(otherAttachments.back().first->GetIdx(), newFragIdx, otherAttachments.back().second); |
206 | 0 | otherAttachments.pop_back(); |
207 | 0 | } |
208 | | |
209 | | //Store the ids of the atoms which replace the alias (the last atoms in the combined molecule). |
210 | | //The ids do not change when other atoms are deleted. |
211 | 0 | for(unsigned i=obFrag.NumAtoms();i;--i) |
212 | 0 | _expandedatoms.push_back(mol.GetAtom(mol.NumAtoms()-i +1)->GetId()); |
213 | | |
214 | | //Make a copy of this AliasData object (currently attached to XxAtom) |
215 | | //and attach it to the first atom of the fragment. |
216 | 0 | mol.GetAtom(newFragIdx)->CloneData(this); |
217 | |
|
218 | 0 | delete(XxAtom); |
219 | 0 | return true; |
220 | 196k | } |
221 | | |
222 | | bool AliasData::LoadFile(SuperAtomTable& table) |
223 | 392k | { |
224 | | //In table: key=alias left-form; value=pair<alias right-form, SMILES> |
225 | 392k | ifstream ifs; |
226 | 392k | if (OpenDatafile(ifs, "superatom.txt").length() == 0) |
227 | 392k | { |
228 | 392k | obErrorLog.ThrowError(__FUNCTION__, "Cannot open superatom.txt", obError); |
229 | 392k | return false; |
230 | 392k | } |
231 | 0 | string ln; |
232 | 0 | while(getline(ifs, ln)) |
233 | 0 | { |
234 | 0 | if (ln[0]=='#' || ln.empty()) |
235 | 0 | continue; |
236 | 0 | std::vector<string> vec; |
237 | 0 | if(tokenize(vec, ln) && vec.size()>=3) |
238 | 0 | { |
239 | | //table[ vec[0] ] = make_pair(vec[1], vec[2]); |
240 | 0 | AliasItem item; |
241 | 0 | item.right_form = vec[1]; |
242 | 0 | item.smiles = vec[2]; |
243 | 0 | item.color = vec.size()>=4 ? vec[3] : ""; |
244 | 0 | table[ vec[0] ] = item; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | return true; |
248 | 392k | } |
249 | | |
250 | | bool AliasData::LoadFile(SmartsTable& smtable) |
251 | 0 | { |
252 | | //Re-parse the datafile. Seems simpler than trying to extract from the map. |
253 | 0 | ifstream ifs; |
254 | 0 | if (OpenDatafile(ifs, "superatom.txt").length() == 0) |
255 | 0 | { |
256 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Cannot open superatom.txt", obError); |
257 | 0 | return false; |
258 | 0 | } |
259 | 0 | string ln; |
260 | 0 | while(getline(ifs, ln)) |
261 | 0 | { |
262 | 0 | if ((ln[0]=='#' && ln[1]!='#') || ln.empty()) |
263 | 0 | continue; |
264 | 0 | if(ln[0]=='#') //stop reading at line starting with ## |
265 | 0 | break; |
266 | 0 | std::vector<string> vec; |
267 | 0 | if(tokenize(vec, ln) && vec.size()>=3) |
268 | 0 | { |
269 | | //Convert SMILES with implicit H to SMARTS with explicit H. |
270 | | //Converting into and out of OBMol is a bit heavy, but saves |
271 | | //worrying about edge cases in a string parse. |
272 | 0 | stringstream ss('*'+vec[2]),// '*' added to SMILES because the superatom has to be attached |
273 | 0 | ssmarts; |
274 | 0 | OBConversion conv(&ss, &ssmarts); |
275 | 0 | conv.AddOption("h",OBConversion::GENOPTIONS);//add explicit Hs... |
276 | 0 | conv.AddOption("h");//...and output them to ensure the superatom itself is not substituted |
277 | 0 | if(conv.SetInAndOutFormats("smi","smi")) |
278 | 0 | conv.Convert(); |
279 | 0 | if(!ssmarts.str().empty()) |
280 | 0 | { |
281 | | //OBSmartsPattern objects are not copyable without complications, |
282 | | //so reference semantics used. |
283 | |
|
284 | 0 | std::shared_ptr<OBSmartsPattern> psp(new OBSmartsPattern); |
285 | 0 | psp->Init(ssmarts.str()); |
286 | 0 | smtable.push_back(make_pair(vec[0], psp)); |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | 0 | return true; |
291 | 0 | } |
292 | | |
293 | 0 | void AliasData::AddExpandedAtom(int id) { _expandedatoms.push_back(id); }; |
294 | | |
295 | | void AliasData::DeleteExpandedAtoms(OBMol& mol) |
296 | 0 | { |
297 | | //The atom that carries the AliasData object remains as an Xx atom with no charge; |
298 | | //the others are deleted. All the attached hydrogens are also deleted. |
299 | 0 | for(unsigned i=0;i<_expandedatoms.size();++i) |
300 | 0 | { |
301 | 0 | OBAtom* at = mol.GetAtomById(_expandedatoms[i]); |
302 | 0 | if(!at) |
303 | 0 | continue; |
304 | 0 | mol.DeleteHydrogens(at); |
305 | 0 | if(at->HasData(AliasDataType)) |
306 | 0 | { |
307 | 0 | at->SetAtomicNum(0); |
308 | 0 | at->SetFormalCharge(0); |
309 | 0 | at->SetSpinMultiplicity(0); |
310 | 0 | } |
311 | 0 | else |
312 | 0 | mol.DeleteAtom(at); |
313 | 0 | } |
314 | 0 | _expandedatoms.clear(); |
315 | 0 | } |
316 | | |
317 | | void AliasData::RevertToAliasForm(OBMol& mol) |
318 | 0 | { |
319 | | //Deleting atoms invalidates the iterator, so start again |
320 | | //and continue until all no unexpanded aliases are found in molecule. |
321 | 0 | bool acted = false; |
322 | 0 | do |
323 | 0 | { |
324 | 0 | FOR_ATOMS_OF_MOL(a, mol) |
325 | 0 | { |
326 | 0 | acted=false; |
327 | 0 | AliasData* ad = nullptr; |
328 | 0 | if((ad = (static_cast<AliasData*>(a->GetData(AliasDataType)))) && ad->IsExpanded()) |
329 | 0 | { |
330 | 0 | ad->DeleteExpandedAtoms(mol); |
331 | 0 | acted = true; |
332 | 0 | break; |
333 | 0 | } |
334 | 0 | } |
335 | 0 | }while(acted); |
336 | 0 | } |
337 | | |
338 | | bool AliasData::AddAliases(OBMol* pmol) |
339 | 0 | { |
340 | 0 | static SmartsTable smtable; |
341 | 0 | if(smtable.empty()) |
342 | 0 | LoadFile(smtable); |
343 | 0 | set<int> AllExAtoms; |
344 | 0 | SmartsTable::iterator iter; |
345 | 0 | for(iter=smtable.begin();iter!=smtable.end();++iter) |
346 | 0 | { |
347 | 0 | if((*iter).second->Match(*pmol)) |
348 | 0 | { |
349 | 0 | vector<std::vector<int> > mlist = (*iter).second->GetUMapList(); |
350 | 0 | for(unsigned imatch=0;imatch<mlist.size();++imatch) //each match |
351 | 0 | { |
352 | 0 | AliasData* ad = new AliasData; |
353 | 0 | ad->SetAlias((*iter).first); |
354 | | //iatom==0 is the * that was added to the front of the SMILES, so start at 1 |
355 | 0 | for(unsigned iatom=1; iatom<mlist[imatch].size();++iatom)//each atom in match |
356 | 0 | { |
357 | 0 | int idx = mlist[imatch][iatom]; |
358 | |
|
359 | 0 | if(AllExAtoms.count(idx)) |
360 | 0 | { |
361 | | //atom already appears in an alias so abandon this (smaller) alias |
362 | 0 | delete ad; |
363 | 0 | ad = nullptr; |
364 | 0 | break; |
365 | 0 | } |
366 | 0 | else |
367 | 0 | { |
368 | 0 | OBAtom* a = pmol->GetAtom(idx); |
369 | | // atom might not be present in original molecule, because SMARTS are all with hydrogens added |
370 | | // original molecule might lack them |
371 | 0 | if (a != nullptr) { |
372 | 0 | AllExAtoms.insert(idx); |
373 | 0 | int id = a->GetId(); |
374 | 0 | ad->AddExpandedAtom(id); |
375 | 0 | } |
376 | 0 | } |
377 | 0 | } |
378 | 0 | if(ad) |
379 | 0 | pmol->GetAtom(mlist[imatch][1])->SetData(ad);//attach alias to first expanded atom |
380 | 0 | } |
381 | 0 | } |
382 | 0 | } |
383 | 0 | return true; |
384 | 0 | } |
385 | | |
386 | | //OpGenAlias is a wrapper for AddAliases. Use like: |
387 | | //babel infile.xxx outfile.yyy --genalias |
388 | | class OpGenAlias : public OBOp |
389 | | { |
390 | | public: |
391 | 6 | OpGenAlias(const char* ID) : OBOp(ID, false){}; |
392 | 0 | const char* Description() override { return "Generate aliases as an alternative representation."; } |
393 | | |
394 | 0 | bool WorksWith(OBBase* pOb) const override { return dynamic_cast<OBMol*>(pOb) != nullptr; } |
395 | | bool Do(OBBase* pOb, const char* OptionText, OpMap* pmap, OBConversion*) override; |
396 | | }; |
397 | | |
398 | | ///////////////////////////////////////////////////////////////// |
399 | | OpGenAlias theOpGenAlias("genalias"); //Global instance |
400 | | |
401 | | ///////////////////////////////////////////////////////////////// |
402 | | bool OpGenAlias::Do(OBBase* pOb, const char* OptionText, OpMap* pmap, OBConversion*) |
403 | 0 | { |
404 | | // Mark variables as unused to avoid warnings |
405 | 0 | MARK_UNUSED(OptionText); |
406 | 0 | MARK_UNUSED(pmap); |
407 | |
|
408 | 0 | OBMol* pmol = dynamic_cast<OBMol*>(pOb); |
409 | 0 | if(!pmol) |
410 | 0 | return false; |
411 | 0 | return AliasData::AddAliases(pmol); |
412 | 0 | } |
413 | | |
414 | | }//namespace |
415 | | |
416 | | //! \file alias.cpp |
417 | | //! \brief OBGenericData class to for atom alias data (e.g., in 2D drawing programs for "COOH") |