Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 773135d

Browse files
refactor(GraphProductCalculator): rewrite strongProduct via edge iteration; add tests
Previous strongProduct() enumerated every pair of product vertices (O((V_G*V_H)^2)) and parsed the synthetic '(u,v)' labels back into component strings on each comparison via indexOf/lastIndexOf. For even modestly sized inputs (e.g. P10 strong P10 -> 100 vertices, 4950 pairs) this dominated runtime and produced a lot of throwaway substrings. The strong product G \u22a0 H is by definition the edge-disjoint union of the Cartesian and tensor products. The new implementation constructs it directly from the edge sets of G and H: 1. Cartesian (G-factor): for each H-vertex v, copy every G-edge. 2. Cartesian (H-factor): for each G-vertex u, copy every H-edge. 3. Tensor: for each (G-edge, H-edge) pair add both diagonals. Cost is O(E_G*V_H + V_G*E_H + E_G*E_H), matching the closed-form edge count, with no string parsing on the hot path. Behaviour is unchanged: same product vertex labels, same edge type ('product'), same caching, same ProductInfo timings/counts. New GraphProductCalculatorTest (17 tests) locks in correctness across all four product types using hand-verifiable small cases plus the closed-form edge-count formulas (K2 box K2 = C4; K2 strong K2 = K4; P3 strong P3 has 20 edges; etc.) and covers caching + report output.
1 parent 481ee34 commit 773135d

2 files changed

Lines changed: 237 additions & 56 deletions

File tree

Gvisual/src/gvisual/GraphProductCalculator.java

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import edu.uci.ics.jung.graph.Graph;
44
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
5+
import edu.uci.ics.jung.graph.util.Pair;
56
import java.util.*;
67

78
/**
@@ -270,58 +271,59 @@ public Graph<String, Edge> strongProduct() {
270271
long start = System.currentTimeMillis();
271272
Graph<String, Edge> product = new UndirectedSparseGraph<String, Edge>();
272273

274+
// Add all product vertices
273275
for (String u : verticesG) {
274276
for (String v : verticesH) {
275277
product.addVertex(productVertex(u, v));
276278
}
277279
}
278280

279-
// Build adjacency sets for fast lookup
280-
Set<String> gEdgeSet = new HashSet<String>();
281-
for (String u1 : verticesG) {
282-
for (String u2 : verticesG) {
283-
if (!u1.equals(u2) && areAdjacent(graphG, u1, u2)) {
284-
gEdgeSet.add(u1 + "\0" + u2);
285-
}
281+
// The strong product G ⊠ H is the edge-disjoint union of the
282+
// Cartesian and tensor products. We construct it directly from
283+
// the edge sets of G and H instead of materialising every pair
284+
// of product vertices and parsing the synthetic labels back.
285+
//
286+
// Cost: O(E_G · V_H + V_G · E_H + E_G · E_H) edges added (which
287+
// matches the theoretical formula). The previous implementation
288+
// was O((V_G · V_H)²) with per-pair String.indexOf parsing,
289+
// dominating runtime on even modest product graphs.
290+
291+
// 1) Cartesian (G-factor): for each H-vertex v, copy edges of G.
292+
for (Edge eg : graphG.getEdges()) {
293+
Pair<String> ep = graphG.getEndpoints(eg);
294+
String u1 = ep.getFirst();
295+
String u2 = ep.getSecond();
296+
for (String v : verticesH) {
297+
product.addEdge(newProductEdge(u1, v, u2, v),
298+
productVertex(u1, v), productVertex(u2, v));
286299
}
287300
}
288-
Set<String> hEdgeSet = new HashSet<String>();
289-
for (String v1 : verticesH) {
290-
for (String v2 : verticesH) {
291-
if (!v1.equals(v2) && areAdjacent(graphH, v1, v2)) {
292-
hEdgeSet.add(v1 + "\0" + v2);
293-
}
301+
302+
// 2) Cartesian (H-factor): for each G-vertex u, copy edges of H.
303+
for (Edge eh : graphH.getEdges()) {
304+
Pair<String> ep = graphH.getEndpoints(eh);
305+
String v1 = ep.getFirst();
306+
String v2 = ep.getSecond();
307+
for (String u : verticesG) {
308+
product.addEdge(newProductEdge(u, v1, u, v2),
309+
productVertex(u, v1), productVertex(u, v2));
294310
}
295311
}
296312

297-
// Iterate all pairs of product vertices
298-
List<String> allProductVertices = new ArrayList<String>(product.getVertices());
299-
for (int i = 0; i < allProductVertices.size(); i++) {
300-
for (int j = i + 1; j < allProductVertices.size(); j++) {
301-
String pv1 = allProductVertices.get(i);
302-
String pv2 = allProductVertices.get(j);
303-
304-
// Parse back the components
305-
String u1 = parseFirst(pv1);
306-
String v1 = parseSecond(pv1);
307-
String u2 = parseFirst(pv2);
308-
String v2 = parseSecond(pv2);
309-
310-
boolean uEqual = u1.equals(u2);
311-
boolean vEqual = v1.equals(v2);
312-
boolean uAdj = gEdgeSet.contains(u1 + "\0" + u2);
313-
boolean vAdj = hEdgeSet.contains(v1 + "\0" + v2);
314-
315-
boolean connected = false;
316-
if (uEqual && vAdj) connected = true; // Cartesian (H-factor)
317-
else if (vEqual && uAdj) connected = true; // Cartesian (G-factor)
318-
else if (uAdj && vAdj) connected = true; // Tensor
319-
320-
if (connected) {
321-
Edge e = new Edge("product", pv1, pv2);
322-
e.setLabel("e" + (edgeCounter++));
323-
product.addEdge(e, pv1, pv2);
324-
}
313+
// 3) Tensor part: for each (G-edge, H-edge) pair add both
314+
// diagonals (u1,v1)-(u2,v2) and (u1,v2)-(u2,v1).
315+
for (Edge eg : graphG.getEdges()) {
316+
Pair<String> egp = graphG.getEndpoints(eg);
317+
String u1 = egp.getFirst();
318+
String u2 = egp.getSecond();
319+
for (Edge eh : graphH.getEdges()) {
320+
Pair<String> ehp = graphH.getEndpoints(eh);
321+
String v1 = ehp.getFirst();
322+
String v2 = ehp.getSecond();
323+
product.addEdge(newProductEdge(u1, v1, u2, v2),
324+
productVertex(u1, v1), productVertex(u2, v2));
325+
product.addEdge(newProductEdge(u1, v2, u2, v1),
326+
productVertex(u1, v2), productVertex(u2, v1));
325327
}
326328
}
327329

@@ -478,19 +480,4 @@ public List<Integer> getDegreeSequence(ProductType type) {
478480
return degrees;
479481
}
480482

481-
// --- Parsing helpers for "(u,v)" vertex labels ---
482-
483-
private String parseFirst(String productVertex) {
484-
// "(u,v)" -> "u"
485-
int start = productVertex.indexOf('(') + 1;
486-
int comma = productVertex.lastIndexOf(',');
487-
return productVertex.substring(start, comma);
488-
}
489-
490-
private String parseSecond(String productVertex) {
491-
// "(u,v)" -> "v"
492-
int comma = productVertex.lastIndexOf(',');
493-
int end = productVertex.lastIndexOf(')');
494-
return productVertex.substring(comma + 1, end);
495-
}
496483
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package gvisual;
2+
3+
import edu.uci.ics.jung.graph.Graph;
4+
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.*;
8+
9+
/**
10+
* Unit tests for {@link GraphProductCalculator}.
11+
*
12+
* <p>The tests use small hand-verifiable examples and the closed-form edge
13+
* counts for each product type:</p>
14+
*
15+
* <ul>
16+
* <li>Cartesian: |E_G|\u00b7|V_H| + |V_G|\u00b7|E_H|</li>
17+
* <li>Tensor: 2\u00b7|E_G|\u00b7|E_H|</li>
18+
* <li>Strong: Cartesian + Tensor</li>
19+
* <li>Lexicographic:|E_G|\u00b7|V_H|\u00b2 + |V_G|\u00b7|E_H|</li>
20+
* </ul>
21+
*/
22+
public class GraphProductCalculatorTest {
23+
24+
private static int edgeId = 0;
25+
26+
private static Graph<String, Edge> build(String[]... edges) {
27+
Graph<String, Edge> g = new UndirectedSparseGraph<String, Edge>();
28+
for (String[] e : edges) {
29+
if (!g.containsVertex(e[0])) g.addVertex(e[0]);
30+
if (!g.containsVertex(e[1])) g.addVertex(e[1]);
31+
Edge ed = new Edge("link", e[0], e[1]);
32+
ed.setLabel("e" + (edgeId++));
33+
g.addEdge(ed, e[0], e[1]);
34+
}
35+
return g;
36+
}
37+
38+
// ─── Constructor ────────────────────────────────────────────────────
39+
40+
@Test(expected = IllegalArgumentException.class)
41+
public void nullFirstGraphRejected() {
42+
new GraphProductCalculator(null,
43+
build(new String[]{"a", "b"}));
44+
}
45+
46+
@Test(expected = IllegalArgumentException.class)
47+
public void nullSecondGraphRejected() {
48+
new GraphProductCalculator(
49+
build(new String[]{"a", "b"}), null);
50+
}
51+
52+
// ─── Cartesian product ──────────────────────────────────────────────
53+
54+
@Test
55+
public void cartesianProductOfTwoEdgesIsC4() {
56+
// K2 □ K2 = C4 (a square)
57+
Graph<String, Edge> g = build(new String[]{"a", "b"});
58+
Graph<String, Edge> h = build(new String[]{"x", "y"});
59+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
60+
61+
Graph<String, Edge> p = calc.cartesianProduct();
62+
// 2 * 2 = 4 product vertices
63+
assertEquals(4, p.getVertexCount());
64+
// |E_G|*|V_H| + |V_G|*|E_H| = 1*2 + 2*1 = 4
65+
assertEquals(4, p.getEdgeCount());
66+
}
67+
68+
@Test
69+
public void cartesianProductIsCached() {
70+
Graph<String, Edge> g = build(new String[]{"a", "b"});
71+
Graph<String, Edge> h = build(new String[]{"x", "y"});
72+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
73+
assertSame(calc.cartesianProduct(), calc.cartesianProduct());
74+
}
75+
76+
// ─── Tensor product ─────────────────────────────────────────────────
77+
78+
@Test
79+
public void tensorProductOfTwoEdgesHasTwoEdges() {
80+
// K2 × K2 = 2K2 (two disjoint edges across the diagonals)
81+
Graph<String, Edge> g = build(new String[]{"a", "b"});
82+
Graph<String, Edge> h = build(new String[]{"x", "y"});
83+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
84+
85+
Graph<String, Edge> p = calc.tensorProduct();
86+
assertEquals(4, p.getVertexCount());
87+
// 2 * |E_G| * |E_H| = 2 * 1 * 1 = 2
88+
assertEquals(2, p.getEdgeCount());
89+
}
90+
91+
// ─── Strong product (the refactored method) ─────────────────────────
92+
93+
@Test
94+
public void strongProductOfTwoEdgesEqualsCartesianPlusTensor() {
95+
Graph<String, Edge> g = build(new String[]{"a", "b"});
96+
Graph<String, Edge> h = build(new String[]{"x", "y"});
97+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
98+
99+
Graph<String, Edge> p = calc.strongProduct();
100+
assertEquals(4, p.getVertexCount());
101+
// 4 Cartesian + 2 tensor = 6
102+
assertEquals(6, p.getEdgeCount());
103+
104+
// All four product vertices should be mutually adjacent in K2⊠K2 = K4
105+
String[] verts = {"(a,x)", "(a,y)", "(b,x)", "(b,y)"};
106+
for (String v : verts) assertTrue("missing " + v, p.containsVertex(v));
107+
for (int i = 0; i < verts.length; i++) {
108+
for (int j = i + 1; j < verts.length; j++) {
109+
assertNotNull(verts[i] + "-" + verts[j],
110+
p.findEdge(verts[i], verts[j]));
111+
}
112+
}
113+
}
114+
115+
@Test
116+
public void strongProductOfPathsMatchesFormula() {
117+
// P3 ⊠ P3
118+
// G: a-b-c (2 edges, 3 verts)
119+
// H: x-y-z (2 edges, 3 verts)
120+
// Strong = |E_G|·|V_H| + |V_G|·|E_H| + 2·|E_G|·|E_H|
121+
// = 2·3 + 3·2 + 2·2·2 = 6 + 6 + 8 = 20
122+
Graph<String, Edge> g = build(
123+
new String[]{"a", "b"}, new String[]{"b", "c"});
124+
Graph<String, Edge> h = build(
125+
new String[]{"x", "y"}, new String[]{"y", "z"});
126+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
127+
128+
Graph<String, Edge> p = calc.strongProduct();
129+
assertEquals(9, p.getVertexCount());
130+
assertEquals(20, p.getEdgeCount());
131+
}
132+
133+
@Test
134+
public void strongProductIsCached() {
135+
Graph<String, Edge> g = build(new String[]{"a", "b"});
136+
Graph<String, Edge> h = build(new String[]{"x", "y"});
137+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
138+
assertSame(calc.strongProduct(), calc.strongProduct());
139+
}
140+
141+
// ─── Lexicographic product ──────────────────────────────────────────
142+
143+
@Test
144+
public void lexicographicProductEdgeCountMatchesFormula() {
145+
// |E_G|·|V_H|² + |V_G|·|E_H|
146+
Graph<String, Edge> g = build(new String[]{"a", "b"}); // 2v 1e
147+
Graph<String, Edge> h = build(new String[]{"x", "y"}); // 2v 1e
148+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
149+
150+
Graph<String, Edge> p = calc.lexicographicProduct();
151+
// 1*4 + 2*1 = 6
152+
assertEquals(6, p.getEdgeCount());
153+
}
154+
155+
// ─── Product info / report ──────────────────────────────────────────
156+
157+
@Test
158+
public void productInfoExposesCounts() {
159+
Graph<String, Edge> g = build(new String[]{"a", "b"});
160+
Graph<String, Edge> h = build(new String[]{"x", "y"});
161+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
162+
163+
GraphProductCalculator.ProductInfo info =
164+
calc.getProductInfo(GraphProductCalculator.ProductType.STRONG);
165+
assertEquals(GraphProductCalculator.ProductType.STRONG, info.getType());
166+
assertEquals(4, info.getVertexCount());
167+
assertEquals(6, info.getEdgeCount());
168+
assertTrue(info.getDensity() > 0.0);
169+
assertTrue(info.toString().contains("STRONG"));
170+
}
171+
172+
@Test
173+
public void reportMentionsAllFourProductTypes() {
174+
Graph<String, Edge> g = build(new String[]{"a", "b"});
175+
Graph<String, Edge> h = build(new String[]{"x", "y"});
176+
String rep = new GraphProductCalculator(g, h).getReport();
177+
assertTrue(rep.contains("CARTESIAN"));
178+
assertTrue(rep.contains("TENSOR"));
179+
assertTrue(rep.contains("STRONG"));
180+
assertTrue(rep.contains("LEXICOGRAPHIC"));
181+
}
182+
183+
@Test
184+
public void degreeSequenceForCartesianK2BoxK2IsAllTwos() {
185+
// C4 is 2-regular
186+
Graph<String, Edge> g = build(new String[]{"a", "b"});
187+
Graph<String, Edge> h = build(new String[]{"x", "y"});
188+
GraphProductCalculator calc = new GraphProductCalculator(g, h);
189+
java.util.List<Integer> deg = calc.getDegreeSequence(
190+
GraphProductCalculator.ProductType.CARTESIAN);
191+
assertEquals(4, deg.size());
192+
for (Integer d : deg) assertEquals(Integer.valueOf(2), d);
193+
}
194+
}

0 commit comments

Comments
 (0)