Пример #1
0
def test_pure_subgame(players, strategies):
    game = rsgame.BaseGame(players, strategies)
    subgames = subgame.pure_subgames(game)
    expectation = game.num_strategies[None].repeat(game.num_roles, 0)
    np.fill_diagonal(expectation, 1)
    expectation = game.role_repeat(expectation.prod(1))
    assert np.all(subgames.sum(0) == expectation)
Пример #2
0
def test_num_subgames():
    game = rsgame.BaseGame([3, 4], [4, 3])
    actual = subgame.num_pure_subgames(game)
    expected = subgame.pure_subgames(game).shape[0]
    assert actual == 12 == expected

    actual = subgame.num_all_subgames(game)
    assert actual == 105
Пример #3
0
def quiesce(sim, game, serial, base_name, configuration={}, dpr=None,
            log=logging, profiles=(), all_devs=True, max_profiles=500,
            max_subgame_size=3, sleep_time=300, required_equilibria=1,
            regret_thresh=1e-3, reschedule_limit=10, process_memory=4096,
            observation_time=600, observation_increment=1, nodes=1):
    """Quiesce a game"""

    # Create scheduler
    sched = sim.create_generic_scheduler(
        name='{base}_generic_quiesce_{random}'.format(
            base=base_name, random=utils.random_string(6)),
        active=1,
        process_memory=process_memory,
        size=game.num_players.sum(),
        time_per_observation=observation_time,
        observations_per_simulation=observation_increment,
        nodes=nodes,
        default_observation_requirement=observation_increment,
        configuration=configuration)

    # Add roles and counts to scheduler
    for role, count in zip(serial.role_names, game.num_players):
        sched.add_role(role, count)

    log.info('Created scheduler %d '
             '(http://egtaonline.eecs.umich.edu/generic_schedulers/%d)',
             sched.id, sched.id)

    # Data lookup
    psched = profsched.ProfileScheduler(
        game, serial, sched, max_profiles, log, profiles)

    # Set up reduction
    if dpr is None:
        red = reduction.Identity(game.num_strategies, game.num_players)
    else:
        red = reduction.DeviationPreserving(game.num_strategies,
                                            game.num_players, dpr)

    # Set up main scheduler abstraction
    qsched = profsched.QuiesceScheduler(game, red, psched)

    confirmed_equilibria = []  # Confirmed equilibra
    explored_subgames = []  # Already explored subgames
    explored_mixtures = []  # Already explored mixtures
    backup = []  # Extra subgames to explore
    subgames = []  # Subgames that are scheduling
    deviations = []  # Deviations that are scheduling

    # Define useful functions
    def add_subgame(subm):
        """Adds a subgame to the scheduler"""
        if not any(np.all(subm <= s) for s in explored_subgames):  # Unexplored
            explored_subgames[:] = [s for s in explored_subgames
                                    if np.any(s > subm)]
            explored_subgames.append(subm)
            log.debug('Exploring subgame:\n%s\n', json.dumps(
                {r: list(s) for r, s in serial.to_prof_json(subm).items()},
                indent=2))
            subgames.append(
                qsched.schedule_subgame(subm, observation_increment))
        else:  # Subgame already explored
            log.debug('Subgame already explored:\n%s\n', json.dumps(
                {r: list(s) for r, s in serial.to_prof_json(subm).items()},
                indent=2))

    def add_mixture(mixture, role_index=None):
        """Adds the given mixture to the scheduler"""
        if any(linalg.norm(mix - mixture) < 1e-3 and (
                role_index is None or role_index <= ri)
               for mix, ri in explored_mixtures):
            if role_index is None:
                log.debug('Mixture already explored:\n%s\n', json.dumps(
                    serial.to_prof_json(mixture), indent=2))
            else:
                log.debug('Mixture already explored for role "%s":\n%s\n',
                          serial.role_names[role_index],
                          json.dumps(serial.to_prof_json(mixture), indent=2))
        else:
            explored_mixtures.append((mixture, role_index))
            if role_index is None:
                log.debug('Exploring equilibrium deviations:\n%s\n',
                          json.dumps(serial.to_prof_json(mixture), indent=2))
            else:
                log.debug(
                    'Exploring equilibrium deviations for role "%s":\n%s\n',
                    serial.role_names[role_index],
                    json.dumps(serial.to_prof_json(mixture), indent=2))
            dev = qsched.schedule_deviations(
                mixture > 0, observation_increment, role_index)
            deviations.append((mixture, dev))

    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)

    if all_devs:
        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))
    else:
        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))

    try:
        # Initialize with pure subgames
        for subm in subgame.pure_subgames(game):
            add_subgame(subm)

        # While still scheduling left to do
        while subgames or deviations:
            if (qsched.update() or any(s.is_complete() for s in subgames) or
                    any(d.is_complete() for _, d in deviations)):
                # Something finished scheduling
                unsched_subgames = []
                for sub in subgames:
                    analyze_subgame(unsched_subgames, sub)
                subgames = unsched_subgames

                unsched_deviations = []
                for mix, dev in deviations:
                    analyze_deviations(unsched_deviations, mix, dev)
                deviations = unsched_deviations

                if (not subgames and not deviations and
                        len(confirmed_equilibria) < required_equilibria):
                    # We've finished all the required stuff, but still haven't
                    # found an equilibrium, so pop a backup off
                    log.debug('Extracting backup game\n')
                    while backup and not subgames:
                        add_subgame(heapq.heappop(backup)[1])

            else:
                # We're still waiting for jobs to complete, so take a break
                log.debug('Waiting %d seconds for simulations to finish...\n',
                          sleep_time)
                time.sleep(sleep_time)

    except KeyboardInterrupt:
        # Manually killed, so just deactivate
        log.error('Manually killed quiesce script. Deactivating scheduler\n')
        sched.deactivate()
        sys.exit(1)

    log.info('Deactivating scheduler %d\n', sched.id)
    sched.deactivate()

    final_game = psched.get_game()
    red_game = red.reduce_game(final_game, True)
    equilibria = (np.array(confirmed_equilibria) if confirmed_equilibria
                  else np.empty((0, game.num_role_strats)))
    complete_subgames = np.array(explored_subgames)
    regrets = np.fromiter((regret.mixture_regret(red_game, eqm)
                           for eqm in confirmed_equilibria),
                          float, len(confirmed_equilibria))
    final_log = [dict(regret=float(r), equilibrium=serial.to_prof_json(eqm))
                 for eqm, r in zip(equilibria, regrets)]

    log.error('Finished quiescing\nConfirmed equilibria:\n%s\n'
              'Explored %d subgames, sampled %d profiles with %d distinct\n',
              json.dumps(final_log, indent=2), complete_subgames.shape[0],
              psched.num_profiles, psched.num_unique_profiles)

    # TODO return failed subgames
    return equilibria, complete_subgames, final_game