feat: Add parallel match execution to tournament graph algorithm#8
feat: Add parallel match execution to tournament graph algorithm#8bohan-contextual wants to merge 14 commits intomainfrom
Conversation
Add parallel execution of tournament matches using asyncio.gather to reduce round count. Introduces max_parallel_matches parameter (default 5) to control parallelism. Includes comprehensive test suite with 540+ lines covering correctness, oracle call parity, and edge cases up to 3000 items. Made-with: Cursor
Show max_parallel_matches=2 in BlitzRank usage example to demonstrate the new parallel execution feature. Made-with: Cursor
Change example to show default value of 5 parallel matches, matching the implementation default. Made-with: Cursor
Introduce _interleave_cycle_reps to distribute cycle representatives at the front of each match. This ensures items in cycles (multi-node SCCs) get compared against singletons, preventing cycles from being starved due to their high in_reach values. Made-with: Cursor
Replace per-node descendants/ancestors with SCC-level transitive closure for O(S^2) complexity where S=num_sccs, typically << V. Precompute per-SCC reach and map back to nodes for significant speedup on large graphs. Made-with: Cursor
Revert performance optimization due to correctness issues. Return to simpler per-node descendants/ancestors calculation. Made-with: Cursor
Remove complex cycle prioritization in favor of simpler representative collection. Prepares for alternative approach using reach calculation fix. Made-with: Cursor
Correct in_reach and out_reach to exclude same-SCC members, preventing inflated counts for nodes in cycles. They were incorrectly counting internal cycle edges. Update tests and comments to reflect external reachability semantics. Simplifies scheduling by removing need for cycle-first ordering. Made-with: Cursor
Increment version for parallel tournament execution release. Made-with: Cursor
| on_round_complete: Callable[["RoundLog"], Awaitable[None] | None] | None = None | ||
|
|
||
| max_num_rounds: int = DEFAULT_MAX_NUM_ROUNDS | ||
| max_parallel_matches: int = 5 # number of matches to run in parallel per round |
There was a problem hiding this comment.
why should the default be 5? should we just set it to 1 for stability and backward compatibility and then let users decide to increase this when needed?
| break | ||
| return TournamentProgress(representatives, [], sorted_node_infos) | ||
|
|
||
| # Determine how many parallel matches to run |
There was a problem hiding this comment.
can we refactor this into a function? also how was the parallel matches designed?
There was a problem hiding this comment.
maybe we should discuss this a bit more?
There was a problem hiding this comment.
yeah, maybe we can jump on a huddle or board for this.
| ancestors = nx.ancestors(self.G, node) | ||
| out_reach[node] = len(descendants) | ||
| in_reach[node] = len(ancestors) | ||
| # Exclude same-SCC members from in_reach/out_reach to avoid inflated |
There was a problem hiding this comment.
Thank you for this fix! Can we decouple this fix + the test into its own separate PR?
|
|
||
| class BlitzRank(Ranker): | ||
| def __init__(self, window_size: int = 20, top_m: int = 10): | ||
| def __init__(self, window_size: int = 20, top_m: int = 10, max_parallel_matches: int = 5): |
There was a problem hiding this comment.
same with default max_parallel_matches here.
There was a problem hiding this comment.
let's go with 1. i don't feel strongly, but i do think some parallelism really help to speed up the algo thus default
do you have a sense what average / median sample size we should aim for out of the box?
Summary
Core Algorithm Changes:
Reachability Fix:
API Changes:
Added comprehensive test suite (test_parallel_tournament.py) with 540+ lines covering: