Add JAX PPO ladder v2 expert pass
This commit is contained in:
@@ -13,6 +13,7 @@ from lost_cities_jax.opponents import (
|
||||
discard_only_action,
|
||||
heuristic_balanced_action,
|
||||
heuristic_cautious_action,
|
||||
heuristic_expert_action,
|
||||
)
|
||||
from lost_cities_jax.ppo import (
|
||||
JaxPPOConfig,
|
||||
@@ -65,7 +66,12 @@ def tiny_config(tmp_path) -> JaxPPOConfig:
|
||||
def test_static_opponents_return_legal_actions():
|
||||
state = reset(jax.random.PRNGKey(0))
|
||||
mask = np.asarray(legal_action_mask(state), dtype=bool)
|
||||
for fn in [discard_only_action, heuristic_balanced_action, heuristic_cautious_action]:
|
||||
for fn in [
|
||||
discard_only_action,
|
||||
heuristic_balanced_action,
|
||||
heuristic_cautious_action,
|
||||
heuristic_expert_action,
|
||||
]:
|
||||
action = int(fn(state, jnp.int32(0), jax.random.PRNGKey(1)))
|
||||
assert mask[action]
|
||||
|
||||
@@ -112,6 +118,66 @@ def test_cautious_uses_own_board_when_playing_as_p1():
|
||||
assert hand[hand_slot] == playable
|
||||
|
||||
|
||||
def test_expert_opens_strong_ev_hand():
|
||||
strong_cards = [
|
||||
_hs_card(0, 0),
|
||||
_rank_card(0, 7),
|
||||
_rank_card(0, 8),
|
||||
_rank_card(0, 9),
|
||||
_rank_card(0, 10),
|
||||
_rank_card(1, 2),
|
||||
_rank_card(2, 2),
|
||||
_rank_card(3, 2),
|
||||
]
|
||||
state = _manual_state(p0_hand=strong_cards, to_move=0)
|
||||
action = int(heuristic_expert_action(state, jnp.int32(0), jax.random.PRNGKey(1)))
|
||||
hand_slot, place_type, _ = [int(x) for x in decode_action(jnp.asarray(action))]
|
||||
hand = [int(x) for x in current_hand_sorted(state, 0)]
|
||||
assert place_type == PLAY
|
||||
assert hand[hand_slot] // CARDS_PER_COLOR == 0
|
||||
|
||||
|
||||
def test_expert_rejects_weak_new_open():
|
||||
weak_cards = [
|
||||
_rank_card(0, 2),
|
||||
_rank_card(0, 3),
|
||||
_rank_card(0, 4),
|
||||
_rank_card(1, 2),
|
||||
_rank_card(2, 2),
|
||||
_rank_card(3, 2),
|
||||
_rank_card(4, 2),
|
||||
_rank_card(4, 3),
|
||||
]
|
||||
state = _manual_state(p0_hand=weak_cards, to_move=0)
|
||||
action = int(heuristic_expert_action(state, jnp.int32(0), jax.random.PRNGKey(1)))
|
||||
_, place_type, _ = [int(x) for x in decode_action(jnp.asarray(action))]
|
||||
assert place_type != PLAY
|
||||
|
||||
|
||||
def test_expert_avoids_discarding_immediately_useful_opponent_card():
|
||||
dangerous = _rank_card(0, 8)
|
||||
safe = _rank_card(4, 2)
|
||||
state = _manual_state(
|
||||
p0_hand=[
|
||||
dangerous,
|
||||
safe,
|
||||
_rank_card(1, 2),
|
||||
_rank_card(1, 3),
|
||||
_rank_card(2, 2),
|
||||
_rank_card(2, 3),
|
||||
_rank_card(3, 2),
|
||||
_rank_card(3, 3),
|
||||
],
|
||||
p1_board=[_rank_card(0, 7)],
|
||||
to_move=0,
|
||||
)
|
||||
action = int(heuristic_expert_action(state, jnp.int32(0), jax.random.PRNGKey(1)))
|
||||
hand_slot, place_type, _ = [int(x) for x in decode_action(jnp.asarray(action))]
|
||||
hand = [int(x) for x in current_hand_sorted(state, 0)]
|
||||
if place_type != PLAY:
|
||||
assert hand[hand_slot] != dangerous
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"policy_name", ["discard_only", "heuristic_balanced", "heuristic_cautious"]
|
||||
)
|
||||
@@ -128,6 +194,23 @@ def test_static_policy_duplicate_mirror_score_diff_is_zero(policy_name):
|
||||
assert result["wins"] == result["losses"]
|
||||
|
||||
|
||||
def test_expert_duplicate_mirror_regression_stats():
|
||||
result = evaluate_static_mirror(
|
||||
"heuristic_expert",
|
||||
games=1000,
|
||||
duplicate=True,
|
||||
shuffle_bank_seed=20260704,
|
||||
batch_games=1000,
|
||||
)
|
||||
assert result["games"] == 2000
|
||||
assert abs(result["mean_score_diff"]) <= 1.0e-6
|
||||
assert result["wins"] == result["losses"]
|
||||
assert result["max_steps_rate"] < 0.05
|
||||
assert 2.0 <= result["opened_colors_per_game"] <= 3.5
|
||||
assert 0.30 <= result["play_action_rate"] <= 0.60
|
||||
assert result["mean_game_length"] <= 70.0
|
||||
|
||||
|
||||
def test_gate3_checkpoint_duplicate_self_mirror_score_diff_is_zero():
|
||||
checkpoint = Path(GATE3_CHECKPOINT)
|
||||
if not checkpoint.exists():
|
||||
@@ -189,6 +272,10 @@ def _rank_card(color: int, rank: int) -> int:
|
||||
return color * CARDS_PER_COLOR + rank + 1
|
||||
|
||||
|
||||
def _hs_card(color: int, slot: int = 0) -> int:
|
||||
return color * CARDS_PER_COLOR + slot
|
||||
|
||||
|
||||
def _manual_state(
|
||||
*,
|
||||
p0_hand: list[int] | None = None,
|
||||
|
||||
Reference in New Issue
Block a user