diff --git a/base/core/src/main/java/org/openscience/cdk/graph/Cycles.java b/base/core/src/main/java/org/openscience/cdk/graph/Cycles.java index c841b3cc378..03e84e07996 100644 --- a/base/core/src/main/java/org/openscience/cdk/graph/Cycles.java +++ b/base/core/src/main/java/org/openscience/cdk/graph/Cycles.java @@ -33,9 +33,11 @@ import org.openscience.cdk.interfaces.IRingSet; import org.openscience.cdk.ringsearch.RingSearch; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; +import java.util.Deque; import java.util.List; import static org.openscience.cdk.graph.GraphUtil.EdgeToBondMap; @@ -410,6 +412,153 @@ public static CycleFinder allOrVertexShort() { return or(all(), vertexShort()); } + /** + * Convenience method to determine the smallest ring size of every atom in + * the molecule. For each atom index the smallest ring size is set in the + * array, if 0 the atom is acyclic. If you just need to check a single atom + * is more efficient to call {@link #smallRingSize(IAtom, int)}. + * + * @param mol the molecule + * @param rsizes the array to be filled + * @see #smallRingSize(IAtom, int) + */ + public static void smallRingSizes(IAtomContainer mol, int[] rsizes) + { + int acount = mol.getAtomCount(); + int marker = acount+1; + if (rsizes == null || acount > rsizes.length) + throw new IllegalArgumentException(); + Arrays.fill(rsizes, 0, acount, marker); + Cycles cycles = Cycles.vertexShort(mol); + for (int[] path : cycles.paths()) { + int rsize = path.length-1; + for (int v : path) { + rsizes[v] = Math.min(rsize, rsizes[v]); + } + } + // replace temporary marker values with '0' + for (int i = 0; i < acount; i++) { + if (rsizes[i] == marker) + rsizes[i] = 0; + } + } + + /** + * Determine the smallest ring size an atom belongs to. This method requires + * that {@link #markRingAtomsAndBonds(IAtomContainer)} has been called + * first to set the {@link IAtom#isInRing()} status of each atom/bond. If + * you need to check every atom in a molecule use + * {@link @see #smallRingSizes(IAtomContainer, int[])}. + * + * @param atom the atom + * @param max the max ring size + * @return the ring size, or 0 if the atom is not in a ring or is in a ring + * larger than 'max' + * @see #smallRingSizes(IAtomContainer, int[]) + */ + public static int smallRingSize(IAtom atom, int max) { + if (!atom.isInRing()) + return 0; + IAtomContainer mol = atom.getContainer(); + int[] distTo = new int[mol.getAtomCount()]; + Arrays.fill(distTo, 1 + distTo.length); + distTo[atom.getIndex()] = 0; + Deque queue = new ArrayDeque<>(); + queue.add(atom); + int smallest = 1 + distTo.length; + while (!queue.isEmpty()) { + IAtom a = queue.poll(); + int dist = 1 + distTo[a.getIndex()]; + for (IBond b : a.bonds()) { + if (!b.isInRing()) + continue; + IAtom nbr = b.getOther(a); + if (dist < distTo[nbr.getIndex()]) { + distTo[nbr.getIndex()] = dist; + queue.add(nbr); + } else if (dist != 2 + distTo[nbr.getIndex()]) { + int tmp = dist + distTo[nbr.getIndex()]; + if (tmp < smallest) + smallest = tmp; + } + } + if (2 * dist > 1 + max) + break; + } + return smallest <= max ? smallest : 0; + } + + /** + * Determine the smallest ring size an atom belongs to. This method requires + * that {@link #markRingAtomsAndBonds(IAtomContainer)} has been called + * first to set the {@link IAtom#isInRing()} status of each atom/bond. If + * you need to check every atom in a molecule use + * {@link @see #smallRingSizes(IAtomContainer, int[])}. + * + * @param atom the atom + * @return the ring size, or 0 if the atom is not in a ring + * @see #smallRingSizes(IAtomContainer, int[]) + */ + public static int smallRingSize(IAtom atom) { + return smallRingSize(atom, atom.getContainer().getAtomCount()); + } + + /** + * Determine the smallest ring size an bond belongs to. This method requires + * that {@link #markRingAtomsAndBonds(IAtomContainer)} has been called + * first to set the {@link IBond#isInRing()} status of each atom/bond. + * + * @param bond the bond + * @param max the max ring size + * @return the ring size, or 0 if the bond is not in a ring or is in a ring + * larger than 'max' + */ + public static int smallRingSize(IBond bond, int max) { + if (!bond.isInRing()) + return 0; + IAtomContainer mol = bond.getContainer(); + int[] distTo = new int[mol.getAtomCount()]; + Arrays.fill(distTo, 1 + distTo.length); + distTo[bond.getBegin().getIndex()] = 0; + distTo[bond.getEnd().getIndex()] = 0; + Deque queue = new ArrayDeque<>(); + queue.add(bond.getBegin()); + queue.add(bond.getEnd()); + int smallest = 1 + distTo.length; + while (!queue.isEmpty()) { + IAtom a = queue.poll(); + int dist = 1 + distTo[a.getIndex()]; + for (IBond b : a.bonds()) { + if (b == bond || !b.isInRing()) + continue; + IAtom nbr = b.getOther(a); + if (dist < distTo[nbr.getIndex()]) { + distTo[nbr.getIndex()] = dist; + queue.add(nbr); + } else if (dist != 2 + distTo[nbr.getIndex()]) { + int tmp = 1 + dist + distTo[nbr.getIndex()]; + if (tmp < smallest) + smallest = tmp; + } + } + if (2 * dist > 1 + max) + break; + } + return smallest <= max ? smallest : 0; + } + + /** + * Determine the smallest ring size an bond belongs to. This method requires + * that {@link #markRingAtomsAndBonds(IAtomContainer)} has been called + * first to set the {@link IBond#isInRing()} status of each atom/bond. + * + * @param bond the bond + * @return the ring size, or 0 if the bond is not in a ring or is in a ring + * larger than 'max' + */ + public static int smallRingSize(IBond bond) { + return smallRingSize(bond, bond.getContainer().getAtomCount()); + } /** * Find and mark all cyclic atoms and bonds in the provided molecule. diff --git a/base/test-core/src/test/java/org/openscience/cdk/graph/CyclesTest.java b/base/test-core/src/test/java/org/openscience/cdk/graph/CyclesTest.java index 1b86a60b4f8..b6eb3a1c209 100644 --- a/base/test-core/src/test/java/org/openscience/cdk/graph/CyclesTest.java +++ b/base/test-core/src/test/java/org/openscience/cdk/graph/CyclesTest.java @@ -2,18 +2,23 @@ import org.junit.Test; import org.openscience.cdk.AtomContainer; +import org.openscience.cdk.exception.InvalidSmilesException; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IBond; import org.openscience.cdk.interfaces.IRingSet; import org.openscience.cdk.io.MDLV2000Reader; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.smiles.SmilesParser; import org.openscience.cdk.templates.TestMoleculeFactory; +import java.util.Arrays; import java.util.Iterator; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.openscience.cdk.templates.TestMoleculeFactory.makeAnthracene; import static org.openscience.cdk.templates.TestMoleculeFactory.makeBicycloRings; @@ -265,4 +270,135 @@ private IAtomContainer fullerene() throws Exception { static void checkSize(Cycles cs, int nCycles) { assertThat(cs.numberOfCycles(), is(nCycles)); } + + private static IAtomContainer loadSmiles(String smi) throws InvalidSmilesException { + SmilesParser smipar = new SmilesParser(SilentChemObjectBuilder.getInstance()); + IAtomContainer mol = smipar.parseSmiles(smi); + return mol; + } + + private static void assertRingSizes(String smi, int ... expected) throws InvalidSmilesException { + SmilesParser smipar = new SmilesParser(SilentChemObjectBuilder.getInstance()); + IAtomContainer mol = smipar.parseSmiles(smi); + Cycles.markRingAtomsAndBonds(mol); + int[] actual = new int[mol.getAtomCount()]; + Cycles.smallRingSizes(mol, actual); + assertThat(Arrays.toString(actual), actual, is(expected)); + } + + @Test + public void testSmallRings() throws InvalidSmilesException { + assertRingSizes("C1CCCCC1", 6, 6, 6, 6, 6, 6); + assertRingSizes("C1CCCC1", 5, 5, 5, 5, 5); + // spiro + assertRingSizes("C1CCCCC11CCCC1", 6, 6, 6, 6, 6, 5, 5, 5, 5, 5); + // fused + assertRingSizes("CCC12CCC2CCCC1", 0, 0, 4, 4, 4, 4, 6, 6, 6, 6); + assertRingSizes("[nH]1ccc2c1cccc2", 5, 5, 5, 5, 5, 6, 6, 6, 6); + // compound + assertRingSizes("Clc4cccc(N3CCN(CCCCOc2ccc1c(NC(=O)CC1)c2)CC3)c4Cl", + 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0); + } + + @Test + public void testSmallRing_atom_simple() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("C1CCCCC1"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(6,Cycles.smallRingSize(mol.getAtom(0))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(2))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(5))); + + assertEquals(0,Cycles.smallRingSize(mol.getAtom(0), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(2), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(5), 4)); + + assertEquals(6,Cycles.smallRingSize(mol.getAtom(0), 6)); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(2), 6)); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(5), 6)); + + assertEquals(6,Cycles.smallRingSize(mol.getAtom(0), 7)); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(2), 7)); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(5), 7)); + } + + @Test + public void testSmallRing_atom_acyclic() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("CCCC"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(0,Cycles.smallRingSize(mol.getAtom(0))); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(1))); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(2))); + } + + @Test + public void testSmallRing_atom_indole() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("[nH]1ccc2c1cccc2"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(5,Cycles.smallRingSize(mol.getAtom(0))); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(1))); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(2))); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(3))); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(4))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(5))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(6))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(7))); + assertEquals(6,Cycles.smallRingSize(mol.getAtom(8))); + } + + @Test + public void testSmallRing_atom_indole_lim() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("[nH]1ccc2c1cccc2"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(5,Cycles.smallRingSize(mol.getAtom(0), 5)); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(1), 5)); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(2), 5)); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(3), 5)); + assertEquals(5,Cycles.smallRingSize(mol.getAtom(4), 5)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(5), 5)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(6), 5)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(7), 5)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(8), 5)); + // limit = 4 + assertEquals(0,Cycles.smallRingSize(mol.getAtom(0), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(1), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(2), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(3), 4)); + assertEquals(0,Cycles.smallRingSize(mol.getAtom(4), 4)); + } + + @Test + public void testSmallRing_bond_indole() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("[nH]1ccc2c1cccc2"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(5,Cycles.smallRingSize(mol.getBond(0))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(1))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(2))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(3))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(4))); + assertEquals(6,Cycles.smallRingSize(mol.getBond(5))); + assertEquals(6,Cycles.smallRingSize(mol.getBond(6))); + assertEquals(6,Cycles.smallRingSize(mol.getBond(7))); + assertEquals(6,Cycles.smallRingSize(mol.getBond(8))); + } + + @Test + public void testSmallRing_bond_spiro() throws InvalidSmilesException { + IAtomContainer mol = loadSmiles("C1CCCC11CCC1"); + Cycles.markRingAtomsAndBonds(mol); // required + assertEquals(5,Cycles.smallRingSize(mol.getBond(0))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(1))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(2))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(3))); + assertEquals(5,Cycles.smallRingSize(mol.getBond(4))); + assertEquals(4,Cycles.smallRingSize(mol.getBond(5))); + assertEquals(4,Cycles.smallRingSize(mol.getBond(6))); + assertEquals(4,Cycles.smallRingSize(mol.getBond(7))); + assertEquals(4,Cycles.smallRingSize(mol.getBond(8))); + // limit=4 + assertEquals(0,Cycles.smallRingSize(mol.getBond(3),4)); + assertEquals(4,Cycles.smallRingSize(mol.getBond(7),4)); + // limit=3 + assertEquals(0,Cycles.smallRingSize(mol.getBond(3),3)); + assertEquals(0,Cycles.smallRingSize(mol.getBond(7),3)); + } }