org.openscience.cdk.ringsearch.AllRingsFinder Maven / Gradle / Ivy
/* Copyright (C) 2002-2007 Christoph Steinbeck
* 2009 Mark Rijnbeek
* 2013 European Bioinformatics Institute (EMBL-EBI)
* John May
* 2014 Mark B Vine (orcid:0000-0002-7794-0426)
*
* Contact: [email protected]
*
* 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 org.openscience.cdk.CDKConstants;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.graph.AllCycles;
import org.openscience.cdk.graph.GraphUtil;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IRing;
import org.openscience.cdk.interfaces.IRingSet;
import static org.openscience.cdk.graph.GraphUtil.EdgeToBondMap;
/**
* Compute the set of all rings in a molecule. This set includes every
* cyclic path of atoms. As the set is exponential it can be very large and is
* often impractical (e.g. fullerenes).
*
* To avoid combinatorial explosion there is a configurable threshold, at which
* the computation aborts. The {@link Threshold} values have been precomputed on
* PubChem-Compound and can be used with the {@link AllRingsFinder#usingThreshold(Threshold)}.
* Alternatively, other ring sets which are a subset of this set offer a
* tractable alternative.
*
*
* AllRingsFinder arf = new AllRingsFinder();
* for (IAtomContainer m : ms) {
* try {
* IRingSet rs = arf.findAllRings(m);
* } catch (CDKException e) {
* // molecule was too complex, handle error
* }
* }
*
*
* @author steinbeck
* @author johnmay
* @cdk.module standard
* @cdk.githash
* @cdk.keyword rings
* @cdk.keyword all rings
* @see AllCycles
*/
public final class AllRingsFinder {
/** Precomputed threshold - stops the computation running forever. */
private final Threshold threshold;
/**
* Constructor for the AllRingsFinder.
*
* @param logging true=logging will be done (slower), false = no logging.
* @deprecated turn logging off by setting the level in the logger
* implementation
*/
@Deprecated
public AllRingsFinder(boolean logging) {
this(Threshold.PubChem_99);
}
/** Default constructor using a threshold of {@link Threshold#PubChem_99}. */
public AllRingsFinder() {
this(Threshold.PubChem_99);
}
/** Internal constructor. */
private AllRingsFinder(Threshold threshold) {
this.threshold = threshold;
}
/**
* Compute all rings in the given {@link IAtomContainer}. The container is
* first partitioned into ring systems which are then processed separately.
* If the molecule has already be partitioned, consider using {@link
* #findAllRingsInIsolatedRingSystem(IAtomContainer)}.
*
* @param container The AtomContainer to be searched for rings
* @return A RingSet with all rings in the AtomContainer
* @throws CDKException An exception thrown if the threshold was exceeded
* @see #findAllRings(IAtomContainer, int)
* @see #findAllRingsInIsolatedRingSystem(IAtomContainer)
*/
public IRingSet findAllRings(IAtomContainer container) throws CDKException {
return findAllRings(container, container.getAtomCount());
}
/**
* Compute all rings up to and including the {@literal maxRingSize}. The
* container is first partitioned into ring systems which are then processed
* separately. If the molecule has already be partitioned, consider using
* {@link #findAllRingsInIsolatedRingSystem(IAtomContainer, int)}.
*
* @param container The AtomContainer to be searched for rings
* @param maxRingSize Maximum ring size to consider. Provides a possible
* breakout from recursion for complex compounds.
* @return A RingSet with all rings in the AtomContainer
* @throws CDKException An exception thrown if the threshold was exceeded
*/
public IRingSet findAllRings(IAtomContainer container, int maxRingSize) throws CDKException {
final EdgeToBondMap edges = EdgeToBondMap.withSpaceFor(container);
final int[][] graph = GraphUtil.toAdjList(container, edges);
RingSearch rs = new RingSearch(container, graph);
IRingSet ringSet = container.getBuilder().newInstance(IRingSet.class);
// don't need to run on isolated rings, just need to put vertices in
// cyclic order
for (int[] isolated : rs.isolated()) {
if (isolated.length <= maxRingSize) {
IRing ring = toRing(container, edges, GraphUtil.cycle(graph, isolated));
ringSet.addAtomContainer(ring);
}
}
// for each set of fused cyclic vertices run the separate search
for (int[] fused : rs.fused()) {
AllCycles ac = new AllCycles(GraphUtil.subgraph(graph, fused), Math.min(maxRingSize, fused.length),
threshold.value);
if (!ac.completed()) throw new CDKException("Threshold exceeded for AllRingsFinder");
for (int[] path : ac.paths()) {
IRing ring = toRing(container, edges, path, fused);
ringSet.addAtomContainer(ring);
}
}
return ringSet;
}
/**
* Compute all rings in the given {@link IAtomContainer}. No pre-processing
* is done on the container.
*
* @param container The Atom Container to find the ring systems of
* @return RingSet for the container
* @throws CDKException An exception thrown if the threshold was exceeded
*/
public IRingSet findAllRingsInIsolatedRingSystem(IAtomContainer container) throws CDKException {
return findAllRingsInIsolatedRingSystem(container, container.getAtomCount());
}
/**
* Compute all rings up to an including the {@literal maxRingSize}. No
* pre-processing is done on the container.
*
* @param atomContainer the molecule to be searched for rings
* @param maxRingSize Maximum ring size to consider. Provides a possible
* breakout from recursion for complex compounds.
* @return a RingSet containing the rings in molecule
* @throws CDKException An exception thrown if the threshold was exceeded
*/
public IRingSet findAllRingsInIsolatedRingSystem(IAtomContainer atomContainer, int maxRingSize) throws CDKException {
final EdgeToBondMap edges = EdgeToBondMap.withSpaceFor(atomContainer);
final int[][] graph = GraphUtil.toAdjList(atomContainer, edges);
AllCycles ac = new AllCycles(graph, maxRingSize, threshold.value);
if (!ac.completed()) throw new CDKException("Threshold exceeded for AllRingsFinder");
IRingSet ringSet = atomContainer.getBuilder().newInstance(IRingSet.class);
for (int[] path : ac.paths()) {
ringSet.addAtomContainer(toRing(atomContainer, edges, path));
}
return ringSet;
}
/**
* Checks if the timeout has been reached and throws an exception if so.
* This is used to prevent this AllRingsFinder to run for ages in certain
* rare cases with ring systems of large size or special topology.
*
* @throws CDKException The exception thrown in case of hitting the timeout
* @deprecated timeout not used
*/
@Deprecated
public void checkTimeout() throws CDKException {
// unused
}
/**
* Sets the timeout value in milliseconds of the AllRingsFinder object This
* is used to prevent this AllRingsFinder to run for ages in certain rare
* cases with ring systems of large size or special topology
*
* @param timeout The new timeout value
* @return a reference to the instance this method was called for
* @deprecated use the new threshold (during construction)
*/
@Deprecated
public AllRingsFinder setTimeout(long timeout) {
System.err.println("AllRingsFinder.setTimeout() is not used, please " + "use the new threshold values");
return this;
}
/**
* Gets the timeout values in milliseconds of the AllRingsFinder object
*
* @return The timeout value
* @deprecated timeout not used
*/
@Deprecated
public long getTimeout() {
return 0;
}
/**
* Convert a cycle in {@literal int[]} representation to an {@link IRing}.
*
* @param container atom container
* @param edges edge map
* @param cycle vertex walk forming the cycle, first and last vertex the
* same
* @return a new ring
*/
private IRing toRing(IAtomContainer container, EdgeToBondMap edges, int[] cycle) {
IRing ring = container.getBuilder().newInstance(IRing.class, 0);
int len = cycle.length - 1;
IAtom[] atoms = new IAtom[len];
IBond[] bonds = new IBond[len];
for (int i = 0; i < len; i++) {
atoms[i] = container.getAtom(cycle[i]);
bonds[i] = edges.get(cycle[i], cycle[i + 1]);
atoms[i].setFlag(CDKConstants.ISINRING, true);
}
ring.setAtoms(atoms);
ring.setBonds(bonds);
return ring;
}
/**
* Convert a cycle in {@literal int[]} representation to an {@link IRing}
* but first map back using the given {@literal mapping}.
*
* @param container atom container
* @param edges edge map
* @param cycle vertex walk forming the cycle, first and last vertex the
* same
* @return a new ring
*/
private IRing toRing(IAtomContainer container, EdgeToBondMap edges, int[] cycle, int[] mapping) {
IRing ring = container.getBuilder().newInstance(IRing.class, 0);
int len = cycle.length - 1;
IAtom[] atoms = new IAtom[len];
IBond[] bonds = new IBond[len];
for (int i = 0; i < len; i++) {
atoms[i] = container.getAtom(mapping[cycle[i]]);
bonds[i] = edges.get(mapping[cycle[i]], mapping[cycle[i + 1]]);
atoms[i].setFlag(CDKConstants.ISINRING, true);
}
ring.setAtoms(atoms);
ring.setBonds(bonds);
return ring;
}
/**
* The threshold values provide a limit at which the computation stops.
* There will always be some ring systems in which we cannot compute every
* possible ring (e.g. Fullerenes). This limit replaces the previous timeout
* and provides a more meaningful measure of what to expect based on
* precomputed percentiles. It is important to consider that, higher is not
* always better - generally the large values generate many more rings then
* can be reasonably be handled.
*
* The latest results were calculated on PubChem Compound (Dec' 12) and
* summarised below.
*
* Table 1. Num of structures processable in PubChem Compound (Dec 2012) as a result of
* setting the max degree Maximum Degree Percent
* (%) Completed
(ring systems) Uncompleted
* (ring systems)
* 72 99.95 17834013 8835
* 84 99.96 17835876 6972
* 126 99.97 17837692 5156
* 216 99.98 17839293 3555
* 684 99.99 (default) 17841065 1783
* 882 99.991 17841342 1506
* 1062 99.992 17841429 1419
* 1440 99.993 17841602 1246
* 3072 99.994 17841789 1059
*
*
* @see AllRingsFinder,
* Sport Edition
*/
public enum Threshold {
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.95% of ring systems.
*/
PubChem_95(72),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.96% of ring systems.
*/
PubChem_96(84),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.97% of ring systems.
*/
PubChem_97(126),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.98% of ring systems.
*/
PubChem_98(216),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.99% of ring systems.
*/
PubChem_99(684),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.991% of ring systems.
*/
PubChem_991(882),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.992% of ring systems.
*/
PubChem_992(1062),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.993% of ring systems.
*/
PubChem_993(1440),
/**
* Based on PubChem Compound (Dec '12), perception will complete for
* 99.994% of ring systems.
*/
PubChem_994(3072),
/** Run without any threshold, possibly until the end of time itself. */
None(Integer.MAX_VALUE);
private final int value;
private Threshold(int value) {
this.value = value;
}
}
/**
* Create an {@link AllRingsFinder} instance using the given threshold.
*
*
* // import static AllRingsFinder.Threshold.PubChem_99;
* AllRingsFinder arf = AllRingsFinder.usingThreshold(PubChem_99);
*
*
* @param threshold the threshold value
* @return instance with the set threshold
*/
public static AllRingsFinder usingThreshold(Threshold threshold) {
return new AllRingsFinder(threshold);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy