def test_hash_array(): """Test that hash array works""" arrayset = {utils.hash_array([3, 4, 5]), utils.hash_array([6, 7])} assert utils.hash_array([3, 4, 5]) in arrayset assert utils.hash_array([6, 7]) in arrayset assert utils.hash_array([3, 4, 6]) not in arrayset
def test_subgame_extract_1(): string, _ = run('sub', '-n', '-t', 'background', 'markov:rmin_500_rmax_1000_thresh_0.8_priceVarEst_1e9', 'hft', 'noop', '-s', '0', '3', '4', input=GAME_STR) expected = {utils.hash_array([False, True, True, False, False, False, False, False, False]), utils.hash_array([True, False, False, True, True, False, False, False, False])} assert {utils.hash_array(SERIAL.from_subgame_json(s)) for s in json.loads(string)} == expected
async def sample_payoffs(self, profile): gu.check(self._is_open, "not open") self._check_fetcher() hprof = gu.hash_array(profile) data = self._profiles.setdefault( hprof, ([0], [0], [0], [None], asyncio.Queue())) scheduled, _, claimed, prof_id, pays = data claimed[0] += 1 if scheduled[0] < claimed[0]: scheduled[0] += self._simult_obs async with self._sched_lock: for _ in range(self._simult_obs): await self._scheduled.acquire() pid = prof_id[0] if pid is not None: await self._sched.remove_profile(pid) assignment = self._game.profile_to_repr(profile) prof_id[0] = (await self._sched.add_profile(assignment, scheduled[0]))["id"] if pid is None: self._prof_ids[prof_id[0]] = data pay = await pays.get() self._check_fetcher() return pay
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 sample_profiles(base, num): # pylint: disable=inconsistent-return-statements """Generate unique profiles from a game Parameters ---------- base : RsGame Game to generate random profiles from. num : int Number of profiles to sample from the game. """ if num == base.num_all_profiles: # pylint: disable=no-else-return return base.all_profiles() elif num == 0: return np.empty((0, base.num_strats), int) elif base.num_all_profiles <= np.iinfo(int).max: inds = rand.choice(base.num_all_profiles, num, replace=False) return base.profile_from_id(inds) else: # Number of times we have to re-query ratio = (sps.digamma(float(base.num_all_profiles)) - sps.digamma(float(base.num_all_profiles - num))) # Max is for underflow num_per = max(round(float(ratio * base.num_all_profiles)), num) profiles = set() while len(profiles) < num: profiles.update( utils.hash_array(p) for p in base.random_profiles(num_per)) profiles = np.stack([h.array for h in profiles]) inds = rand.choice(profiles.shape[0], num, replace=False) return profiles[inds]
def test_sample_game_payoff(): profiles = [ [0, 4, 0, 9], [0, 4, 1, 8], [0, 4, 4, 5], [0, 4, 3, 6], ] payoffs = [ [ [[0] * 4, [1, 2, 3, 4], [0] * 4, [5, 6, 7, 8]], ], [ [[0, 0], [0, 0], [9, 10], [0, 0]], ], [ [[0] * 3, [0] * 3, [0] * 3, [11, 12, 13]], ], [ [[0] * 5, [14, 15, 16, 17, 18], [0] * 5, [0] * 5], ], ] game = rsgame.SampleGame([4, 9], 2, profiles, payoffs) red = reduction.DeviationPreserving([2, 2], [4, 9], [2, 3]) red_game = red.reduce_game(game) prof_map = dict(zip( map(utils.hash_array, red_game.profiles), itertools.chain.from_iterable(red_game.sample_payoffs))) payoffs = prof_map[utils.hash_array([0, 2, 0, 3])] actual = payoffs[1] expected = [1, 2, 3, 4] assert np.setxor1d(actual, expected).size == 0 actual = payoffs[3] expected = [5, 6, 7, 8] assert np.setxor1d(actual, expected).size == 0 payoffs = prof_map[utils.hash_array([0, 2, 1, 2])] actual = payoffs[1] expected = [14, 15, 16, 17, 18] assert np.setxor1d(actual, expected).size == 3 actual = payoffs[2] expected = [9, 10] assert np.setxor1d(actual, expected).size == 0 actual = payoffs[3] expected = [11, 12, 13] assert np.setxor1d(actual, expected).size == 1
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 update(self): """Schedules as many profiles as possible""" changed = False count_left = self._max_profiles all_profiles = self._scheduler.get_info(True).scheduling_requirements for prof in all_profiles or (): if prof.id in self._sid_payoffs: # If prof.id is not in self._sid_payoffs, then we haven't # scheduled it, so we don't care sid, count_ptr, payoffs = self._sid_payoffs[prof.id] if prof.current_count < prof.requirement: count_left -= 1 elif prof.current_count > count_ptr[0]: count_ptr[0] = prof.current_count np.copyto(payoffs, self._serial.from_payoff_symgrp( prof.get_info().symmetry_groups)) changed = True # Loop over necessary that we can schedule while count_left > 0 and self._queue: numobs, profiles = self._queue[0] profile = next(profiles) if profile is _END: # Reached end of list self._queue.popleft() else: # Process profile phash = utils.hash_array(profile) if phash in self._phash_payoffs: sid, [count], _ = self._phash_payoffs[phash] self._explored_sids.add(sid) if numobs > count: sprof = self._scheduler.profile( id=sid, assignment=self._serial.to_prof_string(profile)) sprof.update_count(numobs) count_left -= 1 changed = True else: string = self._serial.to_prof_string(profile) self._log.log(1, 'Scheduling profile: %s', string) sid = self._scheduler.add_profile(string, numobs).id self._explored_sids.add(sid) tup = (sid, [0], np.empty(self._serial.num_role_strats)) self._phash_payoffs[phash] = tup self._sid_payoffs[sid] = tup count_left -= 1 changed = True return changed
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 get_payoffs(self, profile): """Returns an array of profile payoffs if default is not None and game doesn't have profile data, then an array populated by default is returned.""" profile = np.asarray(profile, int) assert self.verify_profile(profile) hashed = utils.hash_array(profile) if hashed not in self._profile_id_map: pay = np.zeros(self.num_role_strats) pay[profile > 0] = np.nan return pay else: return self._profile_id_map[hashed]
async def _get_game(self, profs): """Get a game from the profiles to sample""" futures = [] for prof in profs: hprof = utils.hash_array(prof) future = self._profiles.get(hprof, None) if future is None: future = asyncio.ensure_future( self._sched.sample_payoffs(prof)) self._profiles[hprof] = future futures.append(future) lpays = await asyncio.gather(*futures) pays = np.stack(lpays) return paygame.game_replace(self, profs, pays)
def __init__(self, game, serializer, scheduler, max_profiles, log, profiles=()): self._game = game self._serial = serializer self._scheduler = scheduler self._max_profiles = max_profiles self._log = log self._queue = collections.deque() # Profile hashes: utils.hash_array, Simulator ids: profile.id self._phash_payoffs = {} self._sid_payoffs = {} self._explored_sids = set() for jprof in profiles: phash = utils.hash_array(serializer.from_prof_symgrp( jprof['symmetry_groups'])) sid = jprof['id'] payoff = serializer.from_payoff_symgrp(jprof['symmetry_groups']) data = (sid, [jprof['observations_count']], payoff) self._phash_payoffs[phash] = data self._sid_payoffs[sid] = data
async def sample_payoffs(self, profile): utils.check(self._is_open, "must enter scheduler") hprof = utils.hash_array(profile) counter, queue = self._extra_profs.get(hprof, (None, None)) if counter is not None: # Already scheduling some profiles if next(counter) >= self._count: self._extra_profs.pop(hprof) pay = await queue.get() logging.debug("read payoff for profile: %s", self.profile_to_repr(profile)) return pay else: # Need to schedule new profiles direc = os.path.join(self._prof_dir.name, str(self._num)) self._num += 1 queue = asyncio.Queue() if self._count > 1: self._extra_profs[hprof] = (itertools.count(2), queue) os.makedirs(direc) self._base["assignment"] = self._game.profile_to_assignment( profile) with open(os.path.join(direc, "simulation_spec.json"), "w") as fil: json.dump(self._base, fil) logging.debug( "scheduled %d profile%s: %s", self._count, "" if self._count == 1 else "s", self.profile_to_repr(profile), ) # Limit simultaneous processes async with self._procs: proc = await asyncio.create_subprocess_exec( os.path.join("script", "batch"), direc, str(self._count), cwd=self._sim_root, stderr=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.DEVNULL, ) _, err = await proc.communicate() utils.check( proc.returncode == 0, "process failed with returncode {:d} and stderr {}", proc.returncode, err, ) obs_files = (f for f in os.listdir(direc) if "observation" in f and f.endswith(".json")) for _ in range(self._count): obs_file = next(obs_files, None) utils.check( obs_file is not None, "simulation didn't write enough observation files", ) with open(os.path.join(direc, obs_file)) as fil: pay = self._game.payoff_from_json(json.load(fil)) pay.setflags(write=False) queue.put_nowait(pay) obs_file = next(obs_files, None) utils.check(obs_file is None, "simulation wrote too many observation files") shutil.rmtree(direc) pay = queue.get_nowait() logging.debug("read payoff for profile: %s", self.profile_to_repr(profile)) return pay
def __contains__(self, profile): """Returns true if all data for that profile exists""" # TODO This may be slow. Potentially we should just keep a set of all # the ones with complete data... return (utils.hash_array(np.asarray(profile, int)) in self._complete_profiles)
def test_hash_array(): arrayset = {utils.hash_array([3, 4, 5]), utils.hash_array([6, 7])} assert utils.hash_array([3, 4, 5]) in arrayset assert utils.hash_array([6, 7]) in arrayset assert utils.hash_array([3, 4, 6]) not in arrayset