org.openscience.cdk.hash.stereo.GeometricTetrahedralEncoderFactory Maven / Gradle / Ivy
/*
* Copyright (c) 2013 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.hash.stereo;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IBond;
import javax.vecmath.Point2d;
import javax.vecmath.Point3d;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A stereo encoder factory for tetrahedral centres. This factory generates
* {@link StereoEncoder}s for centres with specified by 2D and 3D coordinates.
* The required preconditions are the central atom must have 3/4 neighboring
* atoms, Sp3 hybridization and no query bonds (e.g. wiggly). If there is at
* least one up/down bond and all required atoms have coordinates a new 2D
* encoder is created. If the there are no stereo bonds (up/down) and all
* required atoms have 3D coordinates then a new 3D encoder is created.
*
* @author John May
* @cdk.module hash
* @cdk.githash
*/
public class GeometricTetrahedralEncoderFactory implements StereoEncoderFactory {
/**
* Create a stereo encoder for all potential 2D and 3D tetrahedral
* elements.
*
* @param container an atom container
* @param graph adjacency list representation of the container
* @return a new encoder for tetrahedral elements
*/
@Override
public StereoEncoder create(IAtomContainer container, int[][] graph) {
// XXX: this code isn't pretty, the current IAtomContainer
// implementations are slow for the queries (i.e. looking at connected
// atoms/bonds) we need to ask to decide if something is a potential
// tetrahedral centre. We can help out a little with the adjacency list
// (int[][]) but this doesn't help with the bonds.
int n = container.getAtomCount();
List encoders = new ArrayList();
Map elevation = new HashMap(10);
ATOMS: for (int i = 0; i < n; i++) {
int degree = graph[i].length;
// ignore those which don't have 3 or 4 neighbors
if (degree < 3 || degree > 4) continue;
IAtom atom = container.getAtom(i);
// only create encoders for SP3 hybridized atom. atom typing is
// currently wrong for some atoms, in sulfoxide for example the atom
// type sets SP2... but there we don't to fuss about with that here
if (!sp3(atom)) continue;
// avoid nitrogen-inversion
if (Integer.valueOf(7).equals(atom.getAtomicNumber()) && degree == 3) continue;
// TODO: we could be more strict with our selection, InChI uses C,
// Si, Ge, P, As, B, Sn, N, P, S, Se but has preconditions for
// certain cases. An atom or ion N, P, As, S or Se is not stereogenic
// if it has a terminal H or two terminal neighbors -XHm, -XHn (n+m>0)
// where X is O, S, Se, Te, or N
// XXX: likely bottle neck
List bonds = container.getConnectedBondsList(atom);
// try to create geometric parity
GeometricParity geometric = geometric(elevation, bonds, i, graph[i], container);
if (geometric != null) {
// add a new encoder if a geometric parity
encoders.add(new GeometryEncoder(i, new BasicPermutationParity(graph[i]), geometric));
}
}
// no encoders, replace with the empty encoder
return encoders.isEmpty() ? StereoEncoder.EMPTY : new MultiStereoEncoder(encoders);
}
/**
* Create the geometric part of an encoder
*
* @param elevationMap temporary map to store the bond elevations (2D)
* @param bonds list of bonds connected to the atom at i
* @param i the central atom (index)
* @param adjacent adjacent atoms (indices)
* @param container container
* @return geometric parity encoder (or null)
*/
private static GeometricParity geometric(Map elevationMap, List bonds, int i,
int[] adjacent, IAtomContainer container) {
int nStereoBonds = nStereoBonds(bonds);
if (nStereoBonds > 0)
return geometric2D(elevationMap, bonds, i, adjacent, container);
else if (nStereoBonds == 0) return geometric3D(i, adjacent, container);
return null;
}
/**
* Create the geometric part of an encoder of 2D configurations
*
* @param elevationMap temporary map to store the bond elevations (2D)
* @param bonds list of bonds connected to the atom at i
* @param i the central atom (index)
* @param adjacent adjacent atoms (indices)
* @param container container
* @return geometric parity encoder (or null)
*/
private static GeometricParity geometric2D(Map elevationMap, List bonds, int i,
int[] adjacent, IAtomContainer container) {
IAtom atom = container.getAtom(i);
// create map of the atoms and their elevation from the center,
makeElevationMap(atom, bonds, elevationMap);
Point2d[] coordinates = new Point2d[4];
int[] elevations = new int[4];
// set the forth ligand to centre as default (overwritten if
// we have 4 neighbors)
if (atom.getPoint2d() != null)
coordinates[3] = atom.getPoint2d();
else
return null;
for (int j = 0; j < adjacent.length; j++) {
IAtom neighbor = container.getAtom(adjacent[j]);
elevations[j] = elevationMap.get(neighbor);
if (neighbor.getPoint2d() != null)
coordinates[j] = neighbor.getPoint2d();
else
return null; // skip to next atom
}
return new Tetrahedral2DParity(coordinates, elevations);
}
/**
* Create the geometric part of an encoder of 3D configurations
*
* @param i the central atom (index)
* @param adjacent adjacent atoms (indices)
* @param container container
* @return geometric parity encoder (or null)
*/
private static GeometricParity geometric3D(int i, int[] adjacent, IAtomContainer container) {
IAtom atom = container.getAtom(i);
Point3d[] coordinates = new Point3d[4];
// set the forth ligand to centre as default (overwritten if
// we have 4 neighbors)
if (atom.getPoint3d() != null)
coordinates[3] = atom.getPoint3d();
else
return null;
// for each neighboring atom check if we have 3D coordinates
for (int j = 0; j < adjacent.length; j++) {
IAtom neighbor = container.getAtom(adjacent[j]);
if (neighbor.getPoint3d() != null)
coordinates[j] = neighbor.getPoint3d();
else
return null; // skip to next atom
}
// add new 3D stereo encoder
return new Tetrahedral3DParity(coordinates);
}
/**
* check whether the atom is Sp3 hybridization
*
* @param atom an atom
* @return whether the atom is Sp3
*/
private static boolean sp3(IAtom atom) {
return IAtomType.Hybridization.SP3.equals(atom.getHybridization());
}
/**
* access the number of stereo bonds in the provided bond list.
*
* @param bonds input list
* @return number of UP/DOWN bonds in the list, -1 if a query bond was
* found
*/
private static int nStereoBonds(List bonds) {
int count = 0;
for (IBond bond : bonds) {
IBond.Stereo stereo = bond.getStereo();
switch (stereo) {
// query bonds... no configuration possible
case E_OR_Z:
case UP_OR_DOWN:
case UP_OR_DOWN_INVERTED:
return -1;
case UP:
case DOWN:
case UP_INVERTED:
case DOWN_INVERTED:
count++;
break;
}
}
return count;
}
/**
* Maps the input bonds to a map of Atom->Elevation where the elevation is
* whether the bond is off the plane with respect to the central atom.
*
* @param atom central atom
* @param bonds bonds connected to the central atom
* @param map map to load with elevation values (can be reused)
*/
private static void makeElevationMap(IAtom atom, List bonds, Map map) {
map.clear();
for (IBond bond : bonds) {
int elevation = 0;
switch (bond.getStereo()) {
case UP:
case DOWN_INVERTED:
elevation = +1;
break;
case DOWN:
case UP_INVERTED:
elevation = -1;
break;
}
// change elevation depending on which end of the wedge/hatch
// the atom is on
if (bond.getAtom(0).equals(atom)) {
map.put(bond.getAtom(1), elevation);
} else {
map.put(bond.getAtom(0), -1 * elevation);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy