Source code for egta.gamesched
"""Module for creating schedulers from game data"""
import math
import random
from gameanalysis import utils
from egta import profsched
# FIXME Add delay dist for testing unscheduled payoffs
# TODO Add common random seed for deterministic runs.
class _RsGameScheduler(profsched._Scheduler): # pylint: disable=protected-access
"""Schedule profiles by adding noise to a game
This scheduler will generate random parameters to assign to each profile.
To generate a sample payoff, the scheduler will add noise to the payoff for
that profile, generated as a function of the parameter. In order to work
for compact game representations this will only sample a number of profiles
logarithmic in the number of profiles in the complete game.
Parameters
----------
game : Game
The game with payoff data to use. An exception will be thrown if a
profile doesn't have any data in the game.
noise_dist : (\\*params) -> ndarray, optional
A distribution which takes the parameters for the profile and generates
random additive payoff noise for each strategy. Strategies that aren't
played will be zeroed out. This allows using different distributions
for different roles.
param_dist () -> \\*params, optional
A function for generating the parameters for each profile that govern
how payoff noise is distributed. By default there are no parameters,
e.g. all noise comes from the same distribution.
size_ratio : int, optional
This won't generate unique parameters for more than a logarithmic
number of profiles. This this the constant to be multiplied by the log
of the game size.
"""
def __init__( # pragma: no branch # noqa
self, game, noise_dist=lambda: 0, param_dist=lambda: (), size_ratio=200
):
super().__init__(game.role_names, game.strat_names, game.num_role_players)
self._noise_dist = noise_dist
self._param_dist = param_dist
self._max_size = max(
int(math.log(game.num_all_profiles) * size_ratio), game.num_profiles
)
self._game = game
self._params = {}
async def sample_payoffs(self, profile):
index = hash(utils.hash_array(profile)) % self._max_size
params = self._params.get(index, None)
if params is None:
params = self._param_dist()
self._params[index] = params
payoff = self._game.get_payoffs(profile) + self._noise_dist(*params)
payoff[profile == 0] = 0
payoff.setflags(write=False)
return payoff
def __str__(self):
return repr(self._game)
[docs]def gamesched(game, noise_dist=lambda: 0, param_dist=lambda: (), size_ratio=200):
"""Create a game scheduler"""
return _RsGameScheduler(
game, noise_dist=noise_dist, param_dist=param_dist, size_ratio=size_ratio
)
class _SampleGameScheduler(profsched._Scheduler): # pylint: disable=protected-access
"""Schedule profiles by adding noise to a sample game
This scheduler will generate random parameters to assign to each profile.
To generate a sample payoff, the scheduler will sample a random payoff
associated with the profile, and then add noise generated as a function of
the parameter.
Parameters
----------
sgame : SampleGame
A sample game with potentially several payoffs for each profile. An
exception will be thrown if a profile doesn't have any data in the
game.
noise_dist : \\*params -> ndarray, optional
A distribution which takes the parameters for the profile and generates
random additive payoff noise for each strategy. Strategies that aren't
played will be zeroed out. This allows using different distributions
for different roles.
param_dist () -> \\*params, optional
A function for generating the parameters for each profile that govern
how payoff noise is distributed. By default there are no parameters,
e.g. all noise comes from the same distribution.
"""
def __init__( # pragma: no branch # noqa
self, sgame, noise_dist=lambda: 0, param_dist=lambda: ()
):
super().__init__(sgame.role_names, sgame.strat_names, sgame.num_role_players)
utils.check(hasattr(sgame, "get_sample_payoffs"), "sgame not a sample game")
self._noise_dist = noise_dist
self._param_dist = param_dist
self._sgame = sgame
self._paymap = {}
async def sample_payoffs(self, profile):
hprof = utils.hash_array(profile)
pays, params = self._paymap.get(hprof, (None, None))
if pays is None:
params = self._param_dist()
pays = self._sgame.get_sample_payoffs(profile)
self._paymap[hprof] = (pays, params)
pay = pays[random.randrange(pays.shape[0])]
payoff = pay + self._noise_dist(*params)
payoff[profile == 0] = 0
payoff.setflags(write=False)
return payoff
def __str__(self):
return repr(self._sgame)
[docs]def samplegamesched(sgame, noise_dist=lambda: 0, param_dist=lambda: ()):
"""Create a samplegame scheduler"""
return _SampleGameScheduler(sgame, noise_dist=noise_dist, param_dist=param_dist)