/// <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 the tetrahedral stereocenters for the provided cycle. /// </summary> /// <param name="cycle">vertices in projected cycle</param> /// <param name="above">vertices above the cycle</param> /// <param name="below">vertices below the cycle</param> /// <param name="type">type of projection</param> /// <returns>zero of more stereocenters</returns> private IReadOnlyList <ITetrahedralChirality> NewTetrahedralCenters(int[] cycle, int[] above, int[] below, WoundProjection type) { var centers = new List <ITetrahedralChirality>(cycle.Length); for (int i = 1; i <= cycle.Length; i++) { int prev = cycle[i - 1]; int curr = cycle[i % cycle.Length]; int next = cycle[(i + 1) % cycle.Length]; int up = above[i % cycle.Length]; int down = below[i % cycle.Length]; if (!stereocenters.IsStereocenter(curr)) { continue; } // Any wedge or hatch bond causes us to exit, this may still be // a valid projection. Currently it can cause a collision with // one atom have two tetrahedral stereo elements. if (!IsPlanarSigmaBond(bonds[curr, prev]) || !IsPlanarSigmaBond(bonds[curr, next]) || (up != curr && !IsPlanarSigmaBond(bonds[curr, up])) || (down != curr && !IsPlanarSigmaBond(bonds[curr, down]))) { return(Array.Empty <ITetrahedralChirality>()); } centers.Add(new TetrahedralChirality(container.Atoms[curr], new IAtom[] { container.Atoms[up], container.Atoms[prev], container.Atoms[down], container.Atoms[next] }, type.Winding )); } return(centers); }
/// <summary> /// Given a projected cycle, assign the exocyclic substituents to being above /// of below the projection. For Haworth projections, the substituents must /// be directly up or down (within some threshold). /// </summary> /// <param name="cycle">vertices that form a cycle</param> /// <param name="above">vertices that will be above the cycle (filled by method)</param> /// <param name="below">vertices that will be below the cycle (filled by method)</param> /// <param name="projection">the type of projection</param> /// <param name="horizontalXy">offset from the horizontal axis</param> /// <returns>assignment okay (true), not okay (false)</returns> private bool AssignSubstituents(int[] cycle, int[] above, int[] below, WoundProjection projection, Vector2 horizontalXy) { bool haworth = projection.Projection == Projection.Haworth; int found = 0; for (int i = 1; i <= cycle.Length; i++) { int j = i % cycle.Length; int prev = cycle[i - 1]; int curr = cycle[j]; int next = cycle[(i + 1) % cycle.Length]; // get the substituents not in the ring (i.e. excl. prev and next) int[] ws = Filter(graph[curr], prev, next); if (ws.Length > 2 || ws.Length < 1) { continue; } var centerXy = container.Atoms[curr].Point2D.Value; // determine the direction of each substituent foreach (var w in ws) { var otherXy = container.Atoms[w].Point2D.Value; var direction = ObtainDirection(centerXy, otherXy, horizontalXy, haworth); switch (direction) { case Direction.Up: if (above[j] != curr) { return(false); } above[j] = w; break; case Direction.Down: if (below[j] != curr) { return(false); } below[j] = w; break; case Direction.Other: return(false); } } if (above[j] != curr || below[j] != curr) { found++; } } // must have at least 2 that look projected for Haworth return(found > 1 || projection.Projection != Projection.Haworth); }