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