/* $Revision$ $Author$ $Date$ * * Copyright (C) 2007 Egon Willighagen <egonw@users.sf.net> * * Contact: cdk-devel@lists.sourceforge.net * * 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. * All we ask is that proper credit is given for our work, which includes * - but is not limited to - adding the above copyright notice to the beginning * of your source code files, and to any copyright notice that you may distribute * with programs based on this work. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openscience.cdk.aromaticity; import java.util.Iterator; import org.openscience.cdk.CDKConstants; import org.openscience.cdk.annotations.TestClass; import org.openscience.cdk.annotations.TestMethod; import org.openscience.cdk.atomtype.CDKAtomTypeMatcher; import org.openscience.cdk.config.AtomTypeFactory; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.graph.ConnectivityChecker; import org.openscience.cdk.graph.SpanningTree; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IAtomType; import org.openscience.cdk.interfaces.IBond; import org.openscience.cdk.interfaces.IRingSet; import org.openscience.cdk.interfaces.IAtomType.Hybridization; import org.openscience.cdk.ringsearch.AllRingsFinder; import org.openscience.cdk.ringsearch.SSSRFinder; /** * This aromaticity detector detects the aromaticity based on the Hückel * 4n+2 pi-electrons rule applied to isolated ring systems. It assumes * CDK atom types to be perceived with the {@link CDKAtomTypeMatcher} or with * any compatible class. For example: * <pre> * Molecule molecule = MoleculeFactory.makePyridineOxide(); * AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule); * CDKHueckelAromaticityDetector.detectAromaticity(molecule); * </pre> * * @author egonw * @cdk.module standard * @cdk.githash * @cdk.created 2007-10-05 * * @see org.openscience.cdk.CDKConstants */ @TestClass("org.openscience.cdk.aromaticity.CDKHueckelAromaticityDetectorTest") public class CDKHueckelAromaticityDetector { private static AtomTypeFactory factory = null; @TestMethod("testDetectAromaticity_IAtomContainer") public static boolean detectAromaticity(IAtomContainer atomContainer) throws CDKException { SpanningTree spanningTree = new SpanningTree(atomContainer); IAtomContainer ringSystems = spanningTree.getCyclicFragmentsContainer(); if (ringSystems.getAtomCount() == 0) { // If there are no rings, then there cannot be any aromaticity return false; } // disregard all atoms we know that cannot be aromatic anyway for (IAtom atom : ringSystems.atoms()) if (!atomIsPotentiallyAromatic(atom)) ringSystems.removeAtomAndConnectedElectronContainers(atom); // FIXME: should not really mark them here Iterator<IAtom> atoms = ringSystems.atoms().iterator(); while (atoms.hasNext()) atoms.next().setFlag(CDKConstants.ISINRING, true); Iterator<IBond> bonds = ringSystems.bonds().iterator(); while (bonds.hasNext()) bonds.next().setFlag(CDKConstants.ISINRING, true); boolean foundSomeAromaticity = false; Iterator<IAtomContainer> isolatedRingSystems = ConnectivityChecker.partitionIntoMolecules(ringSystems).atomContainers().iterator(); while (isolatedRingSystems.hasNext()) { IAtomContainer isolatedSystem = isolatedRingSystems.next(); IRingSet singleRings = new SSSRFinder(isolatedSystem).findSSSR(); Iterator<IAtomContainer> singleRingsIterator = singleRings.atomContainers().iterator(); int maxRingSize = 20; boolean atLeastOneRingIsSprouted = false; boolean allRingsAreAromatic = true; // test single rings in SSSR while (singleRingsIterator.hasNext()) { IAtomContainer singleRing = singleRingsIterator.next(); if (singleRing.getAtomCount() > maxRingSize) maxRingSize = singleRing.getAtomCount(); if (isRingSystemSproutedWithNonRingDoubleBonds(atomContainer, singleRing)) { // OK, this ring is not aromatic atLeastOneRingIsSprouted = true; allRingsAreAromatic = false; } else { // possibly aromatic boolean ringIsAromatic = isHueckelValid(singleRing); foundSomeAromaticity |= ringIsAromatic; allRingsAreAromatic &= ringIsAromatic; if (ringIsAromatic) markRingAtomsAndBondsAromatic(singleRing); } } // OK, what about the one larger ring (if no aromaticity found in SSSR)? if (!allRingsAreAromatic && !atLeastOneRingIsSprouted && singleRings.getAtomContainerCount() <= 3) { // every ring system consisting of more than two rings is too difficult Iterator<IAtomContainer> allRingsIterator = new AllRingsFinder().findAllRingsInIsolatedRingSystem(isolatedSystem).atomContainers().iterator(); while (allRingsIterator.hasNext()) { // there should be exactly three rings, of which only one has a size larger // than the two previous ones IAtomContainer ring = allRingsIterator.next(); if (ring.getAtomCount() <= maxRingSize) { // possibly aromatic boolean ringIsAromatic = isHueckelValid(ring); foundSomeAromaticity |= ringIsAromatic; if (ringIsAromatic) markRingAtomsAndBondsAromatic(ring); } } } } return foundSomeAromaticity; } /** * Tests if the electron count matches the Hückel 4n+2 rule. */ private static boolean isHueckelValid(IAtomContainer singleRing) throws CDKException { int electronCount = 0; for (IAtom ringAtom : singleRing.atoms()) { if (ringAtom.getHybridization() != CDKConstants.UNSET && (ringAtom.getHybridization() == Hybridization.SP2) || ringAtom.getHybridization() == Hybridization.PLANAR3) { // for example, a carbon // note: the double bond is in the ring, that has been tested earlier // FIXME: this does assume bond orders to be resolved too, when detecting // sprouting double bonds if ("N.planar3".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else if ("N.minus.planar3".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else if ("S.2".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else if ("S.planar3".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else if ("C.minus.planar".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else if ("O.planar3".equals(ringAtom.getAtomTypeName())) { electronCount += 2; } else { if (factory == null) { factory = AtomTypeFactory.getInstance( "org/openscience/cdk/dict/data/cdk-atom-types.owl", ringAtom.getBuilder() ); } IAtomType type = factory.getAtomType(ringAtom.getAtomTypeName()); Object property = type.getProperty(CDKConstants.PI_BOND_COUNT); if (property != null && property instanceof Integer) { electronCount += ((Integer)property).intValue(); } } } else if (ringAtom.getHybridization() != null && ringAtom.getHybridization() == Hybridization.SP3 && getLonePairCount(ringAtom) > 0) { // for example, a nitrogen or oxygen electronCount += 2; } } return (electronCount % 4 == 2) && (electronCount > 2); } private static boolean atomIsPotentiallyAromatic(IAtom atom) { if (atom.getHybridization() == Hybridization.SP2) return true; if (atom.getHybridization() == Hybridization.PLANAR3) return true; if (atom.getHybridization() == Hybridization.SP3 && getLonePairCount(atom) > 0) return true; return false; } /** * Determines if the isolatedRingSystem has attached double bonds, which are not part of the ring system itself, * and not part of any other ring system. */ private static boolean isRingSystemSproutedWithNonRingDoubleBonds(IAtomContainer fullContainer, IAtomContainer isolatedRingSystem) { Iterator<IAtom> atoms = isolatedRingSystem.atoms().iterator(); while (atoms.hasNext()) { Iterator<IBond> neighborBonds = fullContainer.getConnectedBondsList(atoms.next()).iterator(); while (neighborBonds.hasNext()) { IBond neighborBond = neighborBonds.next(); if (!neighborBond.getFlag(CDKConstants.ISINRING) && neighborBond.getOrder() == CDKConstants.BONDORDER_DOUBLE || neighborBond.getOrder() == CDKConstants.BONDORDER_TRIPLE) { return true; } } } return false; } private static int getLonePairCount(IAtom atom) { Integer count = (Integer)atom.getProperty(CDKConstants.LONE_PAIR_COUNT); if (count == null) { return 0; } else { return count; } } private static void markRingAtomsAndBondsAromatic(IAtomContainer container) { for (IAtom atom : container.atoms()) atom.setFlag(CDKConstants.ISAROMATIC, true); for (IBond bond : container.bonds()) bond.setFlag(CDKConstants.ISAROMATIC, true); } }