/// <summary> /// Read a molecule from the character buffer. /// </summary> /// <param name="buffer">a character buffer</param> /// <exception cref="InvalidSmilesException">invalid grammar</exception> private void ReadSmiles(CharBuffer buffer) { // primary dispatch while (buffer.HasRemaining()) { char c = buffer.Get(); switch (c) { // aliphatic subset case '*': AddAtom(AtomImpl.AliphaticSubset.Unknown, buffer); break; case 'B': if (buffer.GetIf('r')) { AddAtom(AtomImpl.AliphaticSubset.Bromine, buffer); } else { AddAtom(AtomImpl.AliphaticSubset.Boron, buffer); } break; case 'C': if (buffer.GetIf('l')) { AddAtom(AtomImpl.AliphaticSubset.Chlorine, buffer); } else { AddAtom(AtomImpl.AliphaticSubset.Carbon, buffer); } break; case 'N': AddAtom(AtomImpl.AliphaticSubset.Nitrogen, buffer); break; case 'O': AddAtom(AtomImpl.AliphaticSubset.Oxygen, buffer); break; case 'P': AddAtom(AtomImpl.AliphaticSubset.Phosphorus, buffer); break; case 'S': AddAtom(AtomImpl.AliphaticSubset.Sulfur, buffer); break; case 'F': AddAtom(AtomImpl.AliphaticSubset.Fluorine, buffer); break; case 'I': AddAtom(AtomImpl.AliphaticSubset.Iodine, buffer); break; // aromatic subset case 'b': AddAtom(AtomImpl.AromaticSubset.Boron, buffer); g.AddFlags(Graph.HAS_AROM); break; case 'c': AddAtom(AtomImpl.AromaticSubset.Carbon, buffer); g.AddFlags(Graph.HAS_AROM); break; case 'n': AddAtom(AtomImpl.AromaticSubset.Nitrogen, buffer); g.AddFlags(Graph.HAS_AROM); break; case 'o': AddAtom(AtomImpl.AromaticSubset.Oxygen, buffer); g.AddFlags(Graph.HAS_AROM); break; case 'p': AddAtom(AtomImpl.AromaticSubset.Phosphorus, buffer); g.AddFlags(Graph.HAS_AROM); break; case 's': AddAtom(AtomImpl.AromaticSubset.Sulfur, buffer); g.AddFlags(Graph.HAS_AROM); break; // D/T for hydrogen isotopes - non-standard but OpenSMILES spec // says it's possible. The D and T here are automatic converted // to [2H] and [3H]. case 'H': if (strict) { throw new InvalidSmilesException("hydrogens should be specified in square brackets - '[H]'", buffer); } AddAtom(AtomImpl.EXPLICIT_HYDROGEN, buffer); break; case 'D': if (strict) { throw new InvalidSmilesException("deuterium should be specified as a hydrogen isotope - '[2H]'", buffer); } AddAtom(AtomImpl.DEUTERIUM, buffer); break; case 'T': if (strict) { throw new InvalidSmilesException("tritium should be specified as a hydrogen isotope - '[3H]'", buffer); } AddAtom(AtomImpl.TRITIUM, buffer); break; // bracket atom case '[': AddAtom(ReadBracketAtom(buffer), buffer); break; // ring bonds case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': Ring(c - '0', buffer); break; case '%': int num = buffer.GetNumber(2); if (num < 0) { throw new InvalidSmilesException("a number (<digit>+) must follow '%':", buffer); } if (strict && num < 10) { throw new InvalidSmilesException("two digits must follow '%'", buffer); } Ring(num, buffer); break; // bond/dot case '-': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Single; break; case '=': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Double; break; case '#': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Triple; break; case '$': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Quadruple; break; case ':': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } g.AddFlags(Graph.HAS_AROM); bond = Bond.Aromatic; break; case '/': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Up; g.AddFlags(Graph.HAS_BND_STRO); break; case '\\': // we allow C\\C=C/C since it could be an escaping error if (bond != Bond.Implicit && bond != Bond.Down) { throw new InvalidSmilesException("Multiple bonds specified:", buffer); } bond = Bond.Down; g.AddFlags(Graph.HAS_BND_STRO); break; case '.': if (bond != Bond.Implicit) { throw new InvalidSmilesException("Bond specified before disconnection:", buffer); } bond = Bond.Dot; break; // branching case '(': if (stack.IsEmpty) { throw new InvalidSmilesException("Cannot open branch at this position, SMILES may be truncated:", buffer); } stack.Push(stack.Peek()); break; case ')': if (stack.Count < 2) { throw new InvalidSmilesException("Closing of an unopened branch, SMILES may be truncated:", buffer); } stack.Pop(); break; // termination case '\t': case ' ': // String suffix is title var sb = new StringBuilder(); while (buffer.HasRemaining()) { c = buffer.Get(); if (c == '\n' || c == '\r') { break; } sb.Append(c); } g.Title = sb.ToString(); return; case '\n': case '\r': return; default: throw new InvalidSmilesException("unexpected character:", buffer); } } }
/// <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); } } }
public RingBond(int u, Bond bond) { this.u = u; this.bond = bond; }
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! } }
/// <summary> /// Connect the vertices <paramref name="u"/> and <paramref name="v"/> with the specified bond label. /// </summary> /// <param name="u">a vertex</param> /// <param name="v">another vertex</param> /// <param name="b">bond</param> /// <returns>graph builder for adding more atoms/connections</returns> public GraphBuilder Add(int u, int v, Bond b) { Add(b.CreateEdge(u, v)); return(this); }
/// <summary> /// Set the bond label. /// </summary> /// <param name="bond">the bond label</param> public void SetBond(Bond bond) { this.Bond = bond; }