java-topology/defects/pcl/unit/PclRegionGrowingGetSegmentTest.java
russell@unturf.com a629bd0bbf no-stone-unturned wave: 8 new defects, 15 CLEAN confirmations; count 621→629
New defects (all PASS):
- exim-0001: same_hosts() MX-segment O(H²) → AVL set O(H log H), 10.5x at H=20
- minecraft-0001: DependencySorter.isCyclic no visited set O(E^D) → O(E), 342,000x at D=24
- minecraft-0002: PistonStructureResolver toPush ArrayList O(N²) → HashSet O(N)
- minecraft-0003: RedstoneWireEvaluator Deque.contains O(N²) → HashSet O(N)
- minecraft-0004: MoveThroughVillageGoal visited List O(N²) → HashSet O(N)
- mpich-0001: group_lpid_to_rank O(N²) → HashMap O(N), 313x at N=1000
- ompi-0001: group_overlap process-name scan O(N×M) → HashMap O(N+M), 2048x
- pcl-0001: RegionGrowing::getSegmentFromPoint O(C×S) → point_labels[] O(1), 50000x

CLEAN confirmed: esbuild, express, koa, ktor, lucene, mpich-recvq, ompi-startup,
  prosody, roda, rust/rustc-wave2, signal-server, solana, wiredtiger, wireguard-tools,
  linux-kernel (pointer to linux/)
2026-03-29 16:11:50 -04:00

139 lines
4.9 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.*;
/**
* pcl-0001: RegionGrowing::getSegmentFromPoint() clusters O(C×S) → point_labels[] O(1)
*
* In segmentation/include/pcl/segmentation/impl/region_growing.hpp::getSegmentFromPoint():
*
* for (const auto& i_segment : clusters_)
* {
* const auto it = std::find(i_segment.indices.cbegin(), i_segment.indices.cend(), index);
* if (it != i_segment.indices.cend()) { ... break; }
* }
*
* std::find is a linear O(S) scan per cluster; the outer loop is O(C).
* Total per-call cost: O(C × S) on average = O(N/2) worst case.
*
* The fix: point_labels_[index] is already set to the segment index by
* applySmoothRegionGrowingAlgorithm() and used verbatim by assembleRegions():
*
* clusters_[point_labels_[index]] // O(1) direct array index
*
* Severity: HIGH
* UNDF: assigned by generate_undf.py
*/
public class PclRegionGrowingGetSegmentTest {
static long slowOps = 0;
static long fastOps = 0;
/**
* Simulate the PCL clusters_ data structure:
* a list of clusters, each holding a list of point indices.
*/
static int[][] buildClusters(int numClusters, int pointsPerCluster) {
int[][] clusters = new int[numClusters][pointsPerCluster];
for (int c = 0; c < numClusters; c++) {
for (int p = 0; p < pointsPerCluster; p++) {
clusters[c][p] = c * pointsPerCluster + p;
}
}
return clusters;
}
/**
* SLOW: O(C × S) — scan all clusters, linear find in each.
* Mirrors the defective PCL implementation.
*/
static int[] getSegmentFromPointSlow(int[][] clusters, int queryIndex) {
for (int[] cluster : clusters) {
for (int idx : cluster) {
slowOps++;
if (idx == queryIndex) {
return cluster.clone();
}
}
}
return new int[0];
}
/**
* FAST: O(1) — use point_labels[] direct array index.
* Mirrors the patched PCL implementation.
*/
static int[] getSegmentFromPointFast(int[][] clusters, int[] pointLabels, int queryIndex) {
fastOps++; // one array lookup
int segmentIndex = pointLabels[queryIndex];
if (segmentIndex < 0 || segmentIndex >= clusters.length) return new int[0];
return clusters[segmentIndex].clone();
}
/**
* Build point_labels[]: maps each point index to its cluster index.
* This is what PCL's assembleRegions() establishes.
*/
static int[] buildPointLabels(int[][] clusters) {
int totalPoints = 0;
for (int[] c : clusters) totalPoints += c.length;
int[] labels = new int[totalPoints];
Arrays.fill(labels, -1);
for (int c = 0; c < clusters.length; c++) {
for (int idx : clusters[c]) {
labels[idx] = c;
}
}
return labels;
}
public static void main(String[] args) {
final int C = 500; // clusters (typical LiDAR scan: hundreds to thousands)
final int S = 200; // points per cluster
final int N = C * S; // total points = 100,000
int[][] clusters = buildClusters(C, S);
int[] pointLabels = buildPointLabels(clusters);
// Query every point once — simulates a user loop over all points
// (e.g. building a per-point cluster-membership map)
slowOps = 0;
fastOps = 0;
int[] slowResult = null;
int[] fastResult = null;
for (int q = 0; q < N; q++) {
slowResult = getSegmentFromPointSlow(clusters, q);
fastResult = getSegmentFromPointFast(clusters, pointLabels, q);
}
// Verify correctness on last query point
assert slowResult != null && fastResult != null;
assert slowResult.length == fastResult.length
: "result length mismatch: slow=" + slowResult.length + " fast=" + fastResult.length;
Arrays.sort(slowResult);
Arrays.sort(fastResult);
assert Arrays.equals(slowResult, fastResult)
: "result content mismatch";
// Spot-check a query in the middle of the last cluster (worst case for slow)
int worstQuery = N - 1;
int[] sw = getSegmentFromPointSlow(clusters, worstQuery);
int[] fw = getSegmentFromPointFast(clusters, pointLabels, worstQuery);
Arrays.sort(sw); Arrays.sort(fw);
assert Arrays.equals(sw, fw) : "worst-case query mismatch";
long ratio = slowOps / Math.max(fastOps, 1);
System.out.printf("N=%d C=%d S=%d%n", N, C, S);
System.out.printf("slowOps (O(C×S) per query) : %,d%n", slowOps);
System.out.printf("fastOps (O(1) per query) : %,d%n", fastOps);
System.out.printf("ratio : %,d×%n", ratio);
// Require at least 1000× improvement
assert ratio >= 1000
: "Expected >=1000x speedup, got " + ratio + "x";
System.out.println("PASS");
}
}