Import source: Shayan1/Prod/main

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:14:36 +03:30
parent 7ecf955780
commit 3f25b9904c
+270
View File
@@ -0,0 +1,270 @@
import argparse
import itertools
import json
import random
import sys
from nash import solver, visualize
from nash.game import Game
from nash.simulation import Simulation, EvolutionarySimulation
# ANSI color codes — no external dependencies needed
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
CYAN = "\033[96m"
BOLD = "\033[1m"
DIM = "\033[2m"
RESET = "\033[0m"
def build_arg_parser():
parser = argparse.ArgumentParser(
description="Solve and simulate Nash equilibria for a normal-form game."
)
source = parser.add_mutually_exclusive_group(required=True)
source.add_argument("--game", help="Path to a game JSON file")
source.add_argument(
"--describe",
help="Plain-English scenario description; translated into a game JSON via AI",
)
parser.add_argument("--rounds", type=int, default=50)
parser.add_argument("--memory", choices=["on", "off"], default="on")
parser.add_argument(
"--out", default="strategy_usage.png", help="Path to save the usage chart"
)
parser.add_argument(
"--save-game",
default="games/generated.json",
help="Where to save an AI-translated game (only used with --describe)",
)
parser.add_argument("--seed", type=int, default=None)
parser.add_argument(
"--player",
choices=["best_response", "regret_matching", "softmax", "q_learning", "llm"],
default="best_response",
help="Player decision model for simulation (default: best_response)",
)
parser.add_argument(
"--inspect",
action="store_true",
help="Print game details, payoff matrix, and dominant strategies, then exit",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Print per-round debug data during simulation",
)
parser.add_argument(
"--sim-type",
choices=["agents", "evolutionary"],
default="agents",
help="Type of simulation: 'agents' (default) or 'evolutionary'",
)
parser.add_argument(
"--reduce",
action="store_true",
help="Iteratively eliminate strictly dominated strategies before simulation or inspection",
)
return parser
def load_game(args):
try:
if args.describe:
from nash import translate
print(f"Translating description via AI: {args.describe!r}")
data = translate.describe_to_json(args.describe)
translate.save_json(data, args.save_game)
print(f"Generated game JSON (saved to {args.save_game}):")
print(json.dumps(data, indent=2))
return Game(data["players"], data["strategies"], data["payoffs"])
return Game.load_from_json(args.game)
except ValueError as e:
print(f"{RED}{BOLD}Error:{RESET} {RED}{e}{RESET}", file=sys.stderr)
sys.exit(1)
def print_inspection(game):
"""Print a formatted game inspection: strategies, payoff matrix, and dominant strategies."""
header = f"{BOLD}{CYAN}Game Inspection{RESET}"
rule = f"{DIM}{'─' * 60}{RESET}"
print(f"\n{rule}\n {header}\n{rule}")
# --- Players & strategies ---
print(f"\n {BOLD}Players & Strategies{RESET}")
for i, name in enumerate(game.players):
strats = ", ".join(game.strategies[i])
print(f" {CYAN}{name}{RESET} → [{strats}]")
# --- Payoff matrix ---
print(f"\n {BOLD}Payoff Matrix{RESET}")
header_parts = [f"{CYAN}{p}{RESET}" for p in game.players]
print(f" {' │ '.join(header_parts)}")
for profile in itertools.product(*(range(n) for n in game.strategy_counts())):
labels = " │ ".join(
f"{game.strategy_name(i, s):>12}" for i, s in enumerate(profile)
)
payoffs = ", ".join(
_color_payoff(game.payoff(i, profile)) for i in range(game.num_players)
)
print(f" {labels} │ ({payoffs})")
# --- Dominant strategies ---
print(f"\n {BOLD}Dominant Strategies{RESET}")
try:
dominant = solver.find_dominant_strategies(game)
except AttributeError:
print(f" {YELLOW}(not available — solver.find_dominant_strategies not yet implemented){RESET}")
dominant = None
if dominant is not None:
has_any = False
for i, name in enumerate(game.players):
strat_idx = dominant.get(i)
if strat_idx is not None:
strat_name = game.strategy_name(i, strat_idx)
print(f" {CYAN}{name}{RESET}: {GREEN}{BOLD}{strat_name}{RESET}")
has_any = True
else:
print(f" {CYAN}{name}{RESET}: {DIM}(none){RESET}")
if not has_any:
print(f" {YELLOW}No dominant strategies found for any player.{RESET}")
print(f"\n{rule}")
def _color_payoff(value):
"""Color a payoff value: green for positive, red for negative, plain for zero."""
formatted = f"{value:+.2f}" if value != 0 else " 0.00"
if value > 0:
return f"{GREEN}{formatted}{RESET}"
elif value < 0:
return f"{RED}{formatted}{RESET}"
return formatted
def print_solution(game):
print("\n=== Theoretical solution ===")
pure_eq = solver.find_pure_nash_equilibria(game)
if pure_eq:
for profile in pure_eq:
named = [game.strategy_name(i, s) for i, s in enumerate(profile)]
print(f"Pure Nash equilibrium: {named}")
else:
print("No pure Nash equilibrium found.")
mixed_eq = []
if game.num_players == 2:
mixed_eq = solver.find_mixed_nash_equilibria(game)
for x, y in mixed_eq:
print(
f"Mixed Nash equilibrium: {game.players[0]}={x.round(3).tolist()} "
f"{game.players[1]}={y.round(3).tolist()}"
)
return pure_eq, mixed_eq
def print_comparison(game, simulation, pure_eq):
print("\n=== Comparison ===")
converged_round = simulation.converged_round()
if converged_round is not None:
profile = simulation.converged_profile()
named = [game.strategy_name(i, s) for i, s in enumerate(profile)]
matches_pure = any(profile == eq for eq in pure_eq)
if matches_pure:
print(
f"Simulation converged at round {converged_round + 1} to {named} "
"-- a true Nash equilibrium."
)
else:
print(
f"Play held at {named} for {simulation.convergence_window}+ rounds, "
"but that is NOT a Nash equilibrium of this game. This is expected "
"when there's no pure equilibrium: fictitious play produces streaks "
"that eventually flip as history accumulates. Only the long-run "
"frequencies below reflect the actual (mixed) equilibrium."
)
else:
print("Play never held a single repeated profile within the given rounds.")
frequencies = simulation.strategy_frequencies()
print("Long-run strategy usage frequencies (final round):")
for i, name in enumerate(game.players):
freqs = {
strat: round(frequencies[i][s][-1], 3)
for s, strat in enumerate(game.strategies[i])
}
print(f" {name}: {freqs}")
def main():
args = build_arg_parser().parse_args()
rng = random.Random(args.seed)
game = load_game(args)
if args.reduce:
game, logs = solver.reduce_game(game)
for log in logs:
print(log)
# --inspect mode: print game details and exit
if args.inspect:
print_inspection(game)
sys.exit(0)
print(f"Game: {', '.join(game.players)}")
for i, name in enumerate(game.players):
print(f" {name} strategies: {game.strategies[i]}")
pure_eq, _ = print_solution(game)
if args.sim_type == "evolutionary":
print(f"\n=== Evolutionary Simulation ({args.rounds} rounds) ===")
simulation = EvolutionarySimulation(game, args.rounds)
simulation.run()
print("Final population distribution:")
for i, name in enumerate(game.players):
freqs = {
strat: round(float(simulation.populations[i][s]), 3)
for s, strat in enumerate(game.strategies[i])
}
print(f" {name}: {freqs}")
chart_path = visualize.plot_evolutionary_trajectories(game, simulation, args.out)
print(f"\nStrategy usage chart saved to: {chart_path}")
else:
memory = args.memory == "on"
print(f"\n=== Simulation ({args.rounds} rounds, memory={args.memory}, player={args.player}) ===")
# Build Simulation kwargs
sim_kwargs = dict(
memory=memory,
rng=rng,
player_kind=args.player,
)
# --verbose: wire a debug logger
if args.verbose:
debug_logger = lambda player_index, msg: print(
f"[Round {len(simulation.history) + 1}] Player {player_index}: {msg}"
)
sim_kwargs["debug_logger"] = debug_logger
simulation = Simulation(game, args.rounds, **sim_kwargs)
simulation.run()
visualize.print_history(game, simulation)
print_comparison(game, simulation, pure_eq)
chart_path = visualize.plot_strategy_frequencies(game, simulation, args.out)
print(f"\nStrategy usage chart saved to: {chart_path}")
if __name__ == "__main__":
main()