/// <summary> /// Permute the vertices of a graph using a given permutation. /// </summary> /// <example> /// <code> /// g = CNCO /// h = g.Permuate(new int[]{1, 0, 3, 2}); /// h = NCOC /// </code> /// </example> /// <param name="p">a permutation mapping indicate the new index of each atom</param> /// <returns>a new chemical graph with the vertices permuted by the given Ordering</returns> public Graph Permute(int[] p) { if (p.Length != order) { throw new ArgumentException("permuation size should equal |V| (Order)"); } Graph cpy = new Graph(order) { flags = flags, order = order, size = size }; for (int u = 0; u < order; u++) { int d = degrees[u]; // v is the image of u in the permutation int v = p[u]; if (d > 4) { cpy.edges[v] = new Edge[d]; } cpy.atoms[v] = atoms[u]; cpy.valences[v] = valences[u]; cpy.AddTopology(TopologyOf(u).Transform(p)); while (--d >= 0) { Edge e = EdgeAt(u, d); // important this is the second time we have seen the edge // so the capacity must have been allocated. otherwise we // would get an index out of bounds if (u > e.Other(u)) { // w is the image of vertex adjacen to u int w = p[e.Other(u)]; Edge f = new Edge(v, w, e.GetBond(u)); cpy.edges[v][cpy.degrees[v]++] = f; cpy.edges[w][cpy.degrees[w]++] = f; cpy.size++; } } } // ensure edges are in sorted order return(cpy.Sort(new CanOrderFirst())); }
// invariant, m is a perfect matching private static Graph Assign(Graph g, BitArray subset, BitArray aromatic, Matching m) { g.SetFlags(g.GetFlags() & ~Graph.HAS_AROM); for (int u = BitArrays.NextSetBit(aromatic, 0); u >= 0; u = BitArrays.NextSetBit(aromatic, u + 1)) { g.SetAtom(u, g.GetAtom(u).AsAliphaticForm()); int deg = g.Degree(u); for (int j = 0; j < deg; ++j) { Edge e = g.EdgeAt(u, j); int v = e.Other(u); if (v < u) { var aa = e.Bond; if (aa == Bond.Single) { if (aromatic[u] && aromatic[v]) { e.SetBond(Bond.Single); } else { e.SetBond(Bond.Implicit); } } else if (aa == Bond.Aromatic) { if (subset[u] && m.Other(u) == v) { e.SetBond(Bond.DoubleAromatic); g.UpdateBondedValence(u, +1); g.UpdateBondedValence(v, +1); } else if (aromatic[v]) { e.SetBond(Bond.ImplicitAromatic); } else { e.SetBond(Bond.Implicit); } } else if (aa == Bond.Implicit) { if (subset[u] && m.Other(u) == v) { e.SetBond(Bond.DoubleAromatic); g.UpdateBondedValence(u, +1); g.UpdateBondedValence(v, +1); } else if (aromatic[u] && aromatic[v]) { e.SetBond(Bond.ImplicitAromatic); } } } } } return(g); }
private static bool IsRedundantDirectionalEdge(Graph g, Edge edge, BitArray unspecified) { if (!edge.Bond.IsDirectional) { return(false); } int u = edge.Either(); int v = edge.Other(u); if (!unspecified[u]) { foreach (Edge f in g.GetEdges(u)) { if (f.Bond.IsDirectional && edge != f) { return(true); } } } else if (!unspecified[v]) { foreach (Edge f in g.GetEdges(v)) { if (f.Bond.IsDirectional && edge != f) { return(true); } } } return(false); }
private void Flip(Graph g, Edge e, BitArray dbAtoms) { var u = e.Either(); var v = e.Other(u); if (ordering[u] < ordering[v]) { var first = FirstDirectionalLabel(g, u); if (first != null) { Flip(first, u, dbAtoms); } else { first = FirstDirectionalLabel(g, v); Flip(first, v, dbAtoms); } } else { var first = FirstDirectionalLabel(g, v); if (first != null) { Flip(first, v, dbAtoms); } else { first = FirstDirectionalLabel(g, u); Flip(first, u, dbAtoms); } } }
private void Flip(Edge first, int u, BitArray dbAtoms) { if (ordering[first.Other(u)] < ordering[u]) { if (first.GetBond(u) == Bond.Up) { InvertExistingDirectionalLabels(g, new BitArray(g.Order), acc, dbAtoms, u); } } else { if (first.GetBond(u) == Bond.Down) { InvertExistingDirectionalLabels(g, new BitArray(g.Order), acc, dbAtoms, u); } } }
internal static bool InSmallRing(Graph g, int v, int prev, int t, int d, BitArray visit) { if (d > 7) { return(false); } if (v == t) { return(true); } if (visit[v]) { return(false); } visit.Set(v, true); int deg = g.Degree(v); for (int j = 0; j < deg; ++j) { Edge e = g.EdgeAt(v, j); int w = e.Other(v); if (w == prev) { continue; } if (InSmallRing(g, w, v, t, d + 1, visit)) { return(true); } } return(false); }
private static bool HasAdjDirectionalLabels(Graph g, Edge e) { int u = e.Either(); int v = e.Other(u); return(HasAdjDirectionalLabels(g, u) && HasAdjDirectionalLabels(g, v)); }
/// <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); }
/// <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)); } } } }
private static bool HasAdjDirectionalLabels(Graph g, Edge e, BitArray cyclic) { int u = e.Either(); int v = e.Other(u); return(HasAdjDirectionalLabels(g, u, cyclic) && HasAdjDirectionalLabels(g, v, cyclic)); }
/// <summary> /// Copy constructor. /// </summary> /// <param name="org">original graph</param> Graph(Graph org) { this.order = org.order; this.size = org.size; this.flags = org.flags; this.atoms = Arrays.CopyOf(org.atoms, order); this.valences = Arrays.CopyOf(org.valences, order); this.degrees = new int[order]; this.edges = new Edge[order][]; this.topologies = Arrays.CopyOf(org.topologies, org.topologies.Length); for (int u = 0; u < order; u++) { int deg = org.degrees[u]; this.edges[u] = new Edge[deg]; for (int j = 0; j < deg; ++j) { Edge e = org.edges[u][j]; int v = e.Other(u); // important - we have made use edges are allocated if (u > v) { Edge f = new Edge(e); edges[u][degrees[u]++] = f; edges[v][degrees[v]++] = f; } } } }
private int Visit(int u, Edge from) { depth[u] = ++count; int d = g.Degree(u); int lo = count + 1; while (--d >= 0) { Edge e = g.EdgeAt(u, d); if (e == from) { continue; } int v = e.Other(u); if (depth[v] == 0) { int res = Visit(v, e); if (res < lo) { lo = res; } } else if (depth[v] < lo) { lo = depth[v]; } } if (lo <= depth[u]) { cyclic.Set(u, true); } return(lo); }
/// <summary> /// Replace an edge in the graph. /// </summary> /// <param name="org">the original edge</param> /// <param name="rep">the replacement</param> internal void Replace(Edge org, Edge rep) { int u = org.Either(); int v = org.Other(u); for (int i = 0; i < degrees[u]; i++) { if (edges[u][i] == org) { edges[u][i] = rep; } } for (int i = 0; i < degrees[v]; i++) { if (edges[v][i] == org) { edges[v][i] = rep; } } int ord = rep.Bond.Order - org.Bond.Order; valences[u] += ord; valences[v] += ord; }
/// <summary> /// Create an arbitrary matching on the subset of vertices ('s') of provided /// graph. The provided matching should be empty. /// /// <param name="g">graph to match</param> /// <param name="m">empty matching (presumed)</param> /// <param name="s">subset of vertices</param> /// <returns>number of vertices matched</returns> /// </summary> public static int Initial(Graph g, Matching m, BitArray s) { int nMatched = 0; for (int v = BitArrays.NextSetBit(s, 0); v >= 0; v = BitArrays.NextSetBit(s, v + 1)) { // skip if already matched if (m.Matched(v)) { continue; } // find a single edge which is not matched and match it int d = g.Degree(v); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(v, j); int w = e.Other(v); if ((e.Bond != Bond.Single) && m.Unmatched(w) && s[w]) { m.Match(v, w); nMatched += 2; break; } } } return(nMatched); }
/// <summary> /// Given a double bond edge traverse the neighbors of both endpoints and /// accumulate any explicit replacements in the 'acc' accumulator. /// </summary> /// <param name="g"> the chemical graph</param> /// <param name="e"> a edge in the graph </param>('double bond type') /// <param name="ordering"></param> /// <param name="acc">accumulator for new edges</param> /// <exception cref="InvalidSmilesException">thrown if the edge could not be converted</exception> private static void RemoveRedundant(Graph g, Edge e, int[] ordering, Dictionary <Edge, Edge> acc) { int u = e.Either(), v = e.Other(u); ReplaceImplWithExpl(g, e, u, ordering, acc); ReplaceImplWithExpl(g, e, v, ordering, acc); }
/// <summary> /// Given a double bond edge traverse the neighbors of both endpoints and /// accumulate any explicit replacements in the <paramref name="e"/> accumulator. /// </summary> /// <param name="g"> the chemical graph</param> /// <param name="e"> a edge in the graph </param>('double bond type') /// <param name="acc">accumulator for new edges</param> /// <exception cref="InvalidSmilesException">thrown if the edge could not be converted</exception> private bool ReplaceImplWithExpl(Graph g, Edge e, Dictionary <Edge, Edge> acc) { int u = e.Either(), v = e.Other(u); bool uDone = ReplaceImplWithExpl(g, e, u, acc); bool vDone = ReplaceImplWithExpl(g, e, v, acc); return(uDone || vDone); }
/// <summary> /// Add an labelled edge to the graph. /// </summary> /// <param name="e">new edge</param> public void AddEdge(Edge e) { int u = e.Either(), v = e.Other(u); EnsureEdgeCapacity(u); EnsureEdgeCapacity(v); edges[u][degrees[u]++] = e; edges[v][degrees[v]++] = e; int ord = e.Bond.Order; valences[u] += ord; valences[v] += ord; size++; }
/// <summary> /// Access the edge connecting two adjacent vertices. /// </summary> /// <param name="u">a vertex</param> /// <param name="v">another vertex </param>(adjacent to u) /// <returns>the edge connected u and v</returns> /// <exception cref="ArgumentException">u and v are not adjacent</exception> public Edge CreateEdge(int u, int v) { int d = degrees[u]; for (int j = 0; j < d; ++j) { Edge e = edges[u][j]; if (e.Other(u) == v) { return(e); } } throw new ArgumentException(u + ", " + v + " are not adjacent"); }
private void SetAllenalStereo(Graph g, int[] visitedAt, int u) { Trace.Assert(g.Degree(u) == 2); Edge a = g.EdgeAt(u, 0); Edge b = g.EdgeAt(u, 1); Trace.Assert(a.Bond == Bond.Double && b.Bond == Bond.Double); int aAtom = a.Other(u); int bAtom = b.Other(u); if (!rings.ContainsKey(aAtom) && !rings.ContainsKey(bAtom)) { // no rings on either end, this is simply the order we visited the // atoms in tokens[u].Configure(g.TopologyOf(u).ConfigurationOf(visitedAt)); } else { // hokay this case is harder... this makes me wince but BEAM v2 // has a much better way of handling this // we can be clever here rollback any changes we make (see the // tetrahedral handling) however since this is a very rare // operation it much simpler to copy the array int[] tmp = Arrays.CopyOf(visitedAt, visitedAt.Length); if (visitedAt[aAtom] > visitedAt[bAtom]) { int swap = aAtom; aAtom = bAtom; bAtom = swap; } Trace.Assert(!rings.ContainsKey(aAtom) || rings[aAtom].Count == 1); Trace.Assert(!rings.ContainsKey(bAtom) || rings[bAtom].Count == 1); if (rings.ContainsKey(aAtom)) { tmp[rings[aAtom][0].Other(aAtom)] = visitedAt[aAtom]; } if (rings.ContainsKey(bAtom)) { tmp[rings[bAtom][0].Other(bAtom)] = visitedAt[bAtom]; } tokens[u].Configure(g.TopologyOf(u).ConfigurationOf(tmp)); } }
/// <summary> /// Determine if the vertices '<paramref name="u"/>' and '<paramref name="v"/>' are adjacent and there is an edge /// which connects them. /// </summary> /// <param name="u">a vertex</param> /// <param name="v">another vertex</param> /// <returns>whether they are adjacent</returns> public bool Adjacent(int u, int v) { int d = degrees[u]; for (int j = 0; j < d; ++j) { Edge e = edges[u][j]; if (e.Other(u) == v) { return(true); } } return(false); }
private static bool HasAdjDirectionalLabels(Graph g, int u, BitArray cyclic) { int d = g.Degree(u); for (int j = 0; j < d; ++j) { Edge f = g.EdgeAt(u, j); int v = f.Other(u); if (f.Bond.IsDirectional && cyclic[v]) { return(true); } } return(false); }
/// <summary> /// Create the current local arrangement for vertex 'u' - if the arrangment /// already exists then that arrangement is used. /// </summary> /// <param name="u">vertex to get the arrangement around</param> /// <returns>current local arrangement</returns> private LocalArrangement CreateArrangement(int u) { if (!arrangement.TryGetValue(u, out LocalArrangement la)) { la = new LocalArrangement(); int d = g.Degree(u); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(u, j); la.Add(e.Other(u)); } arrangement[u] = la; } return(la); }
Edge FirstDirectionalLabel(Graph g, int u) { Edge first = null; foreach (var f in g.GetEdges(u)) { if (f.Bond == Bond.Up || f.Bond == Bond.Down) { if (first == null || ordering[f.Other(u)] < ordering[first.Other(u)]) { first = f; } } } return(first); }
private int VisitWithComp(int u, Edge from) { depth[u] = ++count; int j = g.Degree(u); int lo = count + 1; while (--j >= 0) { Edge e = g.EdgeAt(u, j); if (e == from) { continue; } int v = e.Other(u); if (depth[v] == 0) { stack[nstack] = e; ++nstack; int tmp = VisitWithComp(v, e); if (tmp == depth[u]) { StoreWithComp(e); } else if (tmp > depth[u]) { --nstack; } if (tmp < lo) { lo = tmp; } } else if (depth[v] < depth[u]) { // back edge stack[nstack] = e; ++nstack; if (depth[v] < lo) { lo = depth[v]; } } } return(lo); }
/// <summary> /// Add an edge to the graph. /// </summary> /// <param name="e">the edge to add</param> /// <returns>graph builder for adding more atoms/connections</returns> public GraphBuilder Add(Edge e) { Bond b = e.Bond; int u = e.Either(); int v = e.Other(u); if (b == Bond.Single && (!g.GetAtom(u).IsAromatic() || !g.GetAtom(v).IsAromatic())) { e.SetBond(Bond.Implicit); } else if (b == Bond.Aromatic && g.GetAtom(u).IsAromatic() && g.GetAtom(v).IsAromatic()) { e.SetBond(Bond.Implicit); } g.AddEdge(e); valence[u] += b.Order; valence[v] += b.Order; return(this); }
public bool Less(Graph g, int u, Edge e, Edge f) { int v = e.Other(u); int w = f.Other(u); Element vElem = g.GetAtom(v).Element; Element wElem = g.GetAtom(w).Element; if (vElem == Hydrogen && wElem != Hydrogen) { return(true); } if (vElem != Hydrogen && wElem == Hydrogen) { return(false); } // sort hydrogens by isotope return(vElem == Hydrogen && g.GetAtom(v).Isotope < g.GetAtom(w).Isotope); }
public static int FindPath(Graph g, int v, int end, BitArray unvisited, int[] path, int len, Matching m, bool matchNeeded) { unvisited.Set(v, false); path[len++] = v; int l; int d = g.Degree(v); for (int j = 0; j < d; ++j) { Edge e = g.EdgeAt(v, j); // explicit single bond can not be augmented along!! if (e.Bond == Bond.Single) { continue; } int w = e.Other(v); if (unvisited[w]) { if (w == end) { path[len] = w; len++; unvisited.Set(v, true); // odd length path no good return(((len & 0x1) == 1) ? 0 : len); } else if ((m.Other(w) == v) == matchNeeded) { if ((l = FindPath(g, w, end, unvisited, path, len, m, !matchNeeded)) > 0) { unvisited.Set(v, true); return(l); } } } } unvisited.Set(v, true); return(0); }
/// <summary> /// Given a double bond edge traverse the neighbors of one of the endpoints /// and accumulate any explicit replacements in the 'acc' accumulator. /// </summary> /// <param name="g"> the chemical graph</param> /// <param name="e"> a edge in the graph </param>('double bond type') /// <param name="u"> a endpoint of the edge 'e'</param> /// <param name="ordering"></param> /// <param name="acc">accumulator for new edges</param> /// <exception cref="InvalidSmilesException">thrown if the edge could not be converted</exception> private static void ReplaceImplWithExpl(Graph g, Edge e, int u, int[] ordering, Dictionary <Edge, Edge> acc) { ICollection <Edge> edges = new SortedSet <Edge>(new S(e, u, ordering)); foreach (var f in g.GetEdges(u)) { var aa = f.Bond; if (aa == Bond.Double) { if (!f.Equals(e)) { return; } } else if (aa == Bond.Up || aa == Bond.Down) { edges.Add(f); } } if (edges.Count == 2) { Edge explicit_ = edges.First(); int v = explicit_.Either(); int w = explicit_.Other(v); acc[explicit_] = new Edge(v, w, Bond.Implicit); } else if (edges.Count > 2) { throw new InvalidSmilesException("Too many up/down bonds on double bonded atom"); } }
public bool Less(Graph g, int u, Edge e, Edge f) { return(e.Other(u) < f.Other(u)); }
// invariant, m is a perfect matching private static Graph CopyAndAssign(Graph delocalised, BitArray subset, BitArray aromatic, Matching m) { Graph localised = new Graph(delocalised.Order); localised.SetFlags(delocalised.GetFlags() & ~Graph.HAS_AROM); for (int u = 0; u < delocalised.Order; u++) { localised.AddAtom(delocalised.GetAtom(u).AsAliphaticForm()); localised.AddTopology(delocalised.TopologyOf(u)); int d = delocalised.Degree(u); for (int j = 0; j < d; ++j) { Edge e = delocalised.EdgeAt(u, j); int v = e.Other(u); if (v < u) { var aa = e.Bond; if (aa == Bond.Single) { if (aromatic[u] && aromatic[v]) { localised.AddEdge(Bond.Single.CreateEdge(u, v)); } else { localised.AddEdge(Bond.Implicit.CreateEdge(u, v)); } } else if (aa == Bond.Aromatic) { if (subset[u] && m.Other(u) == v) { localised.AddEdge(Bond.DoubleAromatic.CreateEdge(u, v)); } else if (aromatic[u] && aromatic[v]) { localised.AddEdge(Bond.ImplicitAromatic.CreateEdge(u, v)); } else { localised.AddEdge(Bond.Implicit.CreateEdge(u, v)); } } else if (aa == Bond.Implicit) { if (subset[u] && m.Other(u) == v) { localised.AddEdge(Bond.DoubleAromatic.CreateEdge(u, v)); } else if (aromatic[u] && aromatic[v]) { localised.AddEdge(Bond.ImplicitAromatic.CreateEdge(u, v)); } else { localised.AddEdge(e); } } else { localised.AddEdge(e); } } } } return(localised); }