/* $RCSfile$ * $Author$ * $Date$ * $Revision$ * * Copyright (C) 1997-2007 The Chemistry Development Kit (CDK) project * * 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.ringsearch; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.openscience.cdk.Atom; import org.openscience.cdk.Molecule; import org.openscience.cdk.Ring; import org.openscience.cdk.RingSet; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IBond; import org.openscience.cdk.tools.ILoggingTool; import org.openscience.cdk.tools.LoggingToolFactory; import org.openscience.cdk.tools.manipulator.RingSetManipulator; /** * Finds the Smallest Set of Smallest Rings. * This is an implementation of the algorithm published in * {@cdk.cite FIG96}. * * <p>The {@link SSSRFinder} is encouraged to be used, providing an exact * algorithm for finding the SSSR. * * @cdk.module extra * @cdk.githash * @cdk.keyword smallest-set-of-rings * @cdk.keyword ring search * @cdk.dictref blue-obelisk:findSmallestSetOfSmallestRings_Figueras * * @deprecated Use SSSRFinder instead (exact algorithm). */ public class FiguerasSSSRFinder { private static ILoggingTool logger = LoggingToolFactory.createLoggingTool(FiguerasSSSRFinder.class); int trimCounter = 0; private static final String PATH = "org.openscience.cdk.ringsearch.FiguerasSSSRFinderRFinder.PATH"; /** * Finds the Smallest Set of Smallest Rings. * * @param mol the molecule to be searched for rings * @return a RingSet containing the rings in molecule */ public RingSet findSSSR(Molecule mol) { IBond brokenBond = null; RingSet sssr = new RingSet(); Molecule molecule = new Molecule(); molecule.add(mol); IAtom smallest; int smallestDegree, nodesToBreakCounter, degree; Atom[] rememberNodes; Ring ring; //Two Vectors - as defined in the article. One to hold the //full set of atoms in the structure and on to store the numbers //of the nodes that have been trimmed away. //Furhter there is a Vector nodesN2 to store the number of N2 nodes List fullSet = new Vector(); List trimSet = new Vector(); List nodesN2 = new Vector(); initPath(molecule); logger.debug("molecule.getAtomCount(): " + molecule.getAtomCount()); // load fullSet with the numbers of our atoms for (int f = 0; f < molecule.getAtomCount(); f++) { fullSet.add(molecule.getAtom(f)); } logger.debug("fullSet.size(): " + fullSet.size()); do{ //Add nodes of degree zero to trimset. //Also add nodes of degree 2 to nodesN2. //In the same run, check, which node has the lowest degree //greater than zero. smallestDegree = 7; smallest = null; nodesN2.clear(); for (int f = 0; f < molecule.getAtomCount(); f++) { IAtom atom = molecule.getAtom(f); degree = molecule.getConnectedBondsCount(atom); if (degree == 0) { if (!trimSet.contains(atom)) { logger.debug("Atom of degree 0"); trimSet.add(atom); } } if (degree == 2) { nodesN2.add(atom); } if (degree < smallestDegree && degree > 0) { smallest = atom; smallestDegree = degree; } } if (smallest == null ) break; // If there are nodes of degree 1, trim them away if (smallestDegree == 1) { trimCounter ++; trim(smallest, molecule); trimSet.add(smallest); } // if there are nodes of degree 2, find out of which rings // they are part of. else if (smallestDegree == 2) { rememberNodes = new Atom[nodesN2.size()]; nodesToBreakCounter = 0; for (int f = 0; f < nodesN2.size(); f++) { ring = getRing((Atom)nodesN2.get(f), molecule); if (ring != null) { // check, if this ring already is in SSSR if (!RingSetManipulator.ringAlreadyInSet(ring, sssr)) { sssr.addAtomContainer(ring); rememberNodes[nodesToBreakCounter] = (Atom)nodesN2.get(f); nodesToBreakCounter++; } } } if (nodesToBreakCounter == 0) { nodesToBreakCounter = 1; rememberNodes[0] = (Atom)nodesN2.get(0); } for (int f = 0; f < nodesToBreakCounter; f++){ breakBond(rememberNodes[f], molecule); } if (brokenBond != null) { molecule.addBond(brokenBond); brokenBond = null; } } // if there are nodes of degree 3 else if (smallestDegree == 3) { ring = getRing(smallest, molecule); if (ring != null) { // check, if this ring already is in SSSR if (!RingSetManipulator.ringAlreadyInSet(ring, sssr)) { sssr.addAtomContainer(ring); } brokenBond = checkEdges(ring, molecule); molecule.removeElectronContainer(brokenBond); } } } while(trimSet.size() < fullSet.size()); logger.debug("fullSet.size(): " + fullSet.size()); logger.debug("trimSet.size(): " + trimSet.size()); logger.debug("trimCounter: " + trimCounter); // molecule.setProperty(CDKConstants.SMALLEST_RINGS, sssr); return sssr; } /** * This routine is called 'getRing() in Figueras original article * finds the smallest ring of which rootNode is part of. * * @param rootNode The Atom to be searched for the smallest ring it is part of * @param molecule The molecule that contains the rootNode * @return The smallest Ring rootnode is part of */ private Ring getRing(IAtom rootNode, Molecule molecule) { IAtom node, neighbor, mAtom; List neighbors, mAtoms; /** OKatoms is Figueras nomenclature, giving the number of atoms in the structure */ int OKatoms = molecule.getAtomCount(); /** queue for Breadth First Search of this graph */ Queue queue = new Queue(); /* Initialize a path Vector for each node */ //Vector pfad1,pfad2; List path[] = new Vector[OKatoms]; List intersection = new Vector(); List ring = new Vector(); for (int f = 0; f < OKatoms; f++) { path[f] = new Vector(); ((List)molecule.getAtom(f).getProperty(PATH)).clear(); } // Initialize the queue with nodes attached to rootNode neighbors = molecule.getConnectedAtomsList(rootNode); for (int f = 0; f < neighbors.size(); f++){ //if the degree of the f-st neighbor of rootNode is greater //than zero (i.e., it has not yet been deleted from the list) neighbor = (IAtom)neighbors.get(f); // push the f-st node onto our FIFO queue // after assigning rootNode as its source queue.push(neighbor); ((List)neighbor.getProperty(PATH)).add(rootNode); ((List)neighbor.getProperty(PATH)).add(neighbor); } while (queue.size() > 0){ node = (Atom)queue.pop(); mAtoms = molecule.getConnectedAtomsList(node); for (int f = 0; f < mAtoms.size(); f++){ mAtom = (IAtom)mAtoms.get(f); if (mAtom != ((List)node.getProperty(PATH)).get(((Vector)node.getProperty(PATH)).size() - 2)){ if (((List)mAtom.getProperty(PATH)).size() > 0){ intersection = getIntersection((List)node.getProperty(PATH), (List)mAtom.getProperty(PATH)); if (intersection.size() == 1){ // we have found a valid ring closure // now let's prepare the path to // return in tempAtomSet logger.debug("path1 ", ((List)node.getProperty(PATH))); logger.debug("path2 ", ((List)mAtom.getProperty(PATH))); logger.debug("rootNode ", rootNode); logger.debug("ring ", ring); ring = getUnion(((Vector)node.getProperty(PATH)), ((Vector)mAtom.getProperty(PATH))); return prepareRing(ring,molecule); } } else { // if path[mNumber] is null // update the path[mNumber] //pfad2 = (Vector)node.getProperty(PATH); mAtom.setProperty(PATH, (Vector)((Vector)node.getProperty(PATH)).clone()); ((List)mAtom.getProperty(PATH)).add(mAtom); //pfad1 = (Vector)mAtom.getProperty(PATH); // now push the node m onto the queue queue.push(mAtom); } } } } return null; } /** * Returns the ring that is formed by the atoms in the given vector. * * @param vec The vector that contains the atoms of the ring * @param mol The molecule this ring is a substructure of * @return The ring formed by the given atoms */ private Ring prepareRing(List vec, Molecule mol) { // add the atoms in vec to the new ring int atomCount = vec.size(); Ring ring = new Ring(atomCount); Atom[] atoms = new Atom[atomCount]; vec.toArray(atoms); ring.setAtoms(atoms); // add the bonds in mol to the new ring try { IBond b; for (int i = 0; i < atomCount - 1; i++) { b = mol.getBond(atoms[i], atoms[i + 1]); if (b != null) { ring.addBond(b); } else { logger.error("This should not happen."); } } b = mol.getBond(atoms[0], atoms[atomCount - 1]); if (b != null) { ring.addBond(b); } else { logger.error("This should not happen either."); } } catch (Exception exc) { logger.debug(exc); } logger.debug("found Ring ", ring); return ring; } /** * removes all bonds connected to the given atom leaving it with degree zero. * * @param atom The atom to be disconnecred * @param molecule The molecule containing the atom */ private void trim(IAtom atom, Molecule molecule) { List<IBond> bonds = molecule.getConnectedBondsList(atom); for (int i = 0; i < bonds.size(); i++) { molecule.removeElectronContainer((IBond)bonds.get(i)); } // you are erased! Har, har, har..... >8-) } /** * initializes a path vector in every Atom of the given molecule * * @param molecule The given molecule */ private void initPath(Molecule molecule) { for (int i = 0; i < molecule.getAtomCount(); i++) { IAtom atom = molecule.getAtom(i); atom.setProperty(PATH, new Vector()); } } /** * Returns a Vector that contains the intersection of Vectors vec1 and vec2 * * @param vec1 The first vector * @param vec2 The second vector * @return */ private List getIntersection(List vec1, List vec2) { List is = new Vector(); for (int f = 0; f < vec1.size(); f++){ if (vec2.contains((Atom)vec1.get(f))) is.add((Atom)vec1.get(f)); } return is; } /** * Returns a Vector that contains the union of Vectors vec1 and vec2 * * @param vec1 The first vector * @param vec2 The second vector * @return */ private Vector getUnion(Vector vec1, Vector vec2){ // FIXME: the JavaDoc does not describe what happens: that vec1 gets to be the union! Vector is = (Vector)vec1.clone(); for (int f = vec2.size()- 1; f > -1; f--){ if (!vec1.contains((Atom)vec2.elementAt(f))) is.addElement((Atom)vec2.elementAt(f)); } return is; } /** * Eliminates one bond of this atom from the molecule * * @param atom The atom one bond is eliminated of * @param molecule The molecule that contains the atom */ private void breakBond(Atom atom, Molecule molecule) { Iterator<IBond> bonds = molecule.bonds().iterator(); while (bonds.hasNext()) { IBond bond = (IBond) bonds.next(); if (bond.contains(atom)) { molecule.removeElectronContainer(bond); break; } } } /** * Selects an optimum edge for elimination in structures without N2 nodes. * * <p>This might be severely broken! Would have helped if there was an * explanation of how this algorithm worked. * * @param ring * @param molecule */ private IBond checkEdges(Ring ring, Molecule molecule) { Ring r1, r2; RingSet ringSet = new RingSet(); IBond bond; int minMaxSize = Integer.MAX_VALUE; int minMax = 0; logger.debug("Molecule: " + molecule); Iterator<IBond> bonds = ring.bonds().iterator(); while (bonds.hasNext()) { bond = (IBond)bonds.next(); molecule.removeElectronContainer(bond); r1 = getRing(bond.getAtom(0),molecule); r2 = getRing(bond.getAtom(1),molecule); logger.debug("checkEdges: " + bond); if (r1.getAtomCount() > r2.getAtomCount()) { ringSet.addAtomContainer(r1); } else { ringSet.addAtomContainer(r2); } molecule.addBond(bond); } for (int i = 0; i < ringSet.getAtomContainerCount(); i++) { if (((Ring)ringSet.getAtomContainer(i)).getBondCount() < minMaxSize) { minMaxSize = ((Ring)ringSet.getAtomContainer(i)).getBondCount(); minMax = i; } } return (IBond)ring.getElectronContainer(minMax); } }