java-topology/defects/vtk/unit/StaticCleanPolyDataAlgorithm.java

227 lines
8 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.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* Models vtkStaticCleanPolyData::RequestData() point-deduplication within cells.
*
* For each cell the filter collects unique mapped point IDs:
* SLOW: std::find() on a growing std::vector<vtkIdType> — O(npts²) per cell
* FAST: std::unordered_set<vtkIdType> insertion test — O(npts) per cell
*
* Total cost across C cells: O(C × npts²) vs O(C × npts).
*
* CWE-407: VTK Filters/Core/vtkStaticCleanPolyData.cxx:257,293,343,403
*/
public class StaticCleanPolyDataAlgorithm {
// -------------------------------------------------------------------------
// Slow (defective) implementation — mirrors the C++ std::find approach.
// Returns the deduplicated point list and exposes total comparison ops.
// -------------------------------------------------------------------------
static class SlowDedup {
long totalOps = 0;
/** Deduplicate ptsInCell using linear scan; returns ordered unique list. */
List<Integer> dedup(int[] ptsInCell) {
List<Integer> cellIds = new ArrayList<>();
for (int ptId : ptsInCell) {
boolean found = false;
for (int existing : cellIds) { // O(k) scan — the defect
totalOps++;
if (existing == ptId) {
found = true;
break;
}
}
if (!found) {
cellIds.add(ptId);
// account for the full scan that found nothing
if (!found) { /* already counted above */ }
}
}
return cellIds;
}
/** Process C cells each with the given point array. */
List<Integer> processCell(int[] pts) {
return dedup(pts);
}
}
// -------------------------------------------------------------------------
// Fast (fixed) implementation — O(1) amortized via HashSet.
// -------------------------------------------------------------------------
static class FastDedup {
long totalOps = 0;
List<Integer> dedup(int[] ptsInCell) {
HashSet<Integer> seen = new HashSet<>();
List<Integer> cellIds = new ArrayList<>();
for (int ptId : ptsInCell) {
totalOps++; // one O(1) hash lookup per point
if (seen.add(ptId)) {
cellIds.add(ptId);
}
}
return cellIds;
}
List<Integer> processCell(int[] pts) {
return dedup(pts);
}
}
// -------------------------------------------------------------------------
// Helper: build a cell with npts points, last dupFrac fraction are dupes
// -------------------------------------------------------------------------
static int[] buildCell(int npts, int uniquePts) {
// pts[0..uniquePts-1] are unique IDs; rest repeat from start
int[] pts = new int[npts];
for (int i = 0; i < npts; i++) {
pts[i] = i % uniquePts;
}
return pts;
}
// -------------------------------------------------------------------------
// Tests
// -------------------------------------------------------------------------
static int passed = 0;
static int total = 0;
static void check(String label, boolean condition) {
total++;
if (condition) {
passed++;
System.out.println(" PASS " + label);
} else {
System.out.println(" FAIL " + label);
}
}
public static void main(String[] args) {
System.out.println("=== StaticCleanPolyDataAlgorithm ===");
// --- Correctness: no duplicates ---
{
int[] pts = {10, 20, 30, 40};
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
List<Integer> slowResult = slow.dedup(pts);
List<Integer> fastResult = fast.dedup(pts);
check("no-dup: slow size == 4", slowResult.size() == 4);
check("no-dup: fast size == 4", fastResult.size() == 4);
check("no-dup: results equal", slowResult.equals(fastResult));
}
// --- Correctness: all duplicates ---
{
int[] pts = {7, 7, 7, 7, 7};
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
List<Integer> slowResult = slow.dedup(pts);
List<Integer> fastResult = fast.dedup(pts);
check("all-dup: slow size == 1", slowResult.size() == 1);
check("all-dup: fast size == 1", fastResult.size() == 1);
check("all-dup: both return [7]", slowResult.equals(fastResult));
}
// --- Correctness: mixed ---
{
int[] pts = {1, 2, 1, 3, 2, 4};
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
List<Integer> slowResult = slow.dedup(pts);
List<Integer> fastResult = fast.dedup(pts);
check("mixed: slow size == 4", slowResult.size() == 4);
check("mixed: fast size == 4", fastResult.size() == 4);
check("mixed: results equal", slowResult.equals(fastResult));
}
// --- Correctness: empty cell ---
{
int[] pts = {};
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
check("empty: slow size == 0", slow.dedup(pts).size() == 0);
check("empty: fast size == 0", fast.dedup(pts).size() == 0);
}
// --- Performance: O(npts²) vs O(npts) ---
{
// High-valence strip: npts = 128, all unique → slow must scan 0+1+2+...+127 = 8128 ops
int npts = 128;
int[] pts = buildCell(npts, npts); // all unique
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
int runs = 10_000;
long t0 = System.nanoTime();
for (int r = 0; r < runs; r++) {
slow.totalOps = 0;
slow.dedup(pts);
}
long slowNs = System.nanoTime() - t0;
long slowOpsPerCall = (npts * (npts - 1)) / 2; // expected: triangular number
t0 = System.nanoTime();
for (int r = 0; r < runs; r++) {
fast.totalOps = 0;
fast.dedup(pts);
}
long fastNs = System.nanoTime() - t0;
// Fast counts exactly npts hash ops per call
fast.totalOps = 0;
fast.dedup(pts);
long fastOpsPerCall = fast.totalOps;
double ratio = (double) slowNs / fastNs;
System.out.printf(" INFO npts=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n",
npts, slowOpsPerCall, fastOpsPerCall, ratio);
check("slow op count = npts*(npts-1)/2",
slowOpsPerCall == (long) npts * (npts - 1) / 2);
check("fast op count = npts", fastOpsPerCall == npts);
check("fast is meaningfully faster (>= 1.5x)", ratio >= 1.5);
}
// --- Performance at larger scale: npts=256 ---
{
int npts = 256;
int[] pts = buildCell(npts, npts);
SlowDedup slow = new SlowDedup();
FastDedup fast = new FastDedup();
int runs = 5_000;
long t0 = System.nanoTime();
for (int r = 0; r < runs; r++) slow.dedup(pts);
long slowNs = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int r = 0; r < runs; r++) fast.dedup(pts);
long fastNs = System.nanoTime() - t0;
double ratio = (double) slowNs / fastNs;
System.out.printf(" INFO npts=%d ratio=%.1fx%n", npts, ratio);
check("npts=256 fast is meaningfully faster (>= 1.5x)", ratio >= 1.5);
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}