eval.doubles
eval.doubles
What Raccoon’s two-step doubles execution costs in play (exp020).
OpenSpiel splits a doubles turn into two consecutive decisions by the same player, two of the four half-moves each. GNUBG’s side of the arena (:func:raccoon.eval.gnubg_adapter.candidate_equities) recurses through both halves and optimises the turn jointly. Raccoon’s side historically did not: it ranked half 1 by the static value of the intermediate position — which :func:encode_pre_roll hands to the value head with the dice and the mid-doubles flag cleared, so the net is told “you are about to roll” about a position where the mover still owes two half-moves — and then picked half 2 greedily. On 1/6 of turns the two engines were not playing by the same rule, and only one of them was handicapped.
This module measures the gap directly, on the distribution where it matters:
concession = eq_gnubg(final_joint) - eq_gnubg(final_greedy)
Both finals come from the same network, so the teacher’s own move choice never enters and this is purely a question of how the net’s value function is searched. The main line plays the greedy arm by default (play_joint=False), so the sample is the exp019 playing distribution — the one whose ppg shortfall prompted the experiment — and the joint arm is scored as a counterfactual on each turn.
Two properties keep the measurement honest:
- Non-doubles turns are a control, not filler. A turn that needs one OpenSpiel action has one leaf per legal action, so joint and greedy enumerate the identical set and must agree exactly. Any non-zero concession there is a harness bug — a bad resting signature, a sign error, a mis-judged terminal — not a strength difference.
tests/test_doubles.pypins the same property at thechild_valueslevel. - Measuring does not perturb the games. Both arms are explored on clones, and neither
select_moveat temperature 0 norevaluate_equitytouches the RNG, so a run with the same seed visits the same positions as a plaingnubg_arena.
Judged at judge_ply (default 2 — the strongest affordable referee, and the tier the distillation teacher used). GNUBG is only consulted when the two arms actually disagree; when they land on the same position the concession is exactly zero and no evaluation is needed, which is what keeps the run affordable.
Functions
| Name | Description |
|---|---|
| doubles_concession_arena | Play net vs GNUBG and score both doubles arms at every net turn. |
doubles_concession_arena
eval.doubles.doubles_concession_arena(
net,
device,
games,
gnubg_ply=0,
judge_ply=2,
seed=0,
max_moves=2000,
play_joint=False,
)Play net vs GNUBG and score both doubles arms at every net turn.
Returns per-turn arrays (two_step, disagreed, concession, concession_0ply, n_paths) plus per-game points, so a caller can compute an empirical CI on the concession and cross-check the raw ppg against exp019. Seats alternated; seeds the global numpy RNG (dice), matching :func:raccoon.train.td_selfplay.gnubg_arena.