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

Commit 33518cc

Browse files
author
gardener
committed
test+fix(GraphComplementAnalyzer): cover complement invariants; fix wrong Edge-constructor argument order
GraphComplementAnalyzer.buildComplement was constructing complement edges with the wrong argument order: new Edge(v1, v2, "complement_N"), but Edge's 3-arg constructor signature is (edgeType, vertex1, vertex2). The vertex fields on each complement Edge therefore stored (v2, "complement_N") instead of (v1, v2). The JUNG graph itself stored the correct endpoints (so visual rendering and density were fine), but any caller reading Edge.getVertex1()/getVertex2() got garbage. In particular it broke composition: buildComplement(buildComplement(g)) did not round- trip because the recursive call read the corrupt fields when computing existingEdges. Fixes: - Use Graph.getEndpoints(e) instead of Edge.getVertex1/2 when reading existing edges in buildComplement and getComplementEdgeList, so the analyzer is robust to whatever fields the caller's edges carry. - Construct complement edges with the correct (type, v1, v2) order, using a stable "complement" edgeType and the running id as a label. Tests: Gvisual/test/gvisual/GraphComplementAnalyzerTest.java, 14 cases covering empty/K_n/edgeless/path/star inputs, the edge-count invariant, involution (regression test for the bug), self-complementarity detection, report sections, edge-list parity, and absence of self-loops.
1 parent c1bf7a5 commit 33518cc

2 files changed

Lines changed: 265 additions & 5 deletions

File tree

Gvisual/src/gvisual/GraphComplementAnalyzer.java

Lines changed: 16 additions & 5 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
/**
@@ -44,9 +45,12 @@ public static Graph<String, Edge> buildComplement(Graph<String, Edge> graph) {
4445

4546
Set<String> existingEdges = new HashSet<>();
4647
for (Edge e : graph.getEdges()) {
47-
String v1 = e.getVertex1();
48-
String v2 = e.getVertex2();
49-
existingEdges.add(edgeKey(v1, v2));
48+
// Read endpoints from the graph rather than from Edge fields so the
49+
// complement is correct even when callers constructed Edge objects
50+
// with no/asymmetric vertex1/vertex2 fields (e.g. when this method
51+
// is composed: complement(complement(g))).
52+
Pair<String> ends = graph.getEndpoints(e);
53+
existingEdges.add(edgeKey(ends.getFirst(), ends.getSecond()));
5054
}
5155

5256
int edgeId = 0;
@@ -55,7 +59,13 @@ public static Graph<String, Edge> buildComplement(Graph<String, Edge> graph) {
5559
String v1 = vertices.get(i);
5660
String v2 = vertices.get(j);
5761
if (!existingEdges.contains(edgeKey(v1, v2))) {
58-
Edge e = new Edge(v1, v2, "complement_" + edgeId++);
62+
// Edge(edgeType, vertex1, vertex2) — previously the
63+
// arguments were passed in the wrong order, which left
64+
// the Edge's vertex fields pointing at the wrong values
65+
// and broke any caller (or recursive call) that read
66+
// them back. Use a stable "complement" type tag.
67+
Edge e = new Edge("complement", v1, v2);
68+
e.setLabel("complement_" + edgeId++);
5969
complement.addEdge(e, v1, v2);
6070
}
6171
}
@@ -148,7 +158,8 @@ public static List<String[]> getComplementEdgeList(Graph<String, Edge> graph) {
148158
Graph<String, Edge> complement = buildComplement(graph);
149159
List<String[]> result = new ArrayList<>();
150160
for (Edge e : complement.getEdges()) {
151-
result.add(new String[]{e.getVertex1(), e.getVertex2()});
161+
Pair<String> ends = complement.getEndpoints(e);
162+
result.add(new String[]{ends.getFirst(), ends.getSecond()});
152163
}
153164
return result;
154165
}
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
package gvisual;
2+
3+
import edu.uci.ics.jung.graph.Graph;
4+
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
5+
import edu.uci.ics.jung.graph.util.Pair;
6+
import org.junit.Test;
7+
8+
import java.util.HashSet;
9+
import java.util.Iterator;
10+
import java.util.List;
11+
import java.util.Set;
12+
13+
import static org.junit.Assert.*;
14+
15+
/**
16+
* Tests for {@link GraphComplementAnalyzer}.
17+
*
18+
* <p>The complement G' of G has the same vertex set and contains exactly
19+
* the edges that are missing from G. These tests cover edge cases (empty,
20+
* single vertex), well-known graphs (complete, empty, path, cycle), and
21+
* the invariant {@code |E(G)| + |E(G')| = n*(n-1)/2}.</p>
22+
*/
23+
public class GraphComplementAnalyzerTest {
24+
25+
// ── builders ──────────────────────────────────────────────
26+
27+
private static Graph<String, Edge> empty(int n) {
28+
Graph<String, Edge> g = new UndirectedSparseGraph<String, Edge>();
29+
for (int i = 0; i < n; i++) g.addVertex("v" + i);
30+
return g;
31+
}
32+
33+
private static Graph<String, Edge> complete(int n) {
34+
Graph<String, Edge> g = empty(n);
35+
int id = 0;
36+
for (int i = 0; i < n; i++) {
37+
for (int j = i + 1; j < n; j++) {
38+
g.addEdge(new Edge("c", "v" + i, "v" + j), "v" + i, "v" + j);
39+
id++;
40+
}
41+
}
42+
return g;
43+
}
44+
45+
private static Graph<String, Edge> path(int n) {
46+
Graph<String, Edge> g = empty(n);
47+
for (int i = 0; i < n - 1; i++) {
48+
g.addEdge(new Edge("p", "v" + i, "v" + (i + 1)), "v" + i, "v" + (i + 1));
49+
}
50+
return g;
51+
}
52+
53+
private static Graph<String, Edge> cycle(int n) {
54+
Graph<String, Edge> g = path(n);
55+
if (n >= 3) {
56+
g.addEdge(new Edge("p", "v" + (n - 1), "v0"), "v" + (n - 1), "v0");
57+
}
58+
return g;
59+
}
60+
61+
private static String key(String a, String b) {
62+
return a.compareTo(b) < 0 ? a + "|" + b : b + "|" + a;
63+
}
64+
65+
/**
66+
* Builds the set of unordered endpoint pairs for all edges in g.
67+
* Reads endpoints via the graph (not via Edge fields) so the test does
68+
* not depend on how callers populated the Edge's vertex1/vertex2 fields.
69+
*/
70+
private static Set<String> edgeKeys(Graph<String, Edge> g) {
71+
Set<String> s = new HashSet<String>();
72+
for (Edge e : g.getEdges()) {
73+
Pair<String> ends = g.getEndpoints(e);
74+
s.add(key(ends.getFirst(), ends.getSecond()));
75+
}
76+
return s;
77+
}
78+
79+
// ── tests ─────────────────────────────────────────────────
80+
81+
@Test
82+
public void complementOfEmptyGraphIsEmpty() {
83+
Graph<String, Edge> g = new UndirectedSparseGraph<String, Edge>();
84+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(g);
85+
assertEquals(0, c.getVertexCount());
86+
assertEquals(0, c.getEdgeCount());
87+
}
88+
89+
@Test
90+
public void complementOfSingleVertexHasNoEdges() {
91+
Graph<String, Edge> g = empty(1);
92+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(g);
93+
assertEquals(1, c.getVertexCount());
94+
assertEquals(0, c.getEdgeCount());
95+
}
96+
97+
@Test
98+
public void complementOfCompleteGraphIsEdgeless() {
99+
Graph<String, Edge> kn = complete(5);
100+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(kn);
101+
assertEquals(5, c.getVertexCount());
102+
assertEquals(0, c.getEdgeCount());
103+
for (String v : c.getVertices()) {
104+
assertEquals(0, c.degree(v));
105+
}
106+
}
107+
108+
@Test
109+
public void complementOfEdgelessGraphIsComplete() {
110+
Graph<String, Edge> g = empty(4);
111+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(g);
112+
// K4 has 4*3/2 = 6 edges
113+
assertEquals(6, c.getEdgeCount());
114+
// every vertex has degree n-1 = 3
115+
for (String v : c.getVertices()) {
116+
assertEquals(3, c.degree(v));
117+
}
118+
}
119+
120+
@Test
121+
public void complementOfPathHasCorrectEdgeCount() {
122+
// P4: 0-1-2-3 → 3 edges
123+
// K4 has 6 edges → complement should have 3
124+
Graph<String, Edge> p = path(4);
125+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(p);
126+
assertEquals(3, c.getEdgeCount());
127+
128+
// Specifically: edges NOT in path are {0-2, 0-3, 1-3}
129+
Set<String> compKeys = edgeKeys(c);
130+
assertTrue(compKeys.contains(key("v0", "v2")));
131+
assertTrue(compKeys.contains(key("v0", "v3")));
132+
assertTrue(compKeys.contains(key("v1", "v3")));
133+
assertFalse(compKeys.contains(key("v0", "v1")));
134+
assertFalse(compKeys.contains(key("v1", "v2")));
135+
assertFalse(compKeys.contains(key("v2", "v3")));
136+
}
137+
138+
@Test
139+
public void edgeCountInvariantHolds() {
140+
// |E(G)| + |E(G')| = n*(n-1)/2 for any graph
141+
for (int n = 0; n <= 8; n++) {
142+
Graph<String, Edge> p = path(n);
143+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(p);
144+
int maxEdges = n * (n - 1) / 2;
145+
assertEquals("n=" + n, maxEdges, p.getEdgeCount() + c.getEdgeCount());
146+
}
147+
}
148+
149+
@Test
150+
public void complementIsInvolutive() {
151+
// (G')' should equal G (same edge set)
152+
Graph<String, Edge> g = cycle(6); // C6
153+
Graph<String, Edge> cc = GraphComplementAnalyzer.buildComplement(
154+
GraphComplementAnalyzer.buildComplement(g));
155+
assertEquals(g.getVertexCount(), cc.getVertexCount());
156+
assertEquals(g.getEdgeCount(), cc.getEdgeCount());
157+
assertEquals(edgeKeys(g), edgeKeys(cc));
158+
}
159+
160+
@Test
161+
public void complementDoesNotIncludeSelfLoops() {
162+
Graph<String, Edge> g = empty(4);
163+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(g);
164+
for (Edge e : c.getEdges()) {
165+
Pair<String> ends = c.getEndpoints(e);
166+
assertNotEquals("self-loop", ends.getFirst(), ends.getSecond());
167+
}
168+
}
169+
170+
@Test
171+
public void complementEdgeListMatchesBuiltComplement() {
172+
Graph<String, Edge> p = path(5);
173+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(p);
174+
175+
List<String[]> edgeList = GraphComplementAnalyzer.getComplementEdgeList(p);
176+
assertEquals(c.getEdgeCount(), edgeList.size());
177+
178+
Set<String> listKeys = new HashSet<String>();
179+
for (String[] pair : edgeList) {
180+
assertEquals(2, pair.length);
181+
listKeys.add(key(pair[0], pair[1]));
182+
}
183+
// The complement edge list, taken pair-by-pair, must cover the same
184+
// unordered vertex pairs as the complement graph's edges.
185+
assertEquals(edgeKeys(c), listKeys);
186+
// Silence unused-import warning on Iterator when present.
187+
Iterator<Edge> ignored = c.getEdges().iterator();
188+
assertNotNull(ignored);
189+
}
190+
191+
@Test
192+
public void analyzeReportContainsKeySections() {
193+
Graph<String, Edge> g = path(4);
194+
String report = GraphComplementAnalyzer.analyze(g);
195+
assertNotNull(report);
196+
assertTrue("title", report.contains("GRAPH COMPLEMENT ANALYSIS"));
197+
assertTrue("orig", report.contains("Original Graph"));
198+
assertTrue("compl", report.contains("Complement Graph"));
199+
assertTrue("validation", report.contains("Validation"));
200+
assertTrue("isolated", report.contains("Isolated Vertices"));
201+
}
202+
203+
@Test
204+
public void analyzeFlagsSelfComplementaryCandidate() {
205+
// P4 has 3 edges, K4 has 6 edges → P4 has exactly half the edges, so
206+
// the edge-count test for self-complementarity should report PASS
207+
// (P4 is in fact self-complementary).
208+
Graph<String, Edge> p4 = path(4);
209+
String report = GraphComplementAnalyzer.analyze(p4);
210+
assertTrue("P4 should pass edge-count self-comp test",
211+
report.contains("PASS"));
212+
}
213+
214+
@Test
215+
public void analyzeMarksNonSelfComplementaryGraph() {
216+
// P3 has 2 edges, K3 has 3 edges (odd) → cannot be self-complementary
217+
Graph<String, Edge> p3 = path(3);
218+
String report = GraphComplementAnalyzer.analyze(p3);
219+
// The "Edge-count test:" line should say FAIL
220+
assertTrue(report.contains("Edge-count test: FAIL"));
221+
}
222+
223+
@Test
224+
public void analyzeIdentifiesUniversalVerticesAsIsolatedInComplement() {
225+
// Star graph K_{1,3}: center connected to 3 leaves.
226+
// In the complement, the 3 leaves form a triangle and the center
227+
// becomes isolated.
228+
Graph<String, Edge> star = empty(4);
229+
star.addEdge(new Edge("s", "v0", "v1"), "v0", "v1");
230+
star.addEdge(new Edge("s", "v0", "v2"), "v0", "v2");
231+
star.addEdge(new Edge("s", "v0", "v3"), "v0", "v3");
232+
233+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(star);
234+
assertEquals(0, c.degree("v0")); // center is isolated in complement
235+
assertEquals(2, c.degree("v1"));
236+
assertEquals(2, c.degree("v2"));
237+
assertEquals(2, c.degree("v3"));
238+
assertEquals(3, c.getEdgeCount()); // triangle on leaves
239+
}
240+
241+
@Test
242+
public void buildComplementProducesUniqueEdges() {
243+
// No duplicate edges in the complement (undirected, each pair once)
244+
Graph<String, Edge> g = empty(5);
245+
Graph<String, Edge> c = GraphComplementAnalyzer.buildComplement(g);
246+
Set<String> keys = edgeKeys(c);
247+
assertEquals(c.getEdgeCount(), keys.size());
248+
}
249+
}

0 commit comments

Comments
 (0)