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 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.
*
*/
// 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.*;
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 or not 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 diastereotopic/enantiotopic atoms uniquely for atom ranking
public static final int CONSIDER_DIASTEREOTOPICITY = 2;
public static final int CONSIDER_ENANTIOTOPICITY = 4;
// Consider both diastereotopic and enantiotopic atoms uniquely for atom ranking
public static final int CONSIDER_STEREOHETEROTOPICITY = CONSIDER_DIASTEREOTOPICITY | CONSIDER_ENANTIOTOPICITY;
// 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;
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;
public static final int ATOM_BITS = 16;
public static final int MAX_ATOMS = 0xFFFF;
public static final int MAX_BONDS = 0xFFFF;
private ExtendedMolecule mMol;
private int[] mCanRank;
private int[] mCanRankBeforeTieBreaking;
private byte[] mTHParity;
private byte[] mEZParity;
private byte[] mTHConfiguration; // is tetrahedral parity based on atom numbers in graph
private byte[] mEZConfiguration; // is double bond parity based on atom numbers in 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[] 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;
private boolean mIsOddParityRound;
private boolean mZCoordinatesAvailable;
private boolean mCIPParityNoDistinctionProblem;
private boolean mGraphGenerated;
private int mGraphRings;
private int[] mGraphAtom;
private int[] mGraphIndex;
private int[] mGraphBond;
private int[] mGraphFrom;
private int[] mGraphClosure;
private String mIDCode,mCoordinates,mMapping;
private StringBuilder mEncodingBuffer;
private int mEncodingBitsAvail,mEncodingTempData,mMaxConnAtoms;
/**
* Runs a canonicalization process molecule creating a unique atom ranking
* taking stereo features, ESR settings and query features into account.
* @param mol
*/
public Canonizer(ExtendedMolecule mol) {
this(mol, 0);
}
/**
* Runs a canonicalization process molecule creating a unique atom ranking
* taking stereo features, ESR settings and query features into account.
* If mode includes ENCODE_ATOM_CUSTOM_LABELS, than custom atom labels are
* used for atom ranking and are encoded into the idcode.
*
* @param mol
* @param mode 0 or one or more of CONSIDER...TOPICITY, CREATE_SYMMETRY_RANK, ENCODE_ATOM_CUSTOM_LABELS, ASSIGN_PARITIES_TO_TETRAHEDRAL_N
*/
public Canonizer(ExtendedMolecule 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);
canFindNitrogenQualifyingForParity();
for (int atom=0; atom
* - they are quarternary nitrogen atoms
* or - their configuration inversion is hindered in a polycyclic structure
* or - flag ASSIGN_PARITIES_TO_TETRAHEDRAL_N is set
*/
private void canFindNitrogenQualifyingForParity() {
mNitrogenQualifiesForParity = new boolean[mMol.getAtoms()];
for (int atom=0; atom 7)
continue;
RingCollection ringSet = mMol.getRingSet();
int smallRingNo = 0;
while (smallRingNo= 3) {
boolean isAdamantane = false;
int[] ringAtom = ringSet.getRingAtoms(smallRingNo);
for (int i=0; i<6; i++) {
if (atom == ringAtom[i]) {
int potentialOtherBridgeHeadIndex = ringSet.validateMemberIndex(smallRingNo,
(bridgeHead == ringAtom[ringSet.validateMemberIndex(smallRingNo, i+2)]) ? i-2 : i+2);
int potentialOtherBridgeHead = ringAtom[potentialOtherBridgeHeadIndex];
if (mMol.getAtomRingBondCount(potentialOtherBridgeHead) >= 3
&& mMol.getPathLength(pathAtom[1], potentialOtherBridgeHead, 2, null) == 2)
isAdamantane = true;
break;
}
}
if (isAdamantane) {
mNitrogenQualifiesForParity[atom] = true;
continue;
}
}
}
boolean bridgeHeadIsFlat = (mMol.getAtomPi(bridgeHead) == 1
|| mMol.isAromaticAtom(bridgeHead)
|| mMol.isFlatNitrogen(bridgeHead));
boolean bridgeHeadMayInvert = !bridgeHeadIsFlat
&& mMol.getAtomicNo(bridgeHead) == 7
&& mMol.getAtomCharge(bridgeHead) != 1;
if (bondCountToBridgeHead == 1) {
if (!bridgeHeadIsFlat && !bridgeHeadMayInvert && smallRingSize <= 4 && bridgeAtomCount <= 3)
mNitrogenQualifiesForParity[atom] = true;
continue;
}
switch (smallRingSize) {
// case 3 is fully handled
case 4: // must be bondCountToBridgeHead == 2
if (!bridgeHeadIsFlat && !bridgeHeadMayInvert) {
if (bridgeAtomCount <= 4)
mNitrogenQualifiesForParity[atom] = true;
}
break;
case 5: // must be bondCountToBridgeHead == 2
if (bridgeHeadMayInvert) {
if (bridgeAtomCount <= 3)
mNitrogenQualifiesForParity[atom] = true;
}
else if (!bridgeHeadIsFlat) {
if (bridgeAtomCount <= 4)
mNitrogenQualifiesForParity[atom] = true;
}
break;
case 6:
if (bondCountToBridgeHead == 2) {
if (bridgeHeadIsFlat) {
if (bridgeAtomCount <= 4)
mNitrogenQualifiesForParity[atom] = true;
}
else if (!bridgeHeadMayInvert) {
if (bridgeAtomCount <= 3)
mNitrogenQualifiesForParity[atom] = true;
}
}
else if (bondCountToBridgeHead == 3) {
if (bridgeHeadIsFlat) {
if (bridgeAtomCount <= 6)
mNitrogenQualifiesForParity[atom] = true;
}
else {
if (bridgeAtomCount <= 4)
mNitrogenQualifiesForParity[atom] = true;
}
}
break;
case 7:
if (bondCountToBridgeHead == 3) {
if (bridgeAtomCount <= 3)
mNitrogenQualifiesForParity[atom] = true;
}
break;
}
}
}
}
}
/**
* Calculates and stores implicit abnormal valences due to
* explicit hydrogen attachments.
* @param atom
* @return
*/
private int canCalcImplicitAbnormalValence(int atom) {
int explicitAbnormalValence = mMol.getAtomAbnormalValence(atom);
int implicitHigherValence = mMol.getImplicitHigherValence(atom, false);
int newImplicitHigherValence = mMol.getImplicitHigherValence(atom, true);
int valence = -1;
if (implicitHigherValence != newImplicitHigherValence) {
if (explicitAbnormalValence != -1
&& explicitAbnormalValence > 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.getAllConnAtoms(atom) != mMol.getConnAtoms(atom)) {
valence = mMol.getOccupiedValence(atom) - mMol.getElectronValenceCorrection(atom);
}
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 = new int[mMol.getAtoms()];
for (int atom=0; atom();
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() {
// 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 ((mMode & CONSIDER_STEREOHETEROTOPICITY) != 0) {
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
if (mNoOfRanks < mMol.getAtoms()) {
for (int atom=0; atombondRingSize.length; i--)
mCanBase[atom].add(ATOM_BITS+5, 0);
for (int i=bondRingSize.length-1; i>=0; i--)
mCanBase[atom].add(ATOM_BITS+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; atombondQFList.length; i--)
mCanBase[atom].add(ATOM_BITS+Molecule.cBondQFNoOfBits, 0);
for (int i=bondQFList.length-1; i>=0; i--)
mCanBase[atom].add(ATOM_BITS+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();
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; atomj; k--)
connRank[k] = connRank[k-1];
connRank[j] = rank;
}
int neighbours = mMol.getConnAtoms(atom);
mCanBase[atom].init(atom);
mCanBase[atom].add(ATOM_BITS, mCanRank[atom]);
for (int i=neighbours; i 4)
return false;
// don't tetrahedral nitrogen, unless they were found to qualify for parity calculation
if (mMol.getAtomicNo(atom) == 7
&& !mNitrogenQualifiesForParity[atom])
return false;
// create array to remap connAtoms according to canRank order
int remappedConn[] = new int[4];
int remappedRank[] = new int[4];
boolean neighbourUsed[] = new boolean[4];
for (int i=0; i no stereo center
proTHAtom1 = mMol.getConnAtom(atom, remappedConn[i-1]);
proTHAtom2 = mMol.getConnAtom(atom, remappedConn[i]);
if (mMol.isRingBond(mMol.getConnBond(atom,remappedConn[i])))
mProTHAtomsInSameFragment[atom] = true;
proTHAtomsFound = true;
}
}
if (calcProParity && !proTHAtomsFound)
return false;
byte atomTHParity = (mZCoordinatesAvailable) ?
canCalcTHParity3D(atom, remappedConn)
: canCalcTHParity2D(atom, remappedConn);
if (!calcProParity) {
mTHParity[atom] = atomTHParity;
}
else if ((mStereoCentersFound && (mMode & CONSIDER_DIASTEREOTOPICITY) != 0)
|| (!mStereoCentersFound && (mMode & CONSIDER_ENANTIOTOPICITY) != 0)) {
// increment mCanBase[] for atoms that are Pro-Parity1
if (atomTHParity == Molecule.cAtomParity1) {
mCanBase[proTHAtom1].add(0x0400);
mCanBase[proTHAtom2].add(0x0100);
}
else if (atomTHParity == Molecule.cAtomParity2) {
mCanBase[proTHAtom1].add(0x0100);
mCanBase[proTHAtom2].add(0x0400);
}
}
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, boolean calcProParity) {
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 && !calcProParity)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank,atom, atom2);
if (halfParity2.mRanksEqual && !calcProParity)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (calcProParity) {
if (halfParity1.mRanksEqual && halfParity1.mInSameFragment)
mProTHAtomsInSameFragment[atom] = true;
if (halfParity2.mRanksEqual && halfParity2.mInSameFragment)
mProTHAtomsInSameFragment[atom] = true;
}
int hp1 = halfParity1.getValue();
int hp2 = halfParity2.getValue();
if (hp1 == -1 || hp2 == -1 || ((hp1 + hp2) & 1) == 0) {
if (!calcProParity) {
mTHParity[atom] = Molecule.cAtomParityUnknown;
}
return true;
}
byte alleneParity = 0;
switch (hp1 + hp2) {
case 3:
case 7:
alleneParity = Molecule.cAtomParity2;
break;
case 5:
alleneParity = Molecule.cAtomParity1;
break;
}
if (!calcProParity) { // increment mProParity[] for atoms that are Pro-Parity1
mTHParity[atom] = alleneParity;
}
else if ((mStereoCentersFound && (mMode & CONSIDER_DIASTEREOTOPICITY) != 0)
|| (!mStereoCentersFound && (mMode & CONSIDER_ENANTIOTOPICITY) != 0)) {
if (halfParity1.mRanksEqual) {
if (alleneParity == Molecule.cAtomParity1) {
mCanBase[halfParity1.mHighConn].add(0x0040);
mCanBase[halfParity1.mLowConn].add(0x0010);
}
else {
mCanBase[halfParity1.mHighConn].add(0x0010);
mCanBase[halfParity1.mLowConn].add(0x0040);
}
}
if (halfParity2.mRanksEqual) {
if (alleneParity == Molecule.cAtomParity2) {
mCanBase[halfParity2.mHighConn].add(0x0040);
mCanBase[halfParity2.mLowConn].add(0x0010);
}
else {
mCanBase[halfParity2.mHighConn].add(0x0010);
mCanBase[halfParity2.mLowConn].add(0x0040);
}
}
}
return true;
}
private boolean canCalcBINAPParity(int bond, boolean calcProParity) {
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 && !calcProParity)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank, atom2, atom1);
if (halfParity2.mRanksEqual && !calcProParity)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (calcProParity) {
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);
}
int hp1 = halfParity1.getValue();
int hp2 = halfParity2.getValue();
if (hp1 == -1 || hp2 == -1 || ((hp1 + hp2) & 1) == 0) {
if (!calcProParity) {
mEZParity[bond] = Molecule.cBondParityUnknown;
}
return true;
}
byte axialParity = 0;
switch (hp1 + hp2) {
case 3:
case 7:
axialParity = Molecule.cBondParityEor1;
break;
case 5:
axialParity = Molecule.cBondParityZor2;
break;
}
if (!calcProParity) { // increment mProParity[] for atoms that are Pro-E
mEZParity[bond] = axialParity;
}
else if ((mStereoCentersFound && (mMode & CONSIDER_DIASTEREOTOPICITY) != 0)
|| (!mStereoCentersFound && (mMode & CONSIDER_ENANTIOTOPICITY) != 0)) {
if (halfParity1.mRanksEqual) {
if (axialParity == Molecule.cBondParityZor2) {
mCanBase[halfParity1.mHighConn].add(0x0004);
mCanBase[halfParity1.mLowConn].add(0x0001);
}
else {
mCanBase[halfParity1.mHighConn].add(0x0001);
mCanBase[halfParity1.mLowConn].add(0x0004);
}
}
if (halfParity2.mRanksEqual) {
if (axialParity == Molecule.cBondParityZor2) {
mCanBase[halfParity2.mHighConn].add(0x0004);
mCanBase[halfParity2.mLowConn].add(0x0001);
}
else {
mCanBase[halfParity2.mHighConn].add(0x0001);
mCanBase[halfParity2.mLowConn].add(0x0004);
}
}
}
return true;
}
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 && !calcProParity)
return false;
EZHalfParity halfParity2 = new EZHalfParity(mMol, mCanRank, dbAtom1, dbAtom2);
if (halfParity2.mRanksEqual && !calcProParity)
return false;
if (halfParity1.mRanksEqual && halfParity2.mRanksEqual)
return false; // both ends of DB bear equal substituents
if (calcProParity) {
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 (!calcProParity) {
mEZParity[bond] = bondDBParity;
}
else if ((mMode & CONSIDER_DIASTEREOTOPICITY) != 0) {
// increment mProParity[] for atoms that are Pro-E
if (halfParity1.mRanksEqual) {
if (bondDBParity == Molecule.cBondParityEor1) {
mCanBase[halfParity1.mHighConn].add(0x0004);
mCanBase[halfParity1.mLowConn].add(0x0001);
}
else if (bondDBParity == Molecule.cBondParityZor2) {
mCanBase[halfParity1.mHighConn].add(0x0001);
mCanBase[halfParity1.mLowConn].add(0x0004);
}
}
if (halfParity2.mRanksEqual) {
if (bondDBParity == Molecule.cBondParityEor1) {
mCanBase[halfParity2.mHighConn].add(0x0004);
mCanBase[halfParity2.mLowConn].add(0x0001);
}
else if (bondDBParity == Molecule.cBondParityZor2) {
mCanBase[halfParity2.mHighConn].add(0x0001);
mCanBase[halfParity2.mLowConn].add(0x0004);
}
}
}
return true;
}
private boolean isCentralAlleneAtom(int atom) {
return mMol.getConnAtoms(atom) == 2
&& mMol.getConnBondOrder(atom,0) == 2
&& mMol.getConnBondOrder(atom,1) == 2;
}
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;
for (int i=0; i highestRank) {
highestRankingConnAtom = connAtom;
highestRankingConnBond = mMol.getConnBond(mGraphAtom[firstUnhandled],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 0) {
encodeBits(1, 1); // more data to come
encodeBits(8, 4); // 8 = datatype 'AtomList'
encodeBits(count, nbits);
for (int atom=0; atom> Molecule.cAtomRadicalStateShift, 2);
}
}
}
if (mMol.isFragment()) { // more QueryFeatures and fragment specific properties
isSecondFeatureBlock |= addAtomQueryFeatures(22, isSecondFeatureBlock, nbits, Molecule.cAtomQFFlatNitrogen, 1, -1);
isSecondFeatureBlock |= addBondQueryFeatures(23, isSecondFeatureBlock, nbits, Molecule.cBondQFMatchStereo, 1, -1);
isSecondFeatureBlock |= addBondQueryFeatures(24, isSecondFeatureBlock, nbits,
Molecule.cBondQFAromState,
Molecule.cBondQFAromStateBits,
Molecule.cBondQFAromStateShift);
}
if ((mMode & ENCODE_ATOM_SELECTION) != 0) {
for (int atom=0; atom 15) {
ensureSecondFeatureBlock(isSecondFeatureBlock);
codeNo -= 16;
}
encodeBits(1, 1); // more data to come
encodeBits(codeNo, 4); // datatype
encodeBits(count, nbits);
for (int atom=0; atom> qfShift, qfBits);
}
}
return true;
}
private boolean addBondQueryFeatures(int codeNo, boolean isSecondFeatureBlock, int nbits, int qfMask, int qfBits, int qfShift) {
int count = 0;
for (int bond=0; bond 15) {
ensureSecondFeatureBlock(isSecondFeatureBlock);
codeNo -= 16;
}
encodeBits(1, 1); // more data to come
encodeBits(codeNo, 4); // datatype
encodeBits(count, nbits);
for (int bond=0; bond> qfShift, qfBits);
}
}
return true;
}
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 String getEncodedCoordinates() {
return getEncodedCoordinates(mZCoordinatesAvailable);
}
public String getEncodedCoordinates(boolean keepAbsoluteValues) {
if (mCoordinates == null) {
generateGraph();
encodeCoordinates(keepAbsoluteValues);
}
return mCoordinates;
}
/* private void encodeCoordinates(boolean keepAbsoluteValues) {
if (mMol.getAtoms() == 0) {
mCoordinates = "";
return;
}
float maxDelta = 0.0f;
for (int i=1; i mMol.getAtoms()
&& !mMol.isFragment()) {
includeHydrogenCoordinates = true;
for (int i=0; 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 idNormalizeConfigurations() {
// Atom TH-parities of type ABS in meso fragments can be defined in
// two degenerate ways. Normalize them based on the current mCanRank
if (mMesoHelper != null) {
mMesoHelper.normalizeFragmentsAbsAtoms(mCanRank, mTHConfiguration);
}
// Atom TH-parities of ESR-AND or ESR-OR groups are up to now
// arbitrary values, i.e. one of two possible ways of encoding
// the group's relative parity information.
// First we create for every ESR group a list of its atoms.
// All parities of a list's atoms are then normalized by inverting
// all parities of a group if the parity of the highest ranking
// group member is parity2.
int count = 0;
for (int atom=0; atom> ");
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 CanonizerFragment {
int atom[];
int bond[];
protected CanonizerFragment(int[] atom, int atoms, int[] bond, int bonds) {
this.atom = new int[atoms];
this.bond = new int[bonds];
for (int a=0; a {
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy