1 #include <openbabel/stereo/cistrans.h>
2 #include <openbabel/mol.h>
3 #include <openbabel/atom.h>
4 #include <openbabel/oberror.h>
5 
6 using namespace std;
7 
8 namespace OpenBabel {
9 
10   //
11   // OBCisTransStereo::Config struct
12   //
13 
operator ==(const Config & other) const14   bool OBCisTransStereo::Config::operator==(const Config &other) const
15   {
16     if ((begin != other.begin) && (begin != other.end))
17       return false;
18     if ((end != other.begin) && (end != other.end))
19       return false;
20     if ((refs.size() != 4) || (other.refs.size() != 4))
21       return false;
22 
23     Config u1, u2;
24     if (!OBStereo::ContainsSameRefs(refs, other.refs)) {
25       // find a ref that occurs in both
26       for (OBStereo::ConstRefIter i = refs.begin(); i != refs.end(); ++i)
27         if (OBStereo::ContainsRef(other.refs, *i)) {
28           u1 = OBTetraPlanarStereo::ToConfig(*this, *i, OBStereo::ShapeU); // refs[0] = u1.refs[0]
29           u2 = OBTetraPlanarStereo::ToConfig(other, *i, OBStereo::ShapeU); // refs[0] = u2.refs[0]
30         }
31 
32       // check if they actualy share an id...
33       if (u1.refs.empty())
34         return false;
35     } else {
36       // normalize the other Config struct
37       u1 = OBTetraPlanarStereo::ToConfig(*this, refs.at(0), OBStereo::ShapeU); // refs[0] = u1.refs[0]
38       u2 = OBTetraPlanarStereo::ToConfig(other, refs.at(0), OBStereo::ShapeU); // refs[0] = u2.refs[0]
39       // both now start with the same ref
40       //
41       // 2 possiblilities:
42       //
43       //   1 2 3 4      1 2 3 4
44       //   |   |        |   |      <- in any case, refs[0] & refs[2] remain unchanged
45       //   1 2 3 4      1 4 3 2
46       //
47       return (u1.refs[2] == u2.refs[2]);
48     }
49 
50     // possibilities:
51     //
52     //   1 2 3 4
53     //   |   |      <- refs[0] & refs[2] remain unchanged
54     //   1 H 3 H
55     //
56     //   1 2 3 4
57     //   |     |    <- refs[0] & refs[3] remain unchanged
58     //   1 H H 4
59     //
60     //   1 2 3 4
61     //   | |        <- refs[0] & refs[1] remain unchanged
62     //   1 2 H H
63     if ((u1.refs[2] == OBStereo::ImplicitRef) || (u2.refs[2] == OBStereo::ImplicitRef)) {
64       // 1 2 H 4
65       if ((u1.refs[3] == OBStereo::ImplicitRef) || (u2.refs[3] == OBStereo::ImplicitRef)) {
66         return (u1.refs[1] == u2.refs[1]); // 1 2 H H
67       } else {
68         return (u1.refs[3] == u2.refs[3]); // 1 H H 4
69       }
70     } else
71       return (u1.refs[2] == u2.refs[2]); // 1 2 3 4  &  1 H 3 4  &  1 2 3 H
72 
73     return false;
74   }
75 
76   //
77   // OBCisTransStereo class
78   //
79 
OBCisTransStereo(OBMol * mol)80   OBCisTransStereo::OBCisTransStereo(OBMol *mol) : OBTetraPlanarStereo(mol)
81   {
82   }
83 
~OBCisTransStereo()84   OBCisTransStereo::~OBCisTransStereo()
85   {
86   }
87 
IsValid() const88   bool OBCisTransStereo::IsValid() const
89   {
90     if ((m_cfg.begin == OBStereo::NoRef) || (m_cfg.end == OBStereo::NoRef))
91       return false;
92     if (m_cfg.refs.size() != 4)
93       return false;
94     return true;
95   }
96 
SetConfig(const Config & config)97   void OBCisTransStereo::SetConfig(const Config &config)
98   {
99     if (config.begin == OBStereo::NoRef) {
100       obErrorLog.ThrowError(__FUNCTION__,
101           "OBCisTransStereo::SetConfig : double bond begin id is invalid.", obError);
102       m_cfg = Config();
103       return;
104     }
105     if (config.end == OBStereo::NoRef) {
106       obErrorLog.ThrowError(__FUNCTION__,
107           "OBCisTransStereo::SetConfig : double bond end id is invalid.", obError);
108       m_cfg = Config();
109       return;
110     }
111     if (config.refs.size() != 4) {
112       std::stringstream ss;
113       ss << "OBCisTransStereo::SetConfig : found " << config.refs.size();
114       ss << " reference ids, should be 4.";
115       obErrorLog.ThrowError(__FUNCTION__, ss.str(), obError);
116       m_cfg = Config();
117       return;
118     }
119 
120     // store using U shape
121     m_cfg = OBTetraPlanarStereo::ToConfig(config, config.refs.at(0), OBStereo::ShapeU);
122   }
123 
GetConfig(OBStereo::Shape shape) const124   OBCisTransStereo::Config OBCisTransStereo::GetConfig(OBStereo::Shape shape) const
125   {
126     if (!IsValid())
127       return Config();
128 
129     return OBTetraPlanarStereo::ToConfig(m_cfg, m_cfg.refs.at(0), shape);
130   }
131 
GetConfig(unsigned long start,OBStereo::Shape shape) const132   OBCisTransStereo::Config OBCisTransStereo::GetConfig(unsigned long start,
133       OBStereo::Shape shape) const
134   {
135     if (!IsValid())
136       return Config();
137 
138     return OBTetraPlanarStereo::ToConfig(m_cfg, start, shape);
139   }
140 
IsTrans(unsigned long id1,unsigned long id2) const141   bool OBCisTransStereo::IsTrans(unsigned long id1, unsigned long id2) const
142   {
143     return (GetTransRef(id1) == id2);
144   }
145 
IsCis(unsigned long id1,unsigned long id2) const146   bool OBCisTransStereo::IsCis(unsigned long id1, unsigned long id2) const
147   {
148     return (GetCisRef(id1) == id2);
149   }
150 
operator ==(const OBCisTransStereo & other) const151   bool OBCisTransStereo::operator==(const OBCisTransStereo &other) const
152   {
153     if (!IsValid() || !other.IsValid())
154       return false;
155 
156     Config u = OBTetraPlanarStereo::ToConfig(other.GetConfig(),
157         m_cfg.refs.at(0), OBStereo::ShapeU);
158     unsigned long a1 = u.refs.at(0);
159     unsigned long b1 = u.refs.at(2);
160 
161     if ((a1 == OBStereo::ImplicitRef) && (b1 == OBStereo::ImplicitRef)) {
162       a1 = u.refs.at(1);
163       b1 = u.refs.at(3);
164     }
165 
166     if (b1 != OBStereo::ImplicitRef)
167       if (a1 == GetTransRef(b1))
168         return true;
169     if (a1 != OBStereo::ImplicitRef)
170       if (b1 == GetTransRef(a1))
171         return true;
172 
173     return false;
174   }
175 
GetCisOrTransRef(unsigned long id,bool getcisref) const176   unsigned long OBCisTransStereo::GetCisOrTransRef(unsigned long id, bool getcisref) const
177   {
178     if (!IsValid())
179       return OBStereo::NoRef;
180 
181     if (id == OBStereo::ImplicitRef)
182       return OBStereo::NoRef;
183 
184     // find id
185     for (int i = 0; i < 4; ++i) {
186       if (m_cfg.refs.at(i) == id) {
187         // Use its index to find the index of the cis (or trans) atom
188         int j;
189         if (getcisref) // GetCisRef
190           j = 3 - i; // Convert 0 to 3, and 3 to 0
191         else // GetTransRef
192           j = (i > 1) ? i - 2 : i + 2;
193 
194         unsigned long refId = m_cfg.refs.at(j);
195         return refId;
196       }
197     }
198 
199     // id not found
200     return OBStereo::NoRef;
201   }
202 
GetTransRef(unsigned long id) const203   unsigned long OBCisTransStereo::GetTransRef(unsigned long id) const
204   {
205     return GetCisOrTransRef(id, false);
206   }
207 
GetCisRef(unsigned long id) const208   unsigned long OBCisTransStereo::GetCisRef(unsigned long id) const
209   {
210     return GetCisOrTransRef(id, true);
211   }
212 
IsOnSameAtom(unsigned long id1,unsigned long id2) const213   bool OBCisTransStereo::IsOnSameAtom(unsigned long id1, unsigned long id2) const
214   {
215     const OBMol *mol = GetMolecule();
216     if (!mol) {
217       obErrorLog.ThrowError(__FUNCTION__, "OBCisTransStereo::IsOnSameAtom : No valid molecule set", obError);
218       return false;
219     }
220 
221     OBAtom *begin = mol->GetAtomById(m_cfg.begin);
222     if (!begin) {
223       obErrorLog.ThrowError(__FUNCTION__, "OBCisTransStereo::IsOnSameAtom : Begin reference id is not valid.", obError);
224       return false;
225     }
226     OBAtom *end = mol->GetAtomById(m_cfg.end);
227     if (!end) {
228       obErrorLog.ThrowError(__FUNCTION__, "OBCisTransStereo::IsOnSameAtom : End reference id is not valid.", obError);
229       return false;
230     }
231 
232     OBAtom *a = mol->GetAtomById(id1);
233     OBAtom *b = mol->GetAtomById(id2);
234 
235     if (a && b) {
236       // both on begin atom?
237       if (a->IsConnected(begin) && b->IsConnected(begin))
238           return true;
239       // both on end atom?
240       if (a->IsConnected(end) && b->IsConnected(end))
241           return true;
242       return false;
243     } else {
244       if (a) {
245         // b atom not found, could be a deleted hydrogen...
246         if (a->IsConnected(begin)) {
247           // a is connected to begin. if this is the atom missing a hydrogen, return false
248           if (begin->GetExplicitDegree() == 2)
249             return true;
250           // check if the end atom really is missing an atom
251           if (end->GetExplicitDegree() != 2) {
252             obErrorLog.ThrowError(__FUNCTION__,
253                 "OBCisTransStereo::IsOnSameAtom : id2 is not valid and is not a missing hydrogen.", obError);
254             return false;
255           }
256           // inform user we are treating id2 as deleted hydrogen
257           obErrorLog.ThrowError(__FUNCTION__,
258               "OBCisTransStereo::IsOnSameAtom : Atom with id2 doesn't exist anymore, must be a (deleted) hydrogen.", obInfo);
259         } else if (a->IsConnected(end)) {
260           // a is connected to end. again, if this is the atom missing a hydrogen, return false
261           if (end->GetExplicitDegree() == 2)
262             return true;
263           // check if the begin atom really is missing an atom
264           if (begin->GetExplicitDegree() != 2) {
265             obErrorLog.ThrowError(__FUNCTION__,
266                 "OBCisTransStereo::IsOnSameAtom : id2 is not valid and is not a missing hydrogen.", obError);
267             return true;
268           }
269           // inform user we are treating id2 as deleted hydrogen
270           obErrorLog.ThrowError(__FUNCTION__,
271               "OBCisTransStereo::IsOnSameAtom : Atom with id2 doesn't exist, must be a (deleted) hydrogen.", obInfo);
272 
273         } else {
274           obErrorLog.ThrowError(__FUNCTION__,
275               "OBCisTransStereo::IsOnSameAtom : Atom with id1 isn't connected to the begin or end atom.", obError);
276           return true;
277         }
278       } else if (b) {
279         // a atom not found, could be a deleted hydrogen...
280         if (b->IsConnected(begin)) {
281           // b is connected to begin. if this is the atom missing a hydrogen, return false
282           if (begin->GetExplicitDegree() == 2)
283             return true;
284           // check if the end atom really is missing an atom
285           if (end->GetExplicitDegree() != 2) {
286             obErrorLog.ThrowError(__FUNCTION__,
287                 "OBCisTransStereo::IsOnSameAtom : id1 is not valid and is not a missing hydrogen.", obError);
288             return true;
289           }
290           // inform user we are treating id1 as deleted hydrogen
291           obErrorLog.ThrowError(__FUNCTION__,
292               "OBCisTransStereo::IsOnSameAtom : Atom with id1 doesn't exist, must be a (deleted) hydrogen.", obInfo);
293         } else if (b->IsConnected(end)) {
294           // a is connected to end. again, if this is the atom missing a hydrogen, return false
295           if (end->GetExplicitDegree() == 2)
296             return true;
297           // check if the begin atom really is missing an atom
298           if (begin->GetExplicitDegree() != 2) {
299             obErrorLog.ThrowError(__FUNCTION__,
300                 "OBCisTransStereo::IsOnSameAtom : id1 is not valid and is not a missing hydrogen.", obError);
301             return true;
302           }
303           // inform user we are treating id2 as deleted hydrogen
304           obErrorLog.ThrowError(__FUNCTION__,
305               "OBCisTransStereo::IsOnSameAtom : Atom with id1 doesn't exist, must be a (deleted) hydrogen.", obInfo);
306         } else {
307           obErrorLog.ThrowError(__FUNCTION__,
308               "OBCisTransStereo::IsOnSameAtom : Atom with id1 isn't connected to the begin or end atom.", obError);
309           return true;
310         }
311       } else {
312         OBAtom *c = nullptr, *d = nullptr;
313         // no a & b, check the remaining ids which will reveal same info
314         for (int i = 0; i < 4; ++i) {
315           if ((m_cfg.refs.at(i) == id1) || (m_cfg.refs.at(i) == id2))
316             continue;
317           if (!c) {
318             c = mol->GetAtomById(m_cfg.refs.at(i));
319           } else {
320             d = mol->GetAtomById(m_cfg.refs.at(i));
321           }
322         }
323         if (!c || !d) {
324           obErrorLog.ThrowError(__FUNCTION__, "OBCisTransStereo::IsOnSameAtom : invalid stereochemistry!", obError);
325           return true;
326         }
327         if ((begin->GetExplicitDegree() != 2) || (end->GetExplicitDegree() != 2)) {
328           obErrorLog.ThrowError(__FUNCTION__, "OBCisTransStereo::IsOnSameAtom : invalid stereochemistry!", obError);
329           return true;
330         }
331         obErrorLog.ThrowError(__FUNCTION__,
332             "OBCisTransStereo::IsOnSameAtom : Atoms with id1 & id2 don't exist, must be a (deleted) hydrogens.", obInfo);
333         return IsOnSameAtom(c->GetId(), d->GetId());
334       }
335     }
336 
337     return false;
338   }
339 
Clone(OBBase * mol) const340   OBGenericData* OBCisTransStereo::Clone(OBBase *mol) const
341   {
342     OBCisTransStereo *data = new OBCisTransStereo(static_cast<OBMol*>(mol));
343     data->SetConfig(m_cfg);
344     return data;
345   }
346 
347 } // namespace OpenBabel
348 
349 namespace std {
350 
operator <<(ostream & out,const OpenBabel::OBCisTransStereo & ct)351   ostream& operator<<(ostream &out, const OpenBabel::OBCisTransStereo &ct)
352   {
353     OpenBabel::OBCisTransStereo::Config cfg = ct.GetConfig();
354     out << "OBCisTransStereo(begin = " << cfg.begin;
355     out << ", end = " << cfg.end;
356 
357     out << ", refs = ";
358     for (OpenBabel::OBStereo::Refs::iterator i = cfg.refs.begin(); i != cfg.refs.end(); ++i)
359       if (*i != OpenBabel::OBStereo::ImplicitRef)
360         out << *i << " ";
361       else
362         out << "H ";
363 
364     switch (cfg.shape) {
365       case OpenBabel::OBStereo::ShapeU:
366         out << ", shape = U)";
367         break;
368       case OpenBabel::OBStereo::ShapeZ:
369         out << ", shape = Z)";
370         break;
371       case OpenBabel::OBStereo::Shape4:
372         out << ", shape = 4)";
373         break;
374     }
375 
376     return out;
377   }
378 
operator <<(ostream & out,const OpenBabel::OBCisTransStereo::Config & cfg)379   ostream& operator<<(ostream &out, const OpenBabel::OBCisTransStereo::Config &cfg)
380   {
381     out << "OBCisTransStereo::Config(begin = " << cfg.begin;
382     out << ", end = " << cfg.end;
383 
384     out << ", refs = ";
385     for (OpenBabel::OBStereo::Refs::const_iterator i = cfg.refs.begin(); i != cfg.refs.end(); ++i)
386       if (*i != OpenBabel::OBStereo::ImplicitRef)
387         out << *i << " ";
388       else
389         out << "H ";
390 
391     switch (cfg.shape) {
392       case OpenBabel::OBStereo::ShapeU:
393         out << ", shape = U)";
394         break;
395       case OpenBabel::OBStereo::ShapeZ:
396         out << ", shape = Z)";
397         break;
398       case OpenBabel::OBStereo::Shape4:
399         out << ", shape = 4)";
400         break;
401     }
402 
403     return out;
404   }
405 
406 } // namespace std
407 
408