async def test_sparse_trace(): """Test that tracing sparsely samples profiles""" base = rsgame.empty(4, 3) game1 = paygame.game_replace(base, base.all_profiles(), (base.all_profiles() > 0) * [1, 0, 0]) game2 = paygame.game_replace(base, base.all_profiles(), (base.all_profiles() > 0) * [-0.5, 1.5, 0]) save1 = savesched.savesched(gamesched.gamesched(game1)) save2 = savesched.savesched(gamesched.gamesched(game2)) sgame1 = schedgame.schedgame(save1) sgame2 = schedgame.schedgame(save2) await asyncio.gather(innerloop.inner_loop(sgame1), innerloop.inner_loop(sgame2)) # Assert that innerloop doesn't scheduler all profiles assert save1.get_game().num_profiles == 11 assert save2.get_game().num_profiles == 11 ((st1, *_, en1), _), ((st2, *_, en2), _) = ( # pylint: disable=too-many-star-expressions await trace.trace_all_equilibria(sgame1, sgame2)) # Assert that trace found the expected equilibria assert np.isclose(st1, 0) assert np.isclose(en1, 1 / 3, atol=1e-3) assert np.isclose(st2, 1 / 3, atol=1e-3) assert np.isclose(en2, 1) # Assert that trace didn't need many extra profiles assert save1.get_game().num_profiles == 12 assert save2.get_game().num_profiles == 12
def reduce_game(full_game, red_players): """Reduce a game using hierarchical reduction Parameters ---------- full_game : Game The game to reduce. red_players : ndarray-like The reduced number of players for each role. This will be coerced into the proper shape if necessary. """ red_game = rsgame.empty_names(full_game.role_names, red_players, full_game.strat_names) utils.check(np.all(red_game.num_role_players > 0), 'all reduced players must be greater than zero') utils.check( np.all(full_game.num_role_players >= red_game.num_role_players), 'all full counts must not be less than reduced counts') if full_game.is_empty(): return red_game elif full_game.num_profiles < red_game.num_all_profiles: profiles = full_game.profiles() payoffs = full_game.payoffs() else: profiles = expand_profiles(full_game, red_game.all_profiles()) payoffs = full_game.get_payoffs(profiles) valid = ~np.all(np.isnan(payoffs) | (profiles == 0), 1) profiles = profiles[valid] payoffs = payoffs[valid] red_profiles, mask = _common.reduce_profiles( full_game, red_game.num_role_players[None], profiles) return paygame.game_replace(red_game, red_profiles, payoffs[mask])
def full_game(role_names, role_players, strat_names): """Return a full game""" base = rsgame.empty_names( role_names, role_players, strat_names) return paygame.game_replace( base, base.all_profiles(), np.zeros((base.num_all_profiles, base.num_strats)))
def get_game(self): profs = [] pays = [] for hprof, fpay in self._profiles.items(): if fpay.done(): profs.append(hprof.array) pays.append(fpay.result()) return self._wrap(paygame.game_replace( self, np.stack(profs), np.stack(pays)))
def reduce_game(full_game, red_players): # pylint: disable=too-many-locals """Reduce a game using deviation preserving reduction Parameters ---------- full_game : Game The game to reduce. red_players : ndarray-like The reduced number of players for each role. This will be coerced into the proper shape if necessary. """ red_game = rsgame.empty_names(full_game.role_names, red_players, full_game.strat_names) utils.check( np.all((red_game.num_role_players > 1) | (full_game.num_role_players == 1)), 'all reduced players must be greater than zero') utils.check( np.all(full_game.num_role_players >= red_game.num_role_players), 'all full counts must not be less than reduced counts') if full_game.is_empty(): return red_game elif full_game.num_profiles < red_game.num_all_dpr_profiles: full_profiles = full_game.profiles() full_payoffs = full_game.payoffs() else: full_profiles = expand_profiles(full_game, red_game.all_profiles()) full_payoffs = full_game.get_payoffs(full_profiles) valid = ~np.all(np.isnan(full_payoffs) | (full_profiles == 0), 1) full_profiles = full_profiles[valid] full_payoffs = full_payoffs[valid] # Reduce red_profiles, red_inds, full_inds, strat_inds = _reduce_profiles( red_game, full_profiles, True) if red_profiles.size == 0: # Empty reduction return red_game # Build mapping from payoffs to reduced profiles, and use bincount # to count the number of payoffs mapped to a specific location, and # sum the number of payoffs mapped to a specific location cum_inds = red_inds * full_game.num_strats + strat_inds payoff_vals = full_payoffs[full_inds, strat_inds] red_payoffs = np.bincount(cum_inds, payoff_vals, red_profiles.size).reshape(red_profiles.shape) red_payoff_counts = np.bincount( cum_inds, minlength=red_profiles.size).reshape(red_profiles.shape) mask = red_payoff_counts > 1 red_payoffs[mask] /= red_payoff_counts[mask] unknown = (red_profiles > 0) & (red_payoff_counts == 0) red_payoffs[unknown] = np.nan valid = ~np.all((red_profiles == 0) | np.isnan(red_payoffs), 1) return paygame.game_replace(red_game, red_profiles[valid], red_payoffs[valid])
def test_random_game_addition(strats): """Test random addition""" mpayoffs = rand.random(tuple(strats) + (len(strats),)) matg = matgame.matgame(mpayoffs) payoffs = rand.random(matg.payoffs().shape) payoffs[matg.profiles() == 0] = 0 game = paygame.game_replace(matg, matg.profiles(), payoffs) assert paygame.game_copy(matg + game) == game + matg empty = rsgame.empty_copy(matg) assert matg + empty == empty
def test_random_game_addition(strats): """Test random addition""" mpayoffs = rand.random(tuple(strats) + (len(strats), )) matg = matgame.matgame(mpayoffs) payoffs = rand.random(matg.payoffs().shape) payoffs[matg.profiles() == 0] = 0 game = paygame.game_replace(matg, matg.profiles(), payoffs) assert paygame.game_copy(matg + game) == game + matg empty = rsgame.empty_copy(matg) assert matg + empty == empty
def deviation_payoffs(self, mixture, *, jacobian=False, **_): # TODO This is not smooth because there are discontinuities when the # maximum probability profile jumps at the boundary. If we wanted to # make it smooth, one option would be to compute the smoother # interpolation between this and lower probability profiles. All we # need to ensure smoothness is that the weight at profile # discontinuities is 0. profiles = self.nearby_profiles( self.max_prob_prof(mixture), self.num_neighbors) payoffs = self.get_payoffs(profiles) game = paygame.game_replace(self, profiles, payoffs) return game.deviation_payoffs(mixture, ignore_incomplete=True, jacobian=jacobian)
def deviation_payoffs(self, mixture, *, jacobian=False, **_): # TODO This is not smooth because there are discontinuities when the # maximum probability profile jumps at the boundary. If we wanted to # make it smooth, one option would be to compute the smoother # interpolation between this and lower probability profiles. All we # need to ensure smoothness is that the weight at profile # discontinuities is 0. profiles = self.nearby_profiles(self.max_prob_prof(mixture), self.num_neighbors) payoffs = self.get_payoffs(profiles) game = paygame.game_replace(self, profiles, payoffs) return game.deviation_payoffs(mixture, ignore_incomplete=True, jacobian=jacobian)
async def _get_game(self, profs): """Get a game from the profiles to sample""" futures = [] for prof in profs: hprof = utils.hash_array(prof) future = self._profiles.get(hprof, None) if future is None: future = asyncio.ensure_future( self._sched.sample_payoffs(prof)) self._profiles[hprof] = future futures.append(future) lpays = await asyncio.gather(*futures) pays = np.stack(lpays) return paygame.game_replace(self, profs, pays)
def travellers_dilemma(players=2, max_value=100): """Return an instance of travellers dilemma Strategies range from 2 to max_value, thus there will be max_value - 1 strategies.""" utils.check(players > 1, 'players must be more than one') utils.check(max_value > 2, 'max value must be more than 2') base = rsgame.empty(players, max_value - 1) profiles = base.all_profiles() payoffs = np.zeros(profiles.shape) mins = np.argmax(profiles, -1) mask = profiles > 0 payoffs[mask] = mins.repeat(mask.sum(-1)) rows = np.arange(profiles.shape[0]) ties = profiles[rows, mins] > 1 lowest_pays = mins + 4 lowest_pays[ties] -= 2 payoffs[rows, mins] = lowest_pays return paygame.game_replace(base, profiles, payoffs)
def gen_num_profiles(base, num, distribution=default_distribution): """Generate profiles given game structure Parameters ---------- base : RsGame Game to generate payoffs for. count : int The number of profiles to generate. distribution : (shape) -> ndarray, optional Distribution function to draw payoffs from. """ utils.check( 0 <= num <= base.num_all_profiles, 'num must be in [0, {:d}] but was {:d}', base.num_all_profiles, num) profiles = sample_profiles(base, num) payoffs = np.zeros(profiles.shape) mask = profiles > 0 payoffs[mask] = distribution(mask.sum()) return paygame.game_replace(base, profiles, payoffs)
def test_payoff_vals(): """Test payoff values""" profiles = [[2, 0, 1, 0, 0], [2, 0, 0, 1, 0], [2, 0, 0, 0, 1], [1, 1, 1, 0, 0], [1, 1, 0, 1, 0], [1, 1, 0, 0, 1], [0, 2, 1, 0, 0], [0, 2, 0, 1, 0], [0, 2, 0, 0, 1]] payoffs = [[4, 0, 8, 0, 0], [-1, 0, 0, 10, 0], [1, 0, 0, 0, 5], [1, 6, 6, 0, 0], [0, 3, 0, 5, 0], [-2, 3, 0, 0, 9], [0, 6, 11, 0, 0], [0, 3, 0, 6, 0], [0, 3, 0, 0, 10]] copy = paygame.game_replace(_GAME, profiles, payoffs) assert paygame.game_copy(_GAME) == copy
def keep_num_profiles(base, num): """Keep random profiles from an existing game Parameters ---------- base : RsGame Game to keep profiles from. num : int The number of profiles to keep from the game. """ utils.check( 0 <= num <= base.num_profiles, 'num must be in [0, {:d}] but was {:d}', base.num_profiles, num) if num == 0: profiles = np.empty((0, base.num_strats), int) payoffs = np.empty((0, base.num_strats)) elif base.is_complete(): profiles = sample_profiles(base, num) payoffs = base.get_payoffs(profiles) else: inds = rand.choice(base.num_profiles, num, replace=False) profiles = base.profiles()[inds] payoffs = base.payoffs()[inds] return paygame.game_replace(base, profiles, payoffs)
def reduce_game(full_game, red_players): """Reduce a game using hierarchical reduction Parameters ---------- full_game : Game The game to reduce. red_players : ndarray-like The reduced number of players for each role. This will be coerced into the proper shape if necessary. """ red_game = rsgame.empty_names( full_game.role_names, red_players, full_game.strat_names) utils.check( np.all(red_game.num_role_players > 0), 'all reduced players must be greater than zero') utils.check( np.all(full_game.num_role_players >= red_game.num_role_players), 'all full counts must not be less than reduced counts') if full_game.is_empty(): return red_game elif full_game.num_profiles < red_game.num_all_profiles: profiles = full_game.profiles() payoffs = full_game.payoffs() else: profiles = expand_profiles( full_game, red_game.all_profiles()) payoffs = full_game.get_payoffs(profiles) valid = ~np.all(np.isnan(payoffs) | (profiles == 0), 1) profiles = profiles[valid] payoffs = payoffs[valid] red_profiles, mask = _common.reduce_profiles( full_game, red_game.num_role_players[None], profiles) return paygame.game_replace(red_game, red_profiles, payoffs[mask])
def full_game(role_names, role_players, strat_names): """Return a full game""" base = rsgame.empty_names(role_names, role_players, strat_names) return paygame.game_replace( base, base.all_profiles(), np.zeros((base.num_all_profiles, base.num_strats)))
def reduce_game(full_game, red_players): # pylint: disable=too-many-locals """Reduce a game using deviation preserving reduction Parameters ---------- full_game : Game The game to reduce. red_players : ndarray-like The reduced number of players for each role. This will be coerced into the proper shape if necessary. """ red_game = rsgame.empty_names( full_game.role_names, red_players, full_game.strat_names) utils.check( np.all((red_game.num_role_players > 1) | (full_game.num_role_players == 1)), 'all reduced players must be greater than zero') utils.check( np.all(full_game.num_role_players >= red_game.num_role_players), 'all full counts must not be less than reduced counts') if full_game.is_empty(): return red_game elif full_game.num_profiles < red_game.num_all_dpr_profiles: full_profiles = full_game.profiles() full_payoffs = full_game.payoffs() else: full_profiles = expand_profiles( full_game, red_game.all_profiles()) full_payoffs = full_game.get_payoffs(full_profiles) valid = ~np.all(np.isnan(full_payoffs) | (full_profiles == 0), 1) full_profiles = full_profiles[valid] full_payoffs = full_payoffs[valid] # Reduce red_profiles, red_inds, full_inds, strat_inds = _reduce_profiles( red_game, full_profiles, True) if red_profiles.size == 0: # Empty reduction return red_game # Build mapping from payoffs to reduced profiles, and use bincount # to count the number of payoffs mapped to a specific location, and # sum the number of payoffs mapped to a specific location cum_inds = red_inds * full_game.num_strats + strat_inds payoff_vals = full_payoffs[full_inds, strat_inds] red_payoffs = np.bincount( cum_inds, payoff_vals, red_profiles.size).reshape( red_profiles.shape) red_payoff_counts = np.bincount( cum_inds, minlength=red_profiles.size).reshape( red_profiles.shape) mask = red_payoff_counts > 1 red_payoffs[mask] /= red_payoff_counts[mask] unknown = (red_profiles > 0) & (red_payoff_counts == 0) red_payoffs[unknown] = np.nan valid = ~np.all((red_profiles == 0) | np.isnan(red_payoffs), 1) return paygame.game_replace(red_game, red_profiles[valid], red_payoffs[valid])