org.openscience.cdk.stereo.Stereocenters Maven / Gradle / Ivy
/*
* Copyright (c) 2013 European Bioinformatics Institute (EMBL-EBI)
* John May
*
* Contact: [email protected]
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version. All we ask is that proper credit is given
* for our work, which includes - but is not limited to - adding the above
* copyright notice to the beginning of your source code files, and to any
* copyright notice that you may distribute with programs based on this work.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 U
*/
package org.openscience.cdk.stereo;
import org.openscience.cdk.graph.GraphUtil;
import org.openscience.cdk.graph.invariant.Canon;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.ringsearch.RingSearch;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import static org.openscience.cdk.graph.GraphUtil.EdgeToBondMap;
/**
* Find atoms which can support stereo chemistry based on the connectivity.
* Stereocenters are classified as True when they have constitutionally
* different ligands and Para ("resemble") stereo centers with
* constitutionally identical ligands. Some examples of para-centers
* are listed below. Non and potential stereogenic atoms are also indicated. The
* method partially implements the rules described by {@cdk.cite Razinger93}.
* Para centers are identified in isolated rings (more common) but are not
* currently found in fused systems (e.g. decalin), spiro linked 'assemblages'
* or acyclic interdependent centers.
*
* Accepted Stereo Atoms
*
* This atoms accepted as being potentially stereogenic are those defined
* in the InChI Technical Manual {@cdk.cite InChITechManual}. These are:
*
* Tetrahedral Stereochemistry:
*
* - Carbon - 4 valent, 4 sigma bonds
* - Silicon - 4 valent, 4 sigma bonds
* - Germanium - 4 valent, 4 sigma bonds
* - Tin - 4 valent, 4 sigma bonds
* - Nitrogen cation - 4 valent,4 sigma bonds
* - Phosphorus cation - 4 valent, 4 sigma bonds
* - Arsenic cation - 4 valent, 4 sigma bonds
* - Boron anion - 4 valent, 4 sigma bonds
* - Nitrogen - 5 valent, 3 sigma and 1 pi bond
* - Phosphorus - 5 valent, 3 sigma and 1 pi bond
* - Sulphur - 4 valent, 2 sigma and 1 pi bond
* - Sulphur - 6 valent, 2 sigma and 2 pi bonds
* - Sulphur Cation - 3 valent, 3 sigma bonds
* - Sulphur cation - 5 valent, 3 sigma and 1 pi bond
* - Selenium - 4 valent, 2 sigma and 1 pi bond
* - Selenium - 6 valent, 2 sigma and 2 pi bonds
* - Selenium Cation - 3 valent, 3 sigma bonds
* - Selenium cation - 5 valent, 3 sigma and 1 pi bond
* - Nitrogen - 3 valent, 3 sigma bonds and in a 3 member ring
*
* N, P, As, S or Se are not stereogenic if they have a terminal H neighbor
* or if they have 2 neighbors of the same element (O, S, Se, Te, N) which
* have at least one hydrogen. Consider: {@code P(O)(=O)(OC)OCCC}. Phosphines and
* arsines are always stereogenic regardless of H neighbors
*
*
* Double Bond Stereochemistry:
* The following atoms can appear at either end of a double bond.
*
* - Carbon - 4 valent, 2 sigma and 1 pi bond
* - Silicon - 4 valent, 2 sigma and 1 pi bond
* - Germanium - 4 valent, 2 sigma and 1 pi bond
* - Nitrogen - 3 valent, 1 sigma and 1 pi bond
* - Nitrogen cation - 4 valent, 2 sigma and 1 pi bond
*
*
* Examples of Para Stereocenters - inositol - has 9 stereo
* isomers, {@code O[C@H]1[C@H](O)[C@@H](O)[C@H](O)[C@H](O)[C@@H]1O
* myo-inositol}
- decalin - has 2 stereo isomers, {@code
* C1CC[C@H]2CCCC[C@H]2C1} (not currently identified)
- spiro/double-bond
* linked ring - {@code InChI=1/C14H24/c1-11-3-7-13(8-4-11)14-9-5-12(2)6-10-14/h11-12H,3-10H2,1-2H3/b14-13-/t11-,12-}
* (not currently identified)
- An example of a para-center not in a
* cycle {@code C[C@@H](O)[C@H](C)[C@H](C)O} (not currently identified)
*
*
* It should be noted that para-centers may not actually have a configuration. A
* simple example of this is seen that by changing the configuration of one
* center in {@code C[C@@H](O)[C@H:1](C)[C@H](C)O} removes the central
* configuration as the ligands are now equivalent {@code
* C[C@@H](O)[CH:1]](C)[C@@H](C)O}
*
* @author John May
* @cdk.module standard
* @cdk.githash
*/
public final class Stereocenters {
/** native CDK structure representation. */
private final IAtomContainer container;
/** adjacency list representation for fast traversal. */
private final int[][] g;
/** lookup bonds by the index of their atoms. */
private final EdgeToBondMap bondMap;
/** the type of stereo center - indexed by atom. */
private Stereocenter[] stereocenters;
/** the stereo elements - indexed by atom. */
private StereoElement[] elements;
/** basic cycle information (i.e. is atom/bond cyclic) and cycle systems. */
private final RingSearch ringSearch;
private int numStereoElements;
private boolean checkSymmetry = false;
/**
* Determine the stereocenter atoms in the provided container based on
* connectivity.
*
* {@code
* IAtomContainer container = ...;
* Stereocenters centers = Stereocenters.of(container);
* for (int i = 0; i < container.getAtomCount(); i++) {
* if (centers.isStereocenter(i)) {
*
* }
* }
* }
*
* @param container input container
* @return the stereocenters
*/
public static Stereocenters of(IAtomContainer container) {
EdgeToBondMap bondMap = EdgeToBondMap.withSpaceFor(container);
int[][] g = GraphUtil.toAdjList(container, bondMap);
Stereocenters stereocenters = new Stereocenters(container, g, bondMap);
stereocenters.checkSymmetry();
return stereocenters;
}
/**
* Create a perception method for the provided container, graph
* representation and bond map.
*
* @param container native CDK structure representation
* @param graph graph representation (adjacency list)
* @param bondMap fast lookup bonds by atom index
*/
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 Stereocenter[g.length];
this.numStereoElements = createElements();
}
void checkSymmetry() {
if (!checkSymmetry) {
checkSymmetry = true;
numStereoElements = createElements();
int[] symmetry = toIntArray(Canon.symmetry(container, g));
labelTrueCenters(symmetry);
labelIsolatedPara(symmetry);
}
}
/**
* Obtain the type of stereo element support for atom at index {@code v}.
* Supported elements types are:
*
* - {@link Type#Bicoordinate} - an central atom involved in a
* cumulated system (not yet supported)
- {@link Type#Tricoordinate}
* - an atom at one end of a geometric (double-bond) stereo bond or
* cumulated system.
- {@link Type#Tetracoordinate} - a tetrahedral
* atom (could also be square planar in future)
- {@link Type#None} -
* the atom is not a (supported) stereo element type.
*
* @param v atom index (vertex)
* @return the type of element
*/
public Type elementType(final int v) {
if (stereocenters[v] == Stereocenter.Non || elements[v] == null)
return Type.None;
else
return elements[v].type;
}
boolean isSymmetryChecked() {
return checkSymmetry;
}
/**
* Is the atom be a stereocenter (i.e. True or Para).
*
* @param v atom index (vertex)
* @return the atom at index {@code v} is a stereocenter
*/
public boolean isStereocenter(int v) {
return stereocenters[v] == Stereocenter.True || stereocenters[v] == Stereocenter.Para;
}
/**
* Determine the type of stereocenter is the atom at index {@code v}.
*
* - {@link Stereocenter#True} - the atom has constitutionally
* different neighbors
- {@link Stereocenter#Para} - the atom
* resembles a stereo centre but has constitutionally equivalent neighbors
* (e.g. inositol, decalin). The stereocenter depends on the configuration
* of one or more stereocenters.
- {@link Stereocenter#Potential} -
* the atom can supported stereo chemistry but has not be shown ot be a true
* or para center.
- {@link Stereocenter#Non} - the atom is not a
* stereocenter (e.g. methane).
*
* @param v atom index.
* @return the type of stereocenter
*/
public Stereocenter stereocenterType(int v) {
return stereocenters[v];
}
/**
* Create {@link org.openscience.cdk.stereo.Stereocenters.StereoElement}
* instance for atoms which support stereochemistry. Each new element is
* considered a potential stereo center - any atoms which have not been
* assigned an element are non stereo centers.
*
* @return the number of elements created
*/
private int createElements() {
boolean[] tricoordinate = new boolean[g.length];
int nElements = 0;
// all atoms we don't define as potential are considered
// non-stereogenic
Arrays.fill(stereocenters, Stereocenter.Non);
VERTICES: for (int i = 0; i < g.length; i++) {
// determine hydrogen count, connectivity and valence
int h = container.getAtom(i).getImplicitHydrogenCount();
int x = g[i].length + h;
int d = g[i].length;
int v = h;
if (x < 2 || x > 4 || h > 1) continue;
int piNeighbor = 0;
for (int w : g[i]) {
if (atomicNumber(container.getAtom(w)) == 1 &&
container.getAtom(w).getMassNumber() == null)
h++;
switch (bondMap.get(i, w).getOrder()) {
case SINGLE:
v++;
break;
case DOUBLE:
v += 2;
piNeighbor = w;
break;
default:
// triple, quadruple or unset? can't be a stereo centre
continue VERTICES;
}
}
// check the type of stereo chemistry supported
switch (supportedType(i, v, d, h, x)) {
case Bicoordinate:
stereocenters[i] = Stereocenter.Potential;
elements[i] = new Bicoordinate(i, g[i]);
nElements++;
int u = g[i][0];
int w = g[i][1];
if (tricoordinate[u]) {
stereocenters[u] = Stereocenter.Potential;
elements[u] = new Tricoordinate(u, i, g[u]);
}
if (tricoordinate[w]) {
stereocenters[w] = Stereocenter.Potential;
elements[w] = new Tricoordinate(w, i, g[w]);
}
break;
case Tricoordinate:
u = i;
w = piNeighbor;
tricoordinate[u] = true;
if (!tricoordinate[w]) {
if (elements[w] != null && elements[w].type == Type.Bicoordinate) {
stereocenters[u] = Stereocenter.Potential;
elements[u] = new Tricoordinate(u, w, g[u]);
}
continue;
}
stereocenters[w] = Stereocenter.Potential;
stereocenters[u] = Stereocenter.Potential;
elements[u] = new Tricoordinate(u, w, g[u]);
elements[w] = new Tricoordinate(w, u, g[w]);
nElements++;
break;
case Tetracoordinate:
elements[i] = new Tetracoordinate(i, g[i]);
stereocenters[i] = Stereocenter.Potential;
nElements++;
break;
default:
stereocenters[i] = Stereocenter.Non;
}
}
// link up tetracoordinate atoms accross cumulate systems
for (int v = 0; v < g.length; v++) {
if (elements[v] != null && elements[v].type == Type.Bicoordinate) {
int u = elements[v].neighbors[0];
int w = elements[v].neighbors[1];
if (elements[u] != null && elements[w] != null && elements[u].type == Type.Tricoordinate
&& elements[w].type == Type.Tricoordinate) {
((Tricoordinate) elements[u]).other = w;
((Tricoordinate) elements[w]).other = u;
}
}
}
return nElements;
}
/**
* Labels true stereocenters where all neighbors are constitutionally
* different. Potential stereocenters where all constitutionally equivalent
* neighbors are terminal (consider [C@H](C)(C)N) are also eliminated.
*
* @param symmetry symmetry classes of the atoms
*/
private void labelTrueCenters(final int[] symmetry) {
// auxiliary array, has the symmetry class already been 'visited'
boolean[] visited = new boolean[symmetry.length + 1];
for (int v = 0; v < g.length; v++) {
if (stereocenters[v] == Stereocenter.Potential) {
int[] ws = elements[v].neighbors;
int nUnique = 0;
boolean terminal = true;
for (final int w : ws) {
if (!visited[symmetry[w]]) {
visited[symmetry[w]] = true;
nUnique++;
} else {
// is symmetric neighbor non-terminal
if (g[w].length > 1) terminal = false;
}
}
// reset for testing next element
for (int w : ws)
visited[symmetry[w]] = false;
// neighbors are constitutionally different
if (nUnique == ws.length)
stereocenters[v] = Stereocenter.True;
// all the symmetric neighbors are terminal then 'v' can not
// be a stereocenter. There is an automorphism which inverts
// only this stereocenter
else if (terminal) stereocenters[v] = Stereocenter.Non;
}
}
}
/**
* Labels para stereocenter in isolated rings. Any elements which are now
* known to not be stereo centers are also eliminated.
*
* @param symmetry the symmetry classes of each atom
*/
private void labelIsolatedPara(int[] symmetry) {
// auxiliary array, has the symmetry class already been 'visited'
boolean[] visited = new boolean[symmetry.length + 1];
for (int[] isolated : ringSearch.isolated()) {
List potential = new ArrayList();
List trueCentres = new ArrayList();
BitSet cyclic = new BitSet();
for (int v : isolated) {
cyclic.set(v);
if (stereocenters[v] == Stereocenter.Potential)
potential.add(elements[v]);
else if (stereocenters[v] == Stereocenter.True) trueCentres.add(elements[v]);
}
// there is only 1 potential and 0 true stereocenters in this cycle
// the element is not a stereocenter
if (potential.size() + trueCentres.size() < 2) {
for (StereoElement element : potential)
stereocenters[element.focus] = Stereocenter.Non;
continue;
}
List paraElements = new ArrayList();
for (StereoElement element : potential) {
if (element.type == Type.Tetracoordinate) {
int[] ws = element.neighbors;
int nUnique = 0;
boolean terminal = true;
for (int w : ws) {
if (!cyclic.get(w)) {
if (!visited[symmetry[w]]) {
visited[symmetry[w]] = true;
nUnique++;
} else {
if (g[w].length > 1) terminal = false;
}
}
}
// reset for next element
for (int w : ws)
visited[symmetry[w]] = false;
int deg = g[element.focus].length;
if (deg == 3 || (deg == 4 && nUnique == 2)) paraElements.add(element);
// remove those we know cannot possibly be stereocenters
if (deg == 4 && nUnique == 1 && terminal) stereocenters[element.focus] = Stereocenter.Non;
} else if (element.type == Type.Tricoordinate) {
Tricoordinate either = (Tricoordinate) element;
if (stereocenters[either.other] == Stereocenter.True) paraElements.add(element);
}
}
if (paraElements.size() + trueCentres.size() >= 2)
for (StereoElement para : paraElements)
stereocenters[para.focus] = Stereocenter.Para;
else
for (StereoElement para : paraElements)
stereocenters[para.focus] = Stereocenter.Non;
}
}
/**
* Determine the type of stereo chemistry (if any) which could be supported
* by the atom at index 'i'. The rules used to define the types of
* stereochemistry are encoded from the InChI Technical Manual.
*
* @param i atom index
* @param v valence
* @param h hydrogen
* @param x connectivity
* @return type of stereo chemistry
*/
private Type supportedType(int i, int v, int d, int h, int x) {
IAtom atom = container.getAtom(i);
// the encoding a bit daunting and to be concise short variable names
// are used. these parameters make no distinction between implicit/
// explicit values and allow complete (and fast) characterisation of
// the type of stereo atom
//
// i: atom index
// v: valence (bond order sum)
// h: total hydrogen count
// x: connected atoms
// q: formal charge
int q = charge(atom);
// more than one hydrogen
if (checkSymmetry && h > 1)
return Type.None;
switch (atomicNumber(atom)) {
case 0: // stop the nulls on pseudo atoms messing up anything else
return Type.None;
case 5: // boron
return q == -1 && v == 4 && x == 4 ? Type.Tetracoordinate : Type.None;
case 6: // carbon
if (v != 4 || q != 0) return Type.None;
if (x == 2) return Type.Bicoordinate;
if (x == 3) return Type.Tricoordinate;
if (x == 4) return Type.Tetracoordinate;
return Type.None;
case 7: // nitrogen
if (x == 2 && v == 3 && d == 2 && q == 0)
return Type.Tricoordinate;
if (x == 3 && v == 4 && q == 1) return Type.Tricoordinate;
if (x == 4 && h == 0 && (q == 0 && v == 5 || q == 1 && v == 4))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
// note: bridgehead not allowed by InChI but makes sense
return x == 3 && h == 0 && (isBridgeHead(i) || inThreeMemberRing(i)) ? Type.Tetracoordinate : Type.None;
case 14: // silicon
if (v != 4 || q != 0) return Type.None;
if (x == 3) return Type.Tricoordinate;
if (x == 4) return Type.Tetracoordinate;
return Type.None;
case 15: // phosphorus
if (x == 4 && (q == 0 && v == 5 && h == 0 || q == 1 && v == 4))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
// note 3 valent phosphorus not documented as accepted
// by InChI tech manual but tests show it is
if (x == 3 && q == 0 && v == 3 && h == 0)
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
case 16: // sulphur
if (h > 0) return Type.None;
if (q == 0 && ((v == 4 && x == 3) || (v == 6 && x == 4)))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
if (q == 1 && ((v == 3 && x == 3) || (v == 5 && x == 4)))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
return Type.None;
case 32: // germanium
if (v != 4 || q != 0) return Type.None;
if (x == 3) return Type.Tricoordinate;
if (x == 4) return Type.Tetracoordinate;
return Type.None;
case 33: // arsenic
if (x == 4 && q == 1 && v == 4) return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
return Type.None;
case 34: // selenium
if (h > 0) return Type.None;
if (q == 0 && ((v == 4 && x == 3) || (v == 6 && x == 4)))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
if (q == 1 && ((v == 3 && x == 3) || (v == 5 && x == 4)))
return verifyTerminalHCount(i) ? Type.Tetracoordinate : Type.None;
return Type.None;
case 50: // tin
return q == 0 && v == 4 && x == 4 ? Type.Tetracoordinate : Type.None;
}
return Type.None;
}
/**
* Verify that there are is not 2 terminal heavy atoms (of the same element)
* which have a hydrogen count > 0. This follows the InChI specification
* that - An atom or positive ion N, P, As, S, or Se is not treated as
* stereogenic if it has (a) A terminal H atom neighbor or (b) At least two
* terminal neighbors, XHm and XHn, (n+m>0) connected by any kind of bond,
* where X is O, S, Se, Te, or N. This avoids the issue that under
* Cahn-Ingold-Prelog (or canonicalisation) the oxygens in 'P(=O)(O)(*)*'
* would not be found to be equivalent and a parity/winding would be
* assigned.
*
* @param v the vertex (atom index) to check
* @return the atom does not have > 2 terminal neighbors with a combined
* hydrogen count of > 0
*/
private boolean verifyTerminalHCount(int v) {
if (!checkSymmetry)
return true;
int[] counts = new int[6];
int[][] atoms = new int[6][g[v].length];
boolean found = false;
for (int w : g[v]) {
int idx = indexNeighbor(container.getAtom(w));
atoms[idx][counts[idx]++] = w;
found = found || (idx > 0 && counts[idx] > 1);
}
if (!found) return true;
for (int i = 1; i < counts.length; i++) {
if (counts[i] < 2) continue;
int terminalCount = 0;
int terminalHCount = 0;
for (int j = 0; j < counts[i]; j++) {
int hCount = 0;
int[] ws = g[atoms[i][j]];
for (int w : g[atoms[i][j]]) {
IAtom atom = container.getAtom(w);
if (atomicNumber(atom) == 1 && atom.getMassNumber() == null) {
hCount++;
}
}
// is terminal?
if (ws.length - hCount == 1) {
terminalCount++;
terminalHCount += hCount + container.getAtom(atoms[i][j]).getImplicitHydrogenCount();
}
}
if (terminalCount > 1 && terminalHCount > 0) return false;
}
return true;
}
/**
* Index the atom by element to a number between 0-5. This allows us to
* quickly count up neighbors we need to and the ignore those we don't
* (defaults to 0).
*
* @param atom an atom to get the element index of
* @return the element index
*/
private static int indexNeighbor(IAtom atom) {
switch (atomicNumber(atom)) {
case 7: // N
return 1;
case 8: // O
return 2;
case 16: // S
return 3;
case 34: // Se
return 4;
case 52: // Te
return 5;
default:
return 0;
}
}
/**
* Check if the {@code atom} at index {@code v} is a member of a small ring
* (n=3). This is the only time a 3 valent nitrogen is allowed by InChI to
* be potentially stereogenic.
*
* @param v atom index
* @return the atom is a member of a 3 member ring
*/
private boolean inThreeMemberRing(int v) {
BitSet adj = new BitSet();
for (int w : g[v])
adj.set(w);
// is a neighbors neighbor adjacent?
for (int w : g[v])
for (int u : g[w])
if (adj.get(u)) return true;
return false;
}
private void visitPart(boolean[] visit, IAtom atom) {
visit[container.indexOf(atom)] = true;
for (IBond bond : container.getConnectedBondsList(atom)) {
IAtom nbr = bond.getOther(atom);
if (!visit[container.indexOf(nbr)])
visitPart(visit, nbr);
}
}
/**
* Detects if a bond is a fused-ring bond (e.g. napthalene). A bond is a
* ring fusion if deleting it creates a new component. Or to put it another
* way if using a flood-fill we can't visit every atom without going through
* this bond, not we only check ring bonds.
*
* @param bond the bond
* @return the bond is a fused bond
*/
private boolean isFusedBond(IBond bond) {
IAtom beg = bond.getBegin();
IAtom end = bond.getEnd();
if (getRingDegree(container.indexOf(beg)) < 3 &&
getRingDegree(container.indexOf(end)) < 3)
return false;
boolean[] avisit = new boolean[container.getBondCount()];
avisit[container.indexOf(beg)] = true;
avisit[container.indexOf(end)] = true;
int count = 0;
for (IBond nbond : container.getConnectedBondsList(beg)) {
IAtom nbr = nbond.getOther(beg);
if (nbr.equals(end) || !ringSearch.cyclic(nbond))
continue;
if (count == 0) {
count++;
visitPart(avisit, nbr);
} else {
if (!avisit[container.indexOf(nbr)])
return true;
}
}
return false;
}
private int getRingDegree(int v) {
int x = 0;
for (int w : g[v])
if (ringSearch.cyclic(v, w))
x++;
return x;
}
private boolean isBridgeHead(int v) {
if (getRingDegree(v) < 3)
return false;
IAtom atom = container.getAtom(v);
for (IBond bond : container.getConnectedBondsList(atom))
if (isFusedBond(bond))
return false;
return true;
}
/**
* Safely obtain the atomic number of an atom. If the atom has undefined
* atomic number and is not a pseudo-atom it is considered an error. Pseudo
* atoms with undefined atomic number default to 0.
*
* @param a an atom
* @return the atomic number of the atom
*/
private static int atomicNumber(IAtom a) {
Integer elem = a.getAtomicNumber();
if (elem != null) return elem;
if (a instanceof IPseudoAtom) return 0;
throw new IllegalArgumentException("an atom had an undefieind atomic number");
}
/**
* Safely obtain the formal charge on an atom. If the atom had undefined
* formal charge it is considered as neutral (0).
*
* @param a an atom
* @return the formal charge
*/
private static int charge(IAtom a) {
Integer chg = a.getFormalCharge();
return chg != null ? chg : 0;
}
/**
* Convert an array of long (64-bit) values to an array of (32-bit)
* integrals.
*
* @param org the original array of values
* @return the array cast to int values
*/
private static int[] toIntArray(long[] org) {
int[] cpy = new int[org.length];
for (int i = 0; i < cpy.length; i++)
cpy[i] = (int) org[i];
return cpy;
}
/** Defines the type of a stereocenter. */
public enum Stereocenter {
/** Atom is a true stereo-centre. */
True,
/** Atom resembles a stereo-centre (para). */
Para,
/** Atom is a potential stereo-centre */
Potential,
/** Non stereo-centre. */
Non
}
public enum Type {
/** An atom within a cumulated system. (not yet supported) */
Bicoordinate,
/**
* A potentially stereogenic atom with 3 neighbors - one atom in a
* geometric centres or cumulated system (allene, cumulene).
*/
Tricoordinate,
/**
* A potentially stereogenic atom with 4 neighbors - tetrahedral
* centres.
*/
Tetracoordinate,
/** A non-stereogenic atom. */
None
}
/**
* Internal stereo element representation. We need to define the sides of a
* double bond separately and want to avoid reflection (instanceof) by using
* a type parameter. We also store the neighbors we need to check for
* equivalence directly.
*/
private static abstract class StereoElement {
int focus;
int[] neighbors;
Type type;
}
/** Represents a tetrahedral stereocenter with 2 neighbors. */
private static final class Bicoordinate extends StereoElement {
Bicoordinate(int v, int[] neighbors) {
this.focus = v;
this.type = Type.Bicoordinate;
this.neighbors = Arrays.copyOf(neighbors, neighbors.length);
}
}
/** Represents a tetrahedral stereocenter with 3 or 4 neighbors. */
private static final class Tetracoordinate extends StereoElement {
Tetracoordinate(int v, int[] neighbors) {
this.focus = v;
this.type = Type.Tetracoordinate;
this.neighbors = Arrays.copyOf(neighbors, neighbors.length);
}
}
/**
* Represents one end of a double bond. The element only stores non-double
* bonded neighbors and also indexes it's {@code other} end.
*/
private static final class Tricoordinate extends StereoElement {
int other;
/**
* Create a tri-coordinate atom for one end of a double bond. Two
* elements need to be created which reference each other.
*
* @param v the focus of this end
* @param w the double bonded other end of the element
* @param neighbors the neighbors of v
*/
Tricoordinate(int v, int w, int[] neighbors) {
this.focus = v;
this.other = w;
this.type = Type.Tricoordinate;
this.neighbors = new int[neighbors.length - 1];
int n = 0;
// remove the other neighbor from neighbors when checking
// equivalence
for (int i = 0; i < neighbors.length; i++) {
if (neighbors[i] != other) this.neighbors[n++] = neighbors[i];
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy