/// <summary> /// Second traversal writes the bonds and atoms to the SMILES string. /// </summary> /// <param name="u">a vertex</param> /// <param name="p">previous vertex</param> /// <param name="b">the bond from the previous vertex to this vertex</param> public void Write(int u, int p, Bond b) { visitedAt[u] = nVisit++; int remaining = g.Degree(u); if (u != p) { remaining--; } // assign ring numbers if (rings.TryGetValue(u, out IList <RingClosure> closures)) { foreach (var rc in closures) { // as we are composing tokens, make sure apply in reverse int rnum = rnums.Next(); if (rc.Register(rnum)) { int v = rc.Other(u); tokens[u] = new RingNumberToken(new RingBondToken(tokens[u], rc.GetBond(u)), rnum); rnums.Use(rnum); } else { tokens[u] = new RingNumberToken(tokens[u], rc.RNum); rnums.Free(rc.RNum); } remaining--; } } sb.Append(b.Token); tokens[u].Append(sb); int d = g.Degree(u); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(u, j); int v = e.Other(u); if (visitedAt[v] < 0) { if (--remaining > 0) { sb.Append('('); Write(v, u, e.GetBond(u)); sb.Append(')'); } else { Write(v, u, e.GetBond(u)); } } } }
/// <summary> /// First traversal of the molecule assigns ring bonds (numbered later) and /// configures topologies. /// </summary> /// <param name="u">the vertex to visit</param> /// <param name="p">the atom we came from</param> void Prepare(int u, int p) { visitedAt[u] = nVisit++; tokens[u] = g.GetAtom(u).Token; tokens[u].Graph = g; tokens[u].Index = u; int d = g.Degree(u); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(u, j); int v = e.Other(u); if (visitedAt[v] < 0) { Prepare(v, u); } else if (v != p && visitedAt[v] < visitedAt[u]) { CyclicEdge(v, u, e.GetBond(v)); } } PrepareStereochemistry(u, p); }
private Bond FirstDirectionalLabel(int u, int x, BitArray adjToDb) { Edge e = g.CreateEdge(u, x); Bond b = e.GetBond(u); // the edge is next to another double bond configuration, we // need to consider its assignment if (adjToDb[x] && g.Degree(x) > 2) { foreach (var f in g.GetEdges(x)) { if (f.Other(x) != u && f.Bond != Bond.Double && f.Bond.IsDirectional) { return(f.GetBond(x)); } } } // consider other labels on this double-bond if (g.Degree(u) > 2) { foreach (var f in g.GetEdges(u)) { if (f.Other(u) != x && f.Bond != Bond.Double && f.Bond.IsDirectional) { return(f.GetBond(u).Inverse()); } } } return(b.IsDirectional ? b : Bond.Down); }
/// <summary> /// Given a double bond edge traverse the neighbors of one of the endpoints /// and accumulate any explicit replacements in the <paramref name="acc"/> accumulator. /// </summary> /// <param name="g">the chemical graph</param> /// <param name="e">a edge in the graph ('double bond type')</param> /// <param name="u">a endpoint of the edge <paramref name="e"/></param> /// <param name="acc">accumulator for new edges</param> /// <returns>does the edge <paramref name="e"/> need to be reconsidered later</returns> /// <exception cref="InvalidSmilesException">thrown if the edge could not be converted</exception> private bool ReplaceImplWithExpl(Graph g, Edge e, int u, Dictionary <Edge, Edge> acc) { Edge implicit_ = null; Edge explicit_ = null; foreach (var f in g.GetEdges(u)) { Edge f2 = acc.ContainsKey(f) ? acc[f] : f; var aa = f2.GetBond(u); if (aa == Bond.Single || aa == Bond.Implicit) { if (implicit_ != null) { return(true); } implicit_ = f; } else if (aa == Bond.Double) { if (!f.Equals(e)) { return(false); } } else if (aa == Bond.Up || aa == Bond.Down) { if (explicit_ != null) { if (acc.ContainsKey(explicit_)) { explicit_ = acc[explicit_]; } // original bonds are invalid if ((f.Bond == Bond.Up || f.Bond == Bond.Down) && explicit_.GetBond(u).Inverse() != f.GetBond(u)) { throw new InvalidSmilesException("invalid double bond configuration"); } if (explicit_.GetBond(u).Inverse() != f2.GetBond(u)) { acc[f] = f2.Inverse(); BitArray visited = new BitArray(g.Order); visited.Set(u, true); InvertExistingDirectionalLabels(g, visited, acc, f2 .Other(u)); } return(false); } explicit_ = f; } } // no implicit or explicit bond? don't do anything if (implicit_ == null || explicit_ == null) { return(false); } if (acc.ContainsKey(explicit_)) { explicit_ = acc[explicit_]; } int v = implicit_.Other(u); if (!acc.TryGetValue(implicit_, out Edge existing)) { existing = null; } acc[implicit_] = new Edge(u, v, explicit_.GetBond(u) .Inverse()); if (existing != null && existing.GetBond(u) != explicit_.GetBond(u).Inverse()) { throw new InvalidSmilesException("unable to assign explicit type for " + implicit_); } return(false); }
/// <summary> /// Create the topologies (stereo configurations) for the chemical graph. The /// topologies define spacial arrangement around atoms. /// </summary> private void CreateTopologies(CharBuffer buffer) { // create topologies (stereo configurations) foreach (var e in configurations) { AddTopology(e.Key, Topology.ToExplicit(g, e.Key, e.Value)); } for (int v = BitArrays.NextSetBit(checkDirectionalBonds, 0); v >= 0; v = BitArrays.NextSetBit(checkDirectionalBonds, v + 1)) { int nUpV = 0; int nDownV = 0; int nUpW = 0; int nDownW = 0; int w = -1; { int d = g.Degree(v); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(v, j); Bond bond = e.GetBond(v); if (bond == Bond.Up) { nUpV++; } else if (bond == Bond.Down) { nDownV++; } else if (bond == Bond.Double) { w = e.Other(v); } } } if (w < 0) { continue; } BitArrays.EnsureCapacity(checkDirectionalBonds, w + 1); checkDirectionalBonds.Set(w, false); { int d = g.Degree(w); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(w, j); Bond bond = e.GetBond(w); if (bond == Bond.Up) { nUpW++; } else if (bond == Bond.Down) { nDownW++; } } } if (nUpV + nDownV == 0 || nUpW + nDownW == 0) { continue; } if (nUpV > 1) { throw new InvalidSmilesException("Multiple directional bonds on atom " + v, buffer); } if (nDownV > 1) { throw new InvalidSmilesException("Multiple directional bonds on atom " + v, buffer); } if (nUpW > 1) { throw new InvalidSmilesException("Multiple directional bonds on atom " + w, buffer); } if (nDownW > 1) { throw new InvalidSmilesException("Multiple directional bonds on atom " + w, buffer); } } }
private void AssignDirectionalLabels() { if (!builders.Any()) { return; } // handle extended geometric configurations first var buildersToRemove = new List <GeometricBuilder>(); foreach (var builder in builders) { if (!builder.Extended) { continue; } buildersToRemove.Add(builder); Edge e = FindDoubleBond(g, builder.u); Edge f = FindDoubleBond(g, builder.v); if (e == null || f == null) { continue; } Edge eRef = g.CreateEdge(builder.u, builder.X); Edge fRef = g.CreateEdge(builder.v, builder.Y); Edge eLab = FindBondToLabel(g, builder.u); Edge fLab = FindBondToLabel(g, builder.v); // adjust for reference Configuration.ConfigurationDoubleBond config = builder.c; if ((eLab == eRef) != (fRef == fLab)) { if (config == Configuration.ConfigurationDoubleBond.Together) { config = Configuration.ConfigurationDoubleBond.Opposite; } else if (config == Configuration.ConfigurationDoubleBond.Opposite) { config = Configuration.ConfigurationDoubleBond.Together; } } if (eLab.Bond.IsDirectional) { if (fLab.Bond.IsDirectional) { // can't do anything, may be incorrect } else { if (config == Configuration.ConfigurationDoubleBond.Together) { SetDirection(fLab, builder.v, eLab.GetBond(builder.u)); } else if (config == Configuration.ConfigurationDoubleBond.Opposite) { SetDirection(fLab, builder.v, eLab.GetBond(builder.u)); } } } else { if (fLab.Bond.IsDirectional) { if (config == Configuration.ConfigurationDoubleBond.Together) { SetDirection(eLab, builder.v, fLab.GetBond(builder.u)); } else if (config == Configuration.ConfigurationDoubleBond.Opposite) { SetDirection(eLab, builder.v, fLab.GetBond(builder.u)); } } else { SetDirection(eLab, builder.u, Bond.Down); if (config == Configuration.ConfigurationDoubleBond.Together) { SetDirection(fLab, builder.v, Bond.Down); } else if (config == Configuration.ConfigurationDoubleBond.Opposite) { SetDirection(fLab, builder.v, Bond.Up); } } } } foreach (var builder in buildersToRemove) { builders.Remove(builder); } if (!builders.Any()) { return; } // store the vertices which are adjacent to pi bonds with a config BitArray pibonded = new BitArray(g.Order); BitArray unspecified = new BitArray(g.Order); var unspecEdges = new HashSet <Edge>(); // clear existing directional labels, if build is called multiple times // this can cause problems if (g.GetFlags(Graph.HAS_BND_STRO) != 0) { foreach (Edge edge in g.Edges) { if (edge.Bond.IsDirectional) { edge.SetBond(Bond.Implicit); } } } foreach (Edge e in g.Edges) { int u = e.Either(); int v = e.Other(u); if (e.Bond.Order == 2 && g.Degree(u) >= 2 && g.Degree(v) >= 2) { unspecified.Set(u, true); unspecified.Set(v, true); pibonded.Set(u, true); pibonded.Set(v, true); unspecEdges.Add(e); } } foreach (var builder in builders) { g.AddFlags(Graph.HAS_BND_STRO); // unspecified only used for getting not setting configuration if (builder.c == Configuration.ConfigurationDoubleBond.Unspecified) { continue; } CheckGeometricBuilder(builder); // check required vertices are adjacent int u = builder.u, v = builder.v, x = builder.X, y = builder.Y; if (x == y) { continue; } unspecEdges.Remove(g.CreateEdge(u, v)); unspecified.Set(u, false); unspecified.Set(v, false); Fix(g, u, v, pibonded); Fix(g, v, u, pibonded); Bond first = FirstDirectionalLabel(u, x, pibonded); Bond second = builder.c == Configuration.ConfigurationDoubleBond.Together ? first : first.Inverse(); // check if the second label would cause a conflict if (CheckDirectionalAssignment(second, v, y, pibonded)) { // okay to assign the labels as they are g.Replace(g.CreateEdge(u, x), new Edge(u, x, first)); g.Replace(g.CreateEdge(v, y), new Edge(v, y, second)); } // there will be a conflict - check if we invert the first one... else if (CheckDirectionalAssignment(first.Inverse(), u, x, pibonded)) { g.Replace(g.CreateEdge(u, x), new Edge(u, x, (first = first.Inverse()))); g.Replace(g.CreateEdge(v, y), new Edge(v, y, (second = second.Inverse()))); } else { BitArray visited = new BitArray(g.Order); visited.Set(v, true); InvertExistingDirectionalLabels(pibonded, visited, v, u); if (!CheckDirectionalAssignment(first, u, x, pibonded) || !CheckDirectionalAssignment(second, v, y, pibonded)) { throw new ArgumentException("cannot assign geometric configuration"); } g.Replace(g.CreateEdge(u, x), new Edge(u, x, first)); g.Replace(g.CreateEdge(v, y), new Edge(v, y, second)); } // propagate bond directions to other adjacent bonds foreach (var e in g.GetEdges(u)) { if (e.Bond != Bond.Double && !e.Bond.IsDirectional) { e.SetBond(e.Either() == u ? first.Inverse() : first); } } foreach (var e in g.GetEdges(v)) { if (e.Bond != Bond.Double && !e.Bond.IsDirectional) { e.SetBond(e.Either() == v ? second.Inverse() : second); } } } // unspecified pibonds should "not" have a configuration, if they // do we try to eliminate it foreach (Edge unspecEdge in unspecEdges) { int u = unspecEdge.Either(); int v = unspecEdge.Other(u); // no problem if one side isn't defined if (!HasDirectional(g, u) || !HasDirectional(g, v)) { continue; } foreach (Edge e in g.GetEdges(u)) { if (IsRedundantDirectionalEdge(g, e, unspecified)) { e.SetBond(Bond.Implicit); } } if (!HasDirectional(g, u)) { continue; } foreach (Edge e in g.GetEdges(v)) { if (IsRedundantDirectionalEdge(g, e, unspecified)) { e.SetBond(Bond.Implicit); } } // if (hasDirectional(g, v)) // could generate warning! } }