def test_mixed_incomplete_data(): profiles = [[2, 0], [1, 1]] payoffs = [[4.3, 0], [6.2, 6.7]] game = rsgame.Game(2, 2, profiles, payoffs) dg = regret.mixture_deviation_gains(game, [1, 0]) expected_gains = [0.0, 2.4] assert np.allclose(dg, expected_gains), \ "mixture gains wrong {} instead of {}".format(dg, expected_gains) dg = regret.mixture_deviation_gains(game, game.uniform_mixture()) assert np.isnan(dg).all(), "had data for mixture without data"
def test_mixed_incomplete_data(): """Test mixed incomplete data""" profiles = [[2, 0], [1, 1]] payoffs = [[4.3, 0], [6.2, 6.7]] game = paygame.game(2, 2, profiles, payoffs) dev_gain = regret.mixture_deviation_gains(game, [1, 0]) expected_gains = [0.0, 2.4] assert np.allclose(dev_gain, expected_gains), \ 'mixture gains wrong {} instead of {}'.format(dev_gain, expected_gains) dev_gain = regret.mixture_deviation_gains(game, game.uniform_mixture()) assert np.isnan(dev_gain).all(), 'had data for mixture without data'
def eqa_func(mixture): """Equilibrium fixed point function""" mixture = game.mixture_from_simplex(mixture) gains = np.maximum(regret.mixture_deviation_gains(game, mixture), 0) result = (mixture + gains) / (1 + np.add.reduceat( gains, game.role_starts).repeat(game.num_role_strats)) return game.mixture_to_simplex(result)
def test_mixed_incomplete_data_2(): profiles = [[2, 0]] payoffs = [[1.0, 0.0]] game = rsgame.Game(2, 2, profiles, payoffs) dg = regret.mixture_deviation_gains(game, [1, 0]) assert np.allclose(dg, [0, np.nan], equal_nan=True), \ "nonzero regret or deviation without payoff didn't return nan"
def test_nan_deviations(players, strategies): game = gamegen.role_symmetric_game(players, strategies) for mix in game.random_mixtures(20, 0.05): mix = game.trim_mixture_support(mix) gains = regret.mixture_deviation_gains(game, mix) assert not np.isnan(gains).any(), \ "deviation gains in complete game were nan"
async def run(args): """Brute force entry point""" sched = await schedspec.parse_scheduler(args.scheduler) red, red_players = utils.parse_reduction(sched, args) rest = ( np.ones(sched.num_strats, bool) if args.restrict is None else sched.restriction_from_json(json.load(args.restrict)) ) async with sched: data = await schedgame.schedgame(sched, red, red_players).get_deviation_game( rest ) # now find equilibria eqa = sched.trim_mixture_support( restrict.translate( nash.mixed_nash( data.restrict(rest), regret_thresh=args.regret_thresh, dist_thresh=args.dist_thresh, at_least_one=args.one, min_reg=args.min_reg, ), rest, ), thresh=args.supp_thresh, ) reg_info = [] for eqm in eqa: gains = regret.mixture_deviation_gains(data, eqm) bri = np.argmax(gains) reg_info.append((gains[bri],) + sched.role_strat_names[bri]) logging.error( "brute sampling finished finding %d equilibria:\n%s", eqa.shape[0], "\n".join( "{:d}) {} with regret {:g} to {} {}".format( i, sched.mixture_to_repr(eqm), reg, role, strat ) for i, (eqm, (reg, role, strat)) in enumerate(zip(eqa, reg_info), 1) ), ) json.dump( [ { "equilibrium": sched.mixture_to_json(eqm), "regret": reg, "best_response": {"role": role, "strat": strat}, } for eqm, (reg, role, strat) in zip(eqa, reg_info) ], args.output, ) args.output.write("\n")
def gains(game, serial, prof): """the gains from deviating from profile""" if is_pure_profile(game, prof): gains = regret.pure_strategy_deviation_gains(game, prof) return serial.to_deviation_payoff_json(prof, gains) else: gains = regret.mixture_deviation_gains(game, prof) return serial.to_prof_json(gains, False)
def calc_gains(game, prof): """the gains from deviating from profile""" if is_pure_profile(game, prof): # pylint: disable=no-else-return gains = regret.pure_strategy_deviation_gains(game, prof) return game.devpay_to_json(gains) else: gains = regret.mixture_deviation_gains(game, prof) return game.payoff_to_json(gains)
def test_mixed_incomplete_data_2(): """Test mixed with incomplete data""" profiles = [[2, 0]] payoffs = [[1.0, 0.0]] game = paygame.game(2, 2, profiles, payoffs) devgains = regret.mixture_deviation_gains(game, [1, 0]) assert np.allclose(devgains, [0, np.nan], equal_nan=True), \ "nonzero regret or deviation without payoff didn't return nan"
def test_nan_deviations(players, strategies): """Test nan deviations""" game = gamegen.game(players, strategies) for mix in game.random_mixtures(20, alpha=0.05): mix = game.trim_mixture_support(mix) gains = regret.mixture_deviation_gains(game, mix) assert not np.isnan(gains).any(), \ 'deviation gains in complete game were nan'
def analyze_deviations(unsched_deviations, mix, dev): """Analyzes responses to an equilibrium and book keeps""" if dev.is_complete(): dev_game = dev.get_game() role_resps = game.role_split(regret.mixture_deviation_gains( dev_game, mix, assume_complete=True))[dev.role_index] log.debug( '"%s" Responses:\n%s\nto candidate equilibrium:\n%s\n', serial.role_names[dev.role_index], json.dumps(dict(zip(serial.strat_names[dev.role_index], role_resps)), indent=2), json.dumps(serial.to_prof_json(mix), indent=2)) if np.all(role_resps < regret_thresh): # role has no deviations if dev.role_index == game.num_roles - 1: if not any(linalg.norm(m - mix) < 1e-3 for m in confirmed_equilibria): confirmed_equilibria.append(mix) log.info('Confirmed equilibrium:\n%s\n', json.dumps(serial.to_prof_json(mix), indent=2)) else: add_mixture(mix, dev.role_index + 1) else: # Queue up next subgames subsize = dev.subgame_mask.sum() # TODO Normalize role deviations rstart = game.role_starts[dev.role_index] order = np.argpartition(role_resps, role_resps.size - 1) gain = role_resps[order[-1]] # Positive best response exists for role subm = dev.subgame_mask.copy() subm[order[-1] + rstart] = True if subsize < max_subgame_size: add_subgame(subm) else: heapq.heappush(backup, ( (False, False, subsize, -gain), subm)) # Priority for backup is (not best response, not beneficial # response, subgame size, deviation loss). Thus, best # responses are first, then positive responses, then small # subgames, then highest gain. # Add the rest to the backup for ind in order[:-1]: subm = dev.subgame_mask.copy() subm[ind + rstart] = True gain = role_resps[ind] heapq.heappush(backup, ( (True, gain < 0, subsize, -gain, id(subm)), subm)) else: unsched_deviations.append((mix, dev))
def analyze_deviations(unsched_deviations, mix, dev): """Analyzes responses to an equilibrium and book keeps""" if dev.is_complete(): dev_game = dev.get_game() responses = regret.mixture_deviation_gains( dev_game, mix, assume_complete=True) log.debug('Responses:\n%s\nto candidate equilibrium:\n%s\n', json.dumps(serial.to_prof_json( responses, filter_zeros=False), indent=2), json.dumps(serial.to_prof_json(mix), indent=2)) if np.all(responses < regret_thresh): # found equilibria if not any(linalg.norm(m - mix) < 1e-3 for m in confirmed_equilibria): confirmed_equilibria.append(mix) log.info('Confirmed equilibrium:\n%s\n', json.dumps( serial.to_prof_json(mix), indent=2)) else: # Queue up next subgames subsize = dev.subgame_mask.sum() # TODO Normalize role deviations for rstart, role_resps in zip(game.role_starts, game.role_split(responses)): order = np.argpartition( role_resps, role_resps.size - 1) gain = role_resps[order[-1]] if gain > 0: # Positive best response exists for role subm = dev.subgame_mask.copy() subm[order[-1] + rstart] = True if subsize < max_subgame_size: add_subgame(subm) else: heapq.heappush(backup, ( (False, False, subsize, -gain), subm)) order = order[:-1] # Priority for backup is (not best response, not # beneficial response, subgame size, deviation loss). # Thus, best responses are first, then positive # responses, then small subgames, then highest gain. # Add the rest to the backup for ind in order: subm = dev.subgame_mask.copy() subm[ind + rstart] = True gain = role_resps[ind] heapq.heappush(backup, ( (True, gain < 0, subsize, -gain, id(subm)), subm)) else: unsched_deviations.append((mix, dev))
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 main(args): # pylint: disable=too-many-statements,too-many-branches,too-many-locals """Entry point for analysis""" game = gamereader.load(args.input) if args.dpr is not None: red_players = game.role_from_repr(args.dpr, dtype=int) game = reduction.deviation_preserving.reduce_game(game, red_players) elif args.hr is not None: red_players = game.role_from_repr(args.hr, dtype=int) game = reduction.hierarchical.reduce_game(game, red_players) if args.dominance: domsub = dominance.iterated_elimination(game, 'strictdom') game = game.restrict(domsub) if args.restrictions: restrictions = restrict.maximal_restrictions(game) else: restrictions = np.ones((1, game.num_strats), bool) noeq_restrictions = [] candidates = [] for rest in restrictions: rgame = game.restrict(rest) reqa = nash.mixed_equilibria(rgame, style=args.style, regret_thresh=args.regret_thresh, dist_thresh=args.dist_thresh, processes=args.processes) eqa = restrict.translate( rgame.trim_mixture_support(reqa, thresh=args.support), rest) if eqa.size: candidates.extend(eqa) else: noeq_restrictions.append(rest) equilibria = collect.mcces(args.dist_thresh * np.sqrt(2 * game.num_roles)) unconfirmed = collect.mcces(args.dist_thresh * np.sqrt(2 * game.num_roles)) unexplored = {} for eqm in candidates: support = eqm > 0 # FIXME This treats trimming support differently than quiesce does, # which means quiesce could find an equilibria, and this would fail to # find it. gains = regret.mixture_deviation_gains(game, eqm) role_gains = np.fmax.reduceat(gains, game.role_starts) gain = np.nanmax(role_gains) if np.isnan(gains).any() and gain <= args.regret_thresh: # Not fully explored but might be good unconfirmed.add(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( np.split(gains, game.role_starts[1:]), role_gains) ] + game.role_starts)[role_gains > args.regret_thresh] for dind in dev_inds: devsupp = support.copy() devsupp[dind] = True if not np.all(devsupp <= restrictions, -1).any(): ind = restrict.to_id(game, devsupp) old_info = unexplored.get(ind, (0, 0, 0, None)) new_info = (gains[dind], dind, old_info[2] + 1, eqm) unexplored[ind] = max(new_info, old_info) else: # Equilibrium! equilibria.add(eqm, np.max(gains)) # Output Game args.output.write('Game Analysis\n') args.output.write('=============\n') args.output.write(str(game)) args.output.write('\n\n') if args.dpr is not None: args.output.write('With deviation preserving reduction: ') args.output.write(args.dpr.replace(';', ' ')) args.output.write('\n\n') elif args.hr is not None: args.output.write('With hierarchical reduction: ') args.output.write(args.hr.replace(';', ' ')) 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(game.restriction_to_str(~domsub)) args.output.write('\n\n') else: args.output.write('Found no dominated strategies\n\n') if args.restrictions: num = restrictions.shape[0] if num: args.output.write( 'Found {:d} maximal complete restricted game{}\n\n'.format( num, '' if num == 1 else 's')) else: args.output.write('Found no complete restricted games\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(game.profile_to_str(profile)) args.output.write('\nWelfare: {:.4f}\n\n'.format(welfare)) if game.num_roles > 1: for role, welfare, profile in zip( game.role_names, *regret.max_pure_social_welfare(game, by_role=True)): args.output.write( 'Maximum "{}" welfare profile:\n'.format(role)) args.output.write(game.profile_to_str(profile)) args.output.write('\nWelfare: {:.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(game.mixture_to_str(eqm)) args.output.write('\nRegret: {:.4f}\n\n'.format(reg)) else: args.output.write('Found no equilibria\n\n') args.output.write('\n') # Output No-equilibria Subgames args.output.write('No-equilibria Subgames\n') args.output.write('----------------------\n') if noeq_restrictions: args.output.write( 'Found {:d} no-equilibria restricted game{}\n\n'.format( len(noeq_restrictions), '' if len(noeq_restrictions) == 1 else 's')) noeq_restrictions.sort(key=lambda x: x.sum()) for i, subg in enumerate(noeq_restrictions, 1): args.output.write( 'No-equilibria restricted game {:d}:\n'.format(i)) args.output.write(game.restriction_to_str(subg)) args.output.write('\n\n') else: args.output.write('Found no no-equilibria restricted games\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')) ordered = sorted((sum(e > 0 for e in m), r, m) for m, r in unconfirmed) for i, (_, reg_bound, eqm) in enumerate(ordered, 1): args.output.write('Unconfirmed candidate {:d}:\n'.format(i)) args.output.write(game.mixture_to_str(eqm)) args.output.write( '\nRegret 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(restrict.from_id(game, sid).sum() for sid in unexplored) args.output.write( 'Found {:d} unexplored best-response restricted game{}\n'.format( len(unexplored), '' if len(unexplored) == 1 else 's')) args.output.write( 'Smallest unexplored restricted game has support {:d}\n\n'.format( min_supp)) ordered = sorted(( restrict.from_id(game, sind).sum(), -gain, dev, restrict.from_id(game, sind), eqm, ) for sind, (gain, dev, _, eqm) in unexplored.items()) for i, (_, ngain, dev, sub, eqm) in enumerate(ordered, 1): args.output.write('Unexplored restricted game {:d}:\n'.format(i)) args.output.write(game.restriction_to_str(sub)) args.output.write('\n{:.4f} for deviating to {} from:\n'.format( -ngain, game.strat_name(dev))) args.output.write(game.mixture_to_str(eqm)) args.output.write('\n\n') else: args.output.write( 'Found no unexplored best-response restricted games\n\n') args.output.write('\n') # Output json data args.output.write('Json Data\n') args.output.write('=========\n') json_data = { 'equilibria': [game.mixture_to_json(eqm) for eqm, _ in equilibria] } json.dump(json_data, args.output) args.output.write('\n')
def main(args): # pylint: disable=too-many-statements,too-many-branches,too-many-locals """Entry point for analysis""" game = gamereader.load(args.input) if args.dpr is not None: red_players = game.role_from_repr(args.dpr, dtype=int) game = reduction.deviation_preserving.reduce_game(game, red_players) elif args.hr is not None: red_players = game.role_from_repr(args.hr, dtype=int) game = reduction.hierarchical.reduce_game(game, red_players) if args.dominance: domsub = dominance.iterated_elimination(game, 'strictdom') game = game.restrict(domsub) if args.restrictions: restrictions = restrict.maximal_restrictions(game) else: restrictions = np.ones((1, game.num_strats), bool) noeq_restrictions = [] candidates = [] for rest in restrictions: rgame = game.restrict(rest) reqa = nash.mixed_equilibria( rgame, style=args.style, regret_thresh=args.regret_thresh, dist_thresh=args.dist_thresh, processes=args.processes) eqa = restrict.translate(rgame.trim_mixture_support( reqa, thresh=args.support), rest) if eqa.size: candidates.extend(eqa) else: noeq_restrictions.append(rest) equilibria = collect.mcces(args.dist_thresh * np.sqrt(2 * game.num_roles)) unconfirmed = collect.mcces(args.dist_thresh * np.sqrt(2 * game.num_roles)) unexplored = {} for eqm in candidates: support = eqm > 0 # FIXME This treats trimming support differently than quiesce does, # which means quiesce could find an equilibria, and this would fail to # find it. gains = regret.mixture_deviation_gains(game, eqm) role_gains = np.fmax.reduceat(gains, game.role_starts) gain = np.nanmax(role_gains) if np.isnan(gains).any() and gain <= args.regret_thresh: # Not fully explored but might be good unconfirmed.add(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(np.split(gains, game.role_starts[1:]), role_gains)] + game.role_starts)[role_gains > args.regret_thresh] for dind in dev_inds: devsupp = support.copy() devsupp[dind] = True if not np.all(devsupp <= restrictions, -1).any(): ind = restrict.to_id(game, devsupp) old_info = unexplored.get(ind, (0, 0, 0, None)) new_info = (gains[dind], dind, old_info[2] + 1, eqm) unexplored[ind] = max(new_info, old_info) else: # Equilibrium! equilibria.add(eqm, np.max(gains)) # Output Game args.output.write('Game Analysis\n') args.output.write('=============\n') args.output.write(str(game)) args.output.write('\n\n') if args.dpr is not None: args.output.write('With deviation preserving reduction: ') args.output.write(args.dpr.replace(';', ' ')) args.output.write('\n\n') elif args.hr is not None: args.output.write('With hierarchical reduction: ') args.output.write(args.hr.replace(';', ' ')) 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(game.restriction_to_str(~domsub)) args.output.write('\n\n') else: args.output.write('Found no dominated strategies\n\n') if args.restrictions: num = restrictions.shape[0] if num: args.output.write( 'Found {:d} maximal complete restricted game{}\n\n'.format( num, '' if num == 1 else 's')) else: args.output.write('Found no complete restricted games\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(game.profile_to_str(profile)) args.output.write('\nWelfare: {:.4f}\n\n'.format(welfare)) if game.num_roles > 1: for role, welfare, profile in zip( game.role_names, *regret.max_pure_social_welfare(game, by_role=True)): args.output.write('Maximum "{}" welfare profile:\n'.format( role)) args.output.write(game.profile_to_str(profile)) args.output.write('\nWelfare: {:.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(game.mixture_to_str(eqm)) args.output.write('\nRegret: {:.4f}\n\n'.format(reg)) else: args.output.write('Found no equilibria\n\n') args.output.write('\n') # Output No-equilibria Subgames args.output.write('No-equilibria Subgames\n') args.output.write('----------------------\n') if noeq_restrictions: args.output.write( 'Found {:d} no-equilibria restricted game{}\n\n'.format( len(noeq_restrictions), '' if len(noeq_restrictions) == 1 else 's')) noeq_restrictions.sort(key=lambda x: x.sum()) for i, subg in enumerate(noeq_restrictions, 1): args.output.write( 'No-equilibria restricted game {:d}:\n'.format(i)) args.output.write(game.restriction_to_str(subg)) args.output.write('\n\n') else: args.output.write('Found no no-equilibria restricted games\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')) ordered = sorted( (sum(e > 0 for e in m), r, m) for m, r in unconfirmed) for i, (_, reg_bound, eqm) in enumerate(ordered, 1): args.output.write('Unconfirmed candidate {:d}:\n'.format(i)) args.output.write(game.mixture_to_str(eqm)) args.output.write('\nRegret 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(restrict.from_id(game, sid).sum() for sid in unexplored) args.output.write( 'Found {:d} unexplored best-response restricted game{}\n'.format( len(unexplored), '' if len(unexplored) == 1 else 's')) args.output.write( 'Smallest unexplored restricted game has support {:d}\n\n'.format( min_supp)) ordered = sorted(( restrict.from_id(game, sind).sum(), -gain, dev, restrict.from_id(game, sind), eqm, ) for sind, (gain, dev, _, eqm) in unexplored.items()) for i, (_, ngain, dev, sub, eqm) in enumerate(ordered, 1): args.output.write('Unexplored restricted game {:d}:\n'.format(i)) args.output.write(game.restriction_to_str(sub)) args.output.write('\n{:.4f} for deviating to {} from:\n'.format( -ngain, game.strat_name(dev))) args.output.write(game.mixture_to_str(eqm)) args.output.write('\n\n') else: args.output.write( 'Found no unexplored best-response restricted games\n\n') args.output.write('\n') # Output json data args.output.write('Json Data\n') args.output.write('=========\n') json_data = { 'equilibria': [game.mixture_to_json(eqm) for eqm, _ in equilibria]} json.dump(json_data, args.output) args.output.write('\n')