org.openscience.cdk.tools.ErtlFunctionalGroupsFinderUtility Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ErtlFunctionalGroupsFinder Show documentation
Show all versions of ErtlFunctionalGroupsFinder Show documentation
ErtlFunctionalGroupsFinder for CDK
/*
* ErtlFunctionalGroupsFinder for CDK
* Copyright (c) 2023 Sebastian Fritsch, Stefan Neumann, Jonas Schaub, Christoph Steinbeck, and Achim Zielesny
*
* Source code is available at
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.openscience.cdk.tools;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.aromaticity.Aromaticity;
import org.openscience.cdk.atomtype.CDKAtomTypeMatcher;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.exception.Intractable;
import org.openscience.cdk.graph.ConnectivityChecker;
import org.openscience.cdk.hash.AtomEncoder;
import org.openscience.cdk.hash.BasicAtomEncoder;
import org.openscience.cdk.hash.HashGeneratorMaker;
import org.openscience.cdk.hash.MoleculeHashGenerator;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomContainerSet;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.smiles.SmiFlavor;
import org.openscience.cdk.smiles.SmilesGenerator;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
import org.openscience.cdk.tools.manipulator.AtomTypeManipulator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class gives utility methods for using ErtlFunctionalGroupsFinder,
* a CDK-based implementation, published here, of the
* Ertl algorithm for automated functional groups detection.
* The methods of this class are basically public static re-implementations of the routines used for testing and
* evaluating the ErtlFunctionalGroupsFinder, as described in the publication.
*
* @author Jonas Schaub
* @version 1.2
*/
public class ErtlFunctionalGroupsFinderUtility {
//
/**
* Enumeration of custom atom encoders for seeding atomic hash codes.
*
* @author Jonas Schaub
* @see BasicAtomEncoder
* @see AtomEncoder
*/
enum CustomAtomEncoder implements AtomEncoder {
/**
* Encode whether an atom is aromatic or not. This specification is necessary to distinguish functional groups with
* aromatic environments and those without. For example: [H]O[C] and [H]OC* (pseudo SMILES codes) should be
* assigned different hash codes by the MoleculeHashGenerator.
*
* @see IAtom#isAromatic()
*/
AROMATICITY {
/**
*{@inheritDoc}
*/
@Override
public int encode(IAtom anAtom, IAtomContainer aContainer) {
return anAtom.isAromatic()? 3 : 2;
}
};
}
//
//
//
/**
* Atomic numbers that ErtlFunctionalGroupsFinder accepts, see getValidAtomicNumbers()
*/
private static final int[] VALID_ATOMIC_NUMBERS = new int[] {1,2,6,7,8,9,10,15,16,17,18,34,35,36,53,54,86};
/**
* Atomic numbers that ErtlFunctionalGroupsFinder accepts, loaded into a hash set for quick determination; set is
* filled in static initializer (see below)
*/
private static final HashSet VALID_ATOMIC_NUMBERS_SET = new HashSet<>(20, 1);
/**
* Logger of this class
*/
private static final Logger LOGGER = Logger.getLogger(ErtlFunctionalGroupsFinderUtility.class.getName());
//
//
//
/**
* Static initializer that sets up hash maps/sets used by static methods.
*/
static {
for (int i : ErtlFunctionalGroupsFinderUtility.VALID_ATOMIC_NUMBERS) {
ErtlFunctionalGroupsFinderUtility.VALID_ATOMIC_NUMBERS_SET.add(i);
}
}
//
//
private ErtlFunctionalGroupsFinderUtility() {
}
//
//
//
/**
* Returns an integer array containing all atomic numbers that can be passed on to ErtlFunctionalGroupsFinder.find().
* All other atomic numbers are invalid because they represent metal, metalloid or pseudo ('R') atoms.
*
* @return all valid atomic numbers for ErtlFunctionalGroupsFinder.find()
*/
public static int[] getValidAtomicNumbers() {
return Arrays.copyOf(ErtlFunctionalGroupsFinderUtility.VALID_ATOMIC_NUMBERS,
ErtlFunctionalGroupsFinderUtility.VALID_ATOMIC_NUMBERS.length);
}
/**
* Constructs a CDK MoleculeHashGenerator that is configured to count frequencies of the functional groups
* returned by ErtlFunctionalGroupsFinder. It takes elements, bond order sum, and aromaticity of the atoms in
* an atom container into consideration. It does not consider things like isotopes, stereo-chemistry,
* orbitals, or charges.
*
* @return MoleculeHashGenerator object configured for Ertl functional groups
*/
public static MoleculeHashGenerator getFunctionalGroupHashGenerator() {
MoleculeHashGenerator tmpHashGenerator = new HashGeneratorMaker()
.depth(8)
.elemental()
/*following line is used instead of .orbital() because the atom hybridizations take more information into
account than the bond order sum but that is not required here*/
/*Note: This works here because the ErtlFunctionalGroupsFinder extracts the relevant atoms and bonds only
resulting in incomplete valences that can be used here in this way*/
.encode(BasicAtomEncoder.BOND_ORDER_SUM)
.encode(CustomAtomEncoder.AROMATICITY) //See enum CustomAtomEncoder below
.molecular();
return tmpHashGenerator;
}
/**
* Constructs a new ErtlFunctionalGroupsFinder object with generalization of returned functional groups turned ON.
*
* @return new ErtlFunctionalGroupsFinder object that generalizes returned functional groups
*/
public static ErtlFunctionalGroupsFinder getErtlFunctionalGroupsFinderGeneralizingMode() {
ErtlFunctionalGroupsFinder tmpEFGF = new ErtlFunctionalGroupsFinder(ErtlFunctionalGroupsFinder.Mode.DEFAULT);
return tmpEFGF;
}
/**
* Constructs a new ErtlFunctionalGroupsFinder object with generalization of returned functional groups turned OFF.
* The FG will contain their full environments.
*
* @return new ErtlFunctionalGroupsFinder object that does NOT generalize returned functional groups
*/
public static ErtlFunctionalGroupsFinder getErtlFunctionalGroupsFinderNotGeneralizingMode() {
ErtlFunctionalGroupsFinder tmpEFGF = new ErtlFunctionalGroupsFinder(ErtlFunctionalGroupsFinder.Mode.NO_GENERALIZATION);
return tmpEFGF;
}
//
//
//
/**
* Checks whether the given molecule consists of two or more unconnected structures, e.g. ion and counter-ion. This
* would make it unfit to be passed to ErtlFunctionalGroupsFinder.find(). This can be fixed by preprocessing, see
* selectBiggestUnconnectedComponent() below.
*
* @param aMolecule the molecule to check
* @return true, if the molecule consists of two or more unconnected structures
* @throws NullPointerException if the given molecule is 'null'
*/
public static boolean isStructureUnconnected(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'");
boolean tmpIsConnected = ConnectivityChecker.isConnected(aMolecule);
return (!tmpIsConnected);
}
/**
* Checks whether the atom count or bond count of the given molecule is zero. The ErtlFunctionalGroupsFinder.find()
* method would still accept these molecules but it is not recommended to pass them on (simply makes not much sense).
*
* @param aMolecule the molecule to check
* @return true, if the atom or bond count of the molecule is zero
* @throws NullPointerException if the given molecule is 'null'
*/
public static boolean isAtomOrBondCountZero(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
int tmpAtomCount = aMolecule.getAtomCount();
int tmpBondCount = aMolecule.getBondCount();
return (tmpAtomCount == 0 || tmpBondCount == 0);
}
/**
* Iterates through all atoms in the given molecule and checks whether they are charged. If this method returns
* 'true', the molecule cannot be passed on to ErtlFunctionalGroupsFinder.find() but should be filtered or the
* charges neutralized (see neutralizeCharges() below).
*
If no charged atoms are found, this method scales linearly with O(n) with n: number of atoms in the given
* molecule.
*
* @param aMolecule the molecule to check
* @return true, if the molecule contains one or more charged atoms
* @throws NullPointerException if the given molecule (or one of its atoms) is 'null'
*/
public static boolean isMoleculeCharged(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
int tmpAtomCount = aMolecule.getAtomCount();
if (tmpAtomCount == 0) {
return false;
}
Iterable tmpAtoms = aMolecule.atoms();
boolean tmpIsAtomCharged;
for (IAtom tmpAtom : tmpAtoms) {
//Throws NullPointerException if tmpAtom is 'null'
tmpIsAtomCharged = ErtlFunctionalGroupsFinderUtility.isAtomCharged(tmpAtom);
if (tmpIsAtomCharged) {
return true;
}
}
return false;
}
/**
* Checks whether a given atom is charged.
*
* @param anAtom the atom to check
* @return true, if the atom is charged
* @throws NullPointerException if the given atom or its formal charge is 'null'
*/
public static boolean isAtomCharged(IAtom anAtom) throws NullPointerException {
Objects.requireNonNull(anAtom, "Given atom is 'null'.");
Integer tmpFormalCharge = anAtom.getFormalCharge();
Objects.requireNonNull(tmpFormalCharge, "Formal charge is 'null'.");
return (tmpFormalCharge.intValue() != 0);
}
/**
* Checks whether a given atom is a metal, metalloid or pseudo atom judging by its atomic number. Atoms with invalid
* atomic numbers (metal, metalloid or pseudo ('R') atoms) cannot be passed on to ErtlFunctionalGroupsFinder.find()
* but should be filtered.
*
* @param anAtom the atom to check
* @return true, if the atomic number is invalid or 'null'
* @throws NullPointerException if the given atom or its atomic number is 'null'
*/
public static boolean isAtomicNumberInvalid(IAtom anAtom) throws NullPointerException {
Objects.requireNonNull(anAtom, "Given atom is 'null'.");
Integer tmpAtomicNumber = anAtom.getAtomicNumber();
Objects.requireNonNull(tmpAtomicNumber, "Atomic number is 'null'.");
int tmpAtomicNumberInt = tmpAtomicNumber.intValue();
boolean tmpIsAtomicNumberValid = ErtlFunctionalGroupsFinderUtility.VALID_ATOMIC_NUMBERS_SET.contains(tmpAtomicNumberInt);
return !tmpIsAtomicNumberValid;
}
/**
* Iterates through all atoms in the given molecule and checks whether their atomic numbers are invalid. If this
* method returns 'true', the molecule cannot be passed on to ErtlFunctionalGroupsFinder.find() but should be
* filtered.
*
If no invalid atoms are found, this method scales linearly with O(n) with n: number of atoms in the given
* molecule.
*
* @param aMolecule the molecule to check
* @return true, if the molecule contains one or more atoms with invalid atomic numbers
* @throws NullPointerException if the given molecule (or one of its atoms) is 'null'
*/
public static boolean containsInvalidAtomicNumbers(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
int tmpAtomCount = aMolecule.getAtomCount();
if (tmpAtomCount == 0) {
return false;
}
Iterable tmpAtoms = aMolecule.atoms();
boolean tmpIsAtomicNumberInvalid;
for (IAtom tmpAtom : tmpAtoms) {
//Throws NullPointerException if tmpAtom is 'null'
tmpIsAtomicNumberInvalid = ErtlFunctionalGroupsFinderUtility.isAtomicNumberInvalid(tmpAtom);
if (tmpIsAtomicNumberInvalid) {
return true;
}
}
return false;
}
/**
* Checks whether the given molecule represented by an atom container should NOT be passed on to the
* ErtlFunctionalGroupsFinder.find() method but instead be filtered.
*
In detail, this function returns true if the given atom container contains metal, metalloid, or pseudo atoms
* or has an atom or bond count equal to zero.
*
If this method returns false, this does NOT mean the molecule can be passed on to find() without a problem. It
* still might need to be preprocessed first.
*
* @see ErtlFunctionalGroupsFinderUtility#isValidArgumentForFindMethod(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#applyFiltersAndPreprocessing(IAtomContainer, Aromaticity)
* @param aMolecule the atom container to check
* @return true if the given atom container should be discarded
* @throws NullPointerException if parameter is 'null'
*/
public static boolean shouldBeFiltered(IAtomContainer aMolecule) throws NullPointerException {
return ErtlFunctionalGroupsFinderUtility.shouldBeFiltered(aMolecule, true);
}
/**
* Checks whether the given molecule represented by an atom container should NOT be passed on to the
* ErtlFunctionalGroupsFinder.find() method but instead be filtered.
*
In detail, this function returns true if the given atom container contains metal, metalloid, or pseudo atoms
* or has an atom or bond count equal to zero. If the second parameter is set to "false", single atom molecules
* (bond count is 0) are accepted and not recommended to be filtered if they fulfill the other requirements.
*
If this method returns false, this does NOT mean the molecule can be passed on to find() without a problem. It
* still might need to be preprocessed first.
*
* @see ErtlFunctionalGroupsFinderUtility#isValidArgumentForFindMethod(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#applyFiltersAndPreprocessing(IAtomContainer, Aromaticity)
* @param aMolecule the atom container to check
* @param areSingleAtomsFiltered if false, molecules with bond count 0 but atom count 1 will return false (do not filter)
* @return true if the given atom container should be discarded
* @throws NullPointerException if parameter is 'null'
*/
public static boolean shouldBeFiltered(IAtomContainer aMolecule, boolean areSingleAtomsFiltered) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is null.");
boolean tmpShouldBeFiltered;
try {
if (areSingleAtomsFiltered) {
tmpShouldBeFiltered = (ErtlFunctionalGroupsFinderUtility.containsInvalidAtomicNumbers(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isAtomOrBondCountZero(aMolecule));
} else {
tmpShouldBeFiltered = (ErtlFunctionalGroupsFinderUtility.containsInvalidAtomicNumbers(aMolecule)
|| aMolecule.getAtomCount() == 0);
}
} catch (Exception anException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.WARNING,
anException.toString() + " Molecule ID: " + ErtlFunctionalGroupsFinderUtility.getIDForLogging(aMolecule),
anException);
tmpShouldBeFiltered = true;
}
return tmpShouldBeFiltered;
}
/**
* Checks whether the given molecule represented by an atom container needs to be preprocessed before it is passed
* on to the ErtlFunctionalGroupsFinder.find() method because it is unconnected or contains charged atoms.
*
It is advised to check via shouldBeFiltered() whether the given molecule should be discarded anyway before
* calling this function.
*
* @see ErtlFunctionalGroupsFinderUtility#shouldBeFiltered(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#isValidArgumentForFindMethod(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#applyFiltersAndPreprocessing(IAtomContainer, Aromaticity)
* @see ErtlFunctionalGroupsFinderUtility#neutralizeCharges(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#selectBiggestUnconnectedComponent(IAtomContainer)
* @param aMolecule the atom container to check
* @return true is the given molecule needs to be preprocessed
* @throws NullPointerException if parameter is 'null'
*/
public static boolean shouldBePreprocessed(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is null.");
boolean tmpNeedsPreprocessing;
try {
tmpNeedsPreprocessing = (ErtlFunctionalGroupsFinderUtility.isMoleculeCharged(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isStructureUnconnected(aMolecule));
} catch (Exception anException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.WARNING,
anException.toString() + " Molecule ID: " + ErtlFunctionalGroupsFinderUtility.getIDForLogging(aMolecule),
anException);
throw new NullPointerException("An unknown error occurred.");
}
return tmpNeedsPreprocessing;
}
/**
* Checks whether the given molecule represented by an atom container can be passed on to the
* ErtlFunctionalGroupsFinder.find() method without problems.
*
This method will return false if the molecule contains any metal, metalloid, pseudo, or charged atoms, contains
* multiple unconnected parts, or has an atom or bond count of zero.
*
* @see ErtlFunctionalGroupsFinder#find(IAtomContainer, boolean)
* @see ErtlFunctionalGroupsFinderUtility#shouldBeFiltered(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#applyFiltersAndPreprocessing(IAtomContainer, Aromaticity)
* @param aMolecule the molecule to check
* @return true if the given molecule is a valid parameter for ErtlFunctionalGroupsFinder.find() method
* @throws NullPointerException if parameter is 'null'
*/
public static boolean isValidArgumentForFindMethod(IAtomContainer aMolecule) throws NullPointerException {
return ErtlFunctionalGroupsFinderUtility.isValidArgumentForFindMethod(aMolecule, true);
}
/**
* Checks whether the given molecule represented by an atom container can be passed on to the
* ErtlFunctionalGroupsFinder.find() method without problems.
*
This method will return false if the molecule contains any metal, metalloid, pseudo, or charged atoms, contains
* multiple unconnected parts, or has an atom or bond count of zero. If the second parameter is set to "false", single atom molecules
* (bond count is 0) are accepted and not recommended to be filtered if they fulfill the other requirements.
*
* @see ErtlFunctionalGroupsFinder#find(IAtomContainer, boolean)
* @see ErtlFunctionalGroupsFinderUtility#shouldBeFiltered(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#applyFiltersAndPreprocessing(IAtomContainer, Aromaticity)
* @param aMolecule the molecule to check
* @param areSingleAtomsFiltered if false, molecules with bond count 0 but atom count 1 will return true (do not filter)
* @return true if the given molecule is a valid parameter for ErtlFunctionalGroupsFinder.find() method
* @throws NullPointerException if parameter is 'null'
*/
public static boolean isValidArgumentForFindMethod(IAtomContainer aMolecule, boolean areSingleAtomsFiltered) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is null.");
boolean tmpIsValid;
try {
if (areSingleAtomsFiltered) {
tmpIsValid = !(ErtlFunctionalGroupsFinderUtility.containsInvalidAtomicNumbers(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isAtomOrBondCountZero(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isMoleculeCharged(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isStructureUnconnected(aMolecule));
} else {
tmpIsValid = !(ErtlFunctionalGroupsFinderUtility.containsInvalidAtomicNumbers(aMolecule)
|| aMolecule.getAtomCount() == 0
|| ErtlFunctionalGroupsFinderUtility.isMoleculeCharged(aMolecule)
|| ErtlFunctionalGroupsFinderUtility.isStructureUnconnected(aMolecule));
}
} catch (Exception anException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.SEVERE,
anException.toString() + " Molecule ID: " + ErtlFunctionalGroupsFinderUtility.getIDForLogging(aMolecule),
anException);
tmpIsValid = false;
}
return tmpIsValid;
}
//
//
//
/**
* Returns the biggest unconnected component/structure of the given atom container, judging by the atom count. To
* pre-check whether the atom container consists of multiple unconnected components, use isStructureUnconnected().
* All set properties of aMolecule will be set as properties of the returned atom container.
*
NOTE: The atom, bond etc. objects of the given atom container are re-used in the returned atom container but
* the former remains unchanged
*
Iterates through all unconnected components in the given atom container, so the method scales linearly with
* O(n) with n: number of unconnected components.
*
* @param aMolecule the molecule whose biggest unconnected component should be found
* @return the biggest (judging by the atom count) unconnected component of the given atom container
* @throws NullPointerException if aMolecule is null or the biggest component
*/
public static IAtomContainer selectBiggestUnconnectedComponent(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecules is 'null'.");
IAtomContainerSet tmpUnconnectedComponentsSet = ConnectivityChecker.partitionIntoMolecules(aMolecule);
IAtomContainer tmpBiggestComponent = null;
for (IAtomContainer tmpComponent : tmpUnconnectedComponentsSet.atomContainers()) {
if (Objects.isNull(tmpBiggestComponent) || tmpBiggestComponent.getAtomCount() < tmpComponent.getAtomCount()) {
tmpBiggestComponent = tmpComponent;
}
}
Objects.requireNonNull(tmpBiggestComponent, "The resulting biggest component is 'null'.");
tmpBiggestComponent.setProperties(aMolecule.getProperties());
return tmpBiggestComponent;
}
/**
* Neutralizes charged atoms in the given atom container by zeroing the formal atomic charges and filling up free
* valences with implicit hydrogen atoms (according to the CDK atom types). This procedure allows a more general
* charge treatment than a pre-defined transformation list but may produce "wrong" structures, e.g. it turns a
* nitro NO2 group into a structure represented by the SMILES code "[H]O[N](=O)*" with an uncharged four-bonded
* nitrogen atom (other examples are "*[N](*)(*)*", "[C]#[N]*" or "*S(*)(*)*"). Thus, an improved charge
* neutralization scheme is desirable for future implementations.
*
NOTE: This method changes major properties and the composition of the given IAtomContainer object! If you
* want to retain your object unchanged for future calculations, use the IAtomContainer's
* clone() method.
*
Iterates through all atoms in the given atom container, so the method scales linearly with
* O(n) with n: number of atoms.
*
* @param aMolecule the molecule to be neutralized
* @throws NullPointerException if aMolecule is 'null' or one of its atoms
* @throws CDKException if no matching atom type can be determined for one atom or there is a problem with adding
* the implicit hydrogen atoms.
*/
public static void neutralizeCharges(IAtomContainer aMolecule) throws NullPointerException, CDKException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
Iterable tmpAtoms = aMolecule.atoms();
for (IAtom tmpAtom : tmpAtoms) {
ErtlFunctionalGroupsFinderUtility.neutralizeCharges(tmpAtom, aMolecule);
}
}
/**
* Neutralizes a charged atom in the given parent atom container by zeroing the formal atomic charge and filling up free
* valences with implicit hydrogen atoms (according to the CDK atom types).
*
NOTE: This method changes major properties and the composition of the given IAtom and IAtomContainer object!
* If you want to retain your objects unchanged for future calculations, use the IAtomContainer's
* clone() method.
*
* @param anAtom the atom to be neutralized
* @param aParentMolecule the molecule the atom belongs to
* @throws NullPointerException if anAtom or aParentMolecule is 'null'
* @throws CDKException if the atom is not part of the molecule or no matching atom type can be determined for the
* atom or there is a problem with adding the implicit hydrogen atoms.
* @see ErtlFunctionalGroupsFinderUtility#neutralizeCharges(IAtomContainer)
*/
public static void neutralizeCharges(IAtom anAtom, IAtomContainer aParentMolecule) throws NullPointerException, CDKException {
Objects.requireNonNull(anAtom, "Given atom is 'null'.");
Objects.requireNonNull(aParentMolecule, "Given parent molecule is 'null'.");
boolean tmpIsAtomInMolecule = aParentMolecule.contains(anAtom);
if (!tmpIsAtomInMolecule) {
throw new CDKException("Given atom is not part of the given atom container.");
}
Integer tmpFormalChargeObject = anAtom.getFormalCharge();
if (Objects.isNull(tmpFormalChargeObject)) {
return;
}
int tmpFormalCharge = tmpFormalChargeObject.intValue();
if (tmpFormalCharge != 0) {
anAtom.setFormalCharge(0);
IChemObjectBuilder tmpBuilder = aParentMolecule.getBuilder();
if (Objects.isNull(tmpBuilder)) {
throw new CDKException("Builder of the given atom container is 'null'.");
}
CDKHydrogenAdder tmpHAdder = CDKHydrogenAdder.getInstance(tmpBuilder);
CDKAtomTypeMatcher tmpMatcher = CDKAtomTypeMatcher.getInstance(tmpBuilder);
//Can throw CDKException
IAtomType tmpMatchedType = tmpMatcher.findMatchingAtomType(aParentMolecule, anAtom);
if (Objects.isNull(tmpMatchedType)) {
throw new CDKException("Matched atom type is 'null'.");
}
AtomTypeManipulator.configure(anAtom, tmpMatchedType);
//Can throw CDKException
tmpHAdder.addImplicitHydrogens(aParentMolecule, anAtom);
}
}
/**
* Convenience method to perceive atom types for all IAtoms in the IAtomContainer, using the
* CDK AtomContainerManipulator or rather the CDKAtomTypeMatcher. If the matcher finds a matching atom type, the
* IAtom will be configured to have the same properties as the IAtomType. If no matching atom type is found, no
* configuration is performed.
*
Calling this method is equal to calling AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(aMolecule).
* It has been given its own method here because it is a necessary step in the preprocessing for
* ErtlFunctionalGroupsFinder.
*
NOTE: This method changes major properties of the given IAtomContainer object! If you
* want to retain your object unchanged for future calculations, use the IAtomContainer's
* clone() method.
*
* @param aMolecule the molecule to configure
* @throws NullPointerException is aMolecule is 'null'
* @throws CDKException when something went wrong with going through the AtomType options
* @see AtomContainerManipulator#percieveAtomTypesAndConfigureAtoms(IAtomContainer)
* @see CDKAtomTypeMatcher#findMatchingAtomType(IAtomContainer, IAtom)
*/
public static void perceiveAtomTypesAndConfigureAtoms(IAtomContainer aMolecule) throws NullPointerException, CDKException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
//Might throw CDKException but it is unclear in what case
AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(aMolecule);
}
/**
* Convenience method for applying the given aromaticity model to the given molecule. Any existing aromaticity flags
* are removed - even if no aromatic bonds were found. This follows the idea of applying an aromaticity model to a
* molecule such that the result is the same irrespective of existing aromatic flags.
*
Calling this method is equal to calling Aromaticity.apply(aMolecule).
* It has been given its own method here because it is a necessary step in the preprocessing for
* ErtlFunctionalGroupsFinder.
*
NOTE: This method changes major properties and the composition of the given IAtomContainer object! If you
* want to retain your object unchanged for future calculations, use copy() in this class or the IAtomContainer's
* clone() method.
*
* @param aMolecule the molecule to apply the model to
* @param anAromaticityModel the model to apply; Note that the choice of electron donation model and cycle finder
* algorithm has a heavy influence on the functional group detection of
* ErtlFunctionalGroupsFinder
* @return true if the molecule (or parts of it) is determined to be aromatic
* @throws NullPointerException if a parameter is 'null'
* @throws CDKException if a problem occurred with the cycle perception (see CDK docs)
* @see Aromaticity#apply(IAtomContainer)
*/
public static boolean applyAromaticityDetection(IAtomContainer aMolecule, Aromaticity anAromaticityModel) throws NullPointerException, CDKException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
Objects.requireNonNull(anAromaticityModel, "Given aromaticity model is 'null'.");
boolean tmpIsAromatic = false;
try {
//throws CDKException if a problem occurred with the cycle perception (see CDK docs)
//Note: Contrary to the docs, an Intractable exception might be thrown
tmpIsAromatic = anAromaticityModel.apply(aMolecule);
} catch (Intractable anIntractableException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.SEVERE,
anIntractableException.toString() + " Molecule ID: " + ErtlFunctionalGroupsFinderUtility.getIDForLogging(aMolecule),
anIntractableException);
String tmpMessage = anIntractableException.getMessage();
Throwable tmpCause = anIntractableException.getCause();
throw new CDKException(tmpMessage, tmpCause);
}
return tmpIsAromatic;
}
/**
* Checks whether the given molecule represented by an atom container should be filtered instead of being passed
* on to the ErtlFunctionalGroupsFinder.find() method and if not, applies necessary preprocessing steps.
* In the second case, this method applies preprocessing
* to the given atom container that is always needed (setting atom types and applying an aromaticity model) and
* preprocessing steps that are only needed in specific cases (selecting the biggest unconnected component, neutralizing
* charges). Molecules processed by this method can be passed on to find() without problems (Caution: The return value
* of this method is 'null' if the molecule should be filtered!).
*
NOTE: This method changes major properties and the composition of the given IAtomContainer object! If you
* want to retain your object unchanged for future calculations, use the IAtomContainer's
* clone() method.
*
NOTE2: The returned IAtomContainer object is the same as the one given as parameter!
*
* @see ErtlFunctionalGroupsFinder#find(IAtomContainer, boolean)
* @see ErtlFunctionalGroupsFinderUtility#shouldBeFiltered(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @param aMolecule the molecule to check and process
* @param anAromaticityModel the aromaticity model to apply to the molecule in preprocessing; Note: The chosen
* ElectronDonation model can massively influence the extracted function groups of a molecule when using
* ErtlFunctionGroupsFinder!
* @return the preprocessed atom container or 'null' if the molecule should be discarded
* @throws NullPointerException if a parameter is 'null'; Note: All other exceptions are caught and logged by this
* class' logger
*/
public static IAtomContainer applyFiltersAndPreprocessing(IAtomContainer aMolecule, Aromaticity anAromaticityModel) throws NullPointerException {
return ErtlFunctionalGroupsFinderUtility.applyFiltersAndPreprocessing(aMolecule, anAromaticityModel, true);
}
/**
* Checks whether the given molecule represented by an atom container should be filtered instead of being passed
* on to the ErtlFunctionalGroupsFinder.find() method and if not, applies necessary preprocessing steps.
* In the second case, this method applies preprocessing
* to the given atom container that is always needed (setting atom types and applying an aromaticity model) and
* preprocessing steps that are only needed in specific cases (selecting the biggest unconnected component, neutralizing
* charges). Molecules processed by this method can be passed on to find() without problems (Caution: The return value
* of this method is 'null' if the molecule should be filtered!).
*
NOTE: This method changes major properties and the composition of the given IAtomContainer object! If you
* want to retain your object unchanged for future calculations, use the IAtomContainer's
* clone() method.
*
NOTE2: The returned IAtomContainer object is the same as the one given as parameter!
*
* @see ErtlFunctionalGroupsFinder#find(IAtomContainer, boolean)
* @see ErtlFunctionalGroupsFinderUtility#shouldBeFiltered(IAtomContainer)
* @see ErtlFunctionalGroupsFinderUtility#shouldBePreprocessed(IAtomContainer)
* @param aMolecule the molecule to check and process
* @param anAromaticityModel the aromaticity model to apply to the molecule in preprocessing; Note: The chosen
* ElectronDonation model can massively influence the extracted function groups of a molecule when using
* ErtlFunctionGroupsFinder!
* @param areSingleAtomsFiltered if false, molecules with bond count 0 but atom count 1 will be processed and
* not return null
* @return the preprocessed atom container or 'null' if the molecule should be discarded
* @throws NullPointerException if a parameter is 'null'; Note: All other exceptions are caught and logged by this
* class' logger
*/
public static IAtomContainer applyFiltersAndPreprocessing(IAtomContainer aMolecule, Aromaticity anAromaticityModel, boolean areSingleAtomsFiltered) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given atom container is 'null'.");
Objects.requireNonNull(anAromaticityModel, "Given aromaticity model is 'null'.");
try {
ErtlFunctionalGroupsFinderUtility.perceiveAtomTypesAndConfigureAtoms(aMolecule);
//Filter
if (areSingleAtomsFiltered) {
boolean tmpIsAtomOrBondCountZero = ErtlFunctionalGroupsFinderUtility.isAtomOrBondCountZero(aMolecule);
if (tmpIsAtomOrBondCountZero) {
return null;
}
} else {
boolean tmpIsAtomCountZero = aMolecule.getAtomCount() == 0;
if (tmpIsAtomCountZero) {
return null;
}
}
//From structures containing two or more unconnected structures (e.g. ions) choose the largest structure
boolean tmpIsUnconnected = ErtlFunctionalGroupsFinderUtility.isStructureUnconnected(aMolecule);
if (tmpIsUnconnected) {
aMolecule = ErtlFunctionalGroupsFinderUtility.selectBiggestUnconnectedComponent(aMolecule);
}
//Filter
boolean tmpContainsInvalidAtoms = ErtlFunctionalGroupsFinderUtility.containsInvalidAtomicNumbers(aMolecule);
if (tmpContainsInvalidAtoms) {
return null;
}
//Neutralize charges if there are any
boolean tmpIsCharged = ErtlFunctionalGroupsFinderUtility.isMoleculeCharged(aMolecule);
if (tmpIsCharged) {
ErtlFunctionalGroupsFinderUtility.neutralizeCharges(aMolecule);
}
//Application of aromaticity model
ErtlFunctionalGroupsFinderUtility.applyAromaticityDetection(aMolecule, anAromaticityModel);
} catch (Exception anException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.SEVERE,
anException.toString() + " Molecule ID: " + ErtlFunctionalGroupsFinderUtility.getIDForLogging(aMolecule),
anException);
return null;
}
return aMolecule;
}
//
//
//
/**
* Extracts functional groups from the given molecule, using the Ertl algorithm / ErtlFunctionalGroupsFinder, but
* only the marked atoms of every functional group are returned. They do not contain their environment (i.e. connected,
* unmarked carbon atoms) and are also not generalized.
*
* @param aMolecule the molecule to extracts functional groups from; it is not cloned in this method!
* @return List of IAtomContainer objects representing the detected functional groups
* @throws NullPointerException if the given atom container is null
* @throws IllegalArgumentException if the given atom container cannot be passed to ErtlFunctionalGroupsFinder;
* check methods for filtering and preprocessing in this case
*/
public static List findMarkedAtoms(IAtomContainer aMolecule) throws NullPointerException, IllegalArgumentException {
return ErtlFunctionalGroupsFinderUtility.findMarkedAtoms(aMolecule, true);
}
/**
* Extracts functional groups from the given molecule, using the Ertl algorithm / ErtlFunctionalGroupsFinder, but
* only the marked atoms of every functional group are returned. They do not contain their environment (i.e. connected,
* unmarked carbon atoms) and are also not generalized.
*
* @param aMolecule the molecule to extracts functional groups from; it is not cloned in this method!
* @param areSingleAtomsFiltered if false, molecules with bond count 0 but atom count 1 will be processed and not raise
* an IllegalArgumentException
* @return List of IAtomContainer objects representing the detected functional groups
* @throws NullPointerException if the given atom container is null
* @throws IllegalArgumentException if the given atom container cannot be passed to ErtlFunctionalGroupsFinder;
* check methods for filtering and preprocessing in this case
*/
public static List findMarkedAtoms(IAtomContainer aMolecule, boolean areSingleAtomsFiltered) throws NullPointerException, IllegalArgumentException {
Objects.requireNonNull(aMolecule, "Given molecule is null.");
if (aMolecule.isEmpty()) {
return new ArrayList(0);
}
boolean tmpCanBeFragmented = ErtlFunctionalGroupsFinderUtility.isValidArgumentForFindMethod(aMolecule, areSingleAtomsFiltered);
if (!tmpCanBeFragmented) {
throw new IllegalArgumentException("Given molecule cannot be fragmented but needs to be filtered or preprocessed.");
}
HashMap tmpIdToAtomMap = new HashMap<>(aMolecule.getAtomCount() + 1, 1);
for (int i = 0; i < aMolecule.getAtomCount(); i++) {
IAtom tmpAtom = aMolecule.getAtom(i);
tmpAtom.setProperty("EFGFUtility.INDEX", i);
tmpIdToAtomMap.put(i, tmpAtom);
}
ErtlFunctionalGroupsFinder tmpEFGF = new ErtlFunctionalGroupsFinder(ErtlFunctionalGroupsFinder.Mode.DEFAULT);
List tmpFunctionalGroups = tmpEFGF.find(aMolecule, false);
if (tmpFunctionalGroups.isEmpty()) {
return tmpFunctionalGroups;
}
for (IAtomContainer tmpFunctionalGroup : tmpFunctionalGroups) {
for (int i = 0; i < tmpFunctionalGroup.getAtomCount(); i++) {
IAtom tmpAtom = tmpFunctionalGroup.getAtom(i);
if (Objects.isNull(tmpAtom.getProperty("EFGFUtility.INDEX"))) {
if (tmpAtom instanceof IPseudoAtom && "R".equals(((IPseudoAtom)tmpAtom).getLabel())) {
//atom is a pseudo atom added by the EFGF in generalization
tmpFunctionalGroup.removeAtom(tmpAtom);
i = i - 1;
continue;
} else if (tmpAtom.getSymbol().equals("C")){
//atom is an environmental C added by the EFGF
tmpFunctionalGroup.removeAtom(tmpAtom);
i = i - 1;
continue;
} else if (tmpAtom.getSymbol().equals("H")) {
//atom is an explicit H added by the EFGF
tmpFunctionalGroup.removeAtom(tmpAtom);
i = i - 1;
continue;
} else {
//unknown atom
throw new IllegalArgumentException("Something went wrong, identified unknown added atom.");
}
}
}
}
return tmpFunctionalGroups;
}
/**
* Replaces the environmental carbon or pseudo-atoms (new IAtom objects) inserted by the EFGF in an identified
* functional group with the carbon IAtom objects from the original molecule object.
*
Important note: This method only works if the atom container has not been cloned for the extraction of
* functional groups by ErtlFunctionalGroupsFinder. Use the method
* "List{@literal <}IAtomContainer{@literal >} find(IAtomContainer container, boolean clone)" with clone set to false for this purpose.
*
Also note that the result differs if the environment has been generalized by the EFGF or not. In the former
* case, only environmental carbon atoms replaced by R-atoms in the generalized FG are restored.
*
* @param aListOfFunctionalGroups functional groups of the molecule identified by EFGF
* @param aMolecule original structure in which the groups were identified
* @param aConvertExplicitHydrogens should explicit hydrogen atoms in the functional groups be converted to implicit
* hydrogens
* @param aFillEmptyValences should empty valences on the restored environmental carbon atoms be filled with
* implicit hydrogen atoms
* @param aBuilder a chem object builder instance
* @throws NullPointerException if a parameter is null
* @throws IllegalArgumentException if one of the functional groups does not originate from the given molecule
* or the molecule has been cloned for the extraction of functional groups
* @author Michael Wenk, Jonas Schaub
*/
public static void restoreOriginalEnvironmentalCarbons(
List aListOfFunctionalGroups,
IAtomContainer aMolecule,
boolean aConvertExplicitHydrogens,
boolean aFillEmptyValences,
IChemObjectBuilder aBuilder)
throws NullPointerException, IllegalArgumentException {
//
Objects.requireNonNull(aListOfFunctionalGroups, "Given list of functional groups is null.");
Objects.requireNonNull(aMolecule, "Given molecule is null.");
Objects.requireNonNull(aBuilder, "Given chem object builder is null.");
if (aListOfFunctionalGroups.isEmpty()) {
return;
}
if (aMolecule.isEmpty()) {
throw new IllegalArgumentException("Given molecule is empty.");
}
for (IAtomContainer tmpFG : aListOfFunctionalGroups) {
boolean tmpIsFGofMolecule = false;
for (IAtom tmpAtom : tmpFG.atoms()) {
if (aMolecule.contains(tmpAtom)) {
tmpIsFGofMolecule = true;
}
}
if (!tmpIsFGofMolecule) {
throw new IllegalArgumentException("At least one functional group has been given that does not originate " +
"from the given molecule or the molecule has been cloned for the extraction of functional groups.");
}
}
//
CDKHydrogenAdder tmpHadder = CDKHydrogenAdder.getInstance(aBuilder);
for (int i = 0; i < aListOfFunctionalGroups.size(); i++) {
IAtomContainer tmpFG = aListOfFunctionalGroups.get(i);
//convert explicit hydrogens to implicit
if (aConvertExplicitHydrogens) {
AtomContainerManipulator.suppressHydrogens(tmpFG);
}
//create a list of all atoms of the group because of atom removals and additions in group atom container
List tmpListofFGatoms = new ArrayList<>();
for (IAtom tmpAtom : tmpFG.atoms()) {
tmpListofFGatoms.add(tmpAtom);
}
for (IAtom tmpAtom : tmpListofFGatoms) {
//technically, all elements except carbon should be excluded but this way, it is the easiest to also include
// pseudo atoms
if (tmpAtom.getAtomicNumber().equals(1)) {
continue;
}
//detect whether the current atom is an "unknown" one, inserted as new environmental IAtom object
if (!aMolecule.contains(tmpAtom)) {
//environmental carbon and pseudo-atoms (carbon or hydrogen) added by the EFGF can only have one bond partner in the FG.
// identify its bond partner in the FG that should be part of the original molecule
IAtom tmpConnectedAtomInGroup = tmpFG.getConnectedAtomsList(tmpAtom).get(0);
//remove the inserted atom and the bond to it
tmpFG.removeBond(tmpAtom, tmpConnectedAtomInGroup);
tmpFG.removeAtom(tmpAtom);
//starting from the parent atom search for neighboring carbons which are not already in the group and add them
for (IAtom tmpConnectedAtomInOriginalStructure : aMolecule.getConnectedAtomsList(tmpConnectedAtomInGroup)) {
if (tmpConnectedAtomInOriginalStructure.getSymbol().equals("C")
&& !tmpFG.contains(tmpConnectedAtomInOriginalStructure)) {
tmpFG.addAtom(tmpConnectedAtomInOriginalStructure);
tmpFG.addBond(aMolecule.getBond(tmpConnectedAtomInGroup, tmpConnectedAtomInOriginalStructure));
}
}
}
}
if (aFillEmptyValences) {
try {
AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(tmpFG);
tmpHadder.addImplicitHydrogens(tmpFG);
} catch (CDKException aCDKException) {
ErtlFunctionalGroupsFinderUtility.LOGGER.log(Level.WARNING, aCDKException.toString(), aCDKException);
continue;
}
}
}
}
/**
* Gives the pseudo SMILES code for a given molecule / functional group. In this notation, aromatic atoms are marked
* by asterisks (*) and pseudo atoms are indicated by 'R'.
*
The function generates the SMILES string of the given molecule using CDK's SmilesGenerator and then
* replaces lowercase c, n, o etc. by C*, N*, O* etc. and wildcards ('*') by 'R' in the resulting string.
* For that, the function iterates through all characters in the generated SMILES string.
*
Note: All pseudo atoms or atoms that are represented by a wildcard ('*') in the generated SMILES string
* (e.g. the element [Uup] is interpreted by the CDK SmilesGenerator as a wildcard) are turned into an 'R' atom.
*
* @param aMolecule the molecule whose pseudo SMILES code to generate
* @return the pseudo SMILES representation as a string
* @throws NullPointerException if aMolecule is 'null'
* @throws CDKException if the SMILES code of aMolecule cannot be generated
*/
public static String createPseudoSmilesCode(IAtomContainer aMolecule) throws NullPointerException, CDKException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
SmilesGenerator tmpSmilesGenerator = new SmilesGenerator(SmiFlavor.Unique | SmiFlavor.UseAromaticSymbols);
String tmpPseudoSmilesCode;
try {
//Might throw CDKException if the SMILES string cannot be created or NullPointerException if an atom has an
// undefined number of implicit hydrogen atoms in the SMILES string
tmpPseudoSmilesCode = tmpSmilesGenerator.create(aMolecule);
} catch (NullPointerException anException) {
throw new CDKException(anException.getMessage(), anException);
}
tmpPseudoSmilesCode = tmpPseudoSmilesCode.replaceAll("\\*", "R");
tmpPseudoSmilesCode = tmpPseudoSmilesCode.replaceAll("\\[se", "[Se*");
StringBuilder tmpStringBuilder = new StringBuilder(tmpPseudoSmilesCode);
int tmpLength = tmpStringBuilder.length();
for (int tmpIndex = 0; tmpIndex < tmpLength; tmpIndex++) {
char tmpChar = tmpStringBuilder.charAt(tmpIndex);
char tmpPrevChar = '_';
char tmpPrevPrevChar = '_';
if (tmpIndex > 0) {
tmpPrevChar = tmpStringBuilder.charAt(tmpIndex - 1);
}
if (tmpIndex > 1) {
tmpPrevPrevChar = tmpStringBuilder.charAt(tmpIndex - 2);
}
switch (tmpChar) {
case 'c':
//c in [Sc], [Tc], and [Ac] should not be replaced
if ((tmpPrevChar == 'S' || tmpPrevChar == 'T' || tmpPrevChar == 'A') && tmpPrevPrevChar == '[') {
break;
} else {
tmpStringBuilder.setCharAt(tmpIndex, 'C');
tmpStringBuilder.insert(tmpIndex + 1, '*');
}
break;
case 'n':
//n in [Mn], [Zn], [Cn], [In], [Sn], and [Rn] should not be replaced
if ((tmpPrevChar == 'M' || tmpPrevChar == 'Z' || tmpPrevChar == 'C' || tmpPrevChar == 'I' || tmpPrevChar == 'S' || tmpPrevChar == 'R') && tmpPrevPrevChar == '[') {
break;
} else {
tmpStringBuilder.setCharAt(tmpIndex, 'N');
tmpStringBuilder.insert(tmpIndex + 1, '*');
}
break;
case 's':
//s in [Cs], [Os], [As], [Es], [Hs], and [Uus] should not be replaced
if ((tmpPrevChar == 'C' || tmpPrevChar == 'O' || tmpPrevChar == 'A' || tmpPrevChar == 'E' || tmpPrevChar == 'H') && tmpPrevPrevChar == '[') {
break;
} else if (tmpPrevChar == 'u' && tmpPrevPrevChar == 'U') {
break;
} else {
tmpStringBuilder.setCharAt(tmpIndex, 'S');
tmpStringBuilder.insert(tmpIndex + 1, '*');
}
break;
case 'o':
//o in [Mo], [Co], [Po], [Uuo], [Ho], and [No] should not be replaced
if ((tmpPrevChar == 'M' || tmpPrevChar == 'C' || tmpPrevChar == 'P' || tmpPrevChar == 'H' || tmpPrevChar == 'N') && tmpPrevPrevChar == '[') {
break;
} else if (tmpPrevChar == 'u' && tmpPrevPrevChar == 'U') {
break;
} else {
tmpStringBuilder.setCharAt(tmpIndex, 'O');
tmpStringBuilder.insert(tmpIndex + 1, '*');
}
break;
case 'p':
//p in [Uup] and [Np] should not be replaced
if (tmpPrevChar == 'N' && tmpPrevPrevChar == '[') {
break;
} else if (tmpPrevChar == 'u' && tmpPrevPrevChar == 'U') {
break;
} else {
tmpStringBuilder.setCharAt(tmpIndex, 'P');
tmpStringBuilder.insert(tmpIndex + 1, '*');
}
break;
default:
break;
}
tmpLength = tmpStringBuilder.length();
}
tmpPseudoSmilesCode = tmpStringBuilder.toString();
return tmpPseudoSmilesCode;
}
//
//
//
//
/**
* Returns the CDK title or ID of the given molecule.
*
* @param aMolecule the molecule to determine the title or ID of
* @return the CDK title, title or ID of the given molecule (depending on which property is set)
* @throws NullPointerException if aMolecule is 'null'
*/
private static String getIDForLogging(IAtomContainer aMolecule) throws NullPointerException {
Objects.requireNonNull(aMolecule, "Given molecule is 'null'.");
String tmpCdkTitle = aMolecule.getProperty(CDKConstants.TITLE);
String tmpTitle = aMolecule.getTitle();
String tmpID = aMolecule.getID();
if (!Objects.isNull(tmpCdkTitle) && !tmpCdkTitle.isEmpty()) {
return "CDK title: " + tmpCdkTitle;
} else if (!Objects.isNull(tmpTitle) && !tmpTitle.isEmpty()) {
return "Title: " + tmpTitle;
} else if (!Objects.isNull(tmpID) && !tmpID.isEmpty()) {
return "ID: " + tmpID;
} else {
return "No title or id could be determined.";
}
}
//
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy