def __init__(self, data): if isinstance(data, NormalFormGame): if data.N != 2: raise ValueError('input game must be a two-player game') self.g = data else: # data must be array_like payoffs = np.asarray(data) if not (payoffs.ndim in [2, 3]): raise ValueError( 'input data must be a square matrix or a bimatrix') self.g = NormalFormGame(payoffs) self.N = self.g.N # Must be 2 self.players = self.g.players self.nums_actions = self.g.nums_actions self.tie_breaking = 'smallest' self.current_actions = np.zeros(self.N, dtype=int) self.belief_sizes = tuple(self.nums_actions[1 - i] for i in range(self.N)) # Create instance variable `current_belief` for self.players for player, belief_size in zip(self.players, self.belief_sizes): player.current_belief = np.empty(belief_size) self._decreasing_gain = lambda t: 1 / (t + 1) self.step_size = self._decreasing_gain
def test_normalformgame_constant_payoffs(): g = NormalFormGame((2, 2)) ok_(g.is_nash((0, 0))) ok_(g.is_nash((0, 1))) ok_(g.is_nash((1, 0))) ok_(g.is_nash((1, 1)))
class TestNormalFormGame_1p: """Test for trivial NormalFormGame with a single player""" def setUp(self): """Setup a NormalFormGame instance""" data = [[0], [1], [1]] self.g = NormalFormGame(data) def test_construction(self): """Trivial game: construction""" ok_(self.g.N == 1) assert_array_equal(self.g.players[0].payoff_array, [0, 1, 1]) def test_getitem(self): """Trivial game: __getitem__""" eq_(self.g[0], 0) def test_is_nash_pure(self): """Trivial game: is_nash with pure action""" ok_(self.g.is_nash((1,))) ok_(not self.g.is_nash((0,))) def test_is_nash_mixed(self): """Trivial game: is_nash with mixed action""" ok_(self.g.is_nash(([0, 1/2, 1/2],)))
def test_normalformgame_payoff_profile_array(): nums_actions = (2, 3, 4) for N in range(1, len(nums_actions) + 1): payoff_arrays = [ np.arange(np.prod(nums_actions[0:N])).reshape(nums_actions[i:N] + nums_actions[0:i]) for i in range(N) ] players = [Player(payoff_array) for payoff_array in payoff_arrays] g = NormalFormGame(players) g_new = NormalFormGame(g.payoff_profile_array) for player_new, payoff_array in zip(g_new.players, payoff_arrays): assert_array_equal(player_new.payoff_array, payoff_array)
class TestNormalFormGame_Asym2p: """Test the methods of NormalFormGame with asymmetric two players""" def setUp(self): """Setup a NormalFormGame instance""" matching_pennies_bimatrix = [[(1, -1), (-1, 1)], [(-1, 1), (1, -1)]] self.g = NormalFormGame(matching_pennies_bimatrix) def test_getitem(self): assert_array_equal(self.g[1, 0], [-1, 1]) def test_is_nash_against_pure(self): ok_(not self.g.is_nash((0, 0))) def test_is_nash_against_mixed(self): ok_(self.g.is_nash(([1 / 2, 1 / 2], [1 / 2, 1 / 2])))
class TestNormalFormGame_Sym2p: """Test the methods of NormalFormGame with symmetric two players""" def setUp(self): """Setup a NormalFormGame instance""" coordination_game_matrix = [[4, 0], [3, 2]] self.g = NormalFormGame(coordination_game_matrix) def test_getitem(self): assert_array_equal(self.g[0, 1], [0, 3]) def test_is_nash_pure(self): ok_(self.g.is_nash((0, 0))) def test_is_nash_mixed(self): ok_(self.g.is_nash(([2 / 3, 1 / 3], [2 / 3, 1 / 3])))
def test_normalformgame_setitem_1p(): g = NormalFormGame(2) eq_(g.N, 1) # Number of players g[0] = 10 # Set payoff 10 for action 0 eq_(g.players[0].payoff_array[0], 10)
def __init__(self, data): if isinstance(data, NormalFormGame): if data.N != 2: raise ValueError('input game must be a two-player game') self.g = data else: # data must be array_like payoffs = np.asarray(data) if not (payoffs.ndim in [2, 3]): raise ValueError( 'input data must be a square matrix or a bimatrix' ) self.g = NormalFormGame(payoffs) self.N = self.g.N # Must be 2 self.players = self.g.players self.nums_actions = self.g.nums_actions self.tie_breaking = 'smallest' self.current_actions = np.zeros(self.N, dtype=int) self.belief_sizes = tuple( self.nums_actions[1-i] for i in range(self.N) ) # Create instance variable `current_belief` for self.players for player, belief_size in zip(self.players, self.belief_sizes): player.current_belief = np.empty(belief_size) self._decreasing_gain = lambda t: 1 / (t+1) self.step_size = self._decreasing_gain
class TestNormalFormGame_Sym2p: """Test the methods of NormalFormGame with symmetric two players""" def setUp(self): """Setup a NormalFormGame instance""" coordination_game_matrix = [[4, 0], [3, 2]] self.g = NormalFormGame(coordination_game_matrix) def test_getitem(self): assert_array_equal(self.g[0, 1], [0, 3]) def test_is_nash_pure(self): ok_(self.g.is_nash((0, 0))) def test_is_nash_mixed(self): ok_(self.g.is_nash(([2/3, 1/3], [2/3, 1/3])))
def setUp(self): '''Setup a LogitDynamics instance''' # symmetric 2x2 coordination game payoff_matrix = [[4, 0], [3, 2]] beta = 4.0 g = NormalFormGame(payoff_matrix) self.ld = LogitDynamics(g, beta=beta)
class TestNormalFormGame_Asym2p: """Test the methods of NormalFormGame with asymmetric two players""" def setUp(self): """Setup a NormalFormGame instance""" matching_pennies_bimatrix = [[(1, -1), (-1, 1)], [(-1, 1), (1, -1)]] self.g = NormalFormGame(matching_pennies_bimatrix) def test_getitem(self): assert_array_equal(self.g[1, 0], [-1, 1]) def test_is_nash_against_pure(self): ok_(not self.g.is_nash((0, 0))) def test_is_nash_against_mixed(self): ok_(self.g.is_nash(([1/2, 1/2], [1/2, 1/2])))
def setUp(self): """Setup a NormalFormGame instance""" payoffs_2opponents = [[[3, 6], [4, 2]], [[1, 0], [5, 7]]] player = Player(payoffs_2opponents) self.g = NormalFormGame([player for i in range(3)])
def test_normalformgame_input_action_sizes(): g = NormalFormGame((2, 3, 4)) eq_(g.N, 3) # Number of players assert_array_equal(g.players[0].payoff_array, np.zeros((2, 3, 4))) assert_array_equal(g.players[1].payoff_array, np.zeros((3, 4, 2))) assert_array_equal(g.players[2].payoff_array, np.zeros((4, 2, 3)))
def test_normalformgame_setitem(): g = NormalFormGame((2, 2)) g[0, 0] = (0, 10) g[0, 1] = (0, 10) g[1, 0] = (3, 5) g[1, 1] = (-2, 0) assert_array_equal(g.players[0].payoff_array, [[0, 0], [3, -2]]) assert_array_equal(g.players[1].payoff_array, [[10, 5], [10, 0]])
class TestNormalFormGame_3p: """Test the methods of NormalFormGame with three players""" def setUp(self): """Setup a NormalFormGame instance""" payoffs_2opponents = [[[3, 6], [4, 2]], [[1, 0], [5, 7]]] player = Player(payoffs_2opponents) self.g = NormalFormGame([player for i in range(3)]) def test_getitem(self): assert_array_equal(self.g[0, 0, 1], [6, 4, 1]) def test_is_nash_pure(self): ok_(self.g.is_nash((0, 0, 0))) ok_(not self.g.is_nash((0, 0, 1))) def test_is_nash_mixed(self): p = (1 + np.sqrt(65)) / 16 ok_(self.g.is_nash(([1 - p, p], [1 - p, p], [1 - p, p])))
class TestNormalFormGame_1p: """Test for trivial NormalFormGame with a single player""" def setUp(self): """Setup a NormalFormGame instance""" data = [[0], [1], [1]] self.g = NormalFormGame(data) def test_construction(self): """Trivial game: construction""" ok_(self.g.N == 1) assert_array_equal(self.g.players[0].payoff_array, [0, 1, 1]) def test_getitem(self): """Trivial game: __getitem__""" eq_(self.g[0], 0) def test_is_nash_pure(self): """Trivial game: is_nash with pure action""" ok_(self.g.is_nash((1, ))) ok_(not self.g.is_nash((0, ))) def test_is_nash_mixed(self): """Trivial game: is_nash with mixed action""" ok_(self.g.is_nash(([0, 1 / 2, 1 / 2], )))
def test_set_choice_probs_with_asymmetric_payoff_matrix(): bimatrix = np.array([[(4, 4), (1, 1), (0, 3)], [(3, 0), (1, 1), (2, 2)]]) beta = 1.0 g = NormalFormGame(bimatrix) ld = LogitDynamics(g, beta=beta) # (Normalized) CDFs of logit choice cdfs = np.ones((bimatrix.shape[1], bimatrix.shape[0])) cdfs[:, 0] = 1 / (1 + np.exp(beta*(bimatrix[1, :, 0]-bimatrix[0, :, 0]))) # self.ld.players[0].logit_choice_cdfs: unnormalized cdfs_computed = ld.players[0].logit_choice_cdfs cdfs_computed = cdfs_computed / cdfs_computed[..., [-1]] # Normalized assert_array_almost_equal_nulp(cdfs_computed, cdfs)
def setUp(self): """Setup a NormalFormGame instance""" coordination_game_matrix = [[4, 0], [3, 2]] self.g = NormalFormGame(coordination_game_matrix)
def setUp(self): """Setup a NormalFormGame instance""" matching_pennies_bimatrix = [[(1, -1), (-1, 1)], [(-1, 1), (1, -1)]] self.g = NormalFormGame(matching_pennies_bimatrix)
class FictitiousPlay(object): """ Fictitious play with two players Parameters ---------- data : array_like(float) or NormalFormGame Attributes ---------- g : NormalFormGame players : list(Player) # tuple nums_actions : tuple(int) current_beliefs : tuple(ndarray(float, ndim=1)) """ def __init__(self, data): if isinstance(data, NormalFormGame): if data.N != 2: raise ValueError('input game must be a two-player game') self.g = data else: # data must be array_like payoffs = np.asarray(data) if not (payoffs.ndim in [2, 3]): raise ValueError( 'input data must be a square matrix or a bimatrix' ) self.g = NormalFormGame(payoffs) self.N = self.g.N # Must be 2 self.players = self.g.players self.nums_actions = self.g.nums_actions self.tie_breaking = 'smallest' self.current_actions = np.zeros(self.N, dtype=int) self.belief_sizes = tuple( self.nums_actions[1-i] for i in range(self.N) ) # Create instance variable `current_belief` for self.players for player, belief_size in zip(self.players, self.belief_sizes): player.current_belief = np.empty(belief_size) self._decreasing_gain = lambda t: 1 / (t+1) self.step_size = self._decreasing_gain def __repr__(self): msg = "Fictitious play for " g_repr = self.g.__repr__() msg += g_repr return msg def __str__(self): return self.__repr__() def set_init_actions(self, init_actions=None): if init_actions is None: init_actions = np.zeros(self.N, dtype=int) for i, n in enumerate(self.nums_actions): init_actions[i] = np.random.randint(n) self.current_actions[:] = init_actions # Initialize current_belief for each player for i, player in enumerate(self.players): player.current_belief[:] = \ pure2mixed(self.belief_sizes[i], init_actions[1-i]) @property def current_beliefs(self): return tuple(player.current_belief for player in self.players) def play(self): for i, player in enumerate(self.players): self.current_actions[i] = \ player.best_response(player.current_belief, tie_breaking=self.tie_breaking) def update_beliefs(self, step_size): for i, player in enumerate(self.players): # x[i] = x[i] + step_size * (a[1-i] - x[i]) # = (1-step_size) * x[i] + step_size * a[1-i] # where x[i] = player's current_belief, # a[1-i] = opponent's current_action. player.current_belief *= 1 - step_size player.current_belief[self.current_actions[1-i]] += step_size def simulate(self, ts_length, init_actions=None): beliefs_sequence = np.empty((ts_length, sum(self.nums_actions))) beliefs_iter = self.simulate_iter(ts_length, init_actions) for t, beliefs in enumerate(beliefs_iter): (beliefs_sequence[t, :self.belief_sizes[0]], beliefs_sequence[t, self.belief_sizes[0]:]) = beliefs return (beliefs_sequence[:, :self.belief_sizes[0]], beliefs_sequence[:, self.belief_sizes[0]:]) def simulate_iter(self, ts_length, init_actions=None): self.set_init_actions(init_actions) for t in range(ts_length): yield self.current_beliefs self.play() self.update_beliefs(self.step_size(t+1)) def replicate(self, T, num_reps, init_actions=None): """ Returns ------- out : tuple(ndarray(float, ndim=2)) """ out = np.empty((num_reps, sum(self.nums_actions))) for j in range(num_reps): beliefs_iter = self.simulate_iter(T+1, init_actions) for beliefs in beliefs_iter: x = beliefs out[j, :self.belief_sizes[0]], out[j, self.belief_sizes[0]:] = x return out[:, :self.belief_sizes[0]], out[:, self.belief_sizes[0]:]
def test_normalformgame_invalid_input_payoff_profiles(): g = NormalFormGame(np.zeros((2, 2, 1)))
def setUp(self): '''Setup a FictitiousPlay instance''' payoff_bimatrix = np.zeros((2, 3, 2)) # 2 x 3 game g = NormalFormGame(payoff_bimatrix) self.fp = FictitiousPlay(g)
def test_normalformgame_input_action_sizes_1p(): g = NormalFormGame(2) eq_(g.N, 1) # Number of players assert_array_equal(g.players[0].payoff_array, np.zeros(2))
def setUp(self): """Setup a NormalFormGame instance""" data = [[0], [1], [1]] self.g = NormalFormGame(data)
class FictitiousPlay(object): """ Fictitious play with two players Parameters ---------- data : array_like(float) or NormalFormGame Attributes ---------- g : NormalFormGame players : list(Player) # tuple nums_actions : tuple(int) current_beliefs : tuple(ndarray(float, ndim=1)) """ def __init__(self, data): if isinstance(data, NormalFormGame): if data.N != 2: raise ValueError('input game must be a two-player game') self.g = data else: # data must be array_like payoffs = np.asarray(data) if not (payoffs.ndim in [2, 3]): raise ValueError( 'input data must be a square matrix or a bimatrix') self.g = NormalFormGame(payoffs) self.N = self.g.N # Must be 2 self.players = self.g.players self.nums_actions = self.g.nums_actions self.tie_breaking = 'smallest' self.current_actions = np.zeros(self.N, dtype=int) self.belief_sizes = tuple(self.nums_actions[1 - i] for i in range(self.N)) # Create instance variable `current_belief` for self.players for player, belief_size in zip(self.players, self.belief_sizes): player.current_belief = np.empty(belief_size) self._decreasing_gain = lambda t: 1 / (t + 1) self.step_size = self._decreasing_gain def __repr__(self): msg = "Fictitious play for " g_repr = self.g.__repr__() msg += g_repr return msg def __str__(self): return self.__repr__() def set_init_actions(self, init_actions=None): if init_actions is None: init_actions = np.zeros(self.N, dtype=int) for i, n in enumerate(self.nums_actions): init_actions[i] = np.random.randint(n) self.current_actions[:] = init_actions # Initialize current_belief for each player for i, player in enumerate(self.players): player.current_belief[:] = \ pure2mixed(self.belief_sizes[i], init_actions[1-i]) @property def current_beliefs(self): return tuple(player.current_belief for player in self.players) def play(self): for i, player in enumerate(self.players): self.current_actions[i] = \ player.best_response(player.current_belief, tie_breaking=self.tie_breaking) def update_beliefs(self, step_size): for i, player in enumerate(self.players): # x[i] = x[i] + step_size * (a[1-i] - x[i]) # = (1-step_size) * x[i] + step_size * a[1-i] # where x[i] = player's current_belief, # a[1-i] = opponent's current_action. player.current_belief *= 1 - step_size player.current_belief[self.current_actions[1 - i]] += step_size def simulate(self, ts_length, init_actions=None): beliefs_sequence = np.empty((ts_length, sum(self.nums_actions))) beliefs_iter = self.simulate_iter(ts_length, init_actions) for t, beliefs in enumerate(beliefs_iter): (beliefs_sequence[t, :self.belief_sizes[0]], beliefs_sequence[t, self.belief_sizes[0]:]) = beliefs return (beliefs_sequence[:, :self.belief_sizes[0]], beliefs_sequence[:, self.belief_sizes[0]:]) def simulate_iter(self, ts_length, init_actions=None): self.set_init_actions(init_actions) for t in range(ts_length): yield self.current_beliefs self.play() self.update_beliefs(self.step_size(t + 1)) def replicate(self, T, num_reps, init_actions=None): """ Returns ------- out : tuple(ndarray(float, ndim=2)) """ out = np.empty((num_reps, sum(self.nums_actions))) for j in range(num_reps): beliefs_iter = self.simulate_iter(T + 1, init_actions) for beliefs in beliefs_iter: x = beliefs out[j, :self.belief_sizes[0]], out[j, self.belief_sizes[0]:] = x return out[:, :self.belief_sizes[0]], out[:, self.belief_sizes[0]:]
def test_normalformgame_invalid_input_players_num_inconsistent(): p0 = Player(np.zeros((2, 2, 2))) p1 = Player(np.zeros((2, 2, 2))) g = NormalFormGame([p0, p1])
def test_normalformgame_invalid_input_nosquare_matrix(): g = NormalFormGame(np.zeros((2, 3)))