/// <inheritdoc/> public Cycles Find(IAtomContainer molecule, int[][] graph, int length) { RingSearch ringSearch = new RingSearch(molecule, graph); if (this.predefinedLength < length) { length = this.predefinedLength; } IList <int[]> walks = new List <int[]>(6); // all isolated cycles are relevant - all we need to do is walk around // the vertices in the subset 'isolated' foreach (var isolated in ringSearch.Isolated()) { if (isolated.Length <= length) { walks.Add(GraphUtil.Cycle(graph, isolated)); } } // each biconnected component which isn't an isolated cycle is processed // separately as a subgraph. foreach (var fused in ringSearch.Fused()) { // make a subgraph and 'apply' the cycle computation - the walk // (path) is then lifted to the original graph foreach (var cycle in FindInFused(GraphUtil.Subgraph(graph, fused), length)) { walks.Add(Lift(cycle, fused)); } } return(new Cycles(walks.ToArray(), molecule, null)); }
/// <summary> /// Find and mark all cyclic atoms and bonds in the provided molecule. This optimised version /// allows the caller to optionally provided indexed fast access structure which would otherwise /// be created. /// </summary> /// <param name="mol">molecule</param> /// <param name="adjList"></param> /// <param name="bondMap"></param> /// <returns>Number of rings found (circuit rank)</returns> /// <seealso cref="IMolecularEntity.IsInRing"/> /// <seealso href="https://en.wikipedia.org/wiki/Circuit_rank">Circuit Rank</seealso> public static int MarkRingAtomsAndBonds(IAtomContainer mol, int[][] adjList, EdgeToBondMap bondMap) { var ringSearch = new RingSearch(mol, adjList); for (int v = 0; v < mol.Atoms.Count; v++) { mol.Atoms[v].IsInRing = false; foreach (var w in adjList[v]) { // note we only mark the bond on second visit (first v < w) and // clear flag on first visit (or if non-cyclic) if (v > w && ringSearch.Cyclic(v, w)) { bondMap[v, w].IsInRing = true; mol.Atoms[v].IsInRing = true; mol.Atoms[w].IsInRing = true; } else { bondMap[v, w].IsInRing = false; } } } return(ringSearch.NumRings); }
/// <inheritdoc/> public Cycles Find(IAtomContainer molecule, int length) { var bondMap = EdgeToBondMap.WithSpaceFor(molecule); var graph = GraphUtil.ToAdjList(molecule, bondMap); var ringSearch = new RingSearch(molecule, graph); var walks = new List <int[]>(6); // all isolated cycles are relevant - all we need to do is walk around // the vertices in the subset 'isolated' foreach (var isolated in ringSearch.Isolated()) { if (isolated.Length <= length) { walks.Add(GraphUtil.Cycle(graph, isolated)); } } // each biconnected component which isn't an isolated cycle is processed // separately as a subgraph. foreach (var fused in ringSearch.Fused()) { // make a subgraph and 'apply' the cycle computation - the walk // (path) is then lifted to the original graph foreach (var cycle in Apply(GraphUtil.Subgraph(graph, fused), length)) { walks.Add(Lift(cycle, fused)); } } return(new Cycles(walks.ToArray(), molecule, bondMap)); }
/// <summary> /// Adjust all double bond elements in the provided structure. /// </summary> /// <param name="container">the structure to adjust</param> /// <param name="graph">the adjacency list representation of the structure</param> /// <exception cref="ArgumentException">an atom had unset coordinates</exception> CorrectGeometricConfiguration(IAtomContainer container, int[][] graph) { this.container = container; this.graph = graph; this.visited = new bool[graph.Length]; this.atomToIndex = new Dictionary <IAtom, int>(); this.ringSearch = new RingSearch(container, graph); for (int i = 0; i < container.Atoms.Count; i++) { IAtom atom = container.Atoms[i]; atomToIndex[atom] = i; if (atom.Point2D == null) { throw new ArgumentException("atom " + i + " had unset coordinates"); } } foreach (var element in container.StereoElements) { if (element is IDoubleBondStereochemistry) { Adjust((IDoubleBondStereochemistry)element); } else if (element is ExtendedCisTrans) { Adjust((ExtendedCisTrans)element); } } }
/// <inheritdoc/> public override IReadOnlyList <int> Contribution(IAtomContainer container, RingSearch ringSearch) { int n = container.Atoms.Count; var electrons = new int[n]; var piBonds = new int[n]; // count number of cyclic pi bonds foreach (var bond in container.Bonds) { int u = container.Atoms.IndexOf(bond.Begin); int v = container.Atoms.IndexOf(bond.End); if (bond.Order == BondOrder.Double && ringSearch.Cyclic(u, v)) { piBonds[u]++; piBonds[v]++; } } // any atom which is adjacent to one (and only one) cyclic // pi bond contributes 1 electron for (int i = 0; i < n; i++) { electrons[i] = piBonds[i] == 1 ? 1 : -1; } return(electrons); }
/// <summary> /// Find the bonds of a <paramref name="molecule"/> which this model determined were aromatic. /// </summary> /// <example> /// <include file='IncludeExamples.xml' path='Comments/Codes[@id="NCDK.Aromaticities.Aromaticity_Example.cs+FindBonds"]/*' /> /// </example> /// <param name="molecule">the molecule to apply the model to</param> /// <returns>the set of bonds which are aromatic</returns> /// <exception cref="CDKException">a problem occurred with the cycle perception - one can retry with a simpler cycle set</exception> public IEnumerable <IBond> FindBonds(IAtomContainer molecule) { // build graph data-structures for fast cycle perception var bondMap = EdgeToBondMap.WithSpaceFor(molecule); var graph = GraphUtil.ToAdjList(molecule, bondMap); // initial ring/cycle search and get the contribution from each atom RingSearch ringSearch = new RingSearch(molecule, graph); var electrons = model.Contribution(molecule, ringSearch); // obtain the subset of electron contributions which are >= 0 (i.e. // allowed to be aromatic) - we then find the cycles in this subgraph // and 'lift' the indices back to the original graph using the subset // as a lookup var subset = Subset(electrons); var subgraph = GraphUtil.Subgraph(graph, subset); // for each cycle if the electron sum is valid add the bonds of the // cycle to the set or aromatic bonds foreach (var cycle in cycles.Find(molecule, subgraph, subgraph.Length).GetPaths()) { if (CheckElectronSum(cycle, electrons, subset)) { for (int i = 1; i < cycle.Length; i++) { yield return(bondMap[subset[cycle[i]], subset[cycle[i - 1]]]); } } } yield break; }
public static bool IsArene(this IAtomContainer mol) { if (mol == null) { return(false); } var ringSearch = new RingSearch(mol); //this is null for isolated and fused rings var cyclic = ringSearch.Cyclic(); if (cyclic == null) { return(false); } for (var i = 1; i < mol.Bonds.Count; i++) { var p0 = mol.Bonds[i - 1].Order == BondOrder.Single ^ mol.Bonds[i].Order == BondOrder.Single; var p1 = mol.Bonds[i - 1].Order == BondOrder.Double ^ mol.Bonds[i].Order == BondOrder.Double; if (p0 && p1) { continue; } return(false); } return(true); }
/// <summary> /// Recognise the cyclic carbohydrate projections. /// </summary> /// <param name="projections">the types of projections to recognise</param> /// <returns>recognised stereocenters</returns> public IEnumerable <IStereoElement <IChemObject, IChemObject> > Recognise(ICollection <Projection> projections) { if (!projections.Contains(Projection.Haworth) && !projections.Contains(Projection.Chair)) { yield break; } var ringSearch = new RingSearch(container, graph); foreach (var isolated in ringSearch.Isolated()) { if (isolated.Length < 5 || isolated.Length > 7) { continue; } var cycle = Arrays.CopyOf(GraphUtil.Cycle(graph, isolated), isolated.Length); var points = CoordinatesOfCycle(cycle, container); var turns = GetTurns(points); var projection = WoundProjection.OfTurns(turns); if (!projections.Contains(projection.Projection)) { continue; } // ring is not aligned correctly for Haworth if (projection.Projection == Projection.Haworth && !CheckHaworthAlignment(points)) { continue; } var horizontalXy = HorizontalOffset(points, turns, projection.Projection); // near vertical, should also flag as potentially ambiguous if (1 - Math.Abs(horizontalXy.Y) < QuartCardinalityThreshold) { continue; } var above = (int[])cycle.Clone(); var below = (int[])cycle.Clone(); if (!AssignSubstituents(cycle, above, below, projection, horizontalXy)) { continue; } foreach (var center in NewTetrahedralCenters(cycle, above, below, projection)) { yield return(center); } } yield break; }
/// <summary> /// Create a perception method for the provided container, graph /// representation and bond map. /// </summary> /// <param name="container">native CDK structure representation</param> /// <param name="graph">graph representation (adjacency list)</param> /// <param name="bondMap">fast lookup bonds by atom index</param> internal Stereocenters(IAtomContainer container, int[][] graph, EdgeToBondMap bondMap) { this.container = container; this.bondMap = bondMap; this.g = graph; this.ringSearch = new RingSearch(container, graph); this.elements = new StereoElement[g.Length]; this.stereocenters = new CenterType[g.Length]; this.numStereoElements = CreateElements(); }
public override ISet <int> Find(long[] invariants, IAtomContainer container, int[][] graph) { int n = invariants.Length; // find cyclic vertices using DFS RingSearch ringSearch = new RingSearch(container, graph); // ordered map of the set of vertices for each value var equivalent = new SortedDictionary <long, ISet <int> >(); // divide the invariants into equivalent indexed and ordered sets for (int i = 0; i < invariants.Length; i++) { long invariant = invariants[i]; if (!equivalent.TryGetValue(invariant, out ISet <int> set)) { if (ringSearch.Cyclic(i)) { set = new HashSet <int> { i }; equivalent[invariant] = set; } } else { set.Add(i); } } // find the smallest set of equivalent cyclic vertices int minSize = int.MaxValue; ISet <int> min = new SortedSet <int>(); foreach (var e in equivalent) { ISet <int> vertices = e.Value; if (vertices.Count < minSize && vertices.Count > 1) { min = vertices; minSize = vertices.Count; } else if (vertices.Count == minSize) { foreach (var vertice in vertices) { min.Add(vertice); } } } return(min); }
void Main() { { #region // using NCDK.Graphs.GraphUtil; IAtomContainer m = TestMoleculeFactory.MakeAnthracene(); // compute on the whole graph RelevantCycles relevant = new RelevantCycles(ToAdjList(m)); // it is much faster to compute on the separate ring systems of the molecule int[][] graph = ToAdjList(m); RingSearch ringSearch = new RingSearch(m, graph); // all isolated cycles are relevant foreach (int[] isolated in ringSearch.Isolated()) { int[] path = Cycle(graph, isolated); } // compute the relevant cycles for each system foreach (int[] fused in ringSearch.Fused()) { int[][] subgraph = Subgraph(graph, fused); RelevantCycles relevantOfSubgraph = new RelevantCycles(subgraph); foreach (int[] path in relevantOfSubgraph.GetPaths()) { // convert the sub graph vertices back to the super graph indices for (int i = 0; i < path.Length; i++) { path[i] = fused[path[i]]; } } } #endregion } { IAtomContainer mol = null; #region GetPaths RelevantCycles relevant = new RelevantCycles(ToAdjList(mol)); // ensure the number is manageable if (relevant.Count() < 100) { foreach (int[] path in relevant.GetPaths()) { // process the path } } #endregion } }
public static void Main(string[] args) { // convert the molecule to adjacency list - may be redundant in future IAtomContainer m = TestMoleculeFactory.MakeAlphaPinene(); int[][] g = GraphUtil.ToAdjList(m); // efficient computation/partitioning of the ring systems RingSearch rs = new RingSearch(m, g); // isolated cycles don't need to be run rs.Isolated(); // process fused systems separately foreach (var fused in rs.Fused()) { const int maxDegree = 100; // given the fused subgraph, max cycle size is // the number of vertices AllCycles ac = new AllCycles(GraphUtil.Subgraph(g, fused), fused.Length, maxDegree); // cyclic walks int[][] paths = ac.GetPaths(); } }
/// <inheritdoc/> public override IReadOnlyList <int> Contribution(IAtomContainer container, RingSearch ringSearch) { var nAtoms = container.Atoms.Count; var electrons = new int[nAtoms]; Arrays.Fill(electrons, -1); var indexMap = new Dictionary <IAtom, int>(); for (int i = 0; i < nAtoms; i++) { var atom = container.Atoms[i]; indexMap.Add(atom, i); // acyclic atom skipped if (!ringSearch.Cyclic(i)) { continue; } var hyb = atom.Hybridization; CheckNotNull(atom.AtomTypeName, "atom has unset atom type"); // atom has been assigned an atom type but we don't know the hybrid state, // typically for atom type 'X' (unknown) switch (hyb) { case Hybridization.SP2: case Hybridization.Planar3: electrons[i] = ElectronsForAtomType(atom); break; case Hybridization.SP3: electrons[i] = LonePairCount(atom) > 0 ? 2 : -1; break; } } // exocyclic double bonds are allowed no further processing if (exocyclic) { return(electrons); } // check for exocyclic double/triple bonds and disallow their contribution foreach (var bond in container.Bonds) { if (bond.Order == BondOrder.Double || bond.Order == BondOrder.Triple) { var a1 = bond.Begin; var a2 = bond.End; var a1Type = a1.AtomTypeName; var a2Type = a2.AtomTypeName; var u = indexMap[a1]; var v = indexMap[a2]; if (!ringSearch.Cyclic(u, v)) { // XXX: single exception - we could make this more general but // for now this mirrors the existing behavior switch (a1Type) { case "N.sp2.3": switch (a2Type) { case "O.sp2": continue; } break; case "O.sp2": switch (a2Type) { case "N.sp2.3": continue; } break; } electrons[u] = electrons[v] = -1; } } } return(electrons); }
/// <summary> /// Recognise the tetrahedral stereochemistry in the provided structure. /// </summary> /// <param name="projections">allowed projection types</param> /// <returns>zero of more stereo elements</returns> public IEnumerable <IStereoElement <IChemObject, IChemObject> > Recognise(ICollection <Projection> projections) { if (!projections.Contains(Projection.Fischer)) { return(Array.Empty <IStereoElement <IChemObject, IChemObject> >()); } // build atom index and only recognize 2D depictions var atomToIndex = new Dictionary <IAtom, int>(); foreach (var atom in container.Atoms) { if (atom.Point2D == null) { return(Array.Empty <IStereoElement <IChemObject, IChemObject> >()); } atomToIndex.Add(atom, atomToIndex.Count); } var ringSearch = new RingSearch(container, graph); var elements = new List <IStereoElement <IChemObject, IChemObject> >(5); for (int v = 0; v < container.Atoms.Count; v++) { var focus = container.Atoms[v]; if (!focus.AtomicNumber.Equals(AtomicNumbers.Carbon)) { continue; } if (ringSearch.Cyclic(v)) { continue; } if (stereocenters.ElementType(v) != CoordinateType.Tetracoordinate) { continue; } if (!stereocenters.IsStereocenter(v)) { continue; } var element = NewTetrahedralCenter(focus, Neighbors(v, graph, bonds)); if (element == null) { continue; } // east/west bonds must be to terminal atoms var east = element.Ligands[EAST]; var west = element.Ligands[WEST]; if (east != focus && !IsTerminal(east, atomToIndex)) { continue; } if (west != focus && !IsTerminal(west, atomToIndex)) { continue; } elements.Add(element); } return(elements); }
/// <summary> /// Determine the number 'p' electron contributed by each atom in the /// provided <paramref name="container"/>. A value of '0' indicates the atom can /// contribute but that it contributes no electrons. A value of '-1' /// indicates the atom should not contribute at all. /// </summary> /// <param name="container">molecule</param> /// <param name="ringSearch">ring information</param> /// <returns>electron contribution of each atom (-1=none)</returns> public abstract IReadOnlyList <int> Contribution(IAtomContainer container, RingSearch ringSearch);
/// <inheritdoc/> public override IReadOnlyList <int> Contribution(IAtomContainer container, RingSearch ringSearch) { int n = container.Atoms.Count; // we compute values we need for all atoms and then make the decisions // - this avoids costly operations such as looking up connected // bonds on each atom at the cost of memory var degree = new int[n]; var bondOrderSum = new int[n]; var nCyclicPiBonds = new int[n]; var exocyclicPiBond = new int[n]; var electrons = new int[n]; Arrays.Fill(exocyclicPiBond, -1); // index atoms and set the degree to the number of implicit hydrogens var atomIndex = new Dictionary <IAtom, int>(n); for (int i = 0; i < n; i++) { var a = container.Atoms[i]; atomIndex.Add(a, i); degree[i] = CheckNotNull(a.ImplicitHydrogenCount, "Aromaticity model requires implicit hydrogen count is set."); } // for each bond we increase the degree count and check for cyclic and // exocyclic pi bonds. if there is a cyclic pi bond the atom is marked. // if there is an exocyclic pi bond we store the adjacent atom for // lookup later. foreach (var bond in container.Bonds) { var u = atomIndex[bond.Begin]; var v = atomIndex[bond.End]; degree[u]++; degree[v]++; var order = CheckNotNull(bond.Order, "Aromaticity model requires that bond orders must be set"); if (order == BondOrder.Unset) { throw new ArgumentException("Aromaticity model requires that bond orders must be set"); } else if (order == BondOrder.Double) { if (ringSearch.Cyclic(u, v)) { nCyclicPiBonds[u]++; nCyclicPiBonds[v]++; } else { exocyclicPiBond[u] = v; exocyclicPiBond[v] = u; } // note - fall through } if (order == BondOrder.Single || order == BondOrder.Double || order == BondOrder.Triple || order == BondOrder.Quadruple) { bondOrderSum[u] += order.Numeric(); bondOrderSum[v] += order.Numeric(); } } // now make a decision on how many electrons each atom contributes for (int i = 0; i < n; i++) { var element = Element(container.Atoms[i]); var charge = Charge(container.Atoms[i]); // abnormal valence, usually indicated a radical. these cause problems // with kekulisations var bondedValence = bondOrderSum[i] + container.Atoms[i].ImplicitHydrogenCount.Value; if (!Normal(element, charge, bondedValence)) { electrons[i] = -1; } // non-aromatic element, acyclic atoms, atoms with more than three // neighbors and atoms with more than 1 cyclic pi bond are not // considered else if (!AromaticElement(element) || !ringSearch.Cyclic(i) || degree[i] > 3 || nCyclicPiBonds[i] > 1) { electrons[i] = -1; } // exocyclic bond contributes 0 or 1 electrons depending on // preset electronegativity - check the exocyclicContribution method else if (exocyclicPiBond[i] >= 0) { electrons[i] = ExocyclicContribution(element, Element(container.Atoms[exocyclicPiBond[i]]), charge, nCyclicPiBonds[i]); } // any atom (except arsenic) with one cyclic pi bond contributes a // single electron else if (nCyclicPiBonds[i] == 1) { electrons[i] = element == ARSENIC ? -1 : 1; } // a anion with a lone pair contributes 2 electrons - simplification // here is we count the number free valence electrons but also // check if the bonded valence is okay (i.e. not a radical) else if (charge <= 0 && charge > -3) { if (Valence(element, charge) - bondOrderSum[i] >= 2) { electrons[i] = 2; } else { electrons[i] = -1; } } else { // cation with no double bonds - single exception? if (element == Carbon && charge > 0) { electrons[i] = 0; } else { electrons[i] = -1; } } } return(electrons); }