Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
119 lines
4.6 KiB
Java
119 lines
4.6 KiB
Java
package support;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Reference implementations of InferenceGraph.Node.closure() — defective and fixed.
|
|
*
|
|
* DEFECT 0002b (Infer.java:1747): closure() recomputes the DFS reachability
|
|
* set on every call — no caching. Called K times from canInfluence() within
|
|
* the O(N²) loop in buildStuckGraph(). With the O(N) findNode scan (0002a),
|
|
* the combined total = O(N³).
|
|
*
|
|
* Fix: cache the closure result after the first computation.
|
|
*
|
|
* Exact node-visit counts when calling closure(N0) K times on a V-node chain:
|
|
* defective: K * V (each of K calls runs a full O(V) DFS)
|
|
* fixed: V + (K - 1) (first call = V visits; each cache hit = 1)
|
|
*
|
|
* Derivation:
|
|
* Chain N0→N1→...→N(V-1). closure(N0) = all V nodes.
|
|
* Defective: K independent DFS traversals each visiting V nodes = K*V.
|
|
* Fixed: 1st call DFS visits V nodes and stores result. Calls 2..K each
|
|
* find the cached set in one check: 1 visit each. Total = V + (K-1).
|
|
*
|
|
* Growth when K doubles (V fixed): defective ≈2x (linear in K), fixed ≈1x
|
|
* (nearly constant: (V+2K-1)/(V+K-1) → 1 as V grows or K≪V).
|
|
*/
|
|
public class ClosureAlgorithm {
|
|
|
|
public static class Node {
|
|
public final String label;
|
|
public final List<Node> deps = new ArrayList<>();
|
|
// used by fixed version
|
|
Set<Node> cachedClosure = null;
|
|
|
|
public Node(String label) { this.label = label; }
|
|
public void addDep(Node dep) { deps.add(dep); }
|
|
@Override public String toString() { return label; }
|
|
}
|
|
|
|
public static class Result {
|
|
/** The closure set returned on each of the K calls (all identical). */
|
|
public final Set<Node> closure;
|
|
/**
|
|
* Total node visits across all K calls.
|
|
* Defective: K * V. Fixed: V + (K-1).
|
|
*/
|
|
public final long nodeVisits;
|
|
Result(Set<Node> closure, long nodeVisits) {
|
|
this.closure = closure;
|
|
this.nodeVisits = nodeVisits;
|
|
}
|
|
}
|
|
|
|
// ─── DEFECTIVE: uncached DFS on every call ───────────────────────────────
|
|
// Mirrors Infer.java:1747 — computes closure fresh each time it is called.
|
|
|
|
public static Result closureDefective(Node start, int calls) {
|
|
long[] visits = {0};
|
|
Set<Node> last = null;
|
|
for (int i = 0; i < calls; i++) {
|
|
Set<Node> closure = new HashSet<>();
|
|
dfs(start, closure, visits);
|
|
last = closure;
|
|
}
|
|
return new Result(last, visits[0]);
|
|
}
|
|
|
|
private static void dfs(Node n, Set<Node> visited, long[] visits) {
|
|
if (visited.contains(n)) return;
|
|
visits[0]++;
|
|
visited.add(n);
|
|
for (Node dep : n.deps) dfs(dep, visited, visits);
|
|
}
|
|
|
|
// ─── FIXED: cached DFS ───────────────────────────────────────────────────
|
|
// First call runs the DFS and stores the result on the node.
|
|
// Subsequent calls find the cached result in one check.
|
|
|
|
public static Result closureFixed(Node start, int calls) {
|
|
long[] visits = {0};
|
|
Set<Node> last = null;
|
|
for (int i = 0; i < calls; i++) {
|
|
last = closureCached(start, visits);
|
|
}
|
|
return new Result(last, visits[0]);
|
|
}
|
|
|
|
private static Set<Node> closureCached(Node n, long[] visits) {
|
|
if (n.cachedClosure != null) {
|
|
visits[0]++; // one operation: read the cached field
|
|
return n.cachedClosure;
|
|
}
|
|
Set<Node> closure = new HashSet<>();
|
|
dfs(n, closure, visits); // counts V visits
|
|
n.cachedClosure = closure;
|
|
return closure;
|
|
}
|
|
|
|
// ─── Factory ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Linear chain of V nodes: N0→N1→...→N(V-1).
|
|
* closure(N0) = all V nodes.
|
|
* Returns N0 (the start node for closure queries).
|
|
*
|
|
* Creates fresh nodes — call this separately for defective vs fixed tests
|
|
* so the fixed cache does not leak between test runs.
|
|
*/
|
|
public static Node buildLinearChain(int v) {
|
|
List<Node> nodes = new ArrayList<>();
|
|
for (int i = 0; i < v; i++) nodes.add(new Node("N" + i));
|
|
for (int i = 0; i < v - 1; i++) nodes.get(i).addDep(nodes.get(i + 1));
|
|
return nodes.get(0);
|
|
}
|
|
}
|