From 0c0505f045429a2562c13ff80f2c1c005a3a3273 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sat, 15 Jun 2024 04:21:31 +0300 Subject: [PATCH] compatibilty of vertices/edges in pseudographs and multigraphs + match method modification - if two vertices are to be mapped in a pseudograph, besides the same number of self-loops they must have equal labels: the list of labels of the self-loops of node1 from graph1 has to be equal to the list of labels of self-loops from graph2. - same thing for multi edges: they are compatible if have the same multiplicity and the same list of labels - plus, at the matchIterative method I modified the operation made when we find a 'dead' state --- .../iso/general/AbstractGraphIsomorphism.java | 62 ++++---- .../graph4j/iso/general/AbstractState.java | 81 +++++++++-- .../graph4j/iso/general/AbstractVF2State.java | 2 +- .../UllmanExactGraphIsomorphismTest.java | 78 ++++++++++ .../general/VF2ExactGraphIsomorphismTest.java | 137 +++++++++++++++++- .../general/VF2SubGraphIsomorphismTest.java | 21 +++ 6 files changed, 335 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/graph4j/iso/general/AbstractGraphIsomorphism.java b/src/main/java/org/graph4j/iso/general/AbstractGraphIsomorphism.java index 88a7f37..a61f7fa 100644 --- a/src/main/java/org/graph4j/iso/general/AbstractGraphIsomorphism.java +++ b/src/main/java/org/graph4j/iso/general/AbstractGraphIsomorphism.java @@ -122,18 +122,18 @@ private List matchIterative(boolean onlyFirstMapping){ // if the pair is feasible, we continue, otherwise we truncate the branch if(s.isFeasiblePair()){ - - // if the state is dead, we continue with the next pair - if(s.isDead()) { - continue; - } - - // else we add the pair to the mapping + // add to stack the current state for restoring it later stack.push(s); // just like in the recursive approach, we create a copy of the current state and then we add the pair s = getNewStateInstance(s); s.addPair(); + s.resetPreviousVertices(); + + // if the state is dead, we return to the previous state and continue with another candidate pair + if(s.isDead()) { + break; + } // if this state is a goal(complete solution), we add the mapping to the list if(s.isGoal()) { @@ -143,8 +143,6 @@ private List matchIterative(boolean onlyFirstMapping){ if(onlyFirstMapping) return mappings; } - - s.resetPreviousVertices(); } } @@ -170,33 +168,33 @@ private List matchIterative(boolean onlyFirstMapping){ protected abstract State getNewStateInstance(State s); - /* protected List match(boolean onlyFirstMapping){ - List mappings = new ArrayList<>(); - matchRecursive(getStateInstance(dg1, dg2), mappings, onlyFirstMapping); +// protected List match(boolean onlyFirstMapping){ +// List mappings = new ArrayList<>(); +// matchRecursive(getStateInstance(dg1, dg2, cache), mappings, onlyFirstMapping, 0); +// +// return mappings; +// } - return mappings; + private void matchRecursive(State s, List mappings, boolean onlyFirstMapping, int level) { + if(s.isGoal()){ + mappings.add(s.getMapping()); + return; } - private void matchRecursive(State s, List mappings, boolean onlyFirstMapping) { - if(s.isGoal()){ - mappings.add(s.getMapping()); - return; - } + if (s.isDead()) + return; - if (s.isDead()) - return; + while(s.nextPair()){ + if(s.isFeasiblePair()){ + State newS = getNewStateInstance(s); + newS.addPair(); + newS.resetPreviousVertices(); - while(s.nextPair()){ - if(s.isFeasiblePair()){ - State newS = getNewStateInstance(s); - newS.addPair(); - newS.resetPreviousVertices(); - - matchRecursive(newS, mappings, onlyFirstMapping); - newS.backTrack(); - if(onlyFirstMapping && !mappings.isEmpty()) - return; - } + matchRecursive(newS, mappings, onlyFirstMapping, level + 1); + newS.backTrack(); + if(onlyFirstMapping && !mappings.isEmpty()) + return; } - }*/ + } + } } diff --git a/src/main/java/org/graph4j/iso/general/AbstractState.java b/src/main/java/org/graph4j/iso/general/AbstractState.java index a231044..3f63dde 100644 --- a/src/main/java/org/graph4j/iso/general/AbstractState.java +++ b/src/main/java/org/graph4j/iso/general/AbstractState.java @@ -2,8 +2,12 @@ import org.graph4j.DirectedMultigraph; import org.graph4j.DirectedPseudograph; +import org.graph4j.Edge; import org.graph4j.iso.IsomorphicGraphMapping; +import java.util.ArrayList; +import java.util.List; + /** * Abstract class for the state of the search algorithm. * @@ -80,9 +84,39 @@ protected boolean compatibleVertices(int vertexIndex1, int vertexIndex2) { } } - if (o1.getGraph() instanceof DirectedPseudograph ps1 && - o2.getGraph() instanceof DirectedPseudograph ps2){ - return ps1.selfLoops(node_1) == ps2.selfLoops(node_2); + if (o1.getGraph() instanceof DirectedPseudograph ps1 && o2.getGraph() instanceof DirectedPseudograph ps2){ + if (ps1.selfLoops(node_1) != ps2.selfLoops(node_2)) { + return false; + } + + // L1 = list of all labels of self loops of vertex node_1 + // L2 = list of all labels of self loops of vertex node_2 + + List labels1 = new ArrayList<>(); + List labels2 = new ArrayList<>(); + + for (Edge e : ps1.outgoingEdgesFrom(node_1)) { + if (e != null && e.target() == node_1) { + labels1.add(e.label()); + } + } + for (Edge e : ps2.outgoingEdgesFrom(node_2)) { + if (e != null && e.target() == node_2) { + labels2.add(e.label()); + } + } + + if (labels1.size() != labels2.size()) { + return false; + } + + for (Object label : labels1) { + boolean removed = labels2.remove(label); + if (!removed) { + return false; + } + } + return true; } return true; } @@ -103,15 +137,44 @@ protected boolean compatibleEdges(int i1, int j1, int i2, int j2) { int u2 = o2.getVertexNumber(i2); int v2 = o2.getVertexNumber(j2); - if (o1.getGraph() instanceof DirectedMultigraph mg1 && - o2.getGraph() instanceof DirectedMultigraph mg2){ - return mg1.multiplicity(u1, v1) == mg2.multiplicity(u2, v2); - } + if (o1.getGraph() instanceof DirectedMultigraph mg1 && o2.getGraph() instanceof DirectedMultigraph mg2){ + if (mg1.multiplicity(u1, v1) != mg2.multiplicity(u2, v2)) { + return false; + } + // L1 = list of all labels of edge (u1, v1) + // L2 = list of all labels of edge (u2, v2) + // every label from L1 must be in L2 and vice versa - // semantic equivalence - if (o1.getGraph().getEdgeLabel(u1, v1) != null && o2.getGraph().getEdgeLabel(u2, v2) != null) { + List labels1 = new ArrayList<>(); + List labels2 = new ArrayList<>(); + + for (Edge e : mg1.outgoingEdgesFrom(u1)) { + if (e != null && e.target() == v1) { + labels1.add(e.label()); + } + } + for (Edge e : mg2.outgoingEdgesFrom(u2)) { + if (e != null && e.target() == v2) { + labels2.add(e.label()); + } + } + + if (labels1.size() != labels2.size()) { + return false; + } + + for (Object label : labels1) { + boolean removed = labels2.remove(label); + if (!removed) { + return false; + } + } + return true; + } else // not multi edges + if (o1.getGraph().getEdgeLabel(u1, v1) != null && o2.getGraph().getEdgeLabel(u2, v2) != null) { return o1.getGraph().getEdgeLabel(u1, v1).equals(o2.getGraph().getEdgeLabel(u2, v2)); } + return true; } } \ No newline at end of file diff --git a/src/main/java/org/graph4j/iso/general/AbstractVF2State.java b/src/main/java/org/graph4j/iso/general/AbstractVF2State.java index d09e015..da01980 100644 --- a/src/main/java/org/graph4j/iso/general/AbstractVF2State.java +++ b/src/main/java/org/graph4j/iso/general/AbstractVF2State.java @@ -135,7 +135,7 @@ else if (t1in_len - core_len > 0 && t2in_len - core_len > 0) { prev_2 = 0; } } - // else, check the rest of the vertices(new vertices) + // else, check the unmapped vertices else { while (prev_1 < n1 && core_1[prev_1] != NULL_NODE) { prev_1++; diff --git a/src/test/java/org/graph4j/iso/general/UllmanExactGraphIsomorphismTest.java b/src/test/java/org/graph4j/iso/general/UllmanExactGraphIsomorphismTest.java index 56bc7d8..72aac12 100644 --- a/src/test/java/org/graph4j/iso/general/UllmanExactGraphIsomorphismTest.java +++ b/src/test/java/org/graph4j/iso/general/UllmanExactGraphIsomorphismTest.java @@ -535,6 +535,84 @@ public void testDirectedPseudoGraph() { } } + @Test + public void testLabeledDirectedMultigraph() { + DirectedMultigraph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedMultigraph(); + DirectedMultigraph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedMultigraph(); + + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + + g2.addEdge(5, 3, "a"); + g2.addEdge(5, 3, "b"); + g2.addEdge(5, 3); + g2.addEdge(5, 4); + +// System.out.println("g1: " + g1); +// System.out.println("g2: " + g2); + +// System.out.println("g1 edge label of (0, 0): " + g1.getEdgeLabel(0, 0)); +// System.out.println("g2 edge label of (0, 0): " + g2.getEdgeLabel(0, 0)); + +// System.out.println("g1 edge label of (0, 1): " + g1.getEdgeLabel(0, 1)); +// System.out.println("g2 edge label of (0, 1): " + g2.getEdgeLabel(0, 1)); +// System.out.println("g2 edges: " + Arrays.toString(g2.edges())); +// System.out.println("g2 neighbors of 0: " + Arrays.toString(g2.neighbors(0))); +// for (var e : g2.outgoingEdgesFrom(0)) { +// if (e == null) +// continue; +// +// int u = e.source(); +// int v = e.target(); +// +// System.out.println("Edge: (" + u + ", " + v + ", " + e.label() + ")"); +// if (v == 1) { +// System.out.println("Edge from 0 to 1"); +// } +// } + + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + + @Test + public void testLabeledDirectedPseudoGraph() { + DirectedPseudograph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedPseudograph(); + DirectedPseudograph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedPseudograph(); + + g1.addEdge(0, 0); + g1.addEdge(0, 0, "d"); + g1.addEdge(0, 0, "c"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + + g2.addEdge(5, 5); + g2.addEdge(5, 5, "c"); + g2.addEdge(5, 5, "d"); + g2.addEdge(5, 1, "a"); + g2.addEdge(5, 1, "b"); + g2.addEdge(5, 1); + g2.addEdge(5, 4); + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + @Test public void testLargeGraph() { int n = 100; diff --git a/src/test/java/org/graph4j/iso/general/VF2ExactGraphIsomorphismTest.java b/src/test/java/org/graph4j/iso/general/VF2ExactGraphIsomorphismTest.java index 02ca8ae..7cbc09c 100644 --- a/src/test/java/org/graph4j/iso/general/VF2ExactGraphIsomorphismTest.java +++ b/src/test/java/org/graph4j/iso/general/VF2ExactGraphIsomorphismTest.java @@ -6,10 +6,7 @@ import org.graph4j.iso.TestUtil; import org.junit.Test; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -525,6 +522,138 @@ public void testPseudoGraph() { } } + @Test + public void testLabeledMultigraph() { + Multigraph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildMultigraph(); + Multigraph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildMultigraph(); + + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + g1.addEdge(0, 2, "f"); + + g2.addEdge(5, 3, "a"); + g2.addEdge(5, 3, "b"); + g2.addEdge(5, 3); + g2.addEdge(5, 4); + g2.addEdge(5, 4, "f"); + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + + @Test + public void testLabeledDirectedMultigraph() { + DirectedMultigraph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedMultigraph(); + DirectedMultigraph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedMultigraph(); + + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + g1.addEdge(0, 2, "f"); + + g2.addEdge(5, 3, "a"); + g2.addEdge(5, 3, "b"); + g2.addEdge(5, 3); + g2.addEdge(5, 4, "f"); + g2.addEdge(5, 4); + +// System.out.println("g1: " + g1); +// System.out.println("g2: " + g2); +// +// System.out.println("g1 edge label of (0, 1): " + g1.getEdgeLabel(0, 1)); +// System.out.println("g2 edge label of (0, 1): " + g2.getEdgeLabel(5, 3)); +// System.out.println("g1 edges: " + Arrays.toString(g1.edges())); +// System.out.println("g1 neighbors of 0: " + Arrays.toString(g1.neighbors(0))); +// for (var e : g1.outgoingEdgesFrom(0)) { +// if (e == null) +// continue; +// +// int u = e.source(); +// int v = e.target(); +// +// System.out.println("Edge: (" + u + ", " + v + ", " + e.label() + ")"); +// if (v == 1) { +// System.out.println("Edge from 0 to 1"); +// } +// } + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + + @Test + public void testLabeledDirectedPseudoGraph() { + DirectedPseudograph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedPseudograph(); + DirectedPseudograph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildDirectedPseudograph(); + + g1.addEdge(0, 0); + g1.addEdge(0, 0, "d"); + g1.addEdge(0, 0, "c"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + + g2.addEdge(5, 5); + g2.addEdge(5, 5, "c"); + g2.addEdge(5, 5, "d"); + g2.addEdge(5, 1, "a"); + g2.addEdge(5, 1, "b"); + g2.addEdge(5, 1); + g2.addEdge(5, 4); + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + + @Test + public void testLabeledPseudoGraph() { + Pseudograph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildPseudograph(); + Pseudograph g2 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5).buildPseudograph(); + + g1.addEdge(0, 0); + g1.addEdge(0, 0, "d"); + g1.addEdge(0, 0, "c"); + g1.addEdge(0, 1, "a"); + g1.addEdge(0, 1, "b"); + g1.addEdge(0, 1); + g1.addEdge(0, 2); + + g2.addEdge(5, 5); + g2.addEdge(5, 5, "c"); + g2.addEdge(5, 5, "d"); + g2.addEdge(5, 3, "a"); + g2.addEdge(5, 3, "b"); + g2.addEdge(5, 3); + g2.addEdge(5, 4); + + assertTrue(testIsomorphism4J(g1, g2)); + List mapping_list = testGetAllMappings4J(g1, g2); + System.out.println("All mappings: "); + for (var m : mapping_list) { + System.out.println(m); + assertTrue(m.isValidIsomorphism()); + } + } + @Test public void testDirectedPseudoGraph() { DirectedPseudograph g1 = GraphBuilder.vertices(0, 1, 2, 3, 4, 5) diff --git a/src/test/java/org/graph4j/iso/general/VF2SubGraphIsomorphismTest.java b/src/test/java/org/graph4j/iso/general/VF2SubGraphIsomorphismTest.java index 95081bd..b9721a8 100644 --- a/src/test/java/org/graph4j/iso/general/VF2SubGraphIsomorphismTest.java +++ b/src/test/java/org/graph4j/iso/general/VF2SubGraphIsomorphismTest.java @@ -3,6 +3,7 @@ import org.graph4j.Digraph; import org.graph4j.Graph; import org.graph4j.GraphBuilder; +import org.graph4j.generate.BarabasiAlbertGenerator; import org.graph4j.generate.RandomGnpGraphGenerator; import org.graph4j.iso.IsomorphicGraphMapping; import org.graph4j.iso.TestUtil; @@ -80,5 +81,25 @@ public void testHugeGraph() boolean isomorphic = testIsomorphism4J(g2, g1); System.out.println("Isomorphic: " + isomorphic); } + + @Test + public void testBarabasiGraph() { + int n = 60; + int avgDegree = 5; + Graph g1 = new BarabasiAlbertGenerator((int) (0.4 * n), avgDegree, n).createGraph(); + Graph g2 = TestUtil.generateSubgraph(g1, 0.3); + + System.out.println("Graph1: " + g1); + System.out.println("Graph2: " + g2); + + boolean isomorphic = testIsomorphism4J(g2, g1); + System.out.println("Isomorphic: " + isomorphic); + + List maps = testGetAllMappings4J(g2, g1); + + for (var map : maps) { + System.out.println("Mapping: " + map); + } + } }