Import documentation: Shayan1/Prod/docs/DOCS.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Dependencies are installed in a local virtualenv (`.venv/`) because the system Python is externally managed:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the tool against a game file:
|
||||||
|
|
||||||
|
```
|
||||||
|
.venv/bin/python main.py --game games/pricing.json --rounds 30 --memory on --seed 1
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--game <path>` or `--describe "<text>"` — exactly one is required (mutually exclusive). `--describe` uses the optional AI translation layer and needs `anthropic` installed plus `ANTHROPIC_API_KEY` set.
|
||||||
|
- `--memory on|off` — toggles fictitious play vs. last-round-only best response (default `on`).
|
||||||
|
- `--seed <int>` — seeds the RNG for reproducible runs (useful when debugging).
|
||||||
|
- `--out <path>` — where the strategy-usage chart PNG is saved (default `strategy_usage.png`).
|
||||||
|
- `--inspect` — prints game details, payoff matrix, and dominant strategies, then exits.
|
||||||
|
- `--reduce` — iteratively eliminates strictly dominated strategies (IESDS matrix reduction) before simulation or inspection.
|
||||||
|
- `--verbose` — prints per-round debug data (e.g. agent reasoning) during simulation.
|
||||||
|
- `--player <best_response|regret_matching|softmax|q_learning|llm>` — decision model for simulation (default `best_response`).
|
||||||
|
- `--sim-type <agents|evolutionary>` — type of simulation: `agents` (multi-agent) or `evolutionary` (replicator dynamics) (default `agents`).
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
.venv/bin/python -m pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
There's no build/lint step — this is a small, dependency-light script project.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Games are data, not code.** Every game (player names, strategies, payoffs) lives in a JSON file under `games/`; nothing about a specific scenario is hardcoded in `nash/`. `nash/game.py`'s `Game` class loads this JSON and represents payoffs as one N-dimensional numpy tensor per player (`payoffs[i]` has shape `(num_strategies_p0, num_strategies_p1, ...)`), indexed by a full strategy profile tuple. This tensor representation is what lets the pure-equilibrium finder and the simulation work for any number of players, not just 2 — adding a 3+ player game requires no code changes, just a JSON file with matching-shaped payoff tensors.
|
||||||
|
|
||||||
|
**Solver has game reduction and multiple solving algorithms** (`nash/solver.py`):
|
||||||
|
- `find_pure_nash_equilibria` — brute-force over all strategy profiles, works for any N players.
|
||||||
|
- `find_mixed_nash_equilibria` — support enumeration, **2-player only by design**. It assumes non-degenerate games (only tries equal-size supports for both players); N-player mixed equilibria aren't computed because no Lemke-Howson-style algorithm generalizes past 2 players. Calling it on a game with `num_players != 2` raises `NotImplementedError` deliberately.
|
||||||
|
- `find_dominant_strategies` — finds strictly dominant strategies for each player.
|
||||||
|
- `reduce_game` — performs Iterated Elimination of Strictly Dominated Strategies (IESDS) to reduce the matrix size before solving or simulating.
|
||||||
|
|
||||||
|
**Simulation supports multiple player decision models and evolutionary dynamics** (`nash/simulation.py`, `nash/players.py`):
|
||||||
|
- **Agent Simulation (`--sim-type agents`)**: runs agent-based rounds using decision models like `BestResponsePlayer`, `SoftmaxBestResponsePlayer`, `RegretMatchingPlayer`, `QLearningPlayer`, and `LLMPlayer`.
|
||||||
|
- **LLM Player**: `LLMPlayer` queries the Anthropic Claude API using the `anthropic` SDK to decide its next strategy. It is supplied with the payoff matrix and game history, then returns a JSON object containing its reasoning and its selected strategy index.
|
||||||
|
- **Evolutionary Simulation (`--sim-type evolutionary`)**: runs `EvolutionarySimulation` using Replicator Dynamics. Instead of discrete agent actions, it updates a continuous population strategy distribution for each player over time based on expected payoffs against other populations.
|
||||||
|
**Best-response dynamics has a memory switch** (`nash/players.py`, `BestResponsePlayer`): `memory=False` computes the best response to only the immediately preceding round (can cycle indefinitely in games like Rock-Paper-Scissors or Matching Pennies); `memory=True` is fictitious play — best response to the empirical distribution of the *entire* history so far. Ties in expected payoff are broken via a caller-supplied `random.Random` instance (passed down from `main.py`'s `--seed`), not `random` global state, so runs are reproducible end to end.
|
||||||
|
|
||||||
|
**Convergence detection is a heuristic, and `main.py` compensates for its blind spot.** `Simulation.converged_round()` (`nash/simulation.py`) just checks for N (default 5) consecutive identical profiles — it can't distinguish "the game actually settled" from "fictitious play happens to be mid-streak in a game with no pure equilibrium" (e.g. Matching Pennies, Rock-Paper-Scissors under `memory=on` will show long streaks that eventually flip as the historical average slowly shifts). `main.py.print_comparison` handles this by cross-checking the "converged" profile against `solver.find_pure_nash_equilibria`'s actual output before claiming a real equilibrium was reached; if it doesn't match, it explicitly explains why and falls back to reporting long-run strategy frequencies instead (which is the number that's actually meaningful for games whose only equilibrium is mixed).
|
||||||
|
|
||||||
|
**The AI layer (`nash/translate.py`) is intentionally decoupled from everything else.** `describe_to_json` is the only function in the codebase that makes a network/API call (via the `anthropic` SDK), and it's only reachable through `main.py`'s `--describe` flag. It never touches `solver.py` or `simulation.py` directly — it just produces the same JSON shape a hand-written game file would have, prints/saves it, and hands it to the exact same `Game` constructor and pipeline used for `--game`. This means a bad translation surfaces as visibly wrong JSON before any simulation runs, not as a silently wrong answer.
|
||||||
|
|
||||||
|
## Sample games (`games/`)
|
||||||
|
|
||||||
|
- `pricing.json` — 2-player Prisoner's-Dilemma-shaped game (High/Low pricing), unique pure NE at `(Low, Low)`.
|
||||||
|
- `matching_pennies.json` — 2-player zero-sum game with no pure NE, mixed NE at 50/50.
|
||||||
|
- `rock_paper_scissors.json` — 2-player, 3-strategy zero-sum game, mixed NE at 1/3-1/3-1/3; used to confirm the tool isn't hardcoded to 2-strategy games.
|
||||||
Reference in New Issue
Block a user