package unit; import java.util.*; /** * Unit test for open3d-0001: ValidatePoseGraphConnectivity O(V²×E) → O(V×E) * * Simulates Open3D GlobalOptimization.cpp connectivity BFS: * Defective: std::find on component vector inside while-loop × edge scan * Fixed: unordered_set membership O(1) replaces the O(V) linear scan * * Compile: javac -d . PoseGraphConnectivityAlgorithm.java * Run: java -ea unit.PoseGraphConnectivityAlgorithm */ public class PoseGraphConnectivityAlgorithm { static class PoseGraphEdge { final int source; final int target; final boolean uncertain; PoseGraphEdge(int src, int tgt, boolean uncertain) { source = src; target = tgt; this.uncertain = uncertain; } } static class PoseGraph { final int nNodes; final List edges; PoseGraph(int nNodes, List edges) { this.nNodes = nNodes; this.edges = edges; } } // ── defective implementation ────────────────────────────────────────────── static boolean defectiveValidateConnectivity(PoseGraph pg, boolean ignoreUncertain) { int nNodes = pg.nNodes; if (nNodes == 0) return true; List toExplore = new ArrayList<>(); List component = new ArrayList<>(); // O(V) membership toExplore.add(0); component.add(0); while (!toExplore.isEmpty()) { int i = toExplore.remove(toExplore.size() - 1); for (PoseGraphEdge e : pg.edges) { if (ignoreUncertain && e.uncertain) continue; int adj = -1; if (e.source == i) adj = e.target; else if (e.target == i) adj = e.source; if (adj != -1) { // O(V) linear scan inside while-loop × edge-scan boolean found = false; for (int c : component) if (c == adj) { found = true; break; } if (!found) { toExplore.add(adj); component.add(adj); } } } } return component.size() == nNodes; } // ── fixed implementation ────────────────────────────────────────────────── static boolean fixedValidateConnectivity(PoseGraph pg, boolean ignoreUncertain) { int nNodes = pg.nNodes; if (nNodes == 0) return true; List toExplore = new ArrayList<>(); Set componentSet = new HashSet<>(); // O(1) membership toExplore.add(0); componentSet.add(0); while (!toExplore.isEmpty()) { int i = toExplore.remove(toExplore.size() - 1); for (PoseGraphEdge e : pg.edges) { if (ignoreUncertain && e.uncertain) continue; int adj = -1; if (e.source == i) adj = e.target; else if (e.target == i) adj = e.source; if (adj != -1) { if (componentSet.add(adj)) { // O(1) insert+dedup toExplore.add(adj); } } } } return componentSet.size() == nNodes; } // ── graph builders ──────────────────────────────────────────────────────── /** Linear chain: 0-1-2-...-n-1 */ static PoseGraph buildChain(int n) { List edges = new ArrayList<>(); for (int i = 0; i < n - 1; i++) edges.add(new PoseGraphEdge(i, i + 1, false)); return new PoseGraph(n, edges); } /** Complete graph: all pairs connected */ static PoseGraph buildComplete(int n) { List edges = new ArrayList<>(); for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) edges.add(new PoseGraphEdge(i, j, false)); return new PoseGraph(n, edges); } /** Disconnected: two isolated cliques */ static PoseGraph buildDisconnected(int n) { List edges = new ArrayList<>(); int half = n / 2; for (int i = 0; i < half - 1; i++) edges.add(new PoseGraphEdge(i, i + 1, false)); for (int i = half; i < n - 1; i++) edges.add(new PoseGraphEdge(i, i + 1, false)); return new PoseGraph(n, edges); } /** Certain-edge-only graph: some edges marked uncertain */ static PoseGraph buildMixed(int n, double uncertainFraction) { List edges = new ArrayList<>(); // Build a chain where every other edge is uncertain for (int i = 0; i < n - 1; i++) { boolean uncertain = (i % 2 == 1); edges.add(new PoseGraphEdge(i, i + 1, uncertain)); } return new PoseGraph(n, edges); } // ── tests ───────────────────────────────────────────────────────────────── static int pass = 0, total = 0; static void assertTrue(String name, boolean cond) { total++; if (cond) { pass++; System.out.println("PASS " + name); } else System.out.println("FAIL " + name); } public static void main(String[] args) { // Test 1: empty graph → connected (vacuously) { PoseGraph pg = new PoseGraph(0, Collections.emptyList()); assertTrue("empty-defective", defectiveValidateConnectivity(pg, false)); assertTrue("empty-fixed", fixedValidateConnectivity(pg, false)); } // Test 2: single node → connected { PoseGraph pg = new PoseGraph(1, Collections.emptyList()); assertTrue("single-node-defective", defectiveValidateConnectivity(pg, false)); assertTrue("single-node-fixed", fixedValidateConnectivity(pg, false)); } // Test 3: chain of 5 → connected { PoseGraph pg = buildChain(5); assertTrue("chain5-defective", defectiveValidateConnectivity(pg, false)); assertTrue("chain5-fixed", fixedValidateConnectivity(pg, false)); } // Test 4: complete graph of 6 → connected { PoseGraph pg = buildComplete(6); assertTrue("complete6-defective", defectiveValidateConnectivity(pg, false)); assertTrue("complete6-fixed", fixedValidateConnectivity(pg, false)); } // Test 5: disconnected → not connected { PoseGraph pg = buildDisconnected(6); assertTrue("disconnected6-defective", !defectiveValidateConnectivity(pg, false)); assertTrue("disconnected6-fixed", !fixedValidateConnectivity(pg, false)); } // Test 6: mixed uncertain edges — chain connected via uncertain edges only { // 4 nodes: 0--1(certain) 1--2(uncertain) 2--3(certain) List edges = Arrays.asList( new PoseGraphEdge(0, 1, false), new PoseGraphEdge(1, 2, true), new PoseGraphEdge(2, 3, false)); PoseGraph pg = new PoseGraph(4, edges); // Ignoring uncertain: 0-1 certain, 2-3 certain, but 1-2 uncertain → not connected assertTrue("uncertain-ignore-defective", !defectiveValidateConnectivity(pg, true)); assertTrue("uncertain-ignore-fixed", !fixedValidateConnectivity(pg, true)); // Not ignoring uncertain: all connected assertTrue("uncertain-include-defective", defectiveValidateConnectivity(pg, false)); assertTrue("uncertain-include-fixed", fixedValidateConnectivity(pg, false)); } // Test 7: both implementations agree on random graph { Random rng = new Random(42); int V = 30, extraEdges = 60; List edges = new ArrayList<>(); for (int i = 0; i < V - 1; i++) edges.add(new PoseGraphEdge(i, i + 1, false)); for (int i = 0; i < extraEdges; i++) { int a = rng.nextInt(V), b = rng.nextInt(V); edges.add(new PoseGraphEdge(a, b, rng.nextBoolean())); } PoseGraph pg = new PoseGraph(V, edges); boolean d = defectiveValidateConnectivity(pg, false); boolean f = fixedValidateConnectivity(pg, false); assertTrue("random-graph-agree", d == f); assertTrue("random-graph-connected", f); // chain ensures connectivity } // Test 8: performance — component membership check isolated at V=1000 // Builds a large component list, then simulates the find-or-add operation { int V = 2000; // Simulate: component grows to V, each element checked V times = O(V²) long t0 = System.nanoTime(); for (int r = 0; r < 10; r++) { List component = new ArrayList<>(V); for (int i = 0; i < V; i++) { // defective: linear scan to check if i already in component boolean found = false; for (int c : component) if (c == i) { found = true; break; } if (!found) component.add(i); } } long tDef = System.nanoTime() - t0; t0 = System.nanoTime(); for (int r = 0; r < 10; r++) { Set componentSet = new HashSet<>(V * 2); List componentList = new ArrayList<>(V); for (int i = 0; i < V; i++) { // fixed: O(1) set insert if (componentSet.add(i)) componentList.add(i); } } long tFix = System.nanoTime() - t0; double ratio = (double) tDef / tFix; System.out.printf(" Perf component-dedup V=%d: defective=%.1fms fixed=%.1fms ratio=%.1fx%n", V, tDef / 1e6, tFix / 1e6, ratio); assertTrue("perf-speedup", ratio > 5.0); } System.out.println(pass + "/" + total + " PASS"); assert pass == total : pass + "/" + total + " passed"; } }