com.actelion.research.chem.Canonizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openchemlib Show documentation
Show all versions of openchemlib Show documentation
Open Source Chemistry Library
/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Thomas Sander
*/
// Restriction: - Although bond query features are encoded into the idcode, idcodes of
// fragments with bond query features are not necessarily unique.
// - Atom query features are(!) considered during atom priority assignment
// and therefore fragments with atom query features get perfectly unique
// idcode. The only exception are atoms with assigned atom lists, which
// are encoded into idcodes but may result in non-unique idcode.
package com.actelion.research.chem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Canonizer {
public static final int CREATE_SYMMETRY_RANK = 1;
// The following options CONSIDER_DIASTEREOTOPICITY, CONSIDER_ENANTIOTOPICITY
// and CONSIDER_STEREOHETEROTOPICITY have no influence on the idcode,
// i.e. the idcode is the same whether one of these options is used.
// However, if you require e.g. a pro-E atom always to appear
// before the pro-Z, e.g. because you can distinguish them from the
// encoded coordinates, then you need to use one of these options.
// Of course, pro-R or pro-S can only be assigned, if one of the bonds
// of the pro-chiral center is a stereo bond, which, of course,
// will be recognized as an over-specified stereo feature.
// Consider both diastereotopic and enantiotopic atoms as being different when creating the symmetry rank
public static final int CONSIDER_STEREOHETEROTOPICITY = 2;
// Consider custom atom labels for atom ranking and encode them into idcodes
public static final int ENCODE_ATOM_CUSTOM_LABELS = 8;
// Consider the atom selection for atom ranking and encode it into idcodes
public static final int ENCODE_ATOM_SELECTION = 16;
// Assign parities to tetrahedral nitrogen (useful in crystals or at low temp, when N inversion is frozen out)
public static final int ASSIGN_PARITIES_TO_TETRAHEDRAL_N = 32;
// Enforces creation of 3D-coordinate encoding even if all z-coords are 0.0
public static final int COORDS_ARE_3D = 64;
// The Canonizer normalizes pseudo stereo parities within any rigid fragments, where
// two representations of relative stereo features encode equal stereo configurations
// (e.g. cis-1,4-dimethyl-cyclohexane). If the CREATE_PSEUDO_STEREO_GROUPS bit is set,
// then one can use getPseudoTHGroups() and getPseudoEZGroups(), which return stereo
// feature group numbers that are shared among all related relative stereo features
// (tetrahedral and E/Z-bonds).
public static final int CREATE_PSEUDO_STEREO_GROUPS = 128;
// If two molecules are identical except for an inverted configuration of stereo centers
// within one OR group, then they receive the same idcode, because 'this or the other'
// and 'the other or this' are effectively the same. If we know, however, that we have
// both enantiomers, but cannot assign which is which, we may want to create different
// idcodes for either enantiomer. If the mode includes DISTINGUISH_RACEMIC_OR_GROUPS,
// then the normalization of tetrahedral stereo centers is skipped for OR groups retaining
// the given configuration within all OR groups.
public static final int DISTINGUISH_RACEMIC_OR_GROUPS = 256;
// If we have fragments instead of molecules, then unused valences are not meant to be
// filled by implicit hydrogen atoms. That means that atoms may have free valences.
// If two otherwise equivalent (symmetrical) atoms have
// free valences, then these may differ in the context of a super-structure match.
// A stereo center in the super-structure may not be a stereo center in the fragment alone.
// Same is true for stereo bonds. To discover all potential stereo features within a
// substructure fragment use more CONSIDER_FREE_VALENCES, which breaks the ties
// between equivalent atoms that have free valences.
public static final int TIE_BREAK_FREE_VALENCE_ATOMS = 512;
// ENCODE_ATOM_CUSTOM_LABELS (above) causes customs labels to encode into the idcode in
// a canonical way, i.e. the label is considered when ranking. Two otherwise symmetrical
// atoms are considered different, if one has a custom label, or both have different custom
// labels. The ENCODE_ATOM_CUSTOM_LABELS_WITHOUT_RANKING option does not consider such
// atoms being different. This option can be used to mark an atom witghout influencing
// ranking and graph generation. This is typically used for diagnostics.
public static final int ENCODE_ATOM_CUSTOM_LABELS_WITHOUT_RANKING = 1024;
public static final int NEGLECT_ANY_STEREO_INFORMATION = 2048;
protected static final int cIDCodeVersion2 = 8;
// productive version till May 2006 based on the molfile version 2
protected static final int cIDCodeVersion3 = 9;
// productive version since May 2006 based on the molfile version 3
// being compatible with MDL's "Enhanced Stereo Representation"
public static final int cIDCodeCurrentVersion = cIDCodeVersion3;
// New version numbers start with 9 because they are stored in the
// idcode's first 4 bits which were originally used to store the number
// of bits for atom indices which did never exceed the value 8.
// In addition to the four TH/EZ parities from the Molecule
// idcodes may contain four more types encoding the enhanced
// stereo representation modes AND and OR.
protected static final int cParity1And = 4;
protected static final int cParity2And = 5;
protected static final int cParity1Or = 6;
protected static final int cParity2Or = 7;
private static final int CALC_PARITY_MODE_PARITY = 1;
private static final int CALC_PARITY_MODE_PRO_PARITY = 2;
private static final int CALC_PARITY_MODE_IN_SAME_FRAGMENT = 3;
// public static final int mAtomBits = 16;
// public static final int MAX_ATOMS = 0xFFFF;
// public static final int MAX_BONDS = 0xFFFF;
private StereoMolecule mMol;
private int[] mCanRank;
private int[] mCanRankBeforeTieBreaking;
private int[] mPseudoTHGroup;
private int[] mPseudoEZGroup;
private byte[] mTHParity; // is tetrahedral parity based on atom symmetry ranks
private byte[] mEZParity; // is double bond parity based on atom symmetry ranks
private byte[] mTHConfiguration; // is tetrahedral parity based on atom indexes in canonical graph
private byte[] mEZConfiguration; // is double bond parity based on atom indexes in canonical graph
private byte[] mTHCIPParity;
private byte[] mEZCIPParity;
private byte[] mTHESRType;
private byte[] mTHESRGroup;
private byte[] mEZESRType;
private byte[] mEZESRGroup;
private byte[] mAbnormalValence;
private CanonizerBaseValue[] mCanBase;
private CanonizerMesoHelper mMesoHelper;
private boolean mIsMeso,mStereoCentersFound;
private boolean[] mIsStereoCenter; // based on extended stereo ranking, i.e. considering ESR type and group
private boolean[] mTHParityIsMesoInverted; // whether the atom's parity must be inverted in the idcode because of meso fragment parity normalization
private boolean[] mTHParityNeedsNormalization;
private boolean[] mTHESRTypeNeedsNormalization;
private boolean[] mTHParityRoundIsOdd;
private boolean[] mEZParityRoundIsOdd;
private boolean[] mTHParityIsPseudo;
private boolean[] mEZParityIsPseudo;
private boolean[] mProTHAtomsInSameFragment;
private boolean[] mProEZAtomsInSameFragment;
private boolean[] mNitrogenQualifiesForParity;
private ArrayList mFragmentList;
private ArrayList mTHParityNormalizationGroupList;
private int mMode,mNoOfRanks,mNoOfPseudoGroups;
private boolean mIsOddParityRound;
private boolean mZCoordinatesAvailable;
private boolean mCIPParityNoDistinctionProblem;
private boolean mEncodeAvoid127;
private boolean mGraphGenerated;
private int mGraphRings,mFeatureBlock;
private int[] mGraphAtom;
private int[] mGraphIndex;
private int[] mGraphBond;
private int[] mGraphFrom;
private int[] mGraphClosure;
private String mIDCode, mEncodedCoords,mMapping;
private StringBuilder mEncodingBuffer;
private int mEncodingBitsAvail,mEncodingTempData,mAtomBits,mMaxConnAtoms;
/**
* Runs a canonicalization procedure for the given molecule that creates unique atom ranks,
* which takes stereo features, ESR settings and query features into account.
* @param mol
*/
public Canonizer(StereoMolecule mol) {
this(mol, 0);
}
/**
* Runs a canonicalization procedure for the given molecule that creates unique atom ranks,
* which takes stereo features, ESR settings and query features into account.
* If mode includes ENCODE_ATOM_CUSTOM_LABELS, than custom atom labels are
* considered for the atom ranking and are encoded into the idcode.
* If mode includes COORDS_ARE_3D, then getEncodedCoordinates() always returns
* a 3D-encoding even if all z-coordinates are 0.0. Otherwise coordinates are
* encoded in 3D only, if at least one of the z-coords is not 0.0.
* @param mol
* @param mode 0 or one or more of CONSIDER...TOPICITY, CREATE..., ENCODE_ATOM_CUSTOM_LABELS, ASSIGN_PARITIES_TO_TETRAHEDRAL_N, COORDS_ARE_3D
*/
public Canonizer(StereoMolecule mol, int mode) {
// if (mol.getAllAtoms()>MAX_ATOMS)
// throw new IllegalArgumentException("Cannot canonize a molecule having more than "+MAX_ATOMS+" atoms");
// if (mol.getAllBonds()>MAX_BONDS)
// throw new IllegalArgumentException("Cannot canonize a molecule having more than "+MAX_BONDS+" bonds");
mMol = mol;
mMode = mode;
mMol.ensureHelperArrays(Molecule.cHelperRings);
mAtomBits = getNeededBits(mMol.getAtoms());
if ((mMode & NEGLECT_ANY_STEREO_INFORMATION) == 0)
canFindNitrogenQualifyingForParity();
mZCoordinatesAvailable = ((mode & COORDS_ARE_3D) != 0) || mMol.is3D();
if ((mMode & NEGLECT_ANY_STEREO_INFORMATION) == 0) {
mTHParity = new byte[mMol.getAtoms()];
mTHParityIsPseudo = new boolean[mMol.getAtoms()];
mTHParityRoundIsOdd = new boolean[mMol.getAtoms()];
mEZParity = new byte[mMol.getBonds()];
mEZParityRoundIsOdd = new boolean[mMol.getBonds()];
mEZParityIsPseudo = new boolean[mMol.getBonds()];
}
mCIPParityNoDistinctionProblem = false;
canInitializeRanking();
if ((mMode & NEGLECT_ANY_STEREO_INFORMATION) == 0)
canRankStereo();
canRankFinal();
// if (mCIPParityNoDistinctionProblem)
// System.out.println("No distinction applying CIP rules: "+getIDCode()+" "+getEncodedCoordinates());
}
public boolean hasCIPParityDistinctionProblem() {
return mCIPParityNoDistinctionProblem;
}
/**
* Locate those tetrahedral nitrogen atoms with at least 3 neighbors that
* qualify for tetrahedral parity calculation because:
* - being a quarternary nitrogen atom
* - being an aziridin nitrogen atom
* - the configuration inversion is hindered in a polycyclic structure
* - flag ASSIGN_PARITIES_TO_TETRAHEDRAL_N is set
*/
private void canFindNitrogenQualifyingForParity() {
mNitrogenQualifiesForParity = new boolean[mMol.getAtoms()];
for (int atom=0; atom implicitHigherValence)
valence = (byte)explicitAbnormalValence;
else
valence = (byte)implicitHigherValence;
}
else if (explicitAbnormalValence != -1) {
if (explicitAbnormalValence > newImplicitHigherValence
|| (explicitAbnormalValence < newImplicitHigherValence
&& explicitAbnormalValence >= mMol.getOccupiedValence(atom)))
valence = (byte)explicitAbnormalValence;
}
else if (!mMol.supportsImplicitHydrogen(atom)
&& mMol.getExplicitHydrogens(atom) != 0) {
valence = mMol.getOccupiedValence(atom);
valence -= mMol.getElectronValenceCorrection(atom, valence);
}
canSetAbnormalValence(atom, valence);
return valence;
}
private void canSetAbnormalValence(int atom, int valence) {
if (mAbnormalValence == null) {
mAbnormalValence = new byte[mMol.getAtoms()];
Arrays.fill(mAbnormalValence, (byte)-1);
}
mAbnormalValence[atom] = (byte)valence;
}
private void canRankStereo() {
// Store ranking state before considering stereo information
int noOfRanksWithoutStereo = mNoOfRanks;
int[] canRankWithoutStereo = Arrays.copyOf(mCanRank, mMol.getAtoms());
// Calculate the Cahn-Ingold-Prelog stereo assignments based
// on drawn stereo bonds neglecting any ESR group assignments
if (!mMol.isFragment()) {
canRecursivelyFindCIPParities();
//System.out.println("after CIP parity ranking");
// rollback stereo information
initializeParities(noOfRanksWithoutStereo, canRankWithoutStereo);
}
mTHESRType = new byte[mMol.getAtoms()];
mTHESRGroup = new byte[mMol.getAtoms()];
for (int atom=0; atom();
if (mMesoHelper != null)
mMesoHelper.normalizeESRGroupSwappingAndRemoval(mCanRank);
canMarkESRGroupsForParityNormalization();
// rollback stereo information
initializeParities(noOfRanksWithoutStereo, canRankWithoutStereo);
// Find real stereo features based on initial ranking and then
// in a loop check for stereo features that depend on the configuration
// of other stereo features already found and rank atoms again
canRecursivelyFindCanonizedParities();
//System.out.println("after parity ranking");
if (mMesoHelper != null)
mIsMeso = mMesoHelper.isMeso();
// at this point all real stereo features have been found
determineChirality(canRankWithoutStereo);
}
private void canRankFinal() {
// Atoms, which still share an equal rank, can now be considered symmetrical
// regarding connectivity and stereo features excluding stereo hetero-topicity.
if ((mMode & CREATE_SYMMETRY_RANK) != 0
&& (mMode & CONSIDER_STEREOHETEROTOPICITY) == 0) {
mCanRankBeforeTieBreaking = Arrays.copyOf(mCanRank, mMol.getAtoms());
}
if ((mMode & NEGLECT_ANY_STEREO_INFORMATION) == 0) {
// locate atom differences due to pro-chiral or pro-E/Z location and
// detect for every proTH- or proEZ-parity whether pro-atoms are
// in same fragment as the pro-chiral-center or double-bond, respectively
mProTHAtomsInSameFragment = new boolean[mMol.getAtoms()];
mProEZAtomsInSameFragment = new boolean[mMol.getBonds()];
// If we consider stereo information then we try in a reproducible way to distinguish symmetrical atoms
// considering enantio- or diastereo-topicity, i.e. we check for pro-R or -S atoms with hitherto equal ranks.
if (mNoOfRanks < mMol.getAtoms()) {
canBreakTiesByHeteroTopicity();
if ((mMode & NEGLECT_ANY_STEREO_INFORMATION) == 0) {
canNormalizeGroupParities();
if (mMesoHelper != null)
mMesoHelper.normalizeESRGroupSwappingAndRemoval(mCanRank);
}
}
}
// Atoms, which still share an equal rank now, can now be considered symmetrical
// regarding connectivity and stereo features including stereo hetero-topicity.
if (mCanRankBeforeTieBreaking == null
&& (mMode & CREATE_SYMMETRY_RANK) != 0
&& (mMode & CONSIDER_STEREOHETEROTOPICITY) != 0)
mCanRankBeforeTieBreaking = Arrays.copyOf(mCanRank, mMol.getAtoms());
// ############### begin tie breaking ##############
// i.e. if not all atoms have a different rank yet, then
// select randomly one atom of those atoms that share the
// lowest rank and consider it higher ranked. Propagate the
// new ranking asymmetry through the molecule (performFullRanking()).
// Repeat these steps until all atoms have a different rank.
//System.out.println("start of tie breaking");
/*
for (int atom=0; atom> Molecule.cAtomRadicalStateShift);
if (mMol.isFragment()) {
mCanBase[atom].add(Molecule.cAtomQFNoOfBits, mMol.getAtomQueryFeatures(atom));
if (mMol.getAtomList(atom) != null)
atomListFound = true;
}
}
mNoOfRanks = canPerformRanking();
// In very rare cases we need to consider the bond ring size (we neglect rings caused by metal ligand bonds)
if (mNoOfRanks < mMol.getAtoms()) {
for (int atom=0; atombondRingSize.length; i--)
mCanBase[atom].add(mAtomBits+5, 0);
for (int i=bondRingSize.length-1; i>=0; i--)
mCanBase[atom].add(mAtomBits+5, bondRingSize[i]);
}
mNoOfRanks = canPerformRanking();
}
if (atomListFound && mNoOfRanks < mMol.getAtoms()) {
for (int atom=0; atomlistLength; i--)
mCanBase[atom].add(8, 0);
for (int i=listLength-1; i>=0; i--)
mCanBase[atom].add(8, atomList[i]);
}
mNoOfRanks = canPerformRanking();
}
if (bondQueryFeaturesPresent && mNoOfRanks < mMol.getAtoms()) {
for (int atom=0; atom=mMol.getAllConnAtoms(atom)) {
bondQFList[index] = mCanRank[mMol.getConnAtom(atom, i)];
bondQFList[index] <<= Molecule.cBondQFNoOfBits;
bondQFList[index] |= mMol.getBondQueryFeatures(mMol.getConnBond(atom, i));
index++;
}
}
Arrays.sort(bondQFList);
for (int i=mMaxConnAtoms; i>bondQFList.length; i--)
mCanBase[atom].add(mAtomBits+Molecule.cBondQFNoOfBits, 0);
for (int i=bondQFList.length-1; i>=0; i--)
mCanBase[atom].add(mAtomBits+Molecule.cBondQFNoOfBits, bondQFList[i]);
}
mNoOfRanks = canPerformRanking();
}
if ((mMode & ENCODE_ATOM_CUSTOM_LABELS) != 0 && mNoOfRanks < mMol.getAtoms()) {
SortedStringList list = new SortedStringList();
for (int atom=0; atom0; rank--) {
int maxGroup = 0;
int[] maxAtomRank = null;
for (int group=0; group=0; i--) {
if (maxAtomRank[i] < atomRank[group][i]) {
maxAtomRank = atomRank[group];
maxGroup = group;
break;
}
}
}
}
}
groupRank[groupTypeIndex][maxGroup] = rank;
atomRank[maxGroup] = null;
}
}
return groupRank;
}
private boolean canFindParities(boolean doCIP) {
boolean ezFound = false;
for (int bond=0; bond 1) {
canEnsureFragments();
mNoOfPseudoGroups = 0;
for (CanonizerFragment f:mFragmentList) {
int pseudoParitiesInGroup = 0;
int pseudoParity1Or2InGroup = 0;
int highRankingTHAtom = 0;
int highRankingEZBond = 0;
int highTHAtomRank = -1;
int highEZBondRank = -1;
for (int i=0; i rank2) ? (rank1 << 16) + rank2 : (rank2 << 16) + rank1;
if (mEZParity[f.bond[i]] == Molecule.cBondParityEor1
|| mEZParity[f.bond[i]] == Molecule.cBondParityZor2) {
pseudoParity1Or2InGroup++;
pseudoParity1Or2Found = true;
if (highEZBondRank < higherRank) {
highEZBondRank = higherRank;
highRankingEZBond = f.bond[i];
}
}
}
}
if (pseudoParitiesInGroup == 0)
continue;
if (pseudoParitiesInGroup == 1) { // delete meaningless pseudo stereo feature single in fragment
for (int i=0; i();
int fragmentCount = 0;
int[] fragmentNo = new int[mMol.getAtoms()];
int[] fragmentAtom = new int[mMol.getAtoms()];
int[] fragmentBond = new int[mMol.getBonds()];
for (int atom=0; atom=mMol.getAllConnAtoms(atom)) {
int rank = 2 * mCanRank[mMol.getConnAtom(atom, i)];
int connBond = mMol.getConnBond(atom, i);
if (mMol.getBondOrder(connBond) == 2)
if (!mMol.isAromaticBond(connBond))
rank++; // set a flag for non-aromatic double bond
int j;
for (j = 0; j < neighbour; j++)
if (rank < connRank[j])
break;
for (int k = neighbour; k > j; k--)
connRank[k] = connRank[k - 1];
connRank[j] = rank;
neighbour++;
}
}
mCanBase[atom].init(atom);
mCanBase[atom].add(mAtomBits, mCanRank[atom]);
for (int i=neighbours; i 4)
return false;
// no carbenium
if (mMol.getAtomCharge(atom) > 0 && mMol.getAtomicNo(atom) == 6)
return false;
// no trivalent boron
if (mMol.getAtomicNo(atom) == 5 && mMol.getAllConnAtoms(atom) != 4)
return false;
if (mMol.isFragment()) { // don't calculate parities if atom or some neighbours are exclude groups
if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFExcludeGroup) != 0)
return false;
for (int i=0; i no stereo center
proTHAtom1 = mMol.getConnAtom(atom, remappedConn[i-1]);
proTHAtom2 = mMol.getConnAtom(atom, remappedConn[i]);
if (mode == CALC_PARITY_MODE_IN_SAME_FRAGMENT
&& mMol.isRingBond(mMol.getConnBond(atom,remappedConn[i])))
mProTHAtomsInSameFragment[atom] = true;
proTHAtomsFound = true;
}
}
if (mode != CALC_PARITY_MODE_PARITY && !proTHAtomsFound)
return false;
byte atomTHParity = (mZCoordinatesAvailable) ?
canCalcTHParity3D(atom, remappedConn)
: canCalcTHParity2D(atom, remappedConn);
if (mode == CALC_PARITY_MODE_PARITY) {
mTHParity[atom] = atomTHParity;
}
else if (mode == CALC_PARITY_MODE_PRO_PARITY) {
if (atomTHParity == Molecule.cAtomParity1) {
mCanBase[proTHAtom1].add(mCanRank[atom]);
}
else if (atomTHParity == Molecule.cAtomParity2) {
mCanBase[proTHAtom2].add(mCanRank[atom]);
}
}
return true;
}
private byte canCalcTHParity2D(int atom, int[] remappedConn) {
final int[][] up_down = { { 2,1,2,1 }, // direction of stereobond
{ 1,2,2,1 }, // for parity = 1
{ 1,1,2,2 }, // first dimension: order of
{ 2,1,1,2 }, // angles to connected atoms
{ 2,2,1,1 }, // second dimension: number of
{ 1,2,1,2 } };// mMol.getConnAtom that has stereobond
double[] angle = new double[mMol.getAllConnAtoms(atom)];
for (int i=0; i angle[2]) && (angle[1] - angle[2] > Math.PI)))
stereoType = 3 - stereoType;
break;
case 1:
if (angle[2] - angle[0] > Math.PI)
stereoType = 3 - stereoType;
break;
case 2:
if (angle[1] - angle[0] < Math.PI)
stereoType = 3 - stereoType;
break;
}
return (stereoType == 1) ? (byte)Molecule.cAtomParity2 : Molecule.cAtomParity1;
}
int order = 0;
if (angle[1] <= angle[2] && angle[2] <= angle[3]) order = 0;
else if (angle[1] <= angle[3] && angle[3] <= angle[2]) order = 1;
else if (angle[2] <= angle[1] && angle[1] <= angle[3]) order = 2;
else if (angle[2] <= angle[3] && angle[3] <= angle[1]) order = 3;
else if (angle[3] <= angle[1] && angle[1] <= angle[2]) order = 4;
else if (angle[3] <= angle[2] && angle[2] <= angle[1]) order = 5;
return (up_down[order][stereoBond] == stereoType) ?
(byte)Molecule.cAtomParity2 : Molecule.cAtomParity1;
}
private byte canCalcTHParity3D(int atom, int[] remappedConn) {
int[] atomList = new int[4];
for (int i=0; i 0.0) ? (byte)Molecule.cAtomParity1 : Molecule.cAtomParity2;
}
private boolean canCalcAlleneParity(int atom, int mode) {
if (mMol.getAtomicNo(atom) != 6
&& mMol.getAtomicNo(atom) != 7)
return false;
int atom1 = mMol.getConnAtom(atom,0);
int atom2 = mMol.getConnAtom(atom,1);
if (mMol.getAtomPi(atom1) != 1 || mMol.getAtomPi(atom2) != 1)
return false;
if (mMol.getConnAtoms(atom1) == 1 || mMol.getConnAtoms(atom2) == 1)
return false;
if ((mMol.getAllConnAtoms(atom1) > 3) || (mMol.getAllConnAtoms(atom2) > 3))
return false;
EZHalfParity halfParity1 = new EZHalfParity(mMol, mCanRank, atom, atom1);
if (halfParity1.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank,atom, atom2);
if (halfParity2.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (mode == CALC_PARITY_MODE_IN_SAME_FRAGMENT) {
if (halfParity1.mRanksEqual && halfParity1.mInSameFragment)
mProTHAtomsInSameFragment[atom] = true;
if (halfParity2.mRanksEqual && halfParity2.mInSameFragment)
mProTHAtomsInSameFragment[atom] = true;
}
byte alleneParity = mZCoordinatesAvailable ? canCalcAlleneParity3D(halfParity1, halfParity2)
: canCalcAlleneParity2D(halfParity1, halfParity2);
if (mode == CALC_PARITY_MODE_PARITY) { // increment mProParity[] for atoms that are Pro-Parity1
mTHParity[atom] = alleneParity;
}
else if (mode == CALC_PARITY_MODE_PRO_PARITY) {
if (halfParity1.mRanksEqual) {
if (alleneParity == Molecule.cAtomParity1) {
mCanBase[halfParity1.mHighConn].add(mCanRank[atom1]);
}
else {
mCanBase[halfParity1.mLowConn].add(mCanRank[atom1]);
}
}
if (halfParity2.mRanksEqual) {
if (alleneParity == Molecule.cAtomParity2) {
mCanBase[halfParity2.mHighConn].add(mCanRank[atom2]);
}
else {
mCanBase[halfParity2.mLowConn].add(mCanRank[atom2]);
}
}
}
return true;
}
private byte canCalcAlleneParity2D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
int hp1 = halfParity1.getValue();
int hp2 = halfParity2.getValue();
if (hp1 == -1 || hp2 == -1 || ((hp1 + hp2) & 1) == 0)
return Molecule.cAtomParityUnknown;
byte alleneParity = 0;
switch (hp1 + hp2) {
case 3:
case 7:
alleneParity = Molecule.cAtomParity2;
break;
case 5:
alleneParity = Molecule.cAtomParity1;
break;
}
return alleneParity;
}
private byte canCalcAlleneParity3D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
int[] atom = new int[4];
atom[0] = halfParity1.mHighConn;
atom[1] = halfParity1.mCentralAxialAtom;
atom[2] = halfParity2.mCentralAxialAtom;
atom[3] = halfParity2.mHighConn;
double torsion = mMol.calculateTorsion(atom);
// if the torsion is not significant (less than ~10 degrees) then return cAtomParityUnknown
if (Math.abs(torsion) < 0.3 || Math.abs(torsion) > Math.PI-0.3)
return Molecule.cAtomParityUnknown;
if (torsion < 0)
return Molecule.cAtomParity2;
else
return Molecule.cAtomParity1;
}
private boolean canCalcBINAPParity(int bond, int mode) {
if (!mMol.isBINAPChiralityBond(bond))
return false;
int atom1 = mMol.getBondAtom(0, bond);
int atom2 = mMol.getBondAtom(1, bond);
EZHalfParity halfParity1 = new EZHalfParity(mMol, mCanRank, atom1, atom2);
if (halfParity1.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank, atom2, atom1);
if (halfParity2.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (mode == CALC_PARITY_MODE_IN_SAME_FRAGMENT) {
if (halfParity1.mRanksEqual) // this is a hack, we should find a better solution considering other proparities as well
mProEZAtomsInSameFragment[bond] = hasSecondBINAPBond(atom2);
if (halfParity2.mRanksEqual)
mProEZAtomsInSameFragment[bond] = hasSecondBINAPBond(atom1);
}
byte axialParity = mZCoordinatesAvailable ? canCalcBINAPParity3D(halfParity1, halfParity2)
: canCalcBINAPParity2D(halfParity1, halfParity2);
if (mode == CALC_PARITY_MODE_PARITY) { // increment mProParity[] for atoms that are Pro-E
mEZParity[bond] = axialParity;
}
else if (mode == CALC_PARITY_MODE_PRO_PARITY) {
if (halfParity1.mRanksEqual) {
if (axialParity == Molecule.cBondParityZor2) {
mCanBase[halfParity1.mHighConn].add(mCanRank[atom2]);
}
else {
mCanBase[halfParity1.mLowConn].add(mCanRank[atom2]);
}
}
if (halfParity2.mRanksEqual) {
if (axialParity == Molecule.cBondParityZor2) {
mCanBase[halfParity2.mHighConn].add(mCanRank[atom1]);
}
else {
mCanBase[halfParity2.mLowConn].add(mCanRank[atom1]);
}
}
}
return true;
}
private byte canCalcBINAPParity2D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
int hp1 = halfParity1.getValue();
int hp2 = halfParity2.getValue();
if (hp1 == -1 || hp2 == -1 || ((hp1 + hp2) & 1) == 0)
return Molecule.cBondParityUnknown;
byte axialParity = 0;
switch (hp1 + hp2) {
case 3:
case 7:
axialParity = Molecule.cBondParityEor1;
break;
case 5:
axialParity = Molecule.cBondParityZor2;
break;
}
return axialParity;
}
private byte canCalcBINAPParity3D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
int[] atom = new int[4];
atom[0] = halfParity1.mHighConn;
atom[1] = halfParity1.mCentralAxialAtom;
atom[2] = halfParity2.mCentralAxialAtom;
atom[3] = halfParity2.mHighConn;
double torsion = mMol.calculateTorsion(atom);
// if the torsion is not significant (less than ~10 degrees) then return cBondParityUnknown
if (Math.abs(torsion) < 0.3 || Math.abs(torsion) > Math.PI-0.3)
return Molecule.cBondParityUnknown;
if (torsion < 0)
return Molecule.cBondParityEor1;
else
return Molecule.cBondParityZor2;
}
private boolean hasSecondBINAPBond(int atom) {
RingCollection ringSet = mMol.getRingSet();
for (int i=0; i 3) || (mMol.getConnAtoms(dbAtom2) > 3))
return false;
if (mMol.getAtomPi(dbAtom1) == 2 || mMol.getAtomPi(dbAtom2) == 2) // allene
return false;
EZHalfParity halfParity1 = new EZHalfParity(mMol, mCanRank, dbAtom2, dbAtom1);
if (halfParity1.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank, dbAtom1, dbAtom2);
if (halfParity2.mRanksEqual && mode == CALC_PARITY_MODE_PARITY)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (mode == CALC_PARITY_MODE_IN_SAME_FRAGMENT) {
if (halfParity1.mRanksEqual && halfParity1.mInSameFragment)
mProEZAtomsInSameFragment[bond] = true;
if (halfParity2.mRanksEqual && halfParity2.mInSameFragment)
mProEZAtomsInSameFragment[bond] = true;
}
byte bondDBParity = mMol.isBondParityUnknownOrNone(bond) ? Molecule.cBondParityUnknown
: (mZCoordinatesAvailable) ? canCalcEZParity3D(halfParity1, halfParity2)
: canCalcEZParity2D(halfParity1, halfParity2);
if (mode == CALC_PARITY_MODE_PARITY) {
mEZParity[bond] = bondDBParity;
}
else if (mode == CALC_PARITY_MODE_PRO_PARITY) {
if (halfParity1.mRanksEqual) {
if (bondDBParity == Molecule.cBondParityEor1) {
mCanBase[halfParity1.mHighConn].add(mCanRank[dbAtom1]);
}
else if (bondDBParity == Molecule.cBondParityZor2) {
mCanBase[halfParity1.mLowConn].add(mCanRank[dbAtom1]);
}
}
if (halfParity2.mRanksEqual) {
if (bondDBParity == Molecule.cBondParityEor1) {
mCanBase[halfParity2.mHighConn].add(mCanRank[dbAtom2]);
}
else if (bondDBParity == Molecule.cBondParityZor2) {
mCanBase[halfParity2.mLowConn].add(mCanRank[dbAtom2]);
}
}
}
return true;
}
private byte canCalcEZParity2D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
if (halfParity1.getValue() == -1 || halfParity2.getValue() == -1)
return Molecule.cBondParityUnknown;
if (((halfParity1.getValue() | halfParity2.getValue()) & 1) != 0)
return Molecule.cBondParityUnknown;
return (halfParity1.getValue() == halfParity2.getValue()) ?
(byte)Molecule.cBondParityEor1 : Molecule.cBondParityZor2;
}
private byte canCalcEZParity3D(EZHalfParity halfParity1, EZHalfParity halfParity2) {
double[] db = new double[3];
db[0] = mMol.getAtomX(halfParity2.mCentralAxialAtom) - mMol.getAtomX(halfParity1.mCentralAxialAtom);
db[1] = mMol.getAtomY(halfParity2.mCentralAxialAtom) - mMol.getAtomY(halfParity1.mCentralAxialAtom);
db[2] = mMol.getAtomZ(halfParity2.mCentralAxialAtom) - mMol.getAtomZ(halfParity1.mCentralAxialAtom);
double[] s1 = new double[3];
s1[0] = mMol.getAtomX(halfParity1.mHighConn) - mMol.getAtomX(halfParity1.mCentralAxialAtom);
s1[1] = mMol.getAtomY(halfParity1.mHighConn) - mMol.getAtomY(halfParity1.mCentralAxialAtom);
s1[2] = mMol.getAtomZ(halfParity1.mHighConn) - mMol.getAtomZ(halfParity1.mCentralAxialAtom);
double[] s2 = new double[3];
s2[0] = mMol.getAtomX(halfParity2.mHighConn) - mMol.getAtomX(halfParity2.mCentralAxialAtom);
s2[1] = mMol.getAtomY(halfParity2.mHighConn) - mMol.getAtomY(halfParity2.mCentralAxialAtom);
s2[2] = mMol.getAtomZ(halfParity2.mHighConn) - mMol.getAtomZ(halfParity2.mCentralAxialAtom);
// calculate the normal vector n1 of plane from db and s1 (vector product)
double[] n1 = new double[3];
n1[0] = db[1]*s1[2]-db[2]*s1[1];
n1[1] = db[2]*s1[0]-db[0]*s1[2];
n1[2] = db[0]*s1[1]-db[1]*s1[0];
// calculate the normal vector n2 of plane from db and n1 (vector product)
double[] n2 = new double[3];
n2[0] = db[1]*n1[2]-db[2]*n1[1];
n2[1] = db[2]*n1[0]-db[0]*n1[2];
n2[2] = db[0]*n1[1]-db[1]*n1[0];
// calculate cos(angle) of s1 and normal vector n2
double cosa = (s1[0]*n2[0]+s1[1]*n2[1]+s1[2]*n2[2])
/ (Math.sqrt(s1[0]*s1[0]+s1[1]*s1[1]+s1[2]*s1[2])
* Math.sqrt(n2[0]*n2[0]+n2[1]*n2[1]+n2[2]*n2[2]));
// calculate cos(angle) of s2 and normal vector n2
double cosb = (s2[0]*n2[0]+s2[1]*n2[1]+s2[2]*n2[2])
/ (Math.sqrt(s2[0]*s2[0]+s2[1]*s2[1]+s2[2]*s2[2])
* Math.sqrt(n2[0]*n2[0]+n2[1]*n2[1]+n2[2]*n2[2]));
return ((cosa < 0.0) ^ (cosb < 0.0)) ? (byte)Molecule.cBondParityEor1 : Molecule.cBondParityZor2;
}
private void flagStereoProblems() {
for (int atom=0; atom= mMol.getAtoms())
return false;
if (mTHParity[atom] == Molecule.cAtomParity1
|| mTHParity[atom] == Molecule.cAtomParity2)
return true;
if (mTHParity[atom] == Molecule.cAtomParityUnknown)
return false;
int binapBond = mMol.findBINAPChiralityBond(atom);
if (binapBond != -1)
return mEZParity[binapBond] == Molecule.cBondParityEor1
|| mEZParity[binapBond] == Molecule.cBondParityZor2;
for (int i=0; i mCanRank[startAtom])
startAtom = atom;
boolean[] atomHandled = new boolean[mMol.getAtoms()];
boolean[] bondHandled = new boolean[mMol.getBonds()];
mGraphIndex = new int[mMol.getAtoms()];
mGraphAtom = new int[mMol.getAtoms()];
mGraphFrom = new int[mMol.getAtoms()];
mGraphBond = new int[mMol.getBonds()];
mGraphAtom[0] = startAtom;
mGraphIndex[startAtom] = 0;
atomHandled[startAtom] = true;
int atomsWithoutParents = 1; // the startatom has no parent
int firstUnhandled = 0;
int firstUnused = 1;
int graphBonds = 0;
while (firstUnhandled < mMol.getAtoms()) {
if (firstUnhandled < firstUnused) { // attach neighbours in rank order to unhandled
while (true) {
int highestRankingConnAtom = 0;
int highestRankingConnBond = 0;
int highestRank = -1;
int atom = mGraphAtom[firstUnhandled];
for (int i=0; i=mMol.getAllConnAtoms(atom)) {
int connAtom = mMol.getConnAtom(atom, i);
if (!atomHandled[connAtom] && mCanRank[connAtom] > highestRank) {
highestRankingConnAtom = connAtom;
highestRankingConnBond = mMol.getConnBond(atom, i);
highestRank = mCanRank[connAtom];
}
}
}
if (highestRank == -1)
break;
mGraphIndex[highestRankingConnAtom] = firstUnused;
mGraphFrom[firstUnused] = firstUnhandled;
mGraphAtom[firstUnused++] = highestRankingConnAtom;
mGraphBond[graphBonds++] = highestRankingConnBond;
atomHandled[highestRankingConnAtom] = true;
bondHandled[highestRankingConnBond] = true;
}
firstUnhandled++;
}
else {
int highestRankingAtom = 0;
int highestRank = -1;
for (int atom=0; atom highestRank) {
highestRankingAtom = atom;
highestRank = mCanRank[atom];
}
}
atomsWithoutParents++;
mGraphIndex[highestRankingAtom] = firstUnused;
mGraphFrom[firstUnused] = -1; // no parent atom in graph tree
mGraphAtom[firstUnused++] = highestRankingAtom;
atomHandled[highestRankingAtom] = true;
}
}
mGraphClosure = new int[2 * (mMol.getBonds() - graphBonds)];
while (true) { // add ring closure bonds (those with lowest new atom numbers first)
int lowAtomNo1 = mMol.getMaxAtoms();
int lowAtomNo2 = mMol.getMaxAtoms();
int lowBond = -1;
for (int bond=0; bond mol.getBondAtom(1, i)) {
int temp = mol.getBondAtom(0, i);
mol.setBondAtom(0, i, mol.getBondAtom(1, i));
mol.setBondAtom(1, i, temp);
}
mol.setBondESR(i, mEZESRType[mGraphBond[i]], mEZESRGroup[mGraphBond[i]]);
}
if (includeExplicitHydrogen) {
for (int i=0; i 0) {
encodeFeatureNo(8); // 8 = datatype 'AtomList'
encodeBits(count, nbits);
for (int atom=0; atom> Molecule.cAtomRadicalStateShift, 2);
}
}
}
if (mMol.isFragment()) { // more QueryFeatures and fragment specific properties
addAtomQueryFeatures(22, nbits, Molecule.cAtomQFFlatNitrogen, 1, -1);
addBondQueryFeatures(23, nbits, Molecule.cBondQFMatchStereo, 1, -1);
addBondQueryFeatures(24, nbits,
Molecule.cBondQFAromState,
Molecule.cBondQFAromStateBits,
Molecule.cBondQFAromStateShift);
}
if ((mMode & ENCODE_ATOM_SELECTION) != 0) {
for (int atom=0; atom> qfShift, qfBits);
}
}
}
private void addBondQueryFeatures(int codeNo, int nbits, int qfMask, int qfBits, int qfShift) {
int count = 0;
for (int bond=0; bond> qfShift, qfBits);
}
}
}
private boolean[] getAromaticSPBonds() {
boolean[] isAromaticSPBond = null;
RingCollection ringSet = mMol.getRingSet();
for (int r=0; r mGraphAtom[ringBond[i]]) {
minValue = mGraphAtom[ringBond[i]];
minIndex = i;
}
}
while (count > 0) {
isAromaticSPBond[ringBond[minIndex]] = true;
minIndex = validateCyclicIndex(minIndex+2, ringAtom.length);
count -= 2;
}
}
else {
int index = 0;
while (hasTwoAromaticPiElectrons(ringAtom[index]))
index++;
while (!hasTwoAromaticPiElectrons(ringAtom[index]))
index = validateCyclicIndex(index+1, ringAtom.length);
while (count > 0) {
isAromaticSPBond[ringBond[index]] = true;
index = validateCyclicIndex(index+2, ringAtom.length);
count -= 2;
while (!hasTwoAromaticPiElectrons(ringAtom[index]))
index = validateCyclicIndex(index+1, ringAtom.length);
}
}
}
}
}
return isAromaticSPBond;
}
private boolean hasTwoAromaticPiElectrons(int atom) {
if (mMol.getAtomPi(atom) < 2)
return false;
if (mMol.getConnAtoms(atom) == 2)
return true;
int aromaticPi = 0;
for (int i=0; i 1;
}
private int validateCyclicIndex(int index, int limit) {
return (index < limit) ? index : index - limit;
}
public void invalidateCoordinates() {
mEncodedCoords = null;
}
/**
* Encodes the molecule's atom coordinates into a compact String. Together with the
* idcode the coordinate string can be passed to the IDCodeParser to recreate the
* original molecule including coordinates.
* If the molecule's coordinates are 2D, then coordinate encoding will be relative,
* i.e. scale and absolute positions get lost during the encoding.
* 3D-coordinates, however, are encoded retaining scale and absolute positions.
* If the molecule has 3D-coordinates and if there are no implicit hydrogen atoms,
* i.e. all hydrogen atoms are explicitly available with their coordinates, then
* hydrogen 3D-coordinates are also encoded despite the fact that the idcode itself does
* not contain hydrogen atoms, because it must be canonical.
* @return
*/
public String getEncodedCoordinates() {
return getEncodedCoordinates(mZCoordinatesAvailable);
}
/**
* Encodes the molecule's atom coordinates into a compact String. Together with the
* idcode the coordinate string can be passed to the IDCodeParser to recreate the
* original molecule including coordinates.
* If keepPositionAndScale==false, then coordinate encoding will be relative,
* i.e. scale and absolute positions get lost during the encoding.
* Otherwise the encoding retains scale and absolute positions.
* If the molecule has 3D-coordinates and if there are no implicit hydrogen atoms,
* i.e. all hydrogen atoms are explicitly available with their coordinates, then
* hydrogen 3D-coordinates are also encoded despite the fact that the idcode itself does
* not contain hydrogen atoms, because it must be canonical.
* @param keepPositionAndScale if false, then coordinates are scaled to an average bond length of 1.5 units
* @return
*/
public String getEncodedCoordinates(boolean keepPositionAndScale) {
if (mEncodedCoords == null) {
generateGraph();
encodeCoordinates(keepPositionAndScale, mMol.getAtomCoordinates());
}
return mEncodedCoords;
}
/**
* Encodes the molecule's atom coordinates into a compact String. Together with the
* idcode the coordinate string can be passed to the IDCodeParser to recreate the
* original molecule including coordinates.
* If keepPositionAndScale==false, then coordinate encoding will be relative,
* i.e. scale and absolute positions get lost during the encoding.
* Otherwise the encoding retains scale and absolute positions.
* If the molecule has 3D-coordinates and if there are no implicit hydrogen atoms,
* i.e. all hydrogen atoms are explicitly available with their coordinates, then
* hydrogen 3D-coordinates are also encoded despite the fact that the idcode itself does
* not contain hydrogen atoms, because it must be canonical.
* @param keepPositionAndScale if false, then coordinates are scaled to an average bond length of 1.5 units
* @param atomCoordinates external atom coordinate set for the same molecule, e.g. from a Conformer
* @return
*/
public String getEncodedCoordinates(boolean keepPositionAndScale, Coordinates[] atomCoordinates) {
if (mEncodedCoords == null) {
generateGraph();
encodeCoordinates(keepPositionAndScale, atomCoordinates);
}
return mEncodedCoords;
}
private void encodeCoordinates(boolean keepPositionAndScale, Coordinates[] coords) {
if (mMol.getAtoms() == 0) {
mEncodedCoords = "";
return;
}
// if we have 3D-coords and explicit hydrogens and if all hydrogens are explicit then encode hydrogen coordinates
boolean includeHydrogenCoordinates = false;
if (mZCoordinatesAvailable
&& mMol.getAllAtoms() > mMol.getAtoms()
&& !mMol.isFragment()) {
includeHydrogenCoordinates = true;
for (int i=0; i 1 && maxDelta == 0.0) {
mEncodedCoords = "";
return;
}
int binCount = (1 << resolutionBits);
double increment = maxDelta / (binCount / 2.0 - 1);
double maxDeltaPlusHalfIncrement = maxDelta + increment / 2.0;
for (int i=1; i mCanRank[neighbour[1]])
^(mGraphIndex[neighbour[0]] < mGraphIndex[neighbour[1]])))
inversion = !inversion;
}
}
else {
for (int i=1; i mCanRank[connAtom2])
inversion = !inversion;
if (mGraphIndex[connAtom1] < mGraphIndex[connAtom2])
inversion = !inversion;
}
}
}
mTHConfiguration[atom] = ((mTHParity[atom] == Molecule.cAtomParity1) ^ inversion) ?
(byte)Molecule.cAtomParity1 : Molecule.cAtomParity2;
}
else {
mTHConfiguration[atom] = mTHParity[atom];
}
}
mEZConfiguration = new byte[mMol.getBonds()];
for (int bond=0; bond mCanRank[neighbour[1]])
inversion = !inversion;
if (mGraphIndex[neighbour[0]] < mGraphIndex[neighbour[1]])
inversion = !inversion;
}
}
mEZConfiguration[bond] = ((mEZParity[bond] == Molecule.cBondParityEor1) ^ inversion) ?
(byte)Molecule.cBondParityEor1 : Molecule.cBondParityZor2;
}
else {
mEZConfiguration[bond] = mEZParity[bond];
}
}
}
private void idNormalizeESRGroupNumbers() {
idNormalizeESRGroupNumbers(Molecule.cESRTypeAnd);
idNormalizeESRGroupNumbers(Molecule.cESRTypeOr);
}
private void idNormalizeESRGroupNumbers(int type) {
int[] groupRank = new int[Molecule.cESRMaxGroups];
int groups = 0;
for (int atom=0; atom 15) {
encodeBits(1, 1); // more data to come
encodeBits(15, 4); // 15 = datatype 'start next feature set'
codeNo -= 16;
mFeatureBlock++;
}
encodeBits(1, 1); // more features to come
encodeBits(codeNo, 4);
}
private void encodeBits(long data, int bits) {
//System.out.println(bits+" bits:"+data+" mode="+mode);
while (bits != 0) {
if (mEncodingBitsAvail == 0) {
if (!mEncodeAvoid127 || mEncodingTempData != 63)
mEncodingTempData += 64;
mEncodingBuffer.append((char)mEncodingTempData);
mEncodingBitsAvail = 6;
mEncodingTempData = 0;
}
mEncodingTempData <<= 1;
mEncodingTempData |= (data & 1);
data >>= 1;
bits--;
mEncodingBitsAvail--;
}
}
private String encodeBitsEnd() {
mEncodingTempData <<= mEncodingBitsAvail;
if (!mEncodeAvoid127 || mEncodingTempData != 63)
mEncodingTempData += 64;
mEncodingBuffer.append((char)mEncodingTempData);
return mEncodingBuffer.toString();
}
/**
* @param maxNo highest possible index of some kind
* @return number of bits needed to represent numbers up to maxNo
*/
public static int getNeededBits(int maxNo) {
int bits = 0;
while (maxNo > 0) {
maxNo >>= 1;
bits++;
}
return bits;
}
/**
* Returns the absolute tetrahedral parity, which is based on priority ranks.
* @param atom
* @return one of the Molecule.cAtomParityXXX constants
*/
public int getTHParity(int atom) {
return mTHParity[atom];
}
/**
* Returns the atom's enhanced stereo representation type.
* @param atom
* @return one of the Molecule.cESRTypeXXX constants
*/
public int getTHESRType(int atom) {
return mTHESRType[atom];
}
/**
* @param atom
* @return whether this atom's TH-parity is pseudo
*/
public boolean isPseudoTHParity(int atom) {
return mTHParityIsPseudo[atom];
}
/**
* Returns the absolute bond parity, which is based on priority ranks.
* @param bond
* @return one of the Molecule.cBondParityXXX constants
*/
public int getEZParity(int bond) {
return mEZParity[bond];
}
/**
* Returns the bond's enhanced stereo representation type.
* @param bond
* @return one of the Molecule.cESRTypeXXX constants
*/
public int getEZESRType(int bond) {
return mEZESRType[bond];
}
/**
* @param bond
* @return whether this bond's EZ-parity is pseudo
*/
public boolean isPseudoEZParity(int bond) {
return mEZParityIsPseudo[bond];
}
/**
* If mMode includes CREATE_PSEUDO_STEREO_GROUPS, then this method returns
* the number of independent relative stereo feature groups. A relative stereo
* feature group always contains more than one pseudo stereo features (TH or EZ),
* which only in combination define a certain stereo configuration.
* @return
*/
public int getPseudoStereoGroupCount() {
return mNoOfPseudoGroups;
}
/**
* If mMode includes CREATE_PSEUDO_STEREO_GROUPS, then this method returns
* this bond's relative stereo feature group number provided this bond is a
* pseudo stereo bond, i.e. its stereo configuration only is relevant in
* combination with other pseudo stereo features.
* If this bond is not a pseudo stereo bond, then this method returns 0.
* @param bond
* @return
*/
public int getPseudoEZGroup(int bond) {
return mPseudoEZGroup[bond];
}
/**
* If mMode includes CREATE_PSEUDO_STEREO_GROUPS, then this method returns
* this atom's relative stereo feature group number provided this atom is a
* pseudo stereo center, i.e. its stereo configuration only is relevant in
* combination with other pseudo stereo features.
* If this atom is not a pseudo stereo center, then this method returns 0.
* @param atom
* @return
*/
public int getPseudoTHGroup(int atom) {
return mPseudoTHGroup[atom];
}
/**
* This normalizes all absolute tetrahedral-, allene- and atrop-parities within the molecule.
* This is done by finding the lowest atom rank that is shared by an odd number of
* atoms with determines parities, not counting unknown and none.
* If there number of parity2 atoms is higher than parity1 atoms of that rank, then
* all parities are inverted.
* You may call this method before creating the idcode from this Canonizer to convert
* internal parity information to the noermalized enantiomer. When calling getIDCode()
* afterwards, the idcode represents the normalized enantiomer. Stereo information of
* the underlying molecule is not touched.
* @return true, if all internal parities were inverted
*/
public boolean normalizeEnantiomer() {
int[] parityCount = new int[mNoOfRanks + 1];
for (int atom=0; atom mCanRank[neighbour[1]])
^(neighbour[0] < neighbour[1])))
inversion = !inversion;
}
}
else {
for (int i=1; i mCanRank[connAtom2])
inversion = !inversion;
if (connAtom1 < connAtom2)
inversion = !inversion;
}
}
}
mMol.setAtomParity(atom, ((mTHParity[atom] == Molecule.cAtomParity1) ^ inversion) ?
Molecule.cAtomParity1 : Molecule.cAtomParity2,
mTHParityIsPseudo[atom]);
}
else {
mMol.setAtomParity(atom, mTHParity[atom], mTHParityIsPseudo[atom]);
}
}
for (int bond=0; bond mCanRank[neighbour[1]])
inversion = !inversion;
if (neighbour[0] < neighbour[1])
inversion = !inversion;
}
}
mMol.setBondParity(bond, ((mEZParity[bond] == Molecule.cBondParityEor1) ^ inversion) ?
Molecule.cBondParityEor1 : Molecule.cBondParityZor2,
mEZParityIsPseudo[bond]);
}
else {
mMol.setBondParity(bond, mEZParity[bond], mEZParityIsPseudo[bond]);
}
}
}
protected void setStereoCenters() {
for (int atom=0; atom mCanRank[connAtom[1]])
^ cipComparePriority(alleneAtom,connAtom[0],connAtom[1]))
invertedOrder = !invertedOrder;
}
}
}
catch (Exception e) {
mTHCIPParity[atom] = Molecule.cAtomCIPParityProblem; // to indicate assignment problem
return;
}
}
else {
int[] cipConnAtom;
try {
cipConnAtom = cipGetOrderedConns(atom);
}
catch (Exception e) {
mTHCIPParity[atom] = Molecule.cAtomCIPParityProblem; // to indicate assignment problem
return;
}
for (int i=1; i mCanRank[connAtom[1]])
^ cipComparePriority(bondAtom,connAtom[0],connAtom[1]))
invertedOrder = !invertedOrder;
}
}
}
catch (Exception e) {
mEZCIPParity[bond] = Molecule.cBondCIPParityProblem; // to indicate assignment problem
return;
}
if ((mEZParity[bond] == Molecule.cBondParityEor1) ^ invertedOrder)
mEZCIPParity[bond] = Molecule.cBondCIPParityEorP;
else
mEZCIPParity[bond] = Molecule.cBondCIPParityZorM;
}
}
private int[] cipGetOrderedConns(int atom) throws Exception {
int noOfConns = mMol.getAllConnAtoms(atom);
int[] orderedConn = new int[noOfConns];
for (int i=0; i1; i--) {
boolean found = false;
for (int j=1; j mMol.getAtomicNo(atom2));
if (mMol.getAtomMass(atom1) != mMol.getAtomMass(atom2)) {
int mass1 = mMol.isNaturalAbundance(atom1) ? Molecule.cRoundedMass[mMol.getAtomicNo(atom1)] : mMol.getAtomMass(atom1);
int mass2 = mMol.isNaturalAbundance(atom2) ? Molecule.cRoundedMass[mMol.getAtomicNo(atom2)] : mMol.getAtomMass(atom2);
return (mass1 > mass2);
}
int graphSize = mMol.getAtoms();
int[] graphAtom = new int[graphSize];
int[] graphParent = new int[graphSize];
int[] graphRank = new int[graphSize];
boolean[] graphIsPseudo = new boolean[graphSize];
boolean[] atomUsed = new boolean[mMol.getAllAtoms()];
graphAtom[0] = rootAtom;
graphAtom[1] = atom1;
graphAtom[2] = atom2;
graphParent[0] = -1;
graphParent[1] = 0;
graphParent[2] = 0;
atomUsed[rootAtom] = true;
atomUsed[atom1] = true;
atomUsed[atom2] = true;
int current = 1;
int highest = 2;
int[] levelStart = new int[64];
levelStart[1] = 1;
levelStart[2] = 3;
int currentLevel = 2;
while (current <= highest) {
while (current < levelStart[currentLevel]) {
int currentAtom = graphAtom[current];
// do not consider neighbours of pseudo atoms
if (!graphIsPseudo[current]) {
int delocalizedBondCount = 0;
int delocalizedMeanAtomicNo = 0;
for (int i=0; i= graphSize) {
graphSize += mMol.getAtoms();
graphAtom = resize(graphAtom, graphSize);
graphParent = resize(graphParent, graphSize);
graphRank = resize(graphRank, graphSize);
graphIsPseudo = resize(graphIsPseudo, graphSize);
}
if (mMol.isDelocalizedBond(mMol.getConnBond(currentAtom, i))) {
// if candidate is part of a delocalized ring, than we need to add
// one pseudo atom with mean atomic no between all delocalized neighbors
delocalizedBondCount++;
delocalizedMeanAtomicNo += mMol.getAtomicNo(candidate);
}
else if (candidate != rootAtom) { // treat double bond at rootAtom stereo center as single bond
// add pseudo atoms for double and triple bonds
for (int j=1; j> ");
for (int i=levelStart[currentLevel]; i> ");
for (int i=levelStart[currentLevel]; igraphRank[2])?" > ":" < ")+"atom"+atom2);
*/
// check whether both substituents are now recognized to be different
if (graphRank[1] != graphRank[2])
return (graphRank[1] > graphRank[2]);
// generate relative ranking for current level by adding all relative
// ranks of parent levels with higher significance the higher a parent
// is in the tree.
if (currentLevel > 1)
//{
cipCompileRelativeRanks(graphRank, graphParent, levelStart, currentLevel);
/*
System.out.print(" graphRank2:");
for (int i=0; i> ");
for (int i=levelStart[currentLevel]; i graphRank[2]);
}
// consider E/Z configuration differences
Arrays.fill(cipRank, 0);
boolean ezDataFound = false;
for (int bond=0; bond graphRank[2]);
// TODO consider relative configuration differences RR/SS higher than RS/SR etc.
// consider R/S configuration differences
Arrays.fill(cipRank, 0);
boolean rsDataFound = false;
for (int atom=0; atom graphRank[2]);
mCIPParityNoDistinctionProblem = true;
throw new Exception("no distinction applying CIP rules");
}
private boolean cipTryDistinguishBranches(boolean[] graphIsPseudo,
int[] graphRank,
int[] graphParent,
int[] graphAtom,
int[] cipRank,
int[] levelStart,
int currentLevel) {
for (int level=1; level> ");
for (int i=levelStart[level]; i> ");
for (int i=levelStart[level]; igraphRank[2])?" > ":" < ")+"atom"+graphAtom[2]);
*/
if (graphRank[1] != graphRank[2])
return true;
if (level > 1)
//{
cipCompileRelativeRanks(graphRank, graphParent, levelStart, level);
/*
System.out.print(" graphRank2:");
for (int i=0; i> ");
for (int i=levelStart[level]; i1; level--) {
int parentCount = levelStart[level] - levelStart[level-1];
RankObject[] rankObject = new RankObject[parentCount];
int baseIndex = levelStart[level];
for (int parent=0; parent comparator = new Comparator() {
public int compare(RankObject r1, RankObject r2) {
if (r1.parentRank != r2.parentRank)
return (r1.parentRank > r2.parentRank) ? 1 : -1;
int i1 = r1.childRank.length;
int i2 = r2.childRank.length;
int count = Math.min(i1, i2);
for (int i=0; i r2.childRank[i2]) ? 1 : -1;
}
if (i1 != i2)
return (i1 > i2) ? 1 : -1;
if (r1.parentHCount != r2.parentHCount)
return (r1.parentHCount > r2.parentHCount) ? 1 : -1;
return 0;
}
};
Arrays.sort(rankObject, comparator);
int consolidatedRank = 1;
for (int parent=0; parent comparator = new Comparator() {
public int compare(RankObject r1, RankObject r2) {
if (r1.rank != r2.rank)
return (r1.rank > r2.rank) ? 1 : -1;
return 0;
}
};
for (int level=currentLevel; level>1; level--) {
for (int i=0; i {
int[] atomList;
int[] rankList;
protected ESRGroup(int type, int group) {
int count = 0;
for (int atom=0; atom Math.PI - 0.05) && (angleDif < Math.PI + 0.05)) {
mValue = -1; // less than 3 degrees different from double bond
return mValue; // is counted as non-stereo-specified double bond
}
mValue = (angleDif < Math.PI) ? 4 : 2;
return mValue;
}
else {
double angleOther = mMol.getBondAngle(mCentralAxialAtom,mLowConn);
if (angleOther < angleDB)
angleOther += Math.PI*2;
mValue = (angleOther < angleHigh) ? 2 : 4;
return mValue;
}
}
}
class CanonizerBond implements Comparable {
int maxAtomRank,minAtomRank,bond;
protected CanonizerBond(int atomRank1, int atomRank2, int bond) {
maxAtomRank = Math.max(atomRank1, atomRank2);
minAtomRank = Math.min(atomRank1, atomRank2);
this.bond = bond;
}
public int compareTo(CanonizerBond cb) {
if (maxAtomRank != cb.maxAtomRank)
return maxAtomRank > cb.maxAtomRank ? -1 : 1;
if (minAtomRank != cb.minAtomRank)
return minAtomRank > cb.minAtomRank ? -1 : 1; // we want high ranks first
return 0;
}
}
class CanonizerFragment {
int[] atom;
int[] bond;
protected CanonizerFragment(int[] atom, int atoms, int[] bond, int bonds) {
this.atom = Arrays.copyOf(atom, atoms);
this.bond = Arrays.copyOf(bond, bonds);
}
}
class CanonizerParity implements Comparable {
int atom;
int group;
int rank;
protected CanonizerParity(int atom, int group, int type, int rank) {
this.atom = atom;
this.group = group + (type << 8);
this.rank = rank;
}
public int compareTo(CanonizerParity p) {
if (group != p.group)
return group < p.group ? -1 : 1;
if (rank != p.rank)
return rank > p.rank ? -1 : 1; // we want high ranks first
return 0;
}
}