java-topology/defects/opencv/unit/OpenCVTest.java

178 lines
6.5 KiB
Java

import java.util.*;
/**
* Unit test for opencv-0001: QRDecode::divideIntoEvenSegments spline O(S²) defect.
*
* The defect: inside a loop over `num` (1..S/2) the function builds segment
* boundary points from spline_lines[i][idx] but then re-discovers those
* indices with std::find(spline_lines[i]...) — an O(S) scan per segment
* boundary. Total cost becomes O(S² * NUM_SIDES).
*
* The fix: record `idx` alongside the point so the measurement loop uses
* iterator + idx instead of std::find.
*
* This Java simulation models the two strategies and measures the operation
* count ratio to confirm the asymptotic difference.
*/
public class OpenCVTest {
// Simulate "spline line" as a list of float pairs (x,y)
static class Point2f {
float x, y;
Point2f(float x, float y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point2f)) return false;
Point2f p = (Point2f) o;
return Float.compare(p.x, x) == 0 && Float.compare(p.y, y) == 0;
}
@Override public int hashCode() {
return Objects.hash(x, y);
}
}
/** Build a synthetic spline of length S. */
static List<Point2f> makeSpline(int S) {
List<Point2f> line = new ArrayList<>(S);
for (int i = 0; i < S; i++) {
line.add(new Point2f(i, (float)Math.sin(i * 0.1)));
}
return line;
}
/**
* DEFECTIVE: for each (num, side, segment) pair call List.indexOf() (= std::find)
* to locate the boundary point. Returns total operation count.
*/
static long defective(List<Point2f> spline, int maxNum) {
int S = spline.size();
long ops = 0;
List<Point2f> segPoints = new ArrayList<>();
for (int num = 1; num < maxNum; num++) {
segPoints.clear();
float step = (float) S / num;
for (int j = 0; j < num; j++) {
float val = j * step;
int idx = Math.round(val) >= S ? S - 1 : Math.round(val);
segPoints.add(spline.get(idx));
}
segPoints.add(spline.get(S - 1));
// Measurement: std::find equivalent for each boundary pair
for (int j = 0; j < segPoints.size() - 1; j++) {
Point2f start = segPoints.get(j);
Point2f end = segPoints.get(j + 1);
// O(S) scan each — the defect
int idxStart = spline.indexOf(start); ops += idxStart + 1;
int idxEnd = spline.indexOf(end); ops += idxEnd + 1;
}
}
return ops;
}
/**
* FIXED: record indices alongside points; use direct index access.
* Returns total operation count.
*/
static long fixed(List<Point2f> spline, int maxNum) {
int S = spline.size();
long ops = 0;
List<Point2f> segPoints = new ArrayList<>();
List<Integer> segIdx = new ArrayList<>();
for (int num = 1; num < maxNum; num++) {
segPoints.clear(); segIdx.clear();
float step = (float) S / num;
for (int j = 0; j < num; j++) {
float val = j * step;
int idx = Math.round(val) >= S ? S - 1 : Math.round(val);
segPoints.add(spline.get(idx));
segIdx.add(idx);
}
segPoints.add(spline.get(S - 1));
segIdx.add(S - 1);
// Measurement: use recorded index — O(1)
for (int j = 0; j < segPoints.size() - 1; j++) {
int idxStart = segIdx.get(j); ops += 1; // O(1) access
int idxEnd = segIdx.get(j + 1); ops += 1;
}
}
return ops;
}
public static void main(String[] args) {
System.out.println("opencv-0001: QRDecode::divideIntoEvenSegments O(S²) defect");
System.out.println("=============================================================");
int[] sizes = {50, 100, 200, 500};
boolean allPass = true;
for (int S : sizes) {
List<Point2f> spline = makeSpline(S);
int maxNum = S / 2;
long defOps = defective(spline, maxNum);
long fixOps = fixed(spline, maxNum);
double ratio = (double) defOps / fixOps;
System.out.printf("S=%4d maxNum=%3d defect_ops=%,12d fixed_ops=%,8d ratio=%.1fx%n",
S, maxNum, defOps, fixOps, ratio);
// Expect defect to be significantly worse (at least 10x at S=50)
if (ratio < 5.0) {
System.err.println(" FAIL: expected ratio >= 5.0 at S=" + S);
allPass = false;
}
}
// Verify correctness: both should cover same index ranges
{
int S = 100; int maxNum = 50;
List<Point2f> spline = makeSpline(S);
List<Integer> defIndices = new ArrayList<>();
List<Integer> fixIndices = new ArrayList<>();
// Run defective and collect start indices for num=10
{
List<Point2f> seg = new ArrayList<>();
float step = (float) S / 10;
for (int j = 0; j < 10; j++) {
float val = j * step;
int idx = Math.round(val) >= S ? S-1 : Math.round(val);
seg.add(spline.get(idx));
}
seg.add(spline.get(S - 1));
for (int j = 0; j < seg.size() - 1; j++) {
defIndices.add(spline.indexOf(seg.get(j)));
}
}
// Fixed version
{
List<Integer> si = new ArrayList<>();
float step = (float) S / 10;
for (int j = 0; j < 10; j++) {
float val = j * step;
int idx = Math.round(val) >= S ? S-1 : Math.round(val);
si.add(idx);
}
si.add(S - 1);
for (int j = 0; j < si.size() - 1; j++) {
fixIndices.add(si.get(j));
}
}
if (!defIndices.equals(fixIndices)) {
System.err.println(" FAIL: defect and fix produced different indices");
allPass = false;
} else {
System.out.println("Correctness check: PASS (indices match)");
}
}
if (allPass) {
System.out.println("ALL TESTS PASS");
System.exit(0);
} else {
System.out.println("SOME TESTS FAILED");
System.exit(1);
}
}
}