def graph(self, equilibria, graph_options): setupGraph(graph_options) graph_options[GraphOptions.LEGEND_LABELS_KEY] = lambda i: equilibria[i] x = self.independent_parameters[0] y = self.independent_parameters[1] x_values = list(x) y_values = list(y) nx = len(x_values) ny = len(y_values) n_equilibria = len(equilibria) data = numpy.zeros((nx, ny, n_equilibria)) for i in range(nx): for j in range(ny): data[i, j, :] = self.data[i][j] if graph_options['type'] == '3d': plot_3d_data_set( data, x.key, x_values, y.key, y_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f), %s:(%.3f ... %.3f) " % (x.key, x.lb, x.ub, y.key, y.lb, y.ub), n_equilibria, graph_options) elif graph_options['type'] == 'contour': plot_contour_data_set( data, x.key, x_values, y.key, y_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f), %s:(%.3f ... %.3f) " % (x.key, x.lb, x.ub, y.key, y.lb, y.ub), n_equilibria, graph_options) else: print(graph_type)
def graph(self, equilibria, graph_options): setupGraph(graph_options) graph_options[GraphOptions.LEGEND_LABELS_KEY] = lambda i: equilibria[i] x = self.independent_parameters[0] x_values = list(x) n_equilibria = len(equilibria) data = numpy.array(self.data) plot_single_data_set(data, x.key, x_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f)" % (x.key, x.lb, x.ub), n_equilibria, graph_options)
def graph(self, equilibria, graph_options): setupGraph(graph_options) graph_options[GraphOptions.LEGEND_LABELS_KEY] = lambda i: equilibria[i] x = self.independent_parameters[0] y = self.independent_parameters[1] x_values = list(x) y_values = list(y) nx = len(x_values) ny = len(y_values) n_equilibria = len(equilibria) data = numpy.zeros((nx, ny, n_equilibria)) for i in range(nx): for j in range(ny): data[i, j, :] = self.data[i][j] # Don’t include the unclassified equilibrium if it’s proportion is always zero (i.e. unclassified takes the value False) unclassified = False for i in range(len(data)): for j in range(len(data[i])): if data[i][j][-1] == 0.0: unclassified = unclassified or False else: unclassified = unclassified or True if not unclassified: n_equilibria = n_equilibria - 1 final_data = numpy.zeros((nx, ny, n_equilibria)) for i in range(nx): for j in range(ny): final_data[i, j, :] = data[i][j][:-1] else: final_data = data if graph_options['type'] == '3d': plot_3d_data_set( final_data, x.key, x_values, y.key, y_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f), %s:(%.3f ... %.3f) " % (x.key, x.lb, x.ub, y.key, y.lb, y.ub), n_equilibria, graph_options) elif graph_options['type'] == 'contour': plot_contour_data_set( final_data, x.key, x_values, y.key, y_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f), %s:(%.3f ... %.3f) " % (x.key, x.lb, x.ub, y.key, y.lb, y.ub), n_equilibria, graph_options) else: print(graph_type)
def graph(self, equilibria, graph_options): setupGraph(graph_options) graph_options[GraphOptions.LEGEND_LABELS_KEY] = lambda i: equilibria[i] x = self.independent_parameters[0] x_values = list(x) n_equilibria = len(equilibria) data = numpy.array(self.data) # Don’t include the unclassified equilibrium if it’s proportion is zero if not data[:, -1].all(): n_equilibria = n_equilibria - 1 data = data[:, :-1] # Plot the 2D data plot_single_data_set(data, x.key, x_values, "Equilibrium Proportion", "%s:(%.3f ... %.3f)" % (x.key, x.lb, x.ub), n_equilibria, graph_options)
def simulate(self, num_gens=DEFAULT_GENERATIONS, pop_size=100, start_state=None, graph=True, return_labeled=True, burn=0, class_end=False, frac_invasions=False, strategy_indx=0): """ Simulate a game in the presence of group selection for a specific number of generations optionally graphing the results @param num_gens: the number of iterations of the simulation. @type num_gens: int @param group_size: Fixed population in each group. @type group_size: int @param rate: Rate of group selection vs individual selection @type rate: float @param start_state: whether the starting state is to be predetermined @type start_state: list @param graph: the type of graph (false if no graph is wished) @type graph: dict, bool @param return_labeled: whether the distribution of classified equilibria that are returned should be labelled or simply listed with their keys inferred by their order @type return_labeled: bool @param class_end: if False the equilibria are classified at all generations and if True only the last generation is classified. @type class_end: bool @param frac_invasions: Whether the given simulation is to compute the fraction of invasions for a strategy. @type: bool @param strategy_indx: The index of the strategy whose fraction of invasions is to be computed @type strategy_indx: int @return: the frequency of time spent in each equilibria, defined by the game @rtype: numpy.ndarray or dict TO DO: Explain what 'burn' does """ game = self.game_cls(**self.game_kwargs) dyn = self.dynamics_cls(payoff_matrix=game.pm, player_frequencies=game.player_frequencies, pop_size=pop_size, **self.dynamics_kwargs) # Group Selection simulation for a given number of generations. results_total, payoffs_total = dyn.simulate(num_gens, start_state, frac_invasions, strategy_indx) # Classify the equilibria and plot the results params = Obj(**self.game_kwargs) frequencies = np.zeros(self.game_cls.num_equilibria() ) # one extra for the Unclassified key if dyn.stochastic: classifications = [] if class_end: # Only classify the final generation lastGenerationState = [ np.zeros(len(player[0])) for player in results_total ] for playerIdx, player in enumerate(results_total): for stratIdx, strat in enumerate(player[-1]): lastGenerationState[playerIdx][stratIdx] = strat lastGenerationState[playerIdx] /= lastGenerationState[ playerIdx].sum() equi = game.classify(params, lastGenerationState, game.equilibrium_tolerance) frequencies = np.zeros(self.game_cls.num_equilibria()) frequencies[equi] = 1 else: # Classify every generation for state in zip(*results_total): state = [x / x.sum() for x in state] equi = game.classify(params, state, game.equilibrium_tolerance) # note, if equi returns -1, then the -1 index gets the last entry in the array classifications.append(equi) frequencies[equi] += 1 else: last_generation_state = [results_total[-1][-1]] classification = game.classify(params, last_generation_state, game.equilibrium_tolerance) frequencies[classification] = 1 if graph: setupGraph(graph, game, dyn, burn, num_gens, results_total, payoffs_total) else: if return_labeled: return self._convert_equilibria_frequencies(frequencies) else: return frequencies, results_total, payoffs_total
def simulate_many(self, num_iterations=DEFAULT_ITERATIONS, num_gens=DEFAULT_GENERATIONS, pop_size=100, start_state=None, graph=False, histogram=False, return_labeled=True, burn=0, parallelize=True, class_end=True, frac_invasions=False, strategy_indx=0): """ A helper method to call the simulate methods num_iterations times simulating num_gens generations each time, and then averaging the frequency of the resulting equilibria. Method calls are parallelized and attempt to use all available cores on the machine. @param num_iterations: the number of times to iterate the simulation @type num_iterations: int @param num_gens: the number of generations to run each simulation with @type num_gens: int @param pop_size: total population size @type pop_size: int @param start_state: An optional list of distributions of strategies for each player @type start_state: list or None @param graph: the type of graph (false if no graph is wished) @type graph: dict, bool @param histogram: if True plots the histogram of final population sizes over iterations. Both graph and histogram can't be True at the same time. @type histogram: bool @param return_labeled: whether the distribution of classified equilibria that are returned should be labelled or simply listed with their keys inferred by their order @type return_labeled: bool @param parallelize: whether or not to parallelize the computation, defaults to true, but an override when varying the parameters, as seen in the L{VariedGame} class to achieve coarser parallelization @type parallelize: bool @param class_end: if False the equilibria are classified at all generations and if True only the last generation is classified. @type class_end: bool @param frac_invasions: Whether the given simulation is to compute the fraction of invasions for a strategy. @type: bool @param strategy_indx: The index of the strategy whose fraction of invasions is to be computed @type strategy_indx: int @return: the frequency of time spent in each equilibria, defined by the game @rtype: np.ndarray or dict """ # TODO move this graphing into graphSetup and link it to extra options game = self.game_cls(**self.game_kwargs) dyn = self.dynamics_cls(payoff_matrix=game.pm, player_frequencies=game.player_frequencies, pop_size=pop_size, **self.dynamics_kwargs) frequencies = np.zeros(self.game_cls.num_equilibria()) output = par_for(parallelize)( delayed(wrapper_simulate)(self, num_gens=num_gens, pop_size=pop_size, frac_invasions=frac_invasions, strategy_indx=strategy_indx, start_state=start_state, class_end=class_end) for iteration in range(num_iterations)) equilibria = [] strategies = [0] * num_iterations payoffs = [0] * num_iterations for idx, sim in enumerate(output): equilibria.append(sim[0]) strategies[idx] = sim[1] payoffs[idx] = sim[2] #TODO move these averages or only compile them if appropriate simulation type stratAvg = [ np.zeros(shape=(num_gens, dyn.pm.num_strats[playerIdx])) for playerIdx in range(dyn.pm.num_player_types) ] # Storing the final strategy populations per iteration strat_final = [ np.zeros(shape=(num_iterations, dyn.pm.num_strats[playerIdx])) for playerIdx in range(dyn.pm.num_player_types) ] for iteration in range(num_iterations): for player in range(dyn.pm.num_player_types): for gen in range(num_gens - burn): for strat in range(dyn.pm.num_strats[player]): stratAvg[player][gen][strat] += strategies[iteration][ player][gen][strat] if histogram and gen == num_gens - burn - 1: strat_final[player][iteration][ strat] += strategies[iteration][player][gen][ strat] for playerIdx, player in enumerate(stratAvg): for genIdx, gen in enumerate(player): if gen.sum() != 0: gen /= gen.sum() gen *= dyn.num_players[playerIdx] payoffsAvg = [ np.zeros(shape=(num_gens - 1, dyn.pm.num_strats[playerIdx])) for playerIdx in range(dyn.pm.num_player_types) ] for iteration in range(num_iterations): for player in range(dyn.pm.num_player_types): for gen in range(num_gens - 1 - burn): for strat in range(dyn.pm.num_strats[player]): payoffsAvg[player][gen][strat] += payoffs[iteration][ player][gen][strat] for playerIdx, player in enumerate(payoffsAvg): for genIdx, gen in enumerate(player): if gen.sum() != 0: gen /= gen.sum() gen *= dyn.num_players[playerIdx] if graph: assert histogram == False, ( "Can't plot graph and histogram at the same time") setupGraph(graph, game, dyn, burn, num_gens, stratAvg, payoffs[0]) elif histogram: setupHistogram(histogram, game, dyn, num_iterations, dyn.num_players, strat_final) for x in equilibria: frequencies += x frequencies /= frequencies.sum() if return_labeled: return self._convert_equilibria_frequencies(frequencies) else: return frequencies
def simulate(self, num_gens=DEFAULT_GENERATIONS, graph=True, burn=0, return_labeled=True, start_state=None, class_end=False): """ Simulate the game for the given number of generations with the specified dynamics class and optionally graph the results @param num_gens: the number of iterations of the simulation. @type num_gens: int @param graph: the type of graph (false if no graph is wished) @type graph: dict, bool @param return_labeled: whether the distribution of classified equilibria that are returned should be labelled or simply listed with their keys inferred by their order @type return_labeled: bool @param start_state: whether the starting state is to be predeterined @type start_state: list @param graph_payoffs: to graph the payoffs for each generation @type graph_payoffs: bool @param graph_payoff_line: to show the dominate strategy for each generation of the game underneath the graph @type graph_payoff_line: bool @return: the frequency of time spent in each equilibria, defined by the game @rtype: numpy.ndarray or dict """ game = self.game_cls(**self.game_kwargs) dyn = self.dynamics_cls(payoff_matrix=game.pm, player_frequencies=game.player_frequencies, **self.dynamics_kwargs) results, payoffs = dyn.simulate(num_gens=num_gens, debug_state=start_state) # results_obj = SingleSimulationOutcome(self.dynamics_cls, self.dynamics_kwargs, self.game_cls, self.game_kwargs, results) # TODO: serialize results to file params = Obj(**self.game_kwargs) frequencies = numpy.zeros(self.game_cls.num_equilibria() ) # one extra for the Unclassified key for playerIdx, player in enumerate(payoffs): payoffs[playerIdx] = numpy.delete(player, (0), axis=0) if burn: for index, array in enumerate(results): results[index] = array[burn:] if dyn.stochastic: classifications = [] if class_end: # Only classify the final generation lastGenerationState = [ numpy.zeros(len(player[0])) for player in results ] for playerIdx, player in enumerate(results): for stratIdx, strat in enumerate(player[-1]): lastGenerationState[playerIdx][stratIdx] = strat lastGenerationState[playerIdx] /= lastGenerationState[ playerIdx].sum() equi = game.classify(params, lastGenerationState, game.equilibrium_tolerance) frequencies = numpy.zeros(self.game_cls.num_equilibria()) frequencies[equi] = 1 else: # Classify every generation for state in zip(*results): state = [x / x.sum() for x in state] equi = game.classify(params, state, game.equilibrium_tolerance) # note, if equi returns -1, then the -1 index gets the last entry in the array classifications.append(equi) frequencies[equi] += 1 else: last_generation_state = results[-1] classification = game.classify(params, last_generation_state, game.equilibrium_tolerance) frequencies[classification] = 1 if graph: setupGraph(graph, game, dyn, burn, num_gens, results, payoffs) else: if return_labeled: return self._convert_equilibria_frequencies(frequencies) else: return frequencies, results, payoffs
def simulate_many(self, num_iterations=DEFAULT_ITERATIONS, num_gens=DEFAULT_GENERATIONS, return_labeled=True, burn=0, parallelize=True, graph=False, start_state=None, class_end=False): """ A helper method to call the simulate methods num_iterations times simulating num_gens generations each time, and then averaging the frequency of the resulting equilibria. Method calls are parallelized and attempt to use all available cores on the machine. @param num_iterations: the number of times to iterate the simulation @type num_iterations: int @param num_gens: the number of generations to run each simulation witu @type num_gens: int @param return_labeled: whether the distribution of classified equilibria that are returned should be labelled or simply listed with their keys inferred by their order @type return_labeled: bool @param parallelize: whether or not to parallelize the computation, defaults to true, but an override when varying the parameters, as seen in the L{VariedGame} class to achieve coarser parallelization @type parallelize: bool @return: the frequency of time spent in each equilibria, defined by the game @rtype: numpy.ndarray or dict """ # TODO move this graphing into graphSetup and link it to extra options game = self.game_cls(**self.game_kwargs) dyn = self.dynamics_cls(payoff_matrix=game.pm, player_frequencies=game.player_frequencies, **self.dynamics_kwargs) frequencies = numpy.zeros(self.game_cls.num_equilibria()) output = par_for(parallelize)( delayed(wrapper_simulate)(self, num_gens=num_gens, burn=burn, start_state=start_state, class_end=class_end) for iteration in range(num_iterations)) equilibria = [] strategies = [0] * num_iterations payoffs = [0] * num_iterations for idx, sim in enumerate(output): equilibria.append(sim[0]) strategies[idx] = sim[1] payoffs[idx] = sim[2] #TODO move these averages or only compile them if appropriate simulation type stratAvg = [ numpy.zeros(shape=(num_gens, dyn.pm.num_strats[playerIdx])) for playerIdx in range(dyn.pm.num_player_types) ] for iteration in range(num_iterations): for player in range(dyn.pm.num_player_types): for gen in range(num_gens - burn): for strat in range(dyn.pm.num_strats[player]): stratAvg[player][gen][strat] += strategies[iteration][ player][gen][strat] for playerIdx, player in enumerate(stratAvg): for genIdx, gen in enumerate(player): if gen.sum() != 0: gen /= gen.sum() gen *= dyn.num_players payoffsAvg = [ numpy.zeros(shape=(num_gens - 1, dyn.pm.num_strats[playerIdx])) for playerIdx in range(dyn.pm.num_player_types) ] for iteration in range(num_iterations): for player in range(dyn.pm.num_player_types): for gen in range(num_gens - 1 - burn): for strat in range(dyn.pm.num_strats[player]): payoffsAvg[player][gen][strat] += payoffs[iteration][ player][gen][strat] for playerIdx, player in enumerate(payoffsAvg): for genIdx, gen in enumerate(player): if gen.sum() != 0: gen /= gen.sum() gen *= dyn.num_players if graph: setupGraph(graph, game, dyn, burn, num_gens, stratAvg, payoffs[0]) for x in equilibria: frequencies += x frequencies /= frequencies.sum() if return_labeled: return self._convert_equilibria_frequencies(frequencies) else: return frequencies