Esempio n. 1
0
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"
Esempio n. 2
0
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'
Esempio n. 3
0
 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)
Esempio n. 4
0
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"
Esempio n. 5
0
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"
Esempio n. 6
0
 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)
Esempio n. 7
0
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")
Esempio n. 8
0
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)
Esempio n. 9
0
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)
Esempio n. 10
0
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"
Esempio n. 11
0
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'
Esempio n. 12
0
        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))
Esempio n. 13
0
        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))
Esempio n. 14
0
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')
Esempio n. 15
0
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')
Esempio n. 16
0
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')