def mix(agame0, agame1, prob): """Mix two async games""" utils.check( rsgame.empty_copy(agame0) == rsgame.empty_copy(agame1), "games must have identically structure", ) return _MixedAsyncGame(agame0, agame1, prob)
async def test_trace(game, sim_sched, tmpdir, red): """Test tracing""" conf_file = str(tmpdir.join("conf.json")) with open(conf_file, "w") as fil: json.dump( { "markup": "standard", "max_value": 1, "arrivals": "simple", "market": "call", }, fil, ) prof_data = str(tmpdir.join("data.json")) with stdout() as out, stderr() as err: assert await run( "trace", sim_sched + ",save:" + prof_data, sim_sched + ",conf:" + conf_file, *red ), err.getvalue() traces = json.loads(out.getvalue()) verify_trace_json(game, traces) with open(prof_data) as fil: data = gamereader.load(fil) assert rsgame.empty_copy(game) == rsgame.empty_copy(data)
def __init__(self, sched, red, red_players): super().__init__(sched.role_names, sched.strat_names, sched.num_role_players) self._sched = sched self._rgame = rsgame.empty_copy( red.reduce_game(rsgame.empty_copy(self), red_players)) self._red = red self._profiles = {}
async def test_basic_profile_sample(): """Test basic profile in sample game""" sgame = gamegen.samplegame([4, 3], [3, 4]) profs = sgame.random_profiles(20) sched = gamesched.samplegamesched(sgame) assert rsgame.empty_copy(sched) == rsgame.empty_copy(sgame) paylist = await asyncio.gather(*[sched.sample_payoffs(p) for p in profs]) pays = np.stack(paylist) assert np.allclose(pays[profs == 0], 0) assert str(sched) == repr(sgame)
async def test_prof_data(game, game_sched, tmpdir): """Test game scheduler""" prof_file = str(tmpdir.join('profs.json')) with stdout() as out, stderr() as err: assert await run( 'brute', game_sched + ',sample:,save:' + prof_file), err.getvalue() for eqm in json.loads(out.getvalue()): game.mixture_from_json(eqm['equilibrium']) with open(prof_file) as fil: prof_game = gamereader.load(fil) assert rsgame.empty_copy(game) == rsgame.empty_copy(prof_game)
def _rprofs(self, rest): """Get the restricted profiles for a restriction""" return restrict.translate( self._red.expand_profiles( rsgame.empty_copy(self).restrict(rest), self._rgame.restrict(rest).all_profiles()), rest)
async def aopen(self): # pylint: disable=too-many-locals """Open the eosched""" gu.check(not self._is_open, "already open") try: game = await self._api.get_game(self._game_id) obs = await game.get_observations() gu.check( rsgame.empty_copy(self._game) == rsgame.empty_json(obs), "egtaonline game didn't match specified game", ) conf = dict(obs.get("configuration", ()) or ()) profiles = obs.get("profiles", ()) or () # Parse profiles num_profs = len(profiles) num_pays = 0 for jprof in profiles: pid = jprof["id"] prof, spays = self._game.profsamplepay_from_json(jprof) spays.setflags(write=False) hprof = gu.hash_array(prof) pays = asyncio.Queue() num_spays = len(spays) num_pays += num_spays for pay in spays: pays.put_nowait(pay) data = ([num_spays], [num_spays], [0], [pid], pays) self._profiles[hprof] = data self._prof_ids[pid] = data logging.info( "found %d existing profiles with %d payoffs in game %d", num_profs, num_pays, self._game_id, ) # Create and start scheduler self._sched = await obs.create_generic_scheduler( "egta_" + eu.random_string(20), True, self._obs_memory, self._obs_time, self._simult_obs, 1, conf, ) logging.warning( "created scheduler %d for running simulations of game %d: " "https://%s/generic_schedulers/%d", self._sched["id"], self._game_id, self._api.domain, self._sched["id"], ) self._fetcher = asyncio.ensure_future(self._fetch()) self._is_open = True except Exception as ex: await self.aclose() raise ex return self
def __init__( # pylint: disable=too-many-arguments self, game, api, game_id, sleep_time, simultaneous_obs, max_scheduled, obs_memory, obs_time, ): super().__init__(game.role_names, game.strat_names, game.num_role_players) self._api = api self._game = paygame.samplegame_copy(rsgame.empty_copy(game)) self._game_id = game_id self._sleep_time = sleep_time self._obs_memory = obs_memory self._obs_time = obs_time self._simult_obs = simultaneous_obs self._is_open = False self._profiles = {} self._prof_ids = {} self._sched = None self._fetcher = None self._sched_lock = asyncio.Lock() self._scheduled = asyncio.BoundedSemaphore(max_scheduled * simultaneous_obs)
def restrict(self, restriction): restriction = np.asarray(restriction, bool) base = rsgame.empty_copy(self).restrict(restriction) size_mask = restriction.repeat(self._sizes) sizes = self._sizes[restriction] profiles = self._profiles[size_mask] lengths = self._lengths[restriction] zeros = (profiles[:, ~restriction] / lengths[:, ~restriction].repeat(sizes, 0)) removed = np.exp(-np.einsum('ij,ij->i', zeros, zeros) / 2) # pylint: disable=invalid-unary-operand-type uprofs, inds = np.unique( recfunctions.merge_arrays([ np.arange(restriction.sum()).repeat(sizes).view([('s', int)]), utils.axis_to_elem(profiles[:, restriction])], flatten=True), return_inverse=True) new_alpha = np.bincount(inds, removed * self._alpha[size_mask]) new_sizes = np.diff(np.concatenate([ [-1], np.flatnonzero(np.diff(uprofs['s'])), [new_alpha.size - 1]])) return _RbfGpGame( base.role_names, base.strat_names, base.num_role_players, self._offset[restriction], self._coefs[restriction], lengths[:, restriction], new_sizes, uprofs['axis'], new_alpha)
def __init__(self, sched): super().__init__(sched.role_names, sched.strat_names, sched.num_role_players) self._sched = sched self._game = paygame.samplegame_copy(rsgame.empty_copy(self)) self._profiles = [] self._payoffs = []
async def test_basic_asyncgame(): """Test that wrapped async games work""" game = gamegen.game([4, 3], [3, 4]) agame = asyncgame.wrap(game) rest = agame.random_restriction() rgame = await agame.get_restricted_game(rest) assert rgame.is_complete() assert rsgame.empty_copy(rgame) == rsgame.empty_copy(game.restrict(rest)) dgame = await agame.get_deviation_game(rest) mix = restrict.translate(rgame.random_mixture(), rest) assert not np.isnan(dgame.deviation_payoffs(mix)).any() dup = asyncgame.wrap(game) assert hash(dup) == hash(agame) assert dup == agame
def restrict(self, restriction): restriction = np.asarray(restriction, bool) base = rsgame.empty_copy(self).restrict(restriction) size_mask = restriction.repeat(self._sizes) sizes = self._sizes[restriction] profiles = self._profiles[size_mask] lengths = self._lengths[restriction] zeros = (profiles[:, ~restriction] / lengths[:, ~restriction].repeat(sizes, 0)) removed = np.exp(-np.einsum('ij,ij->i', zeros, zeros) / 2) # pylint: disable=invalid-unary-operand-type uprofs, inds = np.unique(recfunctions.merge_arrays([ np.arange(restriction.sum()).repeat(sizes).view([('s', int)]), utils.axis_to_elem(profiles[:, restriction]) ], flatten=True), return_inverse=True) new_alpha = np.bincount(inds, removed * self._alpha[size_mask]) new_sizes = np.diff( np.concatenate([[-1], np.flatnonzero(np.diff(uprofs['s'])), [new_alpha.size - 1]])) return _RbfGpGame(base.role_names, base.strat_names, base.num_role_players, self._offset[restriction], self._coefs[restriction], lengths[:, restriction], new_sizes, uprofs['axis'], new_alpha)
async def test_basic_profile(_): """Test that profiles are saved""" sgame = gamegen.samplegame([4, 3], [3, 4], 0) profs = sgame.all_profiles() basesched = gamesched.samplegamesched(sgame) sched = savesched.savesched(basesched) assert str(sched) == str(basesched) assert rsgame.empty_copy(sgame) == rsgame.empty_copy(sched) await asyncio.gather(*[sched.sample_payoffs(p) for p in profs[:10]]) sched.get_game() sched = savesched.savesched(gamesched.samplegamesched(sgame)) await asyncio.gather(*[sched.sample_payoffs(p) for p in profs]) savegame = sched.get_game() assert sgame == savegame assert sgame == sched.get_game()
def restrict(self, restriction): base = rsgame.empty_copy(self).restrict(restriction) new_rest = self._rest.copy() new_rest[new_rest] = restriction regs = tuple(reg for reg, m in zip(self._regressors, restriction) if m) return _DevRegressionGame(base, regs, self._offset[restriction], self._scale[restriction], self._min_payoffs[restriction], self._max_payoffs[restriction], new_rest)
def restrict(self, restriction): base = rsgame.empty_copy(self).restrict(restriction) new_rest = self._rest.copy() new_rest[new_rest] = restriction regs = tuple(reg for reg, m in zip(self._regressors, restriction) if m) return _DevRegressionGame( base, regs, self._offset[restriction], self._scale[restriction], self._min_payoffs[restriction], self._max_payoffs[restriction], new_rest)
def restrict(self, restriction): restriction = np.asarray(restriction, bool) base = rsgame.empty_copy(self).restrict(restriction) action_weights = self.action_weights[:, restriction] func_mask = np.any(~np.isclose(action_weights, 0), 1) return _AgfnGame(base.role_names, base.strat_names, base.num_role_players, action_weights[func_mask], self.function_inputs[:, func_mask][restriction], self.function_table[func_mask], self.offsets[restriction])
def test_random_game_addition(strats): """Test random addition""" mpayoffs = rand.random(tuple(strats) + (len(strats),)) matg = matgame.matgame(mpayoffs) payoffs = rand.random(matg.payoffs().shape) payoffs[matg.profiles() == 0] = 0 game = paygame.game_replace(matg, matg.profiles(), payoffs) assert paygame.game_copy(matg + game) == game + matg empty = rsgame.empty_copy(matg) assert matg + empty == empty
async def test_trace(game, sim_sched, tmpdir, red): """Test tracing""" conf_file = str(tmpdir.join('conf.json')) with open(conf_file, 'w') as fil: json.dump({ 'markup': 'standard', 'max_value': 1, 'arrivals': 'simple', 'market': 'call', }, fil) prof_data = str(tmpdir.join('data.json')) with stdout() as out, stderr() as err: assert await run( 'trace', sim_sched + ',save:' + prof_data, sim_sched + ',conf:' + conf_file, *red), err.getvalue() traces = json.loads(out.getvalue()) verify_trace_json(game, traces) with open(prof_data) as fil: data = gamereader.load(fil) assert rsgame.empty_copy(game) == rsgame.empty_copy(data)
async def test_mix_asyncgame(): """Test that that mixture async games work""" game0 = gamegen.game([4, 3], [3, 4]) game1 = gamegen.game([4, 3], [3, 4]) agame = asyncgame.mix(asyncgame.wrap(game0), asyncgame.wrap(game1), 0.4) assert agame.get_game() == rsgame.mix(game0, game1, 0.4) assert str(agame) == "{} - 0.4 - {}".format(repr(game0), repr(game1)) rest = agame.random_restriction() rgame = await agame.get_restricted_game(rest) assert rgame.is_complete() assert rsgame.empty_copy(rgame) == rsgame.empty_copy(game0.restrict(rest)) dgame = await agame.get_deviation_game(rest) mix = restrict.translate(rgame.random_mixture(), rest) assert not np.isnan(dgame.deviation_payoffs(mix)).any() dup = asyncgame.mix(asyncgame.wrap(game0), asyncgame.wrap(game1), 0.4) assert hash(dup) == hash(agame) assert dup == agame
def test_random_game_addition(strats): """Test random addition""" mpayoffs = rand.random(tuple(strats) + (len(strats), )) matg = matgame.matgame(mpayoffs) payoffs = rand.random(matg.payoffs().shape) payoffs[matg.profiles() == 0] = 0 game = paygame.game_replace(matg, matg.profiles(), payoffs) assert paygame.game_copy(matg + game) == game + matg empty = rsgame.empty_copy(matg) assert matg + empty == empty
async def test_basic_profile(conf, game): """Test sampling a basic profile""" profs = game.random_profiles(20) cmd = ["python3", "cdasim/sim.py", "--single", "1"] async with simsched.simsched(game, conf, cmd) as sched: assert str(sched) == "python3 cdasim/sim.py --single 1" assert rsgame.empty_copy(sched) == game awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert pays.shape == profs.shape assert np.allclose(pays[profs == 0], 0) assert np.any(pays != 0)
async def test_basic_profile(game): """Test scheduling a standard profile""" async with mockserver.server() as server, api.api() as egta: sim = await egta.get_simulator( server.create_simulator("sim", "1", delay_dist=lambda: random.random() / 10)) strats = dict(zip(game.role_names, game.strat_names)) symgrps = list( zip(game.role_names, game.num_role_players, game.strat_names)) await sim.add_strategies(strats) egame = await egta.get_canon_game(sim["id"], symgrps) profs = game.random_profiles(20) # Schedule all new profiles and verify it works async with eosched.eosched(game, egta, egame["id"], 0.1, 1, 10, 0, 0) as sched: assert str(sched) == str(egame["id"]) assert game == rsgame.empty_copy(sched) awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert np.allclose(pays[profs == 0], 0) # Schedule old profiles and verify it still works async with eosched.eosched(game, egta, egame["id"], 0.1, 1, 10, 0, 0) as sched: awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert np.allclose(pays[profs == 0], 0) # Schedule two at a time, in two batches async with eosched.eosched(game, egta, egame["id"], 0.1, 2, 10, 0, 0) as base_sched: sched = countsched.countsched(base_sched, 2) awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert np.allclose(pays[profs == 0], 0) # Try again now that everything should be scheduled async with eosched.eosched(game, egta, egame["id"], 0.1, 2, 10, 0, 0) as base_sched: sched = countsched.countsched(base_sched, 2) awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert np.allclose(pays[profs == 0], 0)
async def test_no_conf(game): """Test scheduler without a configuration""" profs = game.random_profiles(20) zipf = "cdasim/cdasim.zip" with zipsched.zipsched(game, {}, zipf) as sched: assert rsgame.empty_copy(sched) == game awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert pays.shape == profs.shape assert np.allclose(pays[profs == 0], 0) assert np.any(pays != 0)
async def test_basic_profile(conf, game): """Test scheduling a basic profile""" profs = game.random_profiles(20) zipf = "cdasim/cdasim.zip" with zipsched.zipsched(game, conf, zipf) as sched: assert str(sched) is not None assert rsgame.empty_copy(sched) == game awaited = await asyncio.gather( *[sched.sample_payoffs(p) for p in profs]) pays = np.stack(awaited) assert pays.shape == profs.shape assert np.allclose(pays[profs == 0], 0) assert np.any(pays != 0)
def __init__(self, game, conf, zipf, *, max_procs=4, simultaneous_obs=1): super().__init__(game.role_names, game.strat_names, game.num_role_players) self._game = paygame.game_copy(rsgame.empty_copy(game)) self.conf = conf self.zipf = zipf self._extra_profs = {} self._base = {} self._count = simultaneous_obs self._is_open = False self._sim_dir = None self._prof_dir = None self._sim_root = None self._num = 0 self._procs = asyncio.Semaphore(max_procs)
def __init__(self, game, config, command, buff_size=65536): super().__init__(game.role_names, game.strat_names, game.num_role_players) self._game = paygame.game_copy(rsgame.empty_copy(game)) self._base = {"configuration": config} self.command = command self.buff_size = buff_size self._is_open = False self._proc = None self._reader = None self._read_queue = asyncio.Queue() self._write_lock = asyncio.Lock() self._buffer_empty = asyncio.Event() self._buffer_bytes = 0 self._line_bytes = collections.deque() self._buffer_empty.set()
def test_neighbor(): # pylint: disable=too-many-locals """Test neighbor games""" game = gamegen.sparse_game([2, 3], [3, 2], 10) model = gp.GaussianProcessRegressor(1.0 * gp.kernels.RBF(2, [1, 3]) + gp.kernels.WhiteKernel(1), normalize_y=True) learn = learning.neighbor(learning.sklgame_train(game, model)) full = paygame.game_copy(learn) errors = np.zeros(game.num_strats) for i, mix in enumerate( itertools.chain(game.random_mixtures(20), game.random_sparse_mixtures(20)), 1): tdev = full.deviation_payoffs(mix) dev, _ = learn.deviation_payoffs(mix, jacobian=True) err = (tdev - dev)**2 / (np.abs(dev) + 1e-5) errors += (err - errors) / i assert np.all(errors < 5) submask = game.random_restriction() sublearn = learn.restrict(submask) subfull = full.restrict(submask) assert np.allclose(sublearn.get_payoffs(subfull.profiles()), subfull.payoffs()) norm = learn.normalize() assert np.allclose(norm.min_role_payoffs(), 0) assert np.allclose(norm.max_role_payoffs(), 1) assert learning.neighbor(learn, learn.num_neighbors) == learn learn = learning.neighbor(learning.rbfgame_train(game)) jgame = json.dumps(learn.to_json()) copy = learning.neighbor_json(json.loads(jgame)) assert hash(copy) == hash(learn) assert copy == learn assert learn + copy == copy + learn empty = rsgame.empty_copy(learn) assert learn + empty == empty
def main(args): """Sample objects entry point""" game = rsgame.empty_copy(gamereader.load(args.input)) if args.seed is not None: # Python hash is randomly salted, so we use this to guarantee # determinism np.random.seed(int( hashlib.sha256(args.seed.encode('utf8')).hexdigest()[:8], 16)) if args.types in RESTS: objs = (game.restriction_to_json(rest) for rest in game.random_restrictions( args.num, strat_prob=args.prob, normalize=args.unnormalize)) elif args.types in MIXES: if args.sparse is _NOSPARSE: mix = game.random_mixtures(args.num, alpha=args.alpha) else: mix = game.random_sparse_mixtures(args.num, alpha=args.alpha, support_prob=args.sparse) objs = (game.mixture_to_json(m) for m in mix) elif args.types in PROFS: # pragma: no branch if args.mix is None: prof = game.round_mixture_to_profile( game.random_mixtures(args.num, alpha=args.alpha)) else: mix = game.mixture_from_json(json.load(args.mix)) prof = game.random_profiles(args.num, mix) objs = (game.profile_to_json(p) for p in prof) # We sort the keys when a seed is set to guarantee identical output. This # technically shouldn't be necessary, but on the off chance that a # simulator depends on the order, we want to make sure we produce identical # results. for obj in objs: json.dump(obj, args.output, sort_keys=args.seed is not None) args.output.write('\n')
def test_missing_data_maximal_restrictions(base, prob): """Test missing data""" game = gamegen.game_replace(base, prob) rests = restrict.maximal_restrictions(game) if rests.size: maximal = np.all(rests <= rests[:, None], -1) np.fill_diagonal(maximal, False) assert not maximal.any(), \ 'One maximal restriction dominated another' for rest in rests: rgame = rsgame.empty_copy(game).restrict(rest) restprofs = restrict.translate(rgame.all_profiles(), rest) assert all(p in game for p in restprofs), \ "Maximal restriction didn't have all profiles" for dev in np.nonzero(~rest)[0]: devprofs = restrict.additional_strategy_profiles( game, rest, dev) assert not all(p in game for p in devprofs), ( # pragma: no branch 'Maximal restriction could be bigger {} {}'.format( dev, rest))
def iterated_elimination(game, criterion, *, conditional=True): """Return a restriction resulting from iterated elimination of strategies Parameters ---------- game : Game The game to run iterated elimination on criterion : {'weakdom', 'strictdom', 'neverbr'} The criterion to use to eliminated strategies. conditional : bool Whether to use conditional criteria. In general, conditional set to true will assume that unobserved payoffs are large. See the other methods for a more detailed explanation """ # There's a few recomputed things that could be passed to save computation # time, but they're minimal and probably not that important cfunc = _CRITERIA[criterion] egame = rsgame.empty_copy(game) gains = _gains(game) supports = game.profiles() > 0 rest = np.ones(game.num_strats, bool) mask = ~cfunc(egame, gains, supports, conditional) while (not np.all(mask) and np.any(np.add.reduceat( mask, egame.role_starts) > 1)): rest[rest] = mask prof_mask = ~np.any(supports & ~mask, -1) to_in_mask = mask[egame.dev_to_indices] from_in_mask = mask[egame.dev_from_indices] egame = egame.restrict(mask) gains = gains[prof_mask][:, to_in_mask & from_in_mask] supports = supports[prof_mask][:, mask] mask = ~cfunc(egame, gains, supports, conditional) rest[rest] = mask return rest
def iterated_elimination(game, criterion, *, conditional=True): """Return a restriction resulting from iterated elimination of strategies Parameters ---------- game : Game The game to run iterated elimination on criterion : {'weakdom', 'strictdom', 'neverbr'} The criterion to use to eliminated strategies. conditional : bool Whether to use conditional criteria. In general, conditional set to true will assume that unobserved payoffs are large. See the other methods for a more detailed explanation """ # There's a few recomputed things that could be passed to save computation # time, but they're minimal and probably not that important cfunc = _CRITERIA[criterion] egame = rsgame.empty_copy(game) gains = _gains(game) supports = game.profiles() > 0 rest = np.ones(game.num_strats, bool) mask = ~cfunc(egame, gains, supports, conditional) while (not np.all(mask) and np.any(np.add.reduceat(mask, egame.role_starts) > 1)): rest[rest] = mask prof_mask = ~np.any(supports & ~mask, -1) to_in_mask = mask[egame.dev_to_indices] from_in_mask = mask[egame.dev_from_indices] egame = egame.restrict(mask) gains = gains[prof_mask][:, to_in_mask & from_in_mask] supports = supports[prof_mask][:, mask] mask = ~cfunc(egame, gains, supports, conditional) rest[rest] = mask return rest
def restrict(self, restriction): base = rsgame.empty_copy(self).restrict(restriction) matrix = self._payoff_matrix for i, mask in enumerate(np.split(restriction, self.role_starts[1:])): matrix = matrix[(slice(None),) * i + (mask,)] return _MatrixGame(base.role_names, base.strat_names, matrix.copy())
def trace_equilibrium( # pylint: disable=too-many-locals game0, game1, peq, eqm, target, *, regret_thresh=1e-3, max_step=0.1, singular=1e-7, **ivp_args): """Try to trace an equilibrium out to target Takes two games, a fraction that they're mixed (`peq`), and an equilibrium of the mixed game (`eqm`). It then attempts to find the equilibrium at the `target` mixture. It may not reach target, but will return as far as it got. The return value is two parallel arrays for the probabilities with known equilibria and the equilibria. Parameters ---------- game0 : RsGame The first game that's merged. Represents the payoffs when `peq` is 0. game1 : RsGame The second game that's merged. Represents the payoffs when `peq` is 1. peq : float The amount that the two games are merged such that `eqm` is an equilibrium. Must be in [0, 1]. eqm : ndarray An equilibrium when `game0` and `game1` are merged a `peq` fraction. target : float The desired mixture probability to have an equilibrium at. regret_thresh : float, optional The amount of gain from deviating to a strategy outside support can have before it's considered a beneficial deviation and the tracing stops. This should be larger than zero as most equilibria are approximate due to floating point precision. max_step : float, optional The maximum step to take in t when evaluating. singular : float, optional An absolute determinant below this value is considered singular. Occasionally the derivative doesn't exist, and this is one way in which that manifests. This values regulate when ODE solving terminates due to a singular matrix. ivp_args Any remaining keyword arguments are passed to the ivp solver. """ egame = rsgame.empty_copy(game0) eqm = np.asarray(eqm, float) utils.check(egame.is_mixture(eqm), "equilibrium wasn't a valid mixture") utils.check( regret.mixture_regret(rsgame.mix(game0, game1, peq), eqm) <= regret_thresh + 1e-7, "equilibrium didn't have regret below threshold") ivp_args.update(max_step=max_step) # It may be handy to have the derivative of this so that the ode solver can # be more efficient, except that computing the derivative w.r.t. t requires # the hessian of the deviation payoffs, which would be complicated and so # far has no use anywhere else. def ode(prob, mix_neg): """ODE function for solve_ivp""" div = np.zeros(egame.num_strats) mix = egame.trim_mixture_support(mix_neg, thresh=0) supp = mix > 0 rgame = egame.restrict(supp) dev1, jac1 = game0.deviation_payoffs(mix, jacobian=True) dev2, jac2 = game1.deviation_payoffs(mix, jacobian=True) gvals = (dev1 - dev2)[supp] fvecs = ((1 - prob) * jac1 + prob * jac2)[supp][:, supp] gvec = np.concatenate([ np.delete(np.diff(gvals), rgame.role_starts[1:] - 1), np.zeros(egame.num_roles) ]) fmat = np.concatenate([ np.delete(np.diff(fvecs, 1, 0), rgame.role_starts[1:] - 1, 0), np.eye(egame.num_roles).repeat(rgame.num_role_strats, 1) ]) if singular < np.abs(np.linalg.det(fmat)): div[supp] = np.linalg.solve(fmat, gvec) return div def below_regret_thresh(prob, mix_neg): """Event for regret going above threshold""" mix = egame.trim_mixture_support(mix_neg, thresh=0) reg = regret.mixture_regret(rsgame.mix(game0, game1, prob), mix) return reg - regret_thresh below_regret_thresh.terminal = True below_regret_thresh.direction = 1 def singular_jacobian(prob, mix_neg): """Event for when jacobian is singular""" mix = egame.trim_mixture_support(mix_neg, thresh=0) supp = mix > 0 rgame = egame.restrict(supp) _, jac1 = game0.deviation_payoffs(mix, jacobian=True) _, jac2 = game1.deviation_payoffs(mix, jacobian=True) fvecs = ((1 - prob) * jac1 + prob * jac2)[supp][:, supp] fmat = np.concatenate([ np.delete(np.diff(fvecs, 1, 0), rgame.role_starts[1:] - 1, 0), np.eye(egame.num_roles).repeat(rgame.num_role_strats, 1) ]) return np.abs(np.linalg.det(fmat)) - singular singular_jacobian.terminal = True singular_jacobian.direction = -1 events = [below_regret_thresh, singular_jacobian] # This is to scope the index def create_support_loss(ind): """Create support loss for every ind""" def support_loss(_, mix): """Support loss event""" return mix[ind] support_loss.direction = -1 return support_loss for strat in range(egame.num_strats): events.append(create_support_loss(strat)) with np.errstate(divide='ignore'): res = integrate.solve_ivp(ode, [peq, target], eqm, events=events, **ivp_args) return res.t, egame.trim_mixture_support(res.y.T, thresh=0)
import sys from gameanalysis import gambit from gameanalysis import gamereader from gameanalysis import matgame from gameanalysis import paygame from gameanalysis import rsgame _TYPES = { 'emptygame': ( ['empty'], 'Strip payoff data', """Strip all payoff data from a game and return only its base structure---role and strategy names and player counts.""", lambda game, out: json.dump( rsgame.empty_copy(game).to_json(), out)), 'game': ( [], 'Sparse payoff format', """Convert a game to a sparse mapping of profiles to their corresponding payoff data.""", lambda game, out: json.dump( paygame.game_copy(game).to_json(), out)), 'samplegame': ( ['samp'], 'Multiple payoffs per profile', """Convert a game to a format with a sparse mapping of profiles to potentially several samples of payoff data. There should be little reason to convert a non-samplegame to a samplegame as all profiles will have exactly one sample.""", lambda game, out: json.dump( paygame.samplegame_copy(game).to_json(), out)), 'matgame': ( ['mat'], 'Asymmetric format', """Convert a game to a compact representation for asymmetric games. If the input game is not
async def trace_all_equilibria( # pylint: disable=too-many-locals agame0, agame1, *, regret_thresh=1e-3, dist_thresh=0.1, max_step=0.1, executor=None, **innerloop_args): """Trace out all equilibria between all games Parameters ---------- agame0 : AsyncGame The game that is played when time is 0. agame1 : AsyncGame The game that is played when time is 1. regret_thresh : float, optional The threshold for epsilon regret for equilibria returned. exectutor : Executor, optional The executor to run computation intensive operations in. """ utils.check( rsgame.empty_copy(agame0) == rsgame.empty_copy(agame1), 'games must have same structure') loop = asyncio.get_event_loop() trace_args = dict(regret_thresh=regret_thresh, max_step=max_step) innerloop_args.update(executor=executor, regret_thresh=regret_thresh, dist_thresh=dist_thresh) async def trace_eqm(eqm, prob): """Trace and equilibrium out from prob""" game0 = agame0.get_game() game1 = agame1.get_game() (pr0, eqa0), (pr1, eqa1) = await asyncio.gather( loop.run_in_executor( executor, functools.partial(trace.trace_equilibrium, game0, game1, prob, eqm, 0, **trace_args)), loop.run_in_executor( executor, functools.partial(trace.trace_equilibrium, game0, game1, prob, eqm, 1, **trace_args))) return (np.concatenate([pr0[::-1], pr1[1:]]), np.concatenate([eqa0[::-1], eqa1[1:]])) async def trace_between(lower, upper): """Trace between times lower and upper""" if upper <= lower: return () mid = (lower + upper) / 2 midgame = asyncgame.mix(agame0, agame1, mid) eqa = await innerloop.inner_loop(midgame, **innerloop_args) if not eqa.size: # pragma: no cover logging.warning('found no equilibria in %s', midgame) return () traces = await asyncio.gather(*[trace_eqm(eqm, mid) for eqm in eqa]) lupper = min(t[0] for t, _ in traces) ulower = max(t[-1] for t, _ in traces) logging.warning('traced %s out to %g - %g', midgame, lupper, ulower) lower_traces, upper_traces = await asyncio.gather( trace_between(lower, lupper), trace_between(ulower, upper)) # Lazily extend them return itertools.chain(lower_traces, traces, upper_traces) traces = list(await trace_between(0.0, 1.0)) game0, game1 = agame0.get_game(), agame1.get_game() traces = _merge_traces(game0, game1, traces, dist_thresh, trace_args) for probs, eqa in traces: _smooth_trace(game0, game1, probs, eqa, trace_args) _smooth_trace(game0, game1, probs[::-1], eqa[::-1], trace_args) return sorted(traces, key=lambda tr: (tr[0][0], tr[0][-1]))
import itertools import json import sys from gameanalysis import gambit from gameanalysis import gamereader from gameanalysis import matgame from gameanalysis import paygame from gameanalysis import rsgame _TYPES = { 'emptygame': (['empty'], 'Strip payoff data', """Strip all payoff data from a game and return only its base structure---role and strategy names and player counts.""", lambda game, out: json.dump(rsgame.empty_copy(game).to_json(), out)), 'game': ([], 'Sparse payoff format', """Convert a game to a sparse mapping of profiles to their corresponding payoff data.""", lambda game, out: json.dump(paygame.game_copy(game).to_json(), out)), 'samplegame': (['samp'], 'Multiple payoffs per profile', """Convert a game to a format with a sparse mapping of profiles to potentially several samples of payoff data. There should be little reason to convert a non-samplegame to a samplegame as all profiles will have exactly one sample.""", lambda game, out: json.dump(paygame.samplegame_copy(game).to_json(), out) ), 'matgame': (['mat'], 'Asymmetric format', """Convert a game to a compact representation for asymmetric games. If the input game is not asymmetric, role names will be duplicated and modified to allow for the