-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tool to compute all simple cycles using the new path graph implementa…
…tions. Signed-off-by: Egon Willighagen <egonw@users.sourceforge.net>
- Loading branch information
Showing
2 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package org.openscience.cdk.graph; | ||
|
||
import org.openscience.cdk.annotations.TestClass; | ||
import org.openscience.cdk.annotations.TestMethod; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* Compute all simple cycles (rings) in a graph. Generally speaking one does not | ||
* need all the cycles and tractable subsets offer good alternatives. | ||
* | ||
* <ul> <li>EdgeShortCycles - the smallest cycle through each edge</li> | ||
* <li>{@link RelevantCycles} - union of all minimum cycle bases - unique but | ||
* may be exponential</li> <li>{@link EssentialCycles} - intersection of all | ||
* minimum cycle bases </li> <li>{@link MinimumCycleBasis} - a minimum cycles | ||
* basis, may not be unique. Often used interchangeable with the term SSSR.</li> | ||
* </ul> | ||
* | ||
* | ||
* For maximum performance the algorithm should be run only on ring systems (a | ||
* biconnected component with at least one tri-connected vertex). An example of | ||
* this is shown below: | ||
* | ||
* <blockquote><pre> | ||
* // convert the molecule to adjacency list - may be redundant in future | ||
* IAtomContainer m =...; | ||
* int[][] g = GraphUtil.toAdjList(m); | ||
* | ||
* // efficient computation/partitioning of the ring systems | ||
* RingSearch rs = new RingSearch(m, g); | ||
* | ||
* // isolated cycles don't need to be run | ||
* rs.isolated(); | ||
* | ||
* // process fused systems separately | ||
* for (int[] fused : rs.fused()) { | ||
* // given the fused subgraph, max cycle size is | ||
* // the number of vertices | ||
* AllCycles ac = new AllCycles(subgraph(g, fused), | ||
* fused.length, | ||
* maxDegree); | ||
* // cyclic walks | ||
* int[][] paths = ac.paths(); | ||
* } | ||
* </pre></blockquote> | ||
* | ||
* @author John May | ||
* @cdk.module core | ||
* @see RegularPathGraph | ||
* @see JumboPathGraph | ||
* @see org.openscience.cdk.ringsearch.RingSearch | ||
* @see GraphUtil | ||
* @see <a href="http://efficientbits.blogspot.co.uk/2013/06/allringsfinder-sport-edition.html">Performance | ||
* Analysis (Blog Post)</a> | ||
*/ | ||
@TestClass("org.openscience.cdk.graph.AllCyclesTest") | ||
public final class AllCycles { | ||
|
||
/** All simple cycles. */ | ||
private final List<int[]> cycles = new ArrayList<int[]>(); | ||
|
||
/** Indicates whether the perception completed. */ | ||
private final boolean completed; | ||
|
||
/** | ||
* Compute all simple cycles up to given <i>maxCycleSize</i> in the provided | ||
* <i>graph</i>. In some graphs the topology makes it impracticable to | ||
* compute all the simple. To avoid running forever on these molecules the | ||
* <i>maxDegree</i> provides an escape clause. The value doesn't quantify | ||
* how many cycles we get. The percentage of molecules in PubChem Compound | ||
* (Dec '12) which would successfully complete for a given degree are listed | ||
* below. | ||
* | ||
* <table> <tr><th>Percent</th><th>Max Degree</th></tr> | ||
* <tr><td>99%</td><td>9</td></tr> <tr><td>99.95%</td><td>72</td></tr> | ||
* <tr><td>99.96%</td><td>84</td></tr> <tr><td>99.97%</td><td>126</td></tr> | ||
* <tr><td>99.98%</td><td>216</td></tr> <tr><td>99.99%</td><td>684</td></tr> | ||
* </table> | ||
* | ||
* @param graph adjacency list representation of a graph | ||
* @param maxCycleSize the maximum cycle size to perceive | ||
* @param maxDegree escape clause to stop the algorithm running forever | ||
*/ | ||
public AllCycles(final int[][] graph, | ||
final int maxCycleSize, | ||
final int maxDegree) { | ||
|
||
// get the order in which we remove vertices, the rank tells us | ||
// the index in the ordered array of each vertex | ||
int[] rank = rank(graph); | ||
int[] vertices = verticesInOrder(rank); | ||
|
||
PathGraph pGraph = | ||
graph.length < 64 | ||
? new RegularPathGraph(graph, rank, maxCycleSize) | ||
: new JumboPathGraph(graph, rank, maxCycleSize); | ||
|
||
// perceive the cycles by removing the vertices in order | ||
int removed = 0; | ||
for (final int v : vertices) { | ||
|
||
if (pGraph.degree(v) > maxDegree) | ||
break; // or could throw exception... | ||
|
||
pGraph.remove(v, cycles); | ||
removed++; | ||
} | ||
|
||
completed = removed == graph.length; | ||
} | ||
|
||
/** | ||
* Using the pre-computed rank, get the vertices in order. | ||
* | ||
* @param rank see {@link #rank(int[][])} | ||
* @return vertices in order | ||
*/ | ||
@TestMethod("verticesInOrder") | ||
static int[] verticesInOrder(final int[] rank) { | ||
int[] vs = new int[rank.length]; | ||
for (int v = 0; v < rank.length; v++) | ||
vs[rank[v]] = v; | ||
return vs; | ||
} | ||
|
||
/** | ||
* Compute a rank for each vertex. This rank is based on the degree and | ||
* indicates the position each vertex would be in a sorted array. | ||
* | ||
* @param g a graph in adjacent list representation | ||
* @return array indicating the rank of each vertex. | ||
*/ | ||
@TestMethod("rank") | ||
static int[] rank(final int[][] g) { | ||
final int ord = g.length; | ||
|
||
final int[] count = new int[ord + 1]; | ||
final int[] rank = new int[ord]; | ||
|
||
// frequency of each degree | ||
for (int v = 0; v < ord; v++) | ||
count[g[v].length + 1]++; | ||
// cumulated counts | ||
for (int i = 0; count[i] < ord; i++) | ||
count[i + 1] += count[i]; | ||
// store sorted position of each vertex | ||
for (int v = 0; v < ord; v++) | ||
rank[v] = count[g[v].length]++; | ||
|
||
return rank; | ||
} | ||
|
||
/** | ||
* The paths describing all simple cycles in the given graph. The path stats | ||
* and ends vertex. | ||
* | ||
* @return 2d array of paths | ||
*/ | ||
@TestMethod("k4Paths,k5Paths") | ||
public int[][] paths() { | ||
int[][] paths = new int[cycles.size()][]; | ||
for (int i = 0; i < cycles.size(); i++) | ||
paths[i] = cycles.get(i).clone(); | ||
return paths; | ||
} | ||
|
||
/** | ||
* Cardinality of the set. | ||
* | ||
* @return number of cycles | ||
*/ | ||
@TestMethod("k4Size,k5Size,k6Size,k7Size") | ||
public int size() { | ||
return cycles.size(); | ||
} | ||
|
||
/** | ||
* Did the cycle perception complete - if not the molecule was considered | ||
* impractical and computation was aborted. | ||
* | ||
* @return algorithm completed | ||
*/ | ||
@TestMethod("completed,impractical") | ||
public boolean completed() { | ||
return completed; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package org.openscience.cdk.graph; | ||
|
||
import org.junit.Test; | ||
|
||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertThat; | ||
import static org.junit.Assert.assertTrue; | ||
import static org.openscience.cdk.graph.RegularPathGraphTest.completeGraphOfSize; | ||
|
||
/** | ||
* @author John May | ||
* @cdk.module test-core | ||
*/ | ||
public class AllCyclesTest { | ||
|
||
@Test | ||
public void rank() throws Exception { | ||
// given vertices based on degree | ||
int[][] g = new int[][]{ | ||
{0, 0, 0, 0}, // 5th | ||
{0, 0, 0, 0}, // 6th | ||
{0, 0, 0}, // 4th | ||
{0}, // 2nd | ||
{0, 0}, // 3rd | ||
{} // 1st | ||
}; | ||
assertThat(AllCycles.rank(g), | ||
is(new int[]{4, 5, 3, 1, 2, 0})); | ||
} | ||
|
||
@Test | ||
public void verticesInOrder() { | ||
int[] vertices = AllCycles.verticesInOrder(new int[]{4, 3, 1, 2, 0}); | ||
assertThat(vertices, is(new int[]{4, 2, 3, 1, 0})); | ||
} | ||
|
||
@Test public void completed() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(4), | ||
4, | ||
100); | ||
assertTrue(ac.completed()); | ||
assertThat(ac.size(), is(7)); | ||
} | ||
|
||
@Test(timeout = 50) | ||
public void impractical() { | ||
// k12 - ouch | ||
AllCycles ac = new AllCycles(completeGraphOfSize(12), | ||
12, | ||
100); | ||
assertFalse(ac.completed()); | ||
} | ||
|
||
@Test | ||
public void k4Paths() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(4), | ||
4, | ||
1000); | ||
assertThat(ac.paths(), is(new int[][]{{2, 1, 0, 2}, | ||
{3, 1, 0, 3}, | ||
{3, 2, 0, 3}, | ||
{3, 2, 1, 3}, | ||
{3, 2, 1, 0, 3}, | ||
{3, 2, 0, 1, 3}, | ||
{3, 0, 2, 1, 3}})); | ||
|
||
} | ||
|
||
@Test | ||
public void k5Paths() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(5), | ||
5, | ||
1000); | ||
assertThat(ac.paths(), is(new int[][]{{2, 1, 0, 2}, | ||
{3, 1, 0, 3}, | ||
{4, 1, 0, 4}, | ||
{3, 2, 0, 3}, | ||
{3, 2, 1, 3}, | ||
{3, 2, 1, 0, 3}, | ||
{3, 2, 0, 1, 3}, | ||
{4, 2, 0, 4}, | ||
{4, 2, 1, 4}, | ||
{4, 2, 1, 0, 4}, | ||
{4, 2, 0, 1, 4}, | ||
{3, 0, 2, 1, 3}, | ||
{4, 0, 2, 1, 4}, | ||
{4, 3, 0, 4}, | ||
{4, 3, 1, 4}, | ||
{4, 3, 1, 0, 4}, | ||
{4, 3, 0, 1, 4}, | ||
{4, 3, 2, 4}, | ||
{4, 3, 2, 0, 4}, | ||
{4, 3, 2, 1, 4}, | ||
{4, 3, 2, 1, 0, 4}, | ||
{4, 3, 2, 0, 1, 4}, | ||
{4, 3, 0, 2, 4}, | ||
{4, 3, 1, 2, 4}, | ||
{4, 3, 0, 1, 2, 4}, | ||
{4, 3, 1, 0, 2, 4}, | ||
{4, 3, 0, 2, 1, 4}, | ||
{4, 3, 1, 2, 0, 4}, | ||
{4, 0, 3, 1, 4}, | ||
{4, 0, 3, 2, 4}, | ||
{4, 0, 3, 2, 1, 4}, | ||
{4, 0, 3, 1, 2, 4}, | ||
{4, 1, 3, 2, 4}, | ||
{4, 1, 3, 2, 0, 4}, | ||
{4, 1, 3, 0, 2, 4}, | ||
{4, 0, 1, 3, 2, 4}, | ||
{4, 1, 0, 3, 2, 4}})); | ||
} | ||
|
||
|
||
@Test | ||
public void k4Size() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(4), | ||
4, | ||
1000); | ||
assertThat(ac.size(), is(7)); | ||
} | ||
|
||
@Test | ||
public void k5Size() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(5), | ||
5, | ||
1000); | ||
assertThat(ac.size(), is(37)); | ||
} | ||
|
||
@Test | ||
public void k6Size() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(6), | ||
6, | ||
1000); | ||
assertThat(ac.size(), is(197)); | ||
} | ||
|
||
@Test | ||
public void k7Size() { | ||
AllCycles ac = new AllCycles(completeGraphOfSize(7), | ||
7, | ||
1000); | ||
assertThat(ac.size(), is(1172)); | ||
} | ||
} |