Port safe-heuristic bots to Cython with Python reference fallback
Cython implementation in heuristic_cy.pyx achieves ~2.55× speedup on opponent_act_seconds (200-game eval: 59.20s → 23.24s). Original Python implementation preserved verbatim in heuristic_py.py as the equivalence reference. Action-sequence equivalence is verified by test_safe_heuristic_equivalence.py against seeded game corpora. Key implementation notes: - File-local wraparound=True override required for negative discard indexing; Cython global wraparound=False would segfault. - annotation_typing=False preserves verbatim Python semantics. - _CachedState materializes hands/expeditions/discards/deck once per act() call — this is the dominant performance win. Further C-array optimization of _card_value_for_me / _card_value_for_opponent / _color_commitment / _bonus_potential is deferred. The current 2.55× delivers most of the dense-eval future benefit; further work is gated on actually adopting denser eval schedules (eval_every=5, games=1000). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,10 @@ extensions = cythonize(
|
|||||||
"coolrl_lost_cities.games.classic.deep_cfr.traversal",
|
"coolrl_lost_cities.games.classic.deep_cfr.traversal",
|
||||||
["src/coolrl_lost_cities/games/classic/deep_cfr/traversal.pyx"],
|
["src/coolrl_lost_cities/games/classic/deep_cfr/traversal.pyx"],
|
||||||
),
|
),
|
||||||
|
Extension(
|
||||||
|
"coolrl_lost_cities.games.classic.bots.heuristic_cy",
|
||||||
|
["src/coolrl_lost_cities/games/classic/bots/heuristic_cy.pyx"],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
language_level=3,
|
language_level=3,
|
||||||
compiler_directives={
|
compiler_directives={
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from coolrl_lost_cities.games.classic.game import GameState, LostCitiesConfig
|
||||||
|
|
||||||
|
from coolrl_lost_cities.games.classic.bots.heuristic import SafeHeuristicBot
|
||||||
|
from coolrl_lost_cities.games.classic.bots.heuristic_py import (
|
||||||
|
SafeHeuristicBot as PythonSafeHeuristicBot,
|
||||||
|
)
|
||||||
|
from coolrl_lost_cities.games.classic.bots.registry import (
|
||||||
|
LOOSE_SAFE_HEURISTIC_PARAMS,
|
||||||
|
STRICT_SAFE_HEURISTIC_PARAMS,
|
||||||
|
)
|
||||||
|
|
||||||
|
VARIANTS = (
|
||||||
|
("default", None),
|
||||||
|
("loose", LOOSE_SAFE_HEURISTIC_PARAMS),
|
||||||
|
("strict", STRICT_SAFE_HEURISTIC_PARAMS),
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIGS = (
|
||||||
|
LostCitiesConfig(),
|
||||||
|
LostCitiesConfig(n_colors=2, n_ranks=8, hand_size=3),
|
||||||
|
LostCitiesConfig(n_colors=3, n_ranks=5, n_handshakes=0, hand_size=5),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("variant_name", "params"), VARIANTS)
|
||||||
|
@pytest.mark.parametrize("config", CONFIGS)
|
||||||
|
@pytest.mark.parametrize("seed", range(2))
|
||||||
|
def test_cython_safe_heuristic_matches_python_action_sequence(
|
||||||
|
variant_name: str,
|
||||||
|
params,
|
||||||
|
config: LostCitiesConfig,
|
||||||
|
seed: int,
|
||||||
|
) -> None:
|
||||||
|
py_bot = PythonSafeHeuristicBot(params)
|
||||||
|
cy_bot = SafeHeuristicBot(params)
|
||||||
|
py_state = GameState.new_game(config, seed=seed)
|
||||||
|
cy_state = GameState.new_game(config, seed=seed)
|
||||||
|
|
||||||
|
turn = 0
|
||||||
|
while not py_state.terminal:
|
||||||
|
assert turn < 500, f"variant={variant_name} seed={seed} did not terminate"
|
||||||
|
py_action = py_bot.act(py_state)
|
||||||
|
cy_action = cy_bot.act(cy_state)
|
||||||
|
assert cy_action == py_action, (
|
||||||
|
f"variant={variant_name} seed={seed} turn={turn} "
|
||||||
|
f"phase={py_state.phase} player={py_state.current_player} "
|
||||||
|
f"python={py_action} cython={cy_action}"
|
||||||
|
)
|
||||||
|
py_state.apply_action(py_action)
|
||||||
|
cy_state.apply_action(cy_action)
|
||||||
|
turn += 1
|
||||||
|
|
||||||
|
assert cy_state.terminal is True
|
||||||
Reference in New Issue
Block a user