1616from ._native import NativeEnv
1717from .positions import PositionSet
1818from .ratings import Rating
19+ from .stockfish import StockfishPool
1920from .trajectories import EpisodeRecorder , _time
2021
2122
@@ -32,6 +33,7 @@ class Agent:
3233 opponent : str | None = None
3334 initial_rating : Rating | None = None
3435 fixed : bool = False
36+ stockfish_pool : StockfishPool | None = None
3537
3638 def __post_init__ (self ):
3739 if not isinstance (self .id , str ) or not self .id or len (self .id .encode ("utf-8" )) > 256 :
@@ -48,9 +50,14 @@ def __post_init__(self):
4850 raise ValueError ("policy_factory must be callable" )
4951 if self .opponent is not None and (
5052 not isinstance (self .opponent , str )
51- or (self .opponent != "random" and not re .fullmatch (r"(minimax|mcts):([1-9]|10)" , self .opponent ))
53+ or (self .opponent != "random" and not re .fullmatch (r"(minimax|mcts|stockfish ):([1-9]|10)" , self .opponent ))
5254 ):
53- raise ValueError ("opponent must be random, minimax:1..10, or mcts:1..10" )
55+ raise ValueError ("opponent must be random, minimax:1..10, mcts:1..10, or stockfish:1..10" )
56+ is_stockfish = self .opponent is not None and self .opponent .startswith ("stockfish:" )
57+ if self .stockfish_pool is not None and (not is_stockfish or not isinstance (self .stockfish_pool , StockfishPool )):
58+ raise ValueError ("stockfish_pool is only valid for a Stockfish opponent" )
59+ if is_stockfish and self .stockfish_pool is None :
60+ object .__setattr__ (self , "stockfish_pool" , StockfishPool ())
5461
5562
5663def _wilson (wins , games ):
@@ -61,12 +68,13 @@ def _wilson(wins, games):
6168 denominator = 1 + z * z / games
6269 center = (p + z * z / (2 * games )) / denominator
6370 radius = z * math .sqrt (p * (1 - p ) / games + z * z / (4 * games * games )) / denominator
64- return [max (0.0 , center - radius ), min (1.0 , center + radius )]
71+ return [0.0 if wins == 0 else max (0.0 , center - radius ),
72+ 1.0 if wins == games else min (1.0 , center + radius )]
6573
6674
6775def run_round_robin (
6876 game , agents , * , games_per_pair = 2 , seed = 42 , config = None , position_set = None ,
69- tau = 0.5 , run_id = None , created_at = None , on_episode = None ,
77+ tau = 0.5 , run_id = None , created_at = None , on_episode = None , matchups = None ,
7078):
7179 """Run balanced seats and rate completed games as one Glicko-2 period.
7280
@@ -81,7 +89,21 @@ def run_round_robin(
8189 raise ValueError ("agent snapshot ids must be unique" )
8290 if type (games_per_pair ) is not int or not 2 <= games_per_pair <= 1000 or games_per_pair % 2 :
8391 raise ValueError ("games_per_pair must be even, 2..1000, for balanced seats" )
84- total = len (agents ) * (len (agents ) - 1 ) // 2 * games_per_pair
92+ schedule = list (itertools .combinations (agents , 2 ))
93+ if matchups is not None :
94+ lookup , seen , schedule = {agent .id : agent for agent in agents }, set (), []
95+ for pair in matchups :
96+ if (
97+ not isinstance (pair , (list , tuple )) or len (pair ) != 2
98+ or any (not isinstance (name , str ) or name not in lookup for name in pair )
99+ or pair [0 ] == pair [1 ] or frozenset (pair ) in seen
100+ ):
101+ raise ValueError ("matchups must name unique pairs of distinct registered agents" )
102+ seen .add (frozenset (pair ))
103+ schedule .append ((lookup [pair [0 ]], lookup [pair [1 ]]))
104+ if not schedule :
105+ raise ValueError ("matchups must include at least one pair" )
106+ total = len (schedule ) * games_per_pair
85107 if total > 10000 or type (seed ) is not int or not 0 <= seed <= 2 ** 64 - total :
86108 raise ValueError ("tournament exceeds 10000 games or the 64-bit seed range" )
87109 if isinstance (tau , bool ) or not isinstance (tau , (int , float )) or not 0.2 <= tau <= 1.2 :
@@ -91,6 +113,8 @@ def run_round_robin(
91113 if prototype .num_players != 2 or spec ["turn_structure" ] != "sequential" :
92114 raise ValueError ("round robin currently requires a sequential two-player game" )
93115 for agent in agents :
116+ if agent .stockfish_pool is not None and game != "chess" :
117+ raise ValueError ("Stockfish matchups require chess" )
94118 if agent .opponent not in (None , "random" ) and (
95119 spec ["information" ] != "perfect" or spec ["stochastic" ]
96120 ):
@@ -112,7 +136,7 @@ def run_round_robin(
112136 totals = {agent .id : {"wins" : 0 , "draws" : 0 , "losses" : 0 , "truncated" : 0 , "failed" : 0 ,
113137 "invalid_starts" : 0 , "seats" : [0 , 0 ]} for agent in agents }
114138 matches = []
115- for left , right in itertools . combinations ( agents , 2 ) :
139+ for left , right in schedule :
116140 for repetition in range (games_per_pair ):
117141 order = (left , right ) if repetition % 2 == 0 else (right , left )
118142 match_seed = seed + len (matches )
@@ -154,8 +178,11 @@ def run_round_robin(
154178 action = int (rng .choice (np .flatnonzero (info ["action_mask" ])))
155179 elif agent .opponent is not None :
156180 algorithm , level = agent .opponent .split (":" )
157- action = episode .builtin_action (seat , algorithm , int (level ),
158- int (rng .integers (0 , 2 ** 64 , dtype = np .uint64 )))
181+ planning_seed = int (rng .integers (0 , 2 ** 64 , dtype = np .uint64 ))
182+ if algorithm == "stockfish" :
183+ action = episode .stockfish_action (agent .stockfish_pool , seat , int (level ), planning_seed )
184+ else :
185+ action = episode .builtin_action (seat , algorithm , int (level ), planning_seed )
159186 else :
160187 action = policies [seat ](obs , info )
161188 except Exception :
@@ -222,5 +249,10 @@ def run_round_robin(
222249 "seed" : str (seed ), "created_at" : created_at , "games_per_pair" : games_per_pair ,
223250 "rating_system" : {"name" : "glicko2" , "tau" : tau , "period" : "whole_tournament" },
224251 "position_set" : None if dataset is None else {"id" : dataset .identifier , "sha256" : dataset .sha256 },
252+ "agent_specs" : [{
253+ "id" : agent .id , "opponent" : agent .opponent ,
254+ "stockfish" : None if agent .stockfish_pool is None else agent .stockfish_pool .identity (),
255+ } for agent in agents ],
256+ "matchups" : [[left .id , right .id ] for left , right in schedule ],
225257 "standings" : standings , "matches" : matches ,
226258 }
0 commit comments