Import source: Shayan1/Prod/nash/simulation
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,110 @@
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
from nash.players import make_player
|
||||
|
||||
|
||||
class Simulation:
|
||||
def __init__(self, game, num_rounds, memory=True, rng=None, convergence_window=5,
|
||||
player_kind="best_response", debug_logger=None):
|
||||
self.game = game
|
||||
self.num_rounds = num_rounds
|
||||
self.memory = memory
|
||||
self.convergence_window = convergence_window
|
||||
self.rng = rng or random.Random()
|
||||
self.players = [
|
||||
make_player(player_kind, game, i, memory=memory, rng=self.rng,
|
||||
debug_logger=debug_logger)
|
||||
for i in range(game.num_players)
|
||||
]
|
||||
self.history = []
|
||||
self.payoff_history = []
|
||||
|
||||
def run(self):
|
||||
for _ in range(self.num_rounds):
|
||||
profile = tuple(player.choose(self.history) for player in self.players)
|
||||
payoffs = tuple(
|
||||
self.game.payoff(i, profile) for i in range(self.game.num_players)
|
||||
)
|
||||
self.history.append(profile)
|
||||
self.payoff_history.append(payoffs)
|
||||
for player in self.players:
|
||||
player.update(profile, payoffs)
|
||||
return self.history
|
||||
|
||||
def converged_round(self):
|
||||
"""First round index where the profile repeats for convergence_window rounds
|
||||
straight, or None if it never stabilizes."""
|
||||
window = self.convergence_window
|
||||
if len(self.history) < window:
|
||||
return None
|
||||
for start in range(len(self.history) - window + 1):
|
||||
segment = self.history[start:start + window]
|
||||
if all(profile == segment[0] for profile in segment):
|
||||
return start
|
||||
return None
|
||||
|
||||
def converged_profile(self):
|
||||
round_idx = self.converged_round()
|
||||
return None if round_idx is None else self.history[round_idx]
|
||||
|
||||
def strategy_frequencies(self):
|
||||
"""Per player, per strategy, the running frequency of that strategy having
|
||||
been played, one value per round -- used to chart usage over time."""
|
||||
strategy_counts = self.game.strategy_counts()
|
||||
running_counts = [[0] * n for n in strategy_counts]
|
||||
frequencies = [[[] for _ in range(n)] for n in strategy_counts]
|
||||
|
||||
for round_num, profile in enumerate(self.history, start=1):
|
||||
for player_idx, strategy in enumerate(profile):
|
||||
running_counts[player_idx][strategy] += 1
|
||||
for player_idx, n in enumerate(strategy_counts):
|
||||
for strategy_idx in range(n):
|
||||
freq = running_counts[player_idx][strategy_idx] / round_num
|
||||
frequencies[player_idx][strategy_idx].append(freq)
|
||||
|
||||
return frequencies
|
||||
|
||||
class EvolutionarySimulation:
|
||||
def __init__(self, game, num_rounds):
|
||||
self.game = game
|
||||
self.num_rounds = num_rounds
|
||||
self.populations = [
|
||||
np.full(len(game.strategies[i]), 1.0 / len(game.strategies[i]))
|
||||
for i in range(game.num_players)
|
||||
]
|
||||
self.history = []
|
||||
|
||||
def _expected_payoffs(self, player_idx):
|
||||
payoff_tensor = self.game.payoffs[player_idx]
|
||||
num_players = self.game.num_players
|
||||
expr = payoff_tensor.copy()
|
||||
|
||||
for j in range(num_players):
|
||||
if j == player_idx:
|
||||
continue
|
||||
shape = [1] * num_players
|
||||
shape[j] = len(self.populations[j])
|
||||
x_j_reshaped = self.populations[j].reshape(shape)
|
||||
expr = expr * x_j_reshaped
|
||||
|
||||
axes_to_sum = tuple(j for j in range(num_players) if j != player_idx)
|
||||
if axes_to_sum:
|
||||
return expr.sum(axis=axes_to_sum)
|
||||
else:
|
||||
return expr
|
||||
|
||||
def run(self):
|
||||
min_payoff = min(np.min(p) for p in self.game.payoffs)
|
||||
for _ in range(self.num_rounds):
|
||||
next_populations = []
|
||||
for p in range(self.game.num_players):
|
||||
u = self._expected_payoffs(p)
|
||||
u_shifted = u - min_payoff + 1e-9
|
||||
bar_u = np.sum(self.populations[p] * u_shifted)
|
||||
new_pop = self.populations[p] * (u_shifted / bar_u)
|
||||
new_pop = new_pop / np.sum(new_pop)
|
||||
next_populations.append(new_pop)
|
||||
self.populations = next_populations
|
||||
self.history.append([pop.copy() for pop in self.populations])
|
||||
return self.history
|
||||
Reference in New Issue
Block a user