Source code for gameanalysis.agggen

import numpy as np

from gameanalysis import aggfn
from gameanalysis import rsgame
from gameanalysis import utils


[docs]def random_poly_dist(coef_dist, degree): """Generate a function table distribution from a polynomial Table distributions take a shape and return a random matrix of that shape, where the first axis is the number of functions, and the other axes are the number of players from each role.""" degrees = np.arange(degree + 1) def table_func(shape): funcs, *players = shape table = np.ones(shape, float) for d, play in enumerate(players): polys = coef_dist(np.broadcast_to(degrees, (funcs, degree + 1))) values = polys.dot(np.arange(play) ** degrees[:, None]) values.shape += (1,) * (len(players) - 1) table *= np.rollaxis(values, 1, d + 2) return table return table_func
[docs]def random_aggfn(num_players, num_strategies, num_functions, input_dist=lambda s: utils.random_con_bitmask(.2, s), weight_dist=lambda s: np.random.normal(0, 1, s) * utils.random_con_bitmask(.2, s), func_dist=lambda s: np.random.normal(0, 1, s), by_role=False): """Generate a random AgfnGame Parameters ---------- num_players : int or ndarray num_strategies : int or ndarray num_functions : int input_dist : f(shape) -> ndarray, bool, optional Function that takes a shape and redurns a boolean ndarray with the same shape representing the function inputs. weight_dist : f(shape) -> ndarray, float, optional Function that takes a shape and returns a float array with the same shape representing the action weights. func_dist : f(shape) ndarray, float, optional Function that takes a shape and returns a float array with the same shape representing the function table. To create polynomial functions use `random_poly_dist`. by_role : bool, optional Generate a role form AgfnGame. A role form game uses functions of the number of activations for each role, instead of just the total number of activations.""" base = rsgame.basegame(num_players, num_strategies) weights = weight_dist((num_functions, base.num_role_strats)) inputs = input_dist((base.num_role_strats, num_functions)) shape = ((num_functions,) + tuple(base.num_players + 1) if by_role else (num_functions, base.num_all_players + 1)) func_table = func_dist(shape) return aggfn.aggfn_copy(base, weights, inputs, func_table)
[docs]def congestion(num_players, num_facilities, num_required, degree=2, coef_dist=lambda d: -np.random.exponential(10. ** (1 - d))): """Generate a congestion game Parameters ---------- num_players : int num_facilities : int num_required : int degree : int, optional Degree of payoff polynomials coef_dist : f(int) -> float, optional Numpy compatible function for generating random coefficients conditioned on degree. Leading degree must be negative to generate a true congestion game. """ function_inputs = utils.acomb(num_facilities, num_required) table_dist = random_poly_dist(coef_dist, degree) functions = table_dist((num_facilities, num_players + 1)) return aggfn.aggfn(num_players, function_inputs.shape[0], function_inputs.T, function_inputs, functions)
[docs]def local_effect(num_players, num_strategies, edge_prob=.2, self_dist=random_poly_dist( lambda d: -np.random.exponential(10. ** (1 - d)), 1), other_dist=random_poly_dist( lambda d: np.random.normal(0, 10. ** (-d)), 2)): """Generate a local effect game Parameters ---------- num_players : int num_strategies : int edge_prob : float, optional self_dist : f(shape) -> ndarray, float, optional Table distribution for self functions, e.g. payoff effect for being on the same node. other_dist : f(shape) -> ndarray, float, optional Table distribution for functions affecting other strategies. """ local_effect_graph = np.random.rand( num_strategies, num_strategies) < edge_prob np.fill_diagonal(local_effect_graph, False) num_functions = local_effect_graph.sum() + num_strategies action_weights = np.eye(num_functions, num_strategies, dtype=float) function_inputs = np.eye(num_strategies, num_functions, dtype=bool) in_act, out_act = local_effect_graph.nonzero() func_inds = np.arange(num_strategies, num_functions) function_inputs[in_act, func_inds] = True action_weights[func_inds, out_act] = 1 function_table = np.empty((num_functions, num_players + 1), float) function_table[:num_strategies] = self_dist( (num_strategies, num_players + 1)) function_table[num_strategies:] = self_dist( (num_functions - num_strategies, num_players + 1)) return aggfn.aggfn(num_players, num_strategies, action_weights, function_inputs, function_table)
[docs]def serializer(game): """Generate a random serializer from an AgfnGame""" role_names = ['all'] if game.is_symmetric( ) else utils.prefix_strings('r', game.num_roles) strat_names = [utils.prefix_strings('s', s) for s in game.num_strategies] function_names = utils.prefix_strings('f', game.num_functions) return aggfn.aggfnserializer(role_names, strat_names, function_names)
[docs]def function_serializer(game): """Generate a random serializer from an AgfnGame Generates strategy names that describe the fucntions they input to. Useful for congestion games""" role_names = ['all'] if game.is_symmetric( ) else utils.prefix_strings('r', game.num_roles) function_names = utils.prefix_strings('f', game.num_functions) strat_names = [['_'.join(f for f, i in zip(function_names, inp) if i) for inp in role_inps] for role_inps in game.role_split(game._function_inputs)] return aggfn.aggfnserializer(role_names, strat_names, function_names)