36 lines
1.2 KiB
Python
36 lines
1.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Game-agnostic abstract action-logit API for Phase 4G learned adapters."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import numpy as np
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AbstractActionOutput:
|
|
action_logits: np.ndarray
|
|
|
|
def __post_init__(self) -> None:
|
|
value = np.asarray(self.action_logits, dtype=np.float32)
|
|
if value.shape != (4,) or not np.isfinite(value).all():
|
|
raise ValueError("action_logits must be finite float32[4]")
|
|
object.__setattr__(self, "action_logits", value)
|
|
|
|
def deterministic_argmax(self) -> int:
|
|
return int(np.argmax(self.action_logits))
|
|
|
|
def sample(self, seed: int, temperature: float = 1.0, epsilon: float = 0.0) -> int:
|
|
if temperature <= 0 or not 0 <= epsilon <= 1:
|
|
raise ValueError("temperature must be positive and epsilon in [0,1]")
|
|
rng = np.random.default_rng(seed)
|
|
if rng.random() < epsilon:
|
|
return int(rng.integers(0, 4))
|
|
z = self.action_logits.astype(np.float64) / temperature
|
|
z -= z.max(); p = np.exp(z); p /= p.sum()
|
|
return int(rng.choice(4, p=p))
|
|
|
|
|
|
ACTION_NAMES = ("ACTION_0", "ACTION_1", "ACTION_2", "ACTION_3")
|
|
|