Optimize algorithm hot paths with typed-array CSR kernels - #34
Merged
Conversation
Rewrites the algorithm hot paths onto typed-array CSR kernels: iterative Tarjan SCC, Kahn toposort with cached in-degrees, CSR-direct bipartite coloring, compact-arc Bellman-Ford (plus a single-pair fast path), flat-matrix Floyd-Warshall with copy-on-write tie lists, chunked hand-rolled BFS/DFS/postorder iterators, cached per-arc default weights for Dijkstra/A*/bidirectional search, a per-version degree map, and O(length) shortest-path materialization via a shared backtracking buffer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
BFS/DFS batches start at 8 nodes and double up to 1024, so taking the first few nodes of a huge graph costs a few nodes of traversal (plus the same O(n) visited/queue allocation the generator implementation paid), while full traversals keep amortized chunked speed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
Floyd-Warshall no longer records tying self-loops as predecessors — the sparse diagonal cell made the old push crash, and recording them would let tie merging build self-referential lists that hang reconstruction. Negative self-loops still surface via the negative-cycle check, and reconstruction now carries the same on-path guard as the Dijkstra-side enumeration. Adds regression tests for both shapes. The one-entry index memo now holds WeakRefs, so it never extends the lifetime of the most recently queried graph. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
updateNode swaps the node object in place (same id/position, arrays untouched), which no version or staleness check can observe — the CSR's cached node snapshot kept serving the pre-update object to fresh traversals. The index now notifies derived caches of node replacement and the CSR patches the one slot in O(1), so traversals stay both fast and current. Adds a regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
The BFS/DFS fast serve paths check their cursors before the finished flag, so an injected throw() left buffered nodes servable. return() and throw() now share a close() hook that drops buffered output, matching generator exhaustion semantics. Adds a regression test for both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
Setup errors (e.g. radius validation) left the iterator half-initialized with started=true; a second next() on the postorder iterator then hit undefined internal state instead of returning done. Setup now runs through a shared ensureStarted() that marks the iterator finished on failure, matching generator semantics for all three traversals. Adds a regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Performance overhaul of the algorithm hot paths. Every rewrite preserves observable behavior — same results, same ordering, same laziness and error contracts — and all 1,852 existing tests pass unchanged.
getStronglyConnectedComponents— iterative typed-array Tarjan over the CSR out-arcs (single pass, stack-safe on deep graphs). ~15x faster.getTopologicalSort— Kahn's algorithm over CSR arcs with a typed ring queue and per-CSR cached in-degrees, replacing the Map-based version whosequeue.shift()was O(n²). ~20x faster.isBipartite/getMaximumBipartiteMatching— 2-coloring runs directly over the cached CSR arcs instead of rebuilding an undirected adjacency on every call; the self-loop sweep now only runs on the success path. ~60x faster on repeated queries.getShortestPaththat returns the same path the full enumeration would yield first. ~15x faster.Float64Arraydistance matrix with copy-on-write tie-predecessor pair lists, replacing nested object arrays with per-cell clones andsome()dedup scans. ~4x faster.genBFS/genDFS/genPostorder— hand-rolledGenerator-protocol iterators (no generator resume machinery), with BFS/DFS batching work in 1024-node chunks served from a buffer. Yield order is unchanged (property-checked against the previous implementation across random/scale-free graphs and all directions), validation still throws on firstnext(), and mid-iteration mutation snapshots behave as before — now backed by the CSR's node snapshot instead of a per-iterator array copy. ~1.5–2x faster full traversals.Float64Arraycached on the CSR, removing edge-object loads (and?? 1fallbacks) from the inner loops.getDegree— served from a per-version degree map (one hashed lookup per call); a one-entry memo in front of the index WeakMap speeds all point-query bursts against the same graph.getAllPairsShortestPathsruntime on path-like graphs.Notes for review
GraphIndex/CSR version, so they inherit the documented staleness contract — mutation-API changes invalidate them, in-place field edits still requireinvalidateIndex().getMaximumBipartiteMatching's error may differ from before when several edges prove non-bipartiteness (any witness is valid; tests only assert the message shape).Testing
pnpm verify(typecheck, repo typecheck, generated schemas, conventions, full test suite, build, publint + package smoke test) passes.🤖 Generated with Claude Code
https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
Generated by Claude Code