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.12.1
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 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; 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