java-topology/defects/open3d/unit/PoseGraphConnectivityAlgorithm.java

247 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<PoseGraphEdge> edges;
PoseGraph(int nNodes, List<PoseGraphEdge> 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<Integer> toExplore = new ArrayList<>();
List<Integer> 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<Integer> toExplore = new ArrayList<>();
Set<Integer> 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<PoseGraphEdge> 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<PoseGraphEdge> 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<PoseGraphEdge> 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<PoseGraphEdge> 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<PoseGraphEdge> 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<PoseGraphEdge> 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<Integer> 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<Integer> componentSet = new HashSet<>(V * 2);
List<Integer> 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";
}
}