Source code for gameanalysis.gambit

"""Functions for reading and writing gambit nfg files"""
import io
import itertools
import re
import warnings

import numpy as np

from gameanalysis import matgame
from gameanalysis import utils


[docs]def load(filelike): """Load a gambit game from a file""" return loads(filelike.read())
[docs]def loads(string): """Load a gambit game from a string""" match = _RE_NFG.match(string) if match: # payoff form return _read_payoffs(match) match = _RE_NFGO.match(string) if match: # outcome form return _read_outcomes(match) raise ValueError("failed to parse gambit format")
[docs]def dump(game, filelike): """Dump game to gambit file""" utils.check(game.is_complete(), "gambit games must be complete") game = matgame.matgame_copy(game) filelike.write('NFG 1 R "gameanalysis game"\n{ ') for role in game.role_names: filelike.write('"') filelike.write(role.replace('"', '\\"')) filelike.write('" ') filelike.write("}\n{\n") for strats in game.strat_names: filelike.write(" { ") for strat in strats: filelike.write('"') filelike.write(strat.replace('"', '\\"')) filelike.write('" ') filelike.write("}\n") filelike.write("}\n\n{\n") perm = tuple(range(game.num_roles - 1, -1, -1)) + (game.num_roles,) pays = np.transpose(game.payoff_matrix(), perm) for outcome in pays.reshape((-1, game.num_roles)): filelike.write(' { "" ') filelike.write(", ".join(map(str, outcome))) filelike.write(" }\n") filelike.write("}\n1") for i in range(2, game.num_profiles + 1): filelike.write(" ") filelike.write(str(i))
[docs]def dumps(game): """Dump game as gambit string""" filelike = io.StringIO() dump(game, filelike) return filelike.getvalue()
def _read_payoffs(match): """Read gambit payoff format""" role_names = _string_list(match.group("roles")) num_strats = tuple(map(int, match.group("strats")[1:-1].split())) num_roles = len(num_strats) utils.check( len(role_names) == num_roles, "player names didn't match number of strategies" ) strats = utils.prefix_strings("s", sum(num_strats)) strat_names = [list(itertools.islice(strats, n)) for n in num_strats] payoffs = list(map(float, match.group("payoffs").split())) matrix = np.empty(num_strats + (num_roles,)) utils.check( len(payoffs) == matrix.size, "incorrect number of payoffs for strategies" ) inds = tuple(range(num_roles - 1, -1, -1)) + (num_roles,) np.transpose(matrix, inds).flat = payoffs return _normalize(role_names, strat_names, matrix) def _read_outcomes(match): """Read gambit outcome format""" role_names = _string_list(match.group("roles")) num_roles = len(role_names) strat_names = [ _string_list(m.group()) for m in _RE_STRATS.finditer(match.group("strats")[1:-1]) ] utils.check( len(strat_names) == num_roles, "player names and strategies differed in length" ) num_strats = np.fromiter(map(len, strat_names), int, num_roles) outcomes = [np.zeros(num_roles)] for omatch in _RE_OUTCOME.finditer(match.group("outcomes")[1:-1]): outcome = omatch.group()[1:-1] pays = outcome[next(_RE_STR.finditer(outcome)).end() :].split() utils.check(len(pays) == num_roles, "outcome has wrong number of payoffs") outcomes.append( np.fromiter( # pragma: no branch (float(s.rstrip(",")) for s in pays), float, num_roles ) ) outcomes = np.stack(outcomes) inds = match.group("inds").split() utils.check(len(inds) == num_strats.prod(), "wrong number of outcomes") inds = np.fromiter(map(int, inds), int, len(inds)) matrix = np.empty(tuple(num_strats) + (num_roles,)) tinds = tuple(range(num_roles - 1, -1, -1)) + (num_roles,) np.transpose(matrix, tinds).flat = outcomes[inds] return _normalize(role_names, strat_names, matrix) def _dedup(lst): """Given a list of strings, modify inplace to remove duplicates""" dups = {} for i, string in enumerate(lst): dups.setdefault(string, []).append(i) for prefix, inds in dups.items(): if len(inds) > 1: for i, new in zip(inds, utils.prefix_strings(prefix, len(inds))): lst[i] = new def _normalize(role_names, strat_names, matrix): """Take gambit data and make it comply with gameanalysis standards""" num_roles = len(role_names) # Sort role names if not utils.is_sorted(role_names, strict=True): warnings.warn( "gambit player names aren't strictly sorted; modifying to comply " "with gameanalysis standards" ) _dedup(role_names) if not utils.is_sorted(role_names): order = sorted(range(num_roles), key=lambda i: role_names[i]) role_names = [role_names[i] for i in order] strat_names = [strat_names[i] for i in order] shape = tuple(order) + (num_roles,) matrix = np.transpose(matrix, shape)[..., order] # Sort strat names if not all(utils.is_sorted(strats, strict=True) for strats in strat_names): warnings.warn( "gambit strategy names aren't strictly sorted; modifying to " "comply with gameanalysis standards" ) new_strats = [] for role, strats in enumerate(strat_names): if not utils.is_sorted(strats, strict=True): _dedup(strats) if not utils.is_sorted(strats): strats, order = zip(*sorted((s, i) for i, s in enumerate(strats))) shuffle = [slice(None)] * (num_roles + 1) shuffle[role] = order matrix = matrix[tuple(shuffle)] new_strats.append(strats) strat_names = new_strats return matgame.matgame_names(role_names, strat_names, matrix) def _string_list(lst): """Parse role names out of 'list'""" return [m.group()[1:-1].replace(r"\"", '"') for m in _RE_STR.finditer(lst[1:-1])] def _list(element): """Regular expression for a gambit list""" return r"{(\s+" + element + r")+\s+}" _STR = r'"(.|\n)*?(?<!\\)"' _FLOAT = r"[-+]?(\d*\.?\d+|\d+\.\d*)([eE][-+]?\d+)?" _OUTCOME = r"{{\s+{}(\s+{},?)+\s+}}".format(_STR, _FLOAT) _RE_STR = re.compile(_STR) _RE_STRATS = re.compile(_list(_STR)) _RE_OUTCOME = re.compile(_OUTCOME) _RE_NFG = re.compile( ( r"NFG\s+1\s+R\s+{}\s+(?P<roles>{})\s+(?P<strats>{})(\s+{})?" r"(?P<payoffs>(\s+{})+)\s*$" ).format(_STR, _list(_STR), _list(r"\d+"), _STR, _FLOAT) ) _RE_NFGO = re.compile( ( r"NFG\s+1\s+R\s+{}\s+(?P<roles>{})\s+(?P<strats>{})(\s+{})?\s+" r"(?P<outcomes>{})(?P<inds>(\s+\d+)+)\s*$" ).format(_STR, _list(_STR), _list(_list(_STR)), _STR, _list(_OUTCOME)) )