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

167 lines
7.3 KiB
Java

package unit;
import java.util.*;
/**
* Unit test for open3d-0002: RandomSampler::operator() O(S²) → O(S)
*
* Simulates Open3D PointCloudSegmentation.cpp RandomSampler:
* Defective: std::find on growing samples vector inside rejection-sampling while loop
* Fixed: unordered_set for O(1) duplicate detection
*
* Compile: javac -d . RansacSamplerAlgorithm.java
* Run: java -ea unit.RansacSamplerAlgorithm
*/
public class RansacSamplerAlgorithm {
// ── defective implementation ──────────────────────────────────────────────
/** Returns sampleSize unique indices in [0, totalSize), using O(S²) rejection sampling */
static List<Integer> defectiveSample(int totalSize, int sampleSize, Random rng) {
List<Integer> samples = new ArrayList<>(sampleSize);
while (samples.size() < sampleSize) {
int idx = rng.nextInt(totalSize);
// O(valid_sample) linear scan — same as std::find in Open3D
boolean found = false;
for (int s : samples) if (s == idx) { found = true; break; }
if (!found) samples.add(idx);
}
return samples;
}
// ── fixed implementation ──────────────────────────────────────────────────
/** Returns sampleSize unique indices in [0, totalSize), using O(S) set-based sampling */
static List<Integer> fixedSample(int totalSize, int sampleSize, Random rng) {
List<Integer> samples = new ArrayList<>(sampleSize);
Set<Integer> seen = new HashSet<>(sampleSize * 2);
while (samples.size() < sampleSize) {
int idx = rng.nextInt(totalSize);
if (seen.add(idx)) samples.add(idx); // O(1)
}
return samples;
}
// ── property checks ───────────────────────────────────────────────────────
static boolean isUniqueSubset(List<Integer> sample, int totalSize) {
Set<Integer> seen = new HashSet<>();
for (int idx : sample) {
if (idx < 0 || idx >= totalSize) return false;
if (!seen.add(idx)) return false; // duplicate
}
return true;
}
// ── 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) {
Random rng = new Random(12345);
// Test 1: sample_size=0 → empty result
{
List<Integer> d = defectiveSample(100, 0, new Random(1));
List<Integer> f = fixedSample(100, 0, new Random(1));
assertTrue("zero-sample-defective", d.isEmpty());
assertTrue("zero-sample-fixed", f.isEmpty());
}
// Test 2: sample_size=1 → single unique index
{
List<Integer> d = defectiveSample(1000, 1, new Random(2));
List<Integer> f = fixedSample(1000, 1, new Random(2));
assertTrue("size1-defective", d.size() == 1 && isUniqueSubset(d, 1000));
assertTrue("size1-fixed", f.size() == 1 && isUniqueSubset(f, 1000));
}
// Test 3: sample_size=3 (default ransac_n) → correct size, unique, valid range
{
for (int trial = 0; trial < 20; trial++) {
List<Integer> d = defectiveSample(10000, 3, rng);
List<Integer> f = fixedSample(10000, 3, rng);
if (!isUniqueSubset(d, 10000) || d.size() != 3) {
assertTrue("sample3-defective-trial" + trial, false);
break;
}
if (!isUniqueSubset(f, 10000) || f.size() != 3) {
assertTrue("sample3-fixed-trial" + trial, false);
break;
}
}
assertTrue("sample3-20-trials-defective", true);
assertTrue("sample3-20-trials-fixed", true);
}
// Test 4: sample_size=totalSize → exactly all indices (if feasible)
{
int N = 20;
List<Integer> d = defectiveSample(N, N, new Random(7));
List<Integer> f = fixedSample(N, N, new Random(7));
assertTrue("full-sample-defective", d.size() == N && isUniqueSubset(d, N) && new HashSet<>(d).size() == N);
assertTrue("full-sample-fixed", f.size() == N && isUniqueSubset(f, N) && new HashSet<>(f).size() == N);
}
// Test 5: no duplicates across 100 calls with sample_size=10
{
boolean defOk = true, fixOk = true;
for (int i = 0; i < 100; i++) {
if (!isUniqueSubset(defectiveSample(10000, 10, rng), 10000)) defOk = false;
if (!isUniqueSubset(fixedSample(10000, 10, rng), 10000)) fixOk = false;
}
assertTrue("no-duplicates-100-defective", defOk);
assertTrue("no-duplicates-100-fixed", fixOk);
}
// Test 6: performance — simulate RANSAC: num_iterations=1000, ransac_n=10, totalSize=50000
{
int numIter = 1000, sampleSize = 10, totalSize = 50000;
long t0 = System.nanoTime();
Random r1 = new Random(42);
for (int i = 0; i < numIter; i++) defectiveSample(totalSize, sampleSize, r1);
long tDef = System.nanoTime() - t0;
t0 = System.nanoTime();
Random r2 = new Random(42);
for (int i = 0; i < numIter; i++) fixedSample(totalSize, sampleSize, r2);
long tFix = System.nanoTime() - t0;
double ratio = (double) tDef / tFix;
System.out.printf(" Perf RANSAC iter=%d S=%d N=%d: defective=%.1fms fixed=%.1fms ratio=%.1fx%n",
numIter, sampleSize, totalSize, tDef / 1e6, tFix / 1e6, ratio);
// At S=10 ratio may be modest; the defect scales as S²
assertTrue("perf-not-slower", ratio >= 0.5); // conservative: fixed should not be slower
}
// Test 7: performance at larger sample_size=100 where O(S²) hurts more
{
int numIter = 1000, sampleSize = 100, totalSize = 100000;
long t0 = System.nanoTime();
Random r1 = new Random(99);
for (int i = 0; i < numIter; i++) defectiveSample(totalSize, sampleSize, r1);
long tDef = System.nanoTime() - t0;
t0 = System.nanoTime();
Random r2 = new Random(99);
for (int i = 0; i < numIter; i++) fixedSample(totalSize, sampleSize, r2);
long tFix = System.nanoTime() - t0;
double ratio = (double) tDef / tFix;
System.out.printf(" Perf RANSAC iter=%d S=%d N=%d: defective=%.1fms fixed=%.1fms ratio=%.1fx%n",
numIter, sampleSize, totalSize, tDef / 1e6, tFix / 1e6, ratio);
assertTrue("perf-s100-speedup", ratio > 2.0);
}
System.out.println(pass + "/" + total + " PASS");
assert pass == total : pass + "/" + total + " passed";
}
}