Class ShallowRecursiveBlockingMemo
ShallowRecursiveBlockingStack plus a delta-replay continuation memo.
Same algorithm, same results as the no-memo stack version (and the
recursive reference); this class adds the clique optimization that
RecursiveBlocking carries, adapted so it is correct for the
shallow-commit variant.
Why a delta memo, not a verdict-only memo. RecursiveBlocking
caches the verdict alone and, on a hit, returns it without replaying the
nodes that the original frame added to Z. That is tolerable for the
deep machine, whose commit decision never inspects whether Z grew, but
it is unsafe here: the shallow machine's entire "already closed vs. open"
decision is the Z-growth signal (Branch A blocked without growing
Z ⇒ already closed ⇒ do not condition on b). A verdict-only
hit on a child would report BLOCKED with no growth, the parent would read that
as "already closed," and a required near-x conditioning node would
silently never be added. So we cache the pair
(verdict, delta = Z‑added) and, on a hit, do z.addAll(delta)
before returning the verdict.
Why delta replay is correct. The key is (a, b, entry‑Z) and
we cache only determined verdicts (BLOCKED with its delta, or
UNBLOCKABLE with empty delta), never INDETERMINATE. A determined verdict is a
depth-independent fact: "Z ∪ delta blocks every b→y
continuation" (BLOCKED) or "some continuation is unblockable regardless of
Z" (UNBLOCKABLE) holds no matter the arrival depth or the path taken
to reach (a, b). Because the key pins entry-Z, a hit means the
live Z equals the stored entry-Z, so replaying delta
reproduces the original frame's final Z node-for-node — which also
keeps the depth cap satisfied (same final size) and preserves the
openness signal for the caller.
Taint. The one path-dependent verdict is a cycle hit
(b ∈ path ⇒ BLOCKED), which is an artifact of where we came from.
Such a verdict, and any ancestor verdict computed using it, must not be
cached. A pathTainted flag propagates from a cycle-hit child to its
parent and onward; tainted frames are never stored. Taint gates
storing only — a cache hit is always safe because stored
entries are untainted and therefore path-independent. The flag is single and
never reset, so a Branch-A cycle hit conservatively taints a frame even when
the final verdict came from Branch B; this matches RecursiveBlocking
and keeps the correctness argument simple, at the cost of some cache entries.
Scope. The memo lives for one findPathToTargetVisitShallow
call (one first hop, one driver iteration). It is deliberately not shared
across first hops or fixed-point iterations, because Z grows between
them and a per-call cache keeps every key consistent with the Z in
effect when each frame was entered.
The memo must be a pure speedup: results must be identical to
ShallowRecursiveBlockingStack on every instance. Any divergence is a
memo bug.
-
Method Summary
Modifier and TypeMethodDescriptionblockPathsRecursively(Graph graph, Node x, Node y, Set<Node> containing, Set<Node> notFollowed, int recursiveDepth, int depth, int maxRadius, int nearWhichEndpoint, boolean ignoreDirectEdge) Shallow ("near-x") separating-set search, no deadline.blockPathsRecursively(Graph graph, Node x, Node y, Set<Node> containing, Set<Node> notFollowed, int recursiveDepth, int depth, int maxRadius, int nearWhichEndpoint, boolean ignoreDirectEdge, long deadlineMs) Shallow ("near-x") separating-set search.
-
Method Details
-
blockPathsRecursively
public static RecursiveBlocking.BlockingResult blockPathsRecursively(Graph graph, Node x, Node y, Set<Node> containing, Set<Node> notFollowed, int recursiveDepth, int depth, int maxRadius, int nearWhichEndpoint, boolean ignoreDirectEdge) throws InterruptedException Shallow ("near-x") separating-set search, no deadline.- Parameters:
graph- the graphx- first endpointy- second endpointcontaining- nodes forced into Z (the seed set)notFollowed- nodes not to be traversedrecursiveDepth- maximum recursion depth (-1 = graph.getNumNodes())depth- maximum size of Z (-1 = unlimited)maxRadius- BFS radius for the node pool (-1 = unlimited)nearWhichEndpoint- 1 = near x, 2 = near y, 3 = near bothignoreDirectEdge- whether to ignore the direct x–y edge- Returns:
- a
RecursiveBlocking.BlockingResultdescribing the outcome - Throws:
InterruptedException- if the thread is interrupted
-
blockPathsRecursively
public static RecursiveBlocking.BlockingResult blockPathsRecursively(Graph graph, Node x, Node y, Set<Node> containing, Set<Node> notFollowed, int recursiveDepth, int depth, int maxRadius, int nearWhichEndpoint, boolean ignoreDirectEdge, long deadlineMs) throws InterruptedException Shallow ("near-x") separating-set search.- Parameters:
graph- the graphx- first endpointy- second endpointcontaining- nodes forced into Z (the seed set)notFollowed- nodes not to be traversedrecursiveDepth- maximum recursion depth (-1 = graph.getNumNodes())depth- maximum size of Z (-1 = unlimited)maxRadius- BFS radius for the node pool (-1 = unlimited)nearWhichEndpoint- 1 = near x, 2 = near y, 3 = near bothignoreDirectEdge- whether to ignore the direct x–y edgedeadlineMs- deadline for the operation (in ms)- Returns:
- a
RecursiveBlocking.BlockingResultdescribing the outcome - Throws:
InterruptedException- if the thread is interrupted
-