def nearby_profs(self, prof, num_devs): """Returns profiles reachable by at most num_devs deviations""" # XXX this is the bottleneck for gpgame.neighbor_EVs. It seems like # there should be some clever way to speed it up. assert num_devs >= 0 dev_players = utils.acomb(self.num_roles, num_devs, True) mask = np.all(dev_players <= self.num_players, 1) dev_players = dev_players[mask] supp = prof > 0 sub = subgame.subgame(rsgame.BaseGame(self), supp) profs = [prof[None]] for players in dev_players: to_dev_profs = rsgame.BaseGame( players, self.num_strategies).all_profiles() from_dev_profs = subgame.translate( rsgame.BaseGame(players, sub.num_strategies).all_profiles(), supp) before_devs = prof - from_dev_profs before_devs = before_devs[np.all(before_devs >= 0, 1)] before_devs = utils.unique_axis(before_devs) nearby = before_devs[:, None] + to_dev_profs nearby.shape = (-1, self.num_role_strats) profs.append(utils.unique_axis(nearby)) profs = np.concatenate(profs) return utils.unique_axis(profs)
def dev_profs(red_players, full_players, mask, rs): subg = rsgame.basegame(red_players, support) sub_profs = subgame.translate(subg.all_profiles(), subgame_mask) non_devs = _expand_rsym_profiles(self.full_game, sub_profs, full_players, red_players) ndevs = np.sum(~mask) devs = np.zeros((ndevs, self.full_game.num_role_strats), int) devs[:, rs:rs + mask.size][:, ~mask] = np.eye(ndevs, dtype=int) profs = non_devs[:, None] + devs profs.shape = (-1, self.full_game.num_role_strats) return profs
def analyze_subgame(unsched_subgames, sub): """Process a subgame""" if sub.is_complete(): subg = sub.get_subgame() sub_eqa = nash.mixed_nash(subg, regret_thresh=regret_thresh) eqa = subgame.translate(subg.trim_mixture_support(sub_eqa), sub.subgame_mask) if eqa.size == 0: # No equilibria if sub.counts < reschedule_limit * observation_increment: log.info( 'Found no equilibria in subgame:\n%s\n', json.dumps( {r: list(s) for r, s in serial.to_prof_json(sub.subgame_mask).items()}, indent=2)) sub.update_counts(sub.counts + observation_increment) unsched_subgames.append(sub) else: log.error( 'Failed to find equilibria in subgame:\n%s\n', json.dumps( {r: list(s) for r, s in serial.to_prof_json(subm).items()}, indent=2)) else: log.debug( 'Found candidate equilibria:\n%s\nin subgame:\n%s\n', json.dumps(list(map(serial.to_prof_json, eqa)), indent=2), json.dumps( {r: list(s) for r, s in serial.to_prof_json(sub.subgame_mask).items()}, indent=2)) if all_devs: for eqm in eqa: add_mixture(eqm) else: for eqm in eqa: add_mixture(eqm, 0) else: unsched_subgames.append(sub)
def test_missing_data_maximal_subgames(game_desc, prob): base = rsgame.BaseGame(*game_desc) game = gamegen.add_profiles(base, prob) subs = subgame.maximal_subgames(game) if subs.size: maximal = np.all(subs <= subs[:, None], -1) np.fill_diagonal(maximal, False) assert not maximal.any(), \ "One maximal subgame dominated another" for sub in subs: subprofs = subgame.translate(subgame.subgame(base, sub).all_profiles(), sub) assert all(p in game for p in subprofs), \ "Maximal subgame didn't have all profiles" for dev in np.nonzero(~sub)[0]: devprofs = subgame.additional_strategy_profiles( game, sub, dev) assert not all(p in game for p in devprofs), \ "Maximal subgame could be bigger {} {}".format( dev, sub)
def main(args): game, serial = gameio.read_game(json.load(args.input)) if args.dpr: red_players = serial.from_role_json(dict(zip( args.dpr[::2], map(int, args.dpr[1::2])))) red = reduction.DeviationPreserving(game.num_strategies, game.num_players, red_players) redgame = red.reduce_game(game, True) else: redgame = game redserial = serial if args.dominance: domsub = dominance.iterated_elimination(redgame, 'strictdom') redgame = subgame.subgame(redgame, domsub) redserial = subgame.subserializer(redserial, domsub) if args.subgames: subgames = subgame.maximal_subgames(redgame) else: subgames = np.ones(redgame.num_role_strats, bool)[None] methods = { 'replicator': { 'max_iters': args.max_iters, 'converge_thresh': args.converge_thresh}, 'optimize': {}} noeq_subgames = [] candidates = [] for submask in subgames: subg = subgame.subgame(redgame, submask) subeqa = nash.mixed_nash( subg, regret_thresh=args.regret_thresh, dist_thresh=args.dist_thresh, processes=args.processes, **methods) eqa = subgame.translate(subg.trim_mixture_support( subeqa, supp_thresh=args.supp_thresh), submask) if eqa.size: for eqm in eqa: if not any(linalg.norm(eqm - eq) < args.dist_thresh for eq in candidates): candidates.append(eqm) else: noeq_subgames.append(submask) # pragma: no cover equilibria = [] unconfirmed = [] unexplored = [] for eqm in candidates: support = eqm > 0 gains = regret.mixture_deviation_gains(redgame, eqm) role_gains = redgame.role_reduce(gains, ufunc=np.fmax) gain = np.nanmax(role_gains) if np.isnan(gains).any() and gain <= args.regret_thresh: # Not fully explored but might be good unconfirmed.append((eqm, gain)) elif np.any(role_gains > args.regret_thresh): # There are deviations, did we explore them? dev_inds = ([np.argmax(gs == mg) for gs, mg in zip(redgame.role_split(gains), role_gains)] + redgame.role_starts)[role_gains > args.regret_thresh] for dind in dev_inds: devsupp = support.copy() devsupp[dind] = True if not np.all(devsupp <= subgames, -1).any(): unexplored.append((devsupp, dind, gains[dind], eqm)) else: # Equilibrium! equilibria.append((eqm, np.max(gains))) # Output Game args.output.write('Game Analysis\n') args.output.write('=============\n') args.output.write(serial.to_game_printstr(game)) args.output.write('\n\n') if args.dpr is not None: args.output.write('With DPR reduction: ') args.output.write(' '.join(args.dpr)) args.output.write('\n\n') if args.dominance: num = np.sum(~domsub) if num: args.output.write('Found {:d} dominated strateg{}\n'.format( num, 'y' if num == 1 else 'ies')) args.output.write(serial.to_subgame_printstr(~domsub)) args.output.write('\n') else: args.output.write('Found no dominated strategies\n\n') if args.subgames: num = subgames.shape[0] if num: args.output.write( 'Found {:d} maximal complete subgame{}\n\n'.format( num, '' if num == 1 else 's')) else: args.output.write('Found no complete subgames\n\n') args.output.write('\n') # Output social welfare args.output.write('Social Welfare\n') args.output.write('--------------\n') welfare, profile = regret.max_pure_social_welfare(game) if profile is None: args.output.write('There was no profile with complete payoff data\n\n') else: args.output.write('\nMaximum social welfare profile:\n') args.output.write(serial.to_prof_printstr(profile)) args.output.write('Welfare: {:.4f}\n\n'.format(welfare)) if game.num_roles > 1: for role, welfare, profile in zip( serial.role_names, *regret.max_pure_social_welfare(game, True)): args.output.write('Maximum "{}" welfare profile:\n'.format( role)) args.output.write(serial.to_prof_printstr(profile)) args.output.write('Welfare: {:.4f}\n\n'.format(welfare)) args.output.write('\n') # Output Equilibria args.output.write('Equilibria\n') args.output.write('----------\n') if equilibria: args.output.write('Found {:d} equilibri{}\n\n'.format( len(equilibria), 'um' if len(equilibria) == 1 else 'a')) for i, (eqm, reg) in enumerate(equilibria, 1): args.output.write('Equilibrium {:d}:\n'.format(i)) args.output.write(redserial.to_mix_printstr(eqm)) args.output.write('Regret: {:.4f}\n\n'.format(reg)) else: args.output.write('Found no equilibria\n\n') # pragma: no cover args.output.write('\n') # Output No-equilibria Subgames args.output.write('No-equilibria Subgames\n') args.output.write('----------------------\n') if noeq_subgames: # pragma: no cover args.output.write('Found {:d} no-equilibria subgame{}\n\n'.format( len(noeq_subgames), '' if len(noeq_subgames) == 1 else 's')) noeq_subgames.sort(key=lambda x: x.sum()) for i, subg in enumerate(noeq_subgames, 1): args.output.write('No-equilibria subgame {:d}:\n'.format(i)) args.output.write(redserial.to_subgame_printstr(subg)) args.output.write('\n') else: args.output.write('Found no no-equilibria subgames\n\n') args.output.write('\n') # Output Unconfirmed Candidates args.output.write('Unconfirmed Candidate Equilibria\n') args.output.write('--------------------------------\n') if unconfirmed: args.output.write('Found {:d} unconfirmed candidate{}\n\n'.format( len(unconfirmed), '' if len(unconfirmed) == 1 else 's')) unconfirmed.sort(key=lambda x: ((x[0] > 0).sum(), x[1])) for i, (eqm, reg_bound) in enumerate(unconfirmed, 1): args.output.write('Unconfirmed candidate {:d}:\n'.format(i)) args.output.write(redserial.to_mix_printstr(eqm)) args.output.write('Regret at least: {:.4f}\n\n'.format(reg_bound)) else: args.output.write('Found no unconfirmed candidate equilibria\n\n') args.output.write('\n') # Output Unexplored Subgames args.output.write('Unexplored Best-response Subgames\n') args.output.write('---------------------------------\n') if unexplored: min_supp = min(supp.sum() for supp, _, _, _ in unexplored) args.output.write( 'Found {:d} unexplored best-response subgame{}\n'.format( len(unexplored), '' if len(unexplored) == 1 else 's')) args.output.write( 'Smallest unexplored subgame has support {:d}\n\n'.format( min_supp)) unexplored.sort(key=lambda x: (x[0].sum(), -x[2])) for i, (sub, dev, gain, eqm) in enumerate(unexplored, 1): args.output.write('Unexplored subgame {:d}:\n'.format(i)) args.output.write(redserial.to_subgame_printstr(sub)) args.output.write('{:.4f} for deviating to {} from:\n'.format( gain, redserial.strat_name(dev))) args.output.write(redserial.to_mix_printstr(eqm)) args.output.write('\n') else: args.output.write('Found no unexplored best-response subgames\n\n') args.output.write('\n') # Output json data args.output.write('Json Data\n') args.output.write('=========\n') json_data = { 'equilibria': [redserial.to_mix_json(eqm) for eqm, _ in equilibria]} json.dump(json_data, args.output) args.output.write('\n')
def test_translate(): prof = np.arange(6) + 1 mask = np.array([1, 0, 0, 1, 1, 0, 1, 1, 0, 1], bool) expected = [1, 0, 0, 2, 3, 0, 4, 5, 0, 6] assert np.all(expected == subgame.translate(prof, mask))
def _profiles(self): support = self._sched._game.role_reduce(self.subgame_mask) return self._sched._red.expand_profiles(subgame.translate( rsgame.BaseGame(self._sched._red.reduced_players, support).all_profiles(), self.subgame_mask))