com.actelion.research.chem.ExtendedMolecule 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.
*
*/
package com.actelion.research.chem;
import com.actelion.research.util.Angle;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* While the Molecule class covers all primary molecule information as atom and bond properties,
* the atom connectivity and coordinates, its derived class ExtendedMolecule handles secondary,
* i.e. calculated molecule information. Most important are the directly connected atoms and bonds
* of every atom, information about rings and whether they are aromatic, and some atom properties
* that depend on their near neighbors. This calculated information is cached in helper arrays and
* stays valid as long as the molecule's primary information is not changed. High level methods,
* e.g. getPath(), that require valid helper arrays take care of updating the cache themselves.
* Low level methods, e.g. isAromaticAtom(), which typically are called very often, do not check
* the validity of or update the helper arrays themselves for performance reasons. If you use low
* level methods, then you need to make sure that the required helper array information is valid by
* calling ensureHelperArrays().
* Typically you will never instantiate an ExtendedMolecule, but rather use a StereoMolecule.
*/
public class ExtendedMolecule extends Molecule implements Serializable {
static final long serialVersionUID = 0x2006CAFE;
/**
* To interpret a stereo center as fisher projection, all non stereo
* bonds must be vertical and all stereo bonds must be horizontal.
* FISCHER_PROJECTION_LIMIT is the allowed tolerance (currently 5.0 degrees).
*/
public static final float FISCHER_PROJECTION_LIMIT = (float)Math.PI / 36;
public static final float STEREO_ANGLE_LIMIT = (float)Math.PI / 36; // 5 degrees
public static final int cMaxConnAtoms = 16; // ExtendedMolecule is not restricted anymore
// However, this is a suggestion for editors and other classes
transient private int mAtoms,mBonds;
transient private RingCollection mRingSet;
transient private int mPi[];
transient private int mConnAtoms[];
transient private int mAllConnAtoms[];
transient private int mConnAtom[][];
transient private int mConnBond[][];
transient private int mConnBondOrder[][];
public ExtendedMolecule() {
}
public ExtendedMolecule(int maxAtoms, int maxBonds) {
super(maxAtoms, maxBonds);
}
public ExtendedMolecule(Molecule mol) {
super(mol==null ? 256 : mol.getMaxAtoms(), mol==null ? 256 : mol.getMaxBonds());
if (mol != null)
mol.copyMolecule(this);
}
/**
* Clears destmol and then copies a part of this Molecule into destMol, being defined by a mask of atoms to be included.
* If not all atoms are copied, then destMol is set to be a substructure fragment.
* @param destMol receives the part of this Molecule
* @param includeAtom defines atoms to be copied; its size may be this.getAtoms() or this.getAllAtoms()
* @param recognizeDelocalizedBonds defines whether disconnected delocalized bonds will keep their
* single/double bond status or whether the query feature 'delocalized bond' will be set
* @param atomMap null or int[] not smaller than includeAtom.length; receives atom indices of dest molecule
*/
public void copyMoleculeByAtoms(ExtendedMolecule destMol, boolean[] includeAtom, boolean recognizeDelocalizedBonds, int[] atomMap) {
if (recognizeDelocalizedBonds)
ensureHelperArrays(cHelperRings);
destMol.mAtomList = null;
if (mIsFragment)
destMol.setFragment(true);
int atomCount = includeAtom.length;
if (atomMap == null)
atomMap = new int[atomCount];
destMol.mAllAtoms = 0;
for (int atom=0; atom
* standard P valence is 3, used valence is 4, implicit abnormal valence is 5.
* The molecule is interpreted as O=PH2-OMe. Requires cHelperNeighbours!
* @param atom
* @param neglectExplicitHydrogen
* @return abnormal valence or -1 if valence doesn't exceed standard valence
*/
public int getImplicitHigherValence(int atom, boolean neglectExplicitHydrogen) {
int occupiedValence = getOccupiedValence(atom) - getElectronValenceCorrection(atom);
if (neglectExplicitHydrogen)
occupiedValence -= mAllConnAtoms[atom] - mConnAtoms[atom];
byte[] valenceList = (mAtomicNo[atom] < cAtomValence.length) ?
cAtomValence[mAtomicNo[atom]] : null;
int valence = (valenceList == null) ? cDefaultAtomValence : valenceList[0];
if (occupiedValence <= valence)
return -1;
/* The error may not be the additional hydrogens but an omitted charge.
* Therefore, don't correct and just explain what we have.
* This old handling caused problems with implicit hydrogens in 3D id-coordinates
* because the number og implicit hydrogens was not correctly reproduced during idcode parsing.
* TLS 20-Jun-2015
*
// If we don't neglect hydrogen and don't have allowed higher valences
// then consider explicit hydrogens as errors.
if (!neglectExplicitHydrogen
&& (valenceList == null || valenceList.length == 1)) {
occupiedValence -= mAllConnAtoms[atom] - mConnAtoms[atom];
return (occupiedValence <= valence) ? -1 : occupiedValence;
}
if (valenceList != null)
for (int i=1; (valence 0) {
pathAtom[index-1] = parentAtom[pathAtom[index]];
index--;
}
return graphLevel[parent];
}
if (graphLevel[candidate] == 0) {
graphAtom[++highest] = candidate;
graphLevel[candidate] = graphLevel[parent]+1;
parentAtom[candidate] = parent;
}
}
}
current++;
}
return -1;
}
/**
* Finds bonds of a path that is defined by an atom sequence.
* @param pathAtom pathAtom[0]...[pathLength] -> list of atoms on path
* @param pathBond int array not smaller than pathLength
* @param pathLength no of path bonds == no of path atoms - 1
*/
public void getPathBonds(int[] pathAtom, int[] pathBond, int pathLength) {
ensureHelperArrays(cHelperNeighbours);
for (int i=0; i> 1;
}
occupiedValence -= getElectronValenceCorrection(atom);
int maxValence = getAtomAbnormalValence(atom);
if (maxValence == -1) {
if (mAtomicNo[atom] >= 171 && mAtomicNo[atom] <= 190) {
maxValence = 2;
}
else {
byte[] valenceList = (mAtomicNo[atom] < cAtomValence.length) ?
cAtomValence[mAtomicNo[atom]] : null;
if (valenceList == null) {
maxValence = cDefaultAtomValence;
}
else {
maxValence = valenceList[0];
for (int i=1; (maxValence= 171 && mAtomicNo[atom] <= 190
&& mAllConnAtoms[atom] > 2) // to accomodate for bisulfide bridges
molweight -= (mAllConnAtoms[atom] - 2) * cRoundedMass[1];
}
return molweight;
}
/**
* Simple method to calculate rotatable bonds. This method counts all single
* bonds provided that they
* - are not a terminal bond
* - are not part of a ring
* - are not an amide bond
* - are not the second of two equivalent bonds next to the same triple bond
* @return
*/
public int getRotatableBondCount() {
int rCount = 0;
ensureHelperArrays(Molecule.cHelperRings);
for (int bond=0; bond 1
&& !isAromaticAtom(connAtom)
&& isElectronegative(connAtom)) {
isRotatable = false;
break; // amid bond
}
}
}
}
if (isRotatable && !isPseudoRotatableBond(bond))
rCount++;
}
}
return rCount;
}
/**
* In a consecutive sequence of sp-hybridized atoms multiple single bonds
* cause redundant torsions. Only that single bond with the smallest bond index
* is considered really rotatable; all other single bonds are pseudo rotatable.
* If one/both end(s) of the sp-atom sequence doesn't carry atoms
* outside of the straight line then no bond is considered rotatable.
* A simple terminal single bond
* @param bond
* @return true, if this bond is not considered rotatable because of a redundancy
*/
public boolean isPseudoRotatableBond(int bond) {
if (getBondOrder(bond) != 1)
return false;
for (int i=0; i<2; i++) {
int atom = mBondAtom[i][bond];
int rearAtom = mBondAtom[1-i][bond];
while (mPi[atom] == 2
&& mConnAtoms[atom] == 2
&& mAtomicNo[atom] < 10) {
for (int j=0; j<2; j++) {
int connAtom = mConnAtom[atom][j];
if (connAtom != rearAtom) {
if (mConnAtoms[connAtom] == 1)
return true;
int connBond = mConnBond[atom][j];
if (getBondOrder(connBond) == 1
&& connBond < bond)
return true;
rearAtom = atom;
atom = connAtom;
break;
}
}
}
if (mConnAtoms[atom] == 1)
return true;
}
return false;
}
public int getAromaticRingCount() {
ensureHelperArrays(cHelperRings);
int count = 0;
for (int i=0; i
* This method tells the molecule that current atom/bond parities are valid, even if the
* stereo perception not has been performed. In addition to the stereo parities on may
* declare CIP parities and/or symmetry ranks also to be valid (helperStereoBits != 0).
* This method should be called if no coordinates are available but the parities are valid
* nevertheless, e.g. when the IDCodeParser parses an idcode without coordinates.
* (Note: After idcode parsing unknown stereo centers have parities cAtomParityNone
* instead of cAtomParityUnknown. Thus, calling isStereoCenter(atom) returns false!!!)
* Declaring parities valid prevents the Canonizer to run the stereo recognition again when
* ensureHelperArrays(cHelperParities or higher) is called.
* May also be called after filling valences with explicit hydrogen atoms, which have no
* coordinates, to tell the molecule that the earlier created stereo flags are still valid.
* @param helperStereoBits 0 or combinations of cHelperBitCIP,cHelperBitSymmetry...,cHelperBitIncludeNitrogenParities
*/
public void setParitiesValid(int helperStereoBits) {
mValidHelperArrays |= (cHelperBitsStereo & (cHelperBitParities | helperStereoBits));
}
/**
* This converts one single bond per parity into a stereo up/down bond to
* correctly reflect the given parity. This works for tetrahedral and
* allene atom parities as well as for BINAP type of bond parities.
* Should only be called with valid TH and EZ parities and valid coordinates,
* e.g. after idcode parsing with coordinates or after coordinate generation.
*/
public void setStereoBondsFromParity() {
ensureHelperArrays(cHelperRings); // in case we miss ring and neighbour information
for (int atom=0; atom 4) {
setAtomParity(atom, cAtomParityNone, false);
return;
}
int[] sortedConnMap = getSortedConnMap(atom);
double angle[] = new double[mAllConnAtoms[atom]];
for (int i=0; i sortedConn[i-1]) && lowestConnAtom > mConnAtom[atom][j]) {
lowestConnAtom = mConnAtom[atom][j];
lowestConnIndex = j;
}
}
sortedConn[i] = lowestConnAtom;
if (mConnBond[atom][lowestConnIndex] == preferredBond)
preferredBondIndex = i;
} */
for (int i=1; i angle[2]) && (angle[1] - angle[2] > Math.PI)));
break;
case 1:
inverted = (angle[2] - angle[0] > Math.PI);
break;
case 2:
inverted = (angle[1] - angle[0] < Math.PI);
break;
}
bondType = ((getAtomParity(atom) == cAtomParity1) ^ inverted) ?
cBondTypeUp : cBondTypeDown;
/* // original handling where parity depended solely on the clockwise/anti-clockwise of three angles
bondType = ((getAtomParity(atom) == cAtomParity1)
^ (angle[1] > angle[2])) ?
cBondTypeDown : cBondTypeUp; */
}
else {
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;
bondType = ((getAtomParity(atom) == cAtomParity1)
^ (up_down[order][preferredBondIndex] == 1)) ?
cBondTypeDown : cBondTypeUp;
}
mBondType[preferredBond] = bondType;
}
private boolean setFisherProjectionStereoBondsFromParity(int atom, int[] sortedConnMap, double[] angle) {
int[] direction = new int[mAllConnAtoms[atom]];
int parity = getFisherProjectionParity(atom, sortedConnMap, angle, direction);
if (parity == cAtomParityUnknown)
return false;
int bondType = (getAtomParity(atom) == parity) ? cBondTypeUp : cBondTypeDown;
for (int i=0; i 4)
return false;
boolean[] isUsed = new boolean[4];
for (int i=0; i FISCHER_PROJECTION_LIMIT)
return false;
direction[i] = 3 & (int)(a / (Math.PI/2));
if (isUsed[direction[i]])
return false;
isUsed[direction[i]] = true;
if ((direction[i] & 1) == 0) { // vertical bond
if (mBondType[mConnBond[atom][sortedConnMap[i]]] != cBondTypeSingle)
return false;
}
else {
if (!isStereoBond(mConnBond[atom][sortedConnMap[i]], atom))
return false;
}
}
return isUsed[0] && isUsed[2];
}
private void setAlleneStereoBondFromParity(int atom) {
// find preferred bond to serve as stereobond
if (mConnAtoms[atom] != 2
|| mConnBondOrder[atom][0] != 2
|| mConnBondOrder[atom][1] != 2
|| mConnAtoms[mConnAtom[atom][0]] < 2
|| mConnAtoms[mConnAtom[atom][1]] < 2
|| mPi[mConnAtom[atom][0]] != 1
|| mPi[mConnAtom[atom][1]] != 1) {
setAtomParity(atom, cAtomParityNone, false);
return;
}
int preferredBond = -1;
int preferredAtom = -1;
int preferredAlleneAtom = -1;
int oppositeAlleneAtom = -1;
int bestScore = 0;
for (int i=0; i<2; i++) {
int alleneAtom = mConnAtom[atom][i];
for (int j=0; j connAtom))
highPriorityAtom = connAtom;
}
int[] oppositeAtom = new int[2];
int oppositeAtoms = 0;
for (int i=0; i oppositeAtom[1]) {
int temp = oppositeAtom[0];
oppositeAtom[0] = oppositeAtom[1];
oppositeAtom[1] = temp;
}
double hpAngleDif = getAngleDif(alleneAngle, getBondAngle(oppositeAlleneAtom, oppositeAtom[0]));
double lpAngleDif = getAngleDif(alleneAngle, getBondAngle(oppositeAlleneAtom, oppositeAtom[1]));
angleDif = hpAngleDif - lpAngleDif;
}
else {
angleDif = getAngleDif(alleneAngle, getBondAngle(oppositeAlleneAtom, oppositeAtom[0]));
}
if ((angleDif < 0.0)
^ (getAtomParity(atom) == cAtomParity1)
^ (highPriorityAtom == preferredAtom))
mBondType[preferredBond] = cBondTypeUp;
else
mBondType[preferredBond] = cBondTypeDown;
}
/**
* In case bond is a BINAP kind of chiral bond with defined parity,
* then the preferred neighbour single bond is converted into a
* stereo bond to correctly reflect its defined parity.
* @param bond
*/
public void setStereoBondFromBondParity(int bond) {
// set an optimal bond to up/down to reflect the atom parity
if (getBondParity(bond) == Molecule.cBondParityNone
|| getBondParity(bond) == Molecule.cBondParityUnknown
|| !isBINAPChiralityBond(bond))
return;
int preferredBond = -1;
int preferredAtom = -1;
int preferredBINAPAtom = -1;
int oppositeBINAPAtom = -1;
int bestScore = 0;
for (int i=0; i<2; i++) {
int atom = mBondAtom[i][bond];
for (int j=0; j connAtom))
highPriorityAtom = connAtom;
}
int[] oppositeAtom = new int[2];
int oppositeAtoms = 0;
for (int i=0; i oppositeAtom[1]) {
int temp = oppositeAtom[0];
oppositeAtom[0] = oppositeAtom[1];
oppositeAtom[1] = temp;
}
double hpAngleDif = getAngleDif(binapAngle, getBondAngle(oppositeBINAPAtom, oppositeAtom[0]));
double lpAngleDif = getAngleDif(binapAngle, getBondAngle(oppositeBINAPAtom, oppositeAtom[1]));
angleDif = hpAngleDif - lpAngleDif;
}
else {
angleDif = getAngleDif(binapAngle, getBondAngle(oppositeBINAPAtom, oppositeAtom[0]));
}
if ((angleDif < 0.0)
^ (getBondParity(bond) == cBondParityZor2)
^ (highPriorityAtom == preferredAtom))
mBondType[preferredBond] = cBondTypeUp;
else
mBondType[preferredBond] = cBondTypeDown;
}
protected boolean bondsAreParallel(double angle1, double angle2) {
double angleDif = Math.abs(getAngleDif(angle1, angle2));
return (angleDif < 0.08 || angleDif > Math.PI - 0.08);
}
private int preferredTHStereoBond(int atom) {
// If we have two (anti-)parallel bonds, the we need to select
// that one of those that is closest to the other bonds.
double[] angle = new double[mAllConnAtoms[atom]];
for (int i=0; i= 6)
for (int i=0; i= 6)
for (int i=0; i= 5) {
int orthoSubstituentCount = 0;
for (int j=0; j= 3)
orthoSubstituentCount++;
}
if (orthoSubstituentCount == 2
|| (orthoSubstituentCount == 1 && mConnAtoms[atom] == 3))
continue; // the nitrogen is rotated out of PI-plane
}
return true;
}
// vinyloge amides, etc.
for (int j=0; j 2)
orthoSubstituentCount++;
}
for (int j=0; j 2)
orthoSubstituentCount++;
}
return (orthoSubstituentCount > 2);
}
protected boolean validateBondType(int bond, int type) {
boolean ok = super.validateBondType(bond, type);
if (ok && type == cBondTypeCross) {
ensureHelperArrays(Molecule.cHelperRings);
ok &= !isSmallRingBond(bond);
}
return ok;
}
public void validate() throws Exception {
double avbl = getAverageBondLength();
double minDistanceSquare = avbl * avbl / 16.0;
for (int atom1=1; atom1 getMaxValence(atom))
throw new Exception("atom valence exceeded");
allCharge += mAtomCharge[atom];
}
if (allCharge != 0)
throw new Exception("unbalanced atom charge");
}
/**
* Normalizes different forms of functional groups (e.g. nitro)
* to a preferred one. This step should precede any canonicalization.
* @return true if the molecule was changed
*/
public boolean normalizeAmbiguousBonds() {
ensureHelperArrays(cHelperNeighbours);
boolean found = false;
for (int atom=0; atom 0
&& mAtomCharge[mBondAtom[1][bond]] < 0) {
atom1 = mBondAtom[0][bond];
atom2 = mBondAtom[1][bond];
}
else if (mAtomCharge[mBondAtom[0][bond]] < 0
&& mAtomCharge[mBondAtom[1][bond]] > 0) {
atom1 = mBondAtom[1][bond];
atom2 = mBondAtom[0][bond];
}
else
continue;
if (mAtomicNo[atom1] < 9)
if (getOccupiedValence(atom1) > 3)
continue;
mAtomCharge[atom1] -= 1;
mAtomCharge[atom2] += 1;
if (getBondOrder(bond) == 1)
mBondType[bond] = cBondTypeDouble;
else
mBondType[bond] = cBondTypeTriple;
mValidHelperArrays = cHelperNone;
}
}
int overallCharge = 0;
int negativeAtomCount = 0;
int negativeAdjustableCharge = 0;
for (int atom=0; atom 0) {
if (!hasNegativeNeighbour(atom) && isElectronegative(atom)) {
int chargeReduction = Math.min(getImplicitHydrogens(atom), mAtomCharge[atom]);
if (chargeReduction != 0 && negativeAdjustableCharge >= chargeReduction) {
overallChargeChange -= chargeReduction;
negativeAdjustableCharge += chargeReduction;
mAtomCharge[atom] -= chargeReduction;
mValidHelperArrays &= cHelperNeighbours;
}
}
}
}
if (overallChargeChange < 0) {
int[] negativeAtom = new int[negativeAtomCount];
negativeAtomCount = 0;
for (int atom=0; atom=negativeAtom.length-negativeAtomCount); i--) {
int atom = negativeAtom[i] & 0x0000FFFF;
if (isElectronegative(atom)) {
int chargeReduction = Math.min(-overallChargeChange, -mAtomCharge[atom]);
overallChargeChange += chargeReduction;
mAtomCharge[atom] += chargeReduction;
mValidHelperArrays &= cHelperNeighbours;
}
}
}
return overallCharge;
}
/**
* @param atom
* @return true if atom has a neighbour with a negative charge
*/
private boolean hasNegativeNeighbour(int atom) {
for (int i=0; i 0)
return true;
return false;
}
/**
* While the Molecule class covers all primary molecule information, its derived class
* ExtendedMolecule handles secondary, i.e. calculated molecule information, which is cached
* in helper arrays and stays valid as long as the molecule's primary information is not changed.
* Most methods of ExtendedMolecule require some of the helper array's information. High level
* methods, e.g. getPath(), take care of updating an outdated cache themselves. Low level methods,
* e.g. isAromaticAtom(), which typically are called very often, do not check for validity
* nor update the helper arrays themselves. If you use low level methods, then you need to make
* sure that the needed helper array information is valid by this method.
* For performance reasons there are distinct levels of helper information. (A higher
* level always includes all properties of the previous level):
* cHelperNeighbours: explicit hydrogen atoms are moved to the end of the atom table and
* bonds leading to them are moved to the end of the bond table. This way algorithms can skip
* hydrogen atoms easily. For every atom directly connected atoms and bonds (with and without
* hydrogens) are determined. The number of pi electrons is counted.
* cHelperRings: Aromatic and non-aromatic rings are detected. Atom and bond ring
* properties are set and a ring collection provides a total set of small rings (7 or less atoms).
* Atoms being in allylic/benzylic or stabilized (neighbor of a carbonyl or similar group) position
* are flagged as such.
* cHelperParities: Atom (tetrahedral or axial) and bond (E/Z or atrop) parities are calculated
* from the stereo configurations.
* cHelperCIP: Cahn-Ingold-Prelog stereo information for atoms and bonds.
*
cHelperParities and cHelperCIP require a StereoMolecule!!!
* @param required one of cHelperNeighbours,cHelperRings,cHelperParities,cHelperCIP
* @return true if the molecule was changed
*/
public void ensureHelperArrays(int required) {
// cHelperNeighbours: mConnAtoms,mConnBonds,mPi for all atoms
// cHelperRings: rings,aromaticity/allylic/stabilized for non-H-atoms only
// cHelperParities: stereo parities for non-H-atoms/bonds only
// cHelperCIP: mCanonizer, stereo parities for non-H-atoms/bonds only
if ((required & ~mValidHelperArrays) == 0)
return;
if ((mValidHelperArrays & cHelperBitNeighbours) == 0) {
handleHydrogens();
calculateNeighbours();
mValidHelperArrays |= cHelperBitNeighbours;
if (validateQueryFeatures()) {
handleHydrogens();
calculateNeighbours();
}
}
if ((required & ~mValidHelperArrays) == 0)
return;
if ((mValidHelperArrays & cHelperBitRings) == 0) {
for (int atom=0; atom 1) {
if (mAtomicNo[mConnAtom[connAtom][j]] == 6)
mAtomFlags[atom] |= cAtomFlagAllylic;
else {
if (!isAromaticBond(mConnBond[connAtom][j])
&& isElectronegative(mConnAtom[connAtom][j]))
mAtomFlags[atom] |= cAtomFlagStabilized;
}
}
}
}
}
// propagate stabilized flags to vinylic positions
while (true) {
boolean found = false;
for (int atom=0; atom 0
&& ((cAtomFlagStabilized | cAtomFlagAromatic)
& mAtomFlags[atom]) == cAtomFlagStabilized) {
for (int i=0; i 1) {
int connAtom = mConnAtom[atom][i];
int connBond = mConnBond[atom][i];
for (int j=0; j
* This method moves all simple hydrogen atoms and associated bonds to the end of the atom/bond tables.
* It sets mAtoms to exclude simple hydrogen atoms and mBonds to exclude bonds leading to them.
* Simple hydrogens are not deleted, though. They are always displayed and the stereo perception
* still considers up/down bonds leading to hydrogen atoms. Most other functions, however, can
* happily neglect them.
* mConnAtoms/mConnBonds/mConnBondOrder are neither used nor updated.
* Note: This method changes the order among the non-hydrogen atoms. To translate to the original
* order use getHandleHydrogenMap() before calling ensureHelperArrays() if the original atom order is relevant.
*/
private void handleHydrogens() {
// find all hydrogens that are connected to a non-H atom and therefore can be implicit
boolean[] isSimpleHydrogen = new boolean[mAllAtoms];
for (int bond=0; bond= 0) && isSimpleHydrogen[lastNonHAtom]);
for (int atom=0; atom= 0) && isHydrogenBond[lastNonHBond]);
for (int bond=0; bond= 0 && isSimpleHydrogen[lastNonHAtom]) {
map[lastNonHAtom] = lastNonHAtom;
lastNonHAtom--;
}
for (int atom=0; atom<=lastNonHAtom; atom++) {
if (isSimpleHydrogen[atom]) {
map[atom] = lastNonHAtom;
map[lastNonHAtom] = atom;
lastNonHAtom--;
while (lastNonHAtom >= 0 && isSimpleHydrogen[lastNonHAtom]) {
map[lastNonHAtom] = lastNonHAtom;
lastNonHAtom--;
}
}
else {
map[atom] = atom;
}
}
return map;
}
public boolean isSimpleHydrogen(int atom) {
return mAtomicNo[atom] == 1 && mAtomMass[atom] == 0
&& (mAtomCustomLabel == null || mAtomCustomLabel[atom] == null);
}
/**
* Removes all plain explicit hydrogens atoms from the molecule, converting them
* effectively to implicit ones. If an associated bond is a stereo bond indicating
* a specific configuration, then another bond is converted to a stereo bond to reflect
* the correct stereo geometry. If the removal of a hydrogen atom would change an atom's
* implicit valance, the atom's abnormal valence is set accordingly.
*/
public void removeExplicitHydrogens() {
ensureHelperArrays(cHelperParities); // to calculate stereo center parities
mAllAtoms = mAtoms;
mAllBonds = mBonds;
for (int atom=0; atom 1)
mPi[atom] += order + order - 2;
else if (mBondType[bnd] == cBondTypeDelocalized)
mPi[atom] = 2;
}
}
}
for(int atom=0; atom 3)
mAtomFlags[atom] |= cAtomFlags4RingBonds;
}
for (int ringNo=0; ringNo= getMaxValence(atom))
mAtomQueryFeatures[atom] &= ~(cAtomQFNoMoreNeighbours | cAtomQFMoreNeighbours);
// approximate explicit hydrogens by query features
// and remove explicit hydrogens except those with stereo bonds
boolean deleteHydrogens = false;
for (int atom=0; atom 0) {
if ((mAtomQueryFeatures[atom] & cAtomQFNoMoreNeighbours) == 0) {
if (getFreeValence(atom) == 0)
mAtomQueryFeatures[atom] |= cAtomQFNoMoreNeighbours;
else {
// add query feature hydrogen to explicit hydrogens
int queryFeatureHydrogens = 0;
if ((mAtomQueryFeatures[atom] & cAtomQFNot0Hydrogen) == cAtomQFNot0Hydrogen)
queryFeatureHydrogens++;
if ((mAtomQueryFeatures[atom] & cAtomQFHydrogen) == (cAtomQFNot0Hydrogen | cAtomQFNot1Hydrogen))
queryFeatureHydrogens++;
mAtomQueryFeatures[atom] &= ~cAtomQFHydrogen;
if (getFreeValence(atom) <= queryFeatureHydrogens)
mAtomQueryFeatures[atom] |= cAtomQFNoMoreNeighbours;
else if (queryFeatureHydrogens == 0)
mAtomQueryFeatures[atom] |= cAtomQFNot0Hydrogen;
else // queryFeatureHydrogens > 1
mAtomQueryFeatures[atom] |= (cAtomQFNot0Hydrogen | cAtomQFNot1Hydrogen);
}
}
for (int i=mConnAtoms[atom]; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy