167 lines
7.3 KiB
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";
|
|
}
|
|
}
|