/// <summary> /// Adjust the configuration of the <paramref name="dbs"/> element (if required). /// </summary> /// <param name="dbs">double-bond stereochemistry element</param> private void Adjust(IDoubleBondStereochemistry dbs) { var db = dbs.StereoBond; var bonds = dbs.Bonds; var left = db.Begin; var right = db.End; var p = Parity(dbs.Configure); var q = Parity(GetAtoms(left, bonds[0].GetOther(left), right)) * Parity(GetAtoms(right, bonds[1].GetOther(right), left)); // configuration is unspecified? then we add an unspecified bond. // note: IDoubleBondStereochemistry doesn't indicate this yet if (p == 0) { foreach (var bond in container.GetConnectedBonds(left)) { bond.Stereo = BondStereo.None; } foreach (var bond in container.GetConnectedBonds(right)) { bond.Stereo = BondStereo.None; } bonds[0].Stereo = BondStereo.UpOrDown; return; } // configuration is already correct if (p == q) { return; } Arrays.Fill(visited, false); visited[atomToIndex[left]] = true; if (ringSearch.Cyclic(atomToIndex[left], atomToIndex[right])) { db.Stereo = BondStereo.EOrZ; return; } foreach (var w in graph[atomToIndex[right]]) { if (!visited[w]) { Reflect(w, db); } } }
/// <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 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); }
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); }
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); }
/// <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); }
/// <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); }