wave9b: 455/204 — graphhopper-0001/0002 + valhalla-0001 + OSRM CLEAN

This commit is contained in:
russell@unturf.com 2026-03-27 16:54:46 -04:00
parent 16cc7ecf1d
commit 81bc62b9cb
9 changed files with 811 additions and 5 deletions

View file

@ -0,0 +1,228 @@
package unit;
import java.util.*;
/**
* Unit test for valhalla-0001:
* linkclassification.cc IsSlipLane() nested linear scan O(F×R).
*
* Simulates the intersection-check loop:
* for (node : reverse_nodes) // R iterations
* std::find(forward_nodes.begin(), ..., node) // O(F) linear scan
*
* vs. the fixed version using unordered_set:
* forward_set = unordered_set(forward_nodes)
* for (node : reverse_nodes) // R iterations
* forward_set.count(node) // O(1) hash lookup
*
* Compile: javac -d . IsSlipLaneAlgorithm.java
* Run: java -ea unit.IsSlipLaneAlgorithm
*/
public class IsSlipLaneAlgorithm {
static int checkCount = 0;
static int passCount = 0;
static void check(String desc, boolean cond) {
checkCount++;
if (!cond) {
System.out.println("FAIL [" + checkCount + "]: " + desc);
} else {
passCount++;
System.out.println("PASS [" + checkCount + "]: " + desc);
}
}
// -------------------------------------------------------------------------
// Slow path: std::find linear scan over forward_nodes for each reverse node
// -------------------------------------------------------------------------
static class SlowIntersectionFinder {
int ops = 0;
boolean find(int[] forwardNodes, int target) {
for (int n : forwardNodes) {
ops++;
if (n == target) return true;
}
return false;
}
/** Returns intersection node index, or -1 if none */
int findIntersection(int[] forwardNodes, int[] reverseNodes) {
for (int node : reverseNodes) {
if (find(forwardNodes, node)) {
return node;
}
}
return -1;
}
}
// -------------------------------------------------------------------------
// Fast path: unordered_set (HashSet) O(1) lookup
// -------------------------------------------------------------------------
static class FastIntersectionFinder {
int ops = 0;
int findIntersection(int[] forwardNodes, int[] reverseNodes) {
// Build set from forward_nodes once O(F)
Set<Integer> forwardSet = new HashSet<>(forwardNodes.length * 2);
for (int n : forwardNodes) forwardSet.add(n);
for (int node : reverseNodes) {
ops++;
if (forwardSet.contains(node)) {
return node;
}
}
return -1;
}
}
// -------------------------------------------------------------------------
// Build test data: two vectors of node IDs that share a suffix
// forward_nodes: 0, 1, 2, ..., F-1
// reverse_nodes: F*2, F*2+1, ..., F*2+R-sharedSuffix-1, then F-sharedSuffix, ..., F-1
// Intersection is at node F-sharedSuffix (last shared node in forward, first in overlap)
// If sharedSuffix == 0, no intersection exists (worst-case: scan all R × F)
// -------------------------------------------------------------------------
static int[] buildForwardNodes(int F) {
int[] nodes = new int[F];
for (int i = 0; i < F; i++) nodes[i] = i;
return nodes;
}
static int[] buildReverseNodes(int F, int R, int sharedSuffix) {
// First (R - sharedSuffix) nodes are unique to reverse, rest overlap with forward tail
int[] nodes = new int[R];
int uniqueCount = R - sharedSuffix;
for (int i = 0; i < uniqueCount; i++) {
nodes[i] = F * 2 + i; // disjoint from forward
}
for (int i = 0; i < sharedSuffix; i++) {
nodes[uniqueCount + i] = F - sharedSuffix + i; // overlaps with forward tail
}
return nodes;
}
public static void main(String[] args) {
System.out.println("=== valhalla-0001: IsSlipLane forward_nodes linear scan ===\n");
// --- Test 1: correctness both paths find the same intersection ---
{
int F = 20, R = 20, shared = 5;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, shared);
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("correctness: intersection found by both paths",
slowResult != -1 && fastResult != -1);
check("correctness: both agree on intersection node ("
+ slowResult + " == " + fastResult + ")", slowResult == fastResult);
}
// --- Test 2: correctness no intersection case ---
{
int F = 20, R = 20, shared = 0;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, shared);
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("no-intersection: both return -1 (slow=" + slowResult + " fast=" + fastResult + ")",
slowResult == -1 && fastResult == -1);
}
// --- Test 3: complexity worst case (no intersection) ---
{
// No shared nodes slow does F×R comparisons
int F = 200, R = 200;
int[] fwd = buildForwardNodes(F);
int[] rev = buildReverseNodes(F, R, 0); // no intersection
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
slow.findIntersection(fwd, rev);
fast.findIntersection(fwd, rev);
long expectedSlowOps = (long) F * R; // scans all F for each of R reverse nodes
double ratio = (double) slow.ops / fast.ops;
check("worst-case: slow_ops == F*R ("
+ slow.ops + " == " + expectedSlowOps + ")", slow.ops == expectedSlowOps);
check("worst-case: fast_ops == R ("
+ fast.ops + " == " + R + ")", fast.ops == R);
check("worst-case: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)",
ratio >= 10.0);
System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops
+ " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")");
}
// --- Test 4: complexity early exit (intersection at start of reverse) ---
{
// Intersection at first reverse node slow does F comparisons, fast does 1
int F = 200, R = 200;
int[] fwd = buildForwardNodes(F);
// Make first reverse node = 0 (first forward node found immediately in fast,
// but slow scans forward until it finds it = position 0 = 1 op)
int[] rev = new int[R];
rev[0] = 0; // immediately in forward set
for (int i = 1; i < R; i++) rev[i] = F * 2 + i; // rest disjoint
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
int slowResult = slow.findIntersection(fwd, rev);
int fastResult = fast.findIntersection(fwd, rev);
check("early-exit: both find node 0", slowResult == 0 && fastResult == 0);
check("early-exit: slow_ops=1 (found at start of forward)",
slow.ops == 1);
check("early-exit: fast_ops=1 (hash lookup)", fast.ops == 1);
}
// --- Test 5: complexity intersection at END of forward (worst linear scan) ---
{
// Intersection at forward[F-1] slow must scan all F nodes before finding match
int F = 300, R = 100;
int[] fwd = buildForwardNodes(F);
// rev[0] = F-1 found, but only after scanning all F forward nodes (slow)
int[] rev = new int[R];
rev[0] = F - 1;
for (int i = 1; i < R; i++) rev[i] = F * 2 + i;
SlowIntersectionFinder slow = new SlowIntersectionFinder();
FastIntersectionFinder fast = new FastIntersectionFinder();
slow.findIntersection(fwd, rev);
fast.findIntersection(fwd, rev);
double ratio = (double) slow.ops / fast.ops;
check("tail-match: slow_ops == F (scanned all forward, slow=" + slow.ops + ")",
slow.ops == F);
check("tail-match: fast_ops == 1 (hash, fast=" + fast.ops + ")", fast.ops == 1);
check("tail-match: ratio >= F (" + String.format("%.0f", ratio) + "x >= " + F + "x)",
ratio >= F);
System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops
+ " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")");
}
System.out.println("\n" + passCount + "/" + checkCount + " PASS");
}
}