All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.actelion.research.chem.Canonizer Maven / Gradle / Ivy

There is a newer version: 2024.11.2
Show newest version
/*
* 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; 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy