Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Utility in the Matching API provides simplified procedure to assignin…

…g a perfect matching.

Signed-off-by: Egon Willighagen <egonw@users.sourceforge.net>
  • Loading branch information...
commit 454ed8f0e1fdf27f49a0376fca6b40f97070fa6c 1 parent 7f8e345
@johnmay johnmay authored egonw committed
View
110 base/standard/src/main/java/org/openscience/cdk/graph/Matching.java
@@ -28,6 +28,7 @@
import org.openscience.cdk.annotations.TestMethod;
import java.util.Arrays;
+import java.util.BitSet;
/**
* A matching is an independent edge set of a graph. This is a set of edges that
@@ -121,6 +122,115 @@ private Matching(final int n) {
}
/**
+ * Attempt to augment the matching such that it is perfect over the subset
+ * of vertices in the provided graph.
+ *
+ * @param graph adjacency list representation of graph
+ * @param subset subset of vertices
+ * @return the matching was perfect
+ * @throws IllegalArgumentException the graph was a diffeent size to the
+ * matching capacity
+ */
+ @TestMethod("fulvelene2")
+ public boolean perfect(int[][] graph, BitSet subset) {
+
+ if (graph.length != match.length || subset.cardinality() > graph.length)
+ throw new IllegalArgumentException("graph and matching had different capacity");
+
+ // and odd set can never provide a perfect matching
+ if ((subset.cardinality() & 0x1) == 0x1)
+ return false;
+
+ // arbitary matching was perfect
+ if (arbitaryMatching(graph, subset))
+ return true;
+
+ EdmondsMaximumMatching.maxamise(this, graph, subset);
+
+ // the matching is imperfect if any vertex was
+ for (int v = subset.nextSetBit(0); v >= 0; v = subset.nextSetBit(v + 1))
+ if (unmatched(v))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Assign an abirary matching that covers the subset of vertices.
+ *
+ * @param graph adjacency list representation of graph
+ * @param subset subset of vertices in the graph
+ * @return the matching was perfect
+ */
+ @TestMethod("perfectArbitaryMatching,imperfectArbitaryMatching")
+ boolean arbitaryMatching(final int[][] graph, final BitSet subset) {
+
+ final BitSet unmatched = new BitSet();
+
+ // indiciates the deg of each vertex in unmatched subset
+ final int[] deg = new int[graph.length];
+
+ // queue/stack of vertices with deg1 vertices
+ final int[] deg1 = new int[graph.length];
+ int nd1 = 0, nMatched = 0;
+
+ for (int v = subset.nextSetBit(0); v >= 0; v = subset.nextSetBit(v + 1)) {
+ if (matched(v)) {
+ assert subset.get(other(v));
+ nMatched++;
+ continue;
+ }
+ unmatched.set(v);
+ for (int w : graph[v])
+ if (subset.get(w) && unmatched(w))
+ deg[v]++;
+ if (deg[v] == 1)
+ deg1[nd1++] = v;
+ }
+
+ while (!unmatched.isEmpty()) {
+
+ int v = -1;
+
+ // attempt to select a vertex with degree = 1 (in matched set)
+ while (nd1 > 0) {
+ v = deg1[--nd1];
+ if (unmatched.get(v))
+ break;
+ }
+
+ // no unmatched degree 1 vertex, select the first unmatched
+ if (v < 0 || unmatched.get(v))
+ v = unmatched.nextSetBit(0);
+
+ unmatched.clear(v);
+
+ // find a unmatched edge and match it, adjacent degrees are updated
+ for (final int w : graph[v]) {
+ if (unmatched.get(w)) {
+ match(v, w);
+ nMatched += 2;
+ unmatched.clear(w);
+ // upate neighbors of w and v (if needed)
+ for (final int u : graph[w])
+ if (--deg[u] == 1 && unmatched.get(u))
+ deg1[nd1++] = u;
+
+ // if deg == 1, w is the only neighbor
+ if (deg[v] > 1) {
+ for (final int u : graph[v])
+ if (--deg[u] == 1 && unmatched.get(u))
+ deg1[nd1++] = u;
+ }
+ break;
+ }
+ }
+ }
+
+ return nMatched == subset.cardinality();
+ }
+
+ /**
* Create an empty matching with the specified capacity.
*
* @param capacity maxmium number of vertices
View
58 base/test-standard/src/test/java/org/openscience/cdk/graph/MatchingTest.java
@@ -26,6 +26,11 @@
import org.junit.Ignore;
import org.junit.Test;
+import org.openscience.cdk.interfaces.IChemObjectBuilder;
+import org.openscience.cdk.silent.SilentChemObjectBuilder;
+import org.openscience.cdk.smiles.SmilesParser;
+
+import java.util.BitSet;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -38,6 +43,9 @@
*/
public class MatchingTest {
+ private IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
+ private SmilesParser smipar = new SmilesParser(bldr);
+
@Ignore("no operation performed")
public void nop() {
}
@@ -85,6 +93,56 @@ public void nop() {
assertFalse(matching.matched(2));
}
+ @Test public void perfectArbitaryMatching() {
+ Matching matching = Matching.withCapacity(4);
+ BitSet subset = new BitSet();
+ subset.flip(0, 4);
+ assertTrue(matching.arbitaryMatching(new int[][]{
+ {1},
+ {0, 2},
+ {1, 3},
+ {2}
+ }, subset));
+ }
+
+ @Test public void imperfectArbitaryMatching() {
+ Matching matching = Matching.withCapacity(5);
+ BitSet subset = new BitSet();
+ subset.flip(0, 5);
+ assertFalse(matching.arbitaryMatching(new int[][]{
+ {1},
+ {0, 2},
+ {1, 3},
+ {2, 4},
+ {3}
+ }, subset));
+ }
+
+ @Test public void fulvelene1() throws Exception {
+ int[][] graph = GraphUtil.toAdjList(smipar.parseSmiles("c1cccc1c1cccc1"));
+ Matching m = Matching.withCapacity(graph.length);
+ BitSet subset = new BitSet();
+ subset.flip(0, graph.length);
+ // arbitary matching will assign a perfect matching here
+ assertTrue(m.arbitaryMatching(graph, subset));
+ }
+
+ @Test public void fulvelene2() throws Exception {
+ int[][] graph = GraphUtil.toAdjList(smipar.parseSmiles("c1cccc1c1cccc1"));
+ Matching m = Matching.withCapacity(graph.length);
+ BitSet subset = new BitSet();
+ subset.flip(0, graph.length);
+
+ // induced match - can't be perfected without removing this match
+ m.match(1, 2);
+
+ // arbitary matching will not be able assign a perfect matching
+ assertFalse(m.arbitaryMatching(graph, subset));
+
+ // but perfect() will
+ assertTrue(m.perfect(graph, subset));
+ }
+
@Test public void string() {
Matching matching = Matching.withCapacity(9);
matching.match(1, 3);
Please sign in to comment.
Something went wrong with that request. Please try again.