From a23feb6519f4ef39aa92052460ad63086bdaa775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Foramitti?= <57440945+JoelForamitti@users.noreply.github.com> Date: Tue, 27 Oct 2020 15:52:40 +0100 Subject: [PATCH] Update 0.0.3 (grids) --- README.md | 12 +- agentpy/__init__.py | 13 +- agentpy/analysis.py | 125 +- agentpy/experiment.py | 155 +- agentpy/framework.py | 318 +- agentpy/interactive.py | 103 - agentpy/output.py | 290 +- agentpy/sample.py | 91 + agentpy/tools.py | 69 +- docs/Agentpy_Button_Network.ipynb | 157 + docs/Agentpy_Forest_Fire.ipynb | 21105 +++++++++++ docs/Agentpy_Virus_Spread.ipynb | 39228 ++++++++++++++++++++ docs/Agentpy_Wealth_Transfer.ipynb | 259 + docs/T01_Wealth_Transfer.ipynb | 251 - docs/T02_Virus_Spread.ipynb | 51387 --------------------------- docs/conf.py | 10 +- docs/index.rst | 50 +- docs/installation.rst | 6 +- docs/models.rst | 12 + docs/overview.rst | 20 + docs/reference.rst | 85 +- docs/tutorials.rst | 10 - setup.py | 10 +- 23 files changed, 61649 insertions(+), 52117 deletions(-) delete mode 100644 agentpy/interactive.py create mode 100644 agentpy/sample.py create mode 100644 docs/Agentpy_Button_Network.ipynb create mode 100644 docs/Agentpy_Forest_Fire.ipynb create mode 100644 docs/Agentpy_Virus_Spread.ipynb create mode 100644 docs/Agentpy_Wealth_Transfer.ipynb delete mode 100644 docs/T01_Wealth_Transfer.ipynb delete mode 100644 docs/T02_Virus_Spread.ipynb create mode 100644 docs/models.rst create mode 100644 docs/overview.rst delete mode 100644 docs/tutorials.rst diff --git a/README.md b/README.md index 86cbf7a..05a925c 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Agentpy - Agent-based modeling in Python -Agentpy is a package for the development and analysis of agent-based models in Python. The packages is still in a very early stage of development. If you need help using Agentpy or want to contribute, feel free to write me via joel.foramitti@uab.cat. +Agentpy is a package for the development and analysis of agent-based models in Python. The project is still in an early stage of development. If you need help or want to contribute, feel free to write me via joel.foramitti@uab.cat. **Main features:** +- Design of agent-based models with complex procedures. - Creation of custom agent types, environments, and networks. -- Design of models with complex procedures and multiple environments. -- Standard operators can be used on whole groups of agents simultaneously. +- Container classes for operations on groups of agents and environments. - Experiments with repeated iterations, large parameter samples, and distinct scenarios. -- Output data that can be saved, loaded, and re-arranged for further analysis. -- Tools for sensitivity analysis, interactive output, animations, and visualization. +- Output data that can be saved, loaded, and transformed for further analysis. +- Tools for sensitivity analysis, interactive output, animations, and plots. **Documentation:** https://agentpy.readthedocs.io -**Tutorials:** https://agentpy.readthedocs.io/en/latest/tutorials.html +**Model library:** https://agentpy.readthedocs.io/en/latest/models.html **Requirements:** Python 3, NumPy, scipy, matplotlib, networkx, pandas, SALib, and ipywidgets. diff --git a/agentpy/__init__.py b/agentpy/__init__.py index fc58fc2..619c60c 100644 --- a/agentpy/__init__.py +++ b/agentpy/__init__.py @@ -7,15 +7,16 @@ """ -from .framework import model, environment, network, agent, agent_list, env_dict -from .output import data_dict, load, save +from .framework import model, environment, network, grid, agent, agent_list, env_dict + +from .sample import sample, sample_discrete, sample_saltelli +from .experiment import experiment -from .experiment import experiment, sample +from .output import data_dict, load, save -from .interactive import interactive, animate -from .analysis import sensitivity, phaseplot +from .analysis import sensitivity, phaseplot, gridplot, interactive, animate -from .tools import attr_dict, attr_list +from .tools import attr_dict, obj_list # Aliases exp = experiment diff --git a/agentpy/analysis.py b/agentpy/analysis.py index fdec65a..36cd290 100644 --- a/agentpy/analysis.py +++ b/agentpy/analysis.py @@ -10,11 +10,13 @@ import numpy as np import pandas as pd import matplotlib.pyplot as plt +import matplotlib.colors as colors from scipy.interpolate import griddata from SALib.analyze import sobol from .tools import make_list +from .framework import agent_list def sensitivity( output, param_ranges, measures = None, **kwargs ): @@ -59,7 +61,7 @@ def sensitivity( output, param_ranges, measures = None, **kwargs ): keys = ['S1','ST'] s2 = {k:v for k,v in SI.items() if k in keys} df = pd.DataFrame(s2) - df['parameter'] = output.ps_keys() + df['parameter'] = output.parameters.varied.keys() df['measure'] = measure df = df.set_index(['measure','parameter']) dfs_SI.append(df) @@ -67,7 +69,7 @@ def sensitivity( output, param_ranges, measures = None, **kwargs ): keys1 = ['S1_conf','ST_conf'] s3 = {k:v for k,v in SI.items() if k in keys1} df = pd.DataFrame(s3) - df['parameter'] = output.ps_keys() + df['parameter'] = output.parameters.varied.keys() df['measure'] = measure df = df.set_index(['measure','parameter']) df.columns = ['S1','ST'] @@ -81,6 +83,125 @@ def sensitivity( output, param_ranges, measures = None, **kwargs ): return output['sensitivity'] + + +import ipywidgets as widgets +import matplotlib.pyplot as plt + +from .tools import make_list +from matplotlib import animation + +def animate(model, parameters, fig, axs, plot, skip_t0 = False, **kwargs): + + """ Returns an animation of the model simulation """ + + m = model(parameters) + m._stop = False + m.setup() + m.update() + + step0 = True + step00 = not skip_t0 + + def frames(): + + nonlocal m, step0, step00 + + while not m.stop_if() and not m._stop: + + if step0: step0 = False + elif step00: step00 = False + else: + m.t += 1 + m.step() + m.update() + m.create_output() + yield m.t + + def update(t, m, axs): + + for ax in make_list(axs): + ax.clear() + + if m.t == 0 and skip_t0: pass + else: plot( m, axs ) + + ani = animation.FuncAnimation(fig, update, frames=frames, fargs=(m, axs), **kwargs) + plt.close() # Don't display static plot + + return ani + + +def interactive(model,param_ranges,output_function,*args,**kwargs): + + """ + + Returns 'output_function' as an interactive ipywidget + More infos at https://ipywidgets.readthedocs.io/ + + """ + + def make_param(param_updates): + + parameters = dict(param_ranges) # Copy + i = 0 + + for key, value in parameters.items(): + + if isinstance(v,tuple): + parameters[key] = param_updates[i] + i += 1 + + def var_run(**param_updates): + + parameters = dict(param_ranges) + parameters.update(param_updates)#make_param(param_updates) + temp_model = model(parameters) + temp_model.run(display=False) + + output_function(temp_model.output,*args,**kwargs) + + # Create widget dict + widget_dict = {} + param_ranges_tuples = { k:v for k,v in param_ranges.items() if isinstance(v,tuple) } + for var_key, var_range in param_ranges_tuples.items(): + + widget_dict[var_key] = widgets.FloatSlider( + description=var_key, + value = (var_range[1] - var_range[0]) / 2 , + min = var_range[0], + max = var_range[1], + step = (var_range[1] - var_range[0]) / 10 , + style = dict(description_width='initial') , + layout = {'width': '300px'} ) + + out = widgets.interactive_output(var_run, widget_dict) + + return widgets.HBox([ widgets.VBox(list(widget_dict.values())), out ]) + + + + + +def gridplot(model,ax,grid_key,attr_key,color_dict): + + def apply_colors(grid,color_assignment,final_type,attr_key): + if not isinstance(grid[0],final_type): + return [apply_colors(subgrid,color_assignment,final_type,attr_key) for subgrid in grid] + else: + return [colors.to_rgb(color_assignment(i,attr_key)) for i in grid] + + def color_assignment(a_list,attr_key): + + if len(a_list) == 0: return color_dict['empty'] + else: return color_dict[a_list[0][attr_key]] + + grid = model.envs[grid_key].grid + color_grid = apply_colors(grid,color_assignment,agent_list,attr_key) + im = ax.imshow(color_grid) + + + def phaseplot(data,x,y,z,n,fill=True,**kwargs): """ Creates a contour plot displaying the interpolated diff --git a/agentpy/experiment.py b/agentpy/experiment.py index 5cf8e16..03e3119 100644 --- a/agentpy/experiment.py +++ b/agentpy/experiment.py @@ -8,7 +8,7 @@ """ -import numpy as np + import pandas as pd import networkx as nx import warnings @@ -18,10 +18,6 @@ from .tools import attr_dict, make_list, AgentpyError from .output import data_dict -import itertools -from SALib.sample import saltelli as SALibSaltelli - - class experiment(): """ Experiment for an agent-based model. @@ -29,11 +25,11 @@ class experiment(): Arguments: model(class): The model that the experiment should use. - parameters(dict or list): Parameters or parameter sample. + parameters(dict or list of dict): Parameter dictionary or parameter sample (list of parameter dictionaries). name(str,optional): Name of the experiment. Takes model name at default. scenarios(str or list,optional): Scenarios that should be tested, if any. iterations(int,optional): How often to repeat the experiment (default 1). - output_vars(bool,optional): Whether to record dynamic variables. Default False. + record(bool,optional): Whether to record dynamic variables (default False). Attributes: output(data_dict): Recorded experiment data @@ -46,7 +42,7 @@ def __init__( self, name = None, scenarios = None, iterations = 1, - output_vars = False + record = False ): # Experiment objects @@ -60,9 +56,10 @@ def __init__( self, else: self.name = 'experiment' self.scenarios = scenarios self.iterations = iterations - self.output_vars = output_vars + self.record = record # Log + self.output.log = {} self.output.log['name'] = self.name self.output.log['time_stamp'] = str(datetime.now()) self.output.log['iterations'] = iterations @@ -80,39 +77,72 @@ def run(self, display=True): Returns: data_dict: Recorded experiment data, also stored in `experiment.output`. """ + parameter_sample = make_list(self.parameters,keep_none=True) scenarios = make_list(self.scenarios,keep_none=True) runs = parameter_sample * self.iterations self.output.log['n_runs'] = n_runs = len(runs) - self.output.parameter_sample = pd.DataFrame(parameter_sample) - self.output.parameter_sample.index.rename('sample_id', inplace=True) - + # Document parameters (seperately for fixed & variable) + df = pd.DataFrame(parameter_sample) + df.index.rename('sample_id', inplace=True) + fixed_pars = {} + + for col in df.columns: + s = df[col] + if len(s.unique()) == 1: + fixed_pars[s.name] = df[col][0] + df.drop(col, inplace=True, axis=1) + + if fixed_pars and df.empty: + self.output['parameters'] = fixed_pars + elif not fixed_pars and not df.empty: + self.output['parameters'] = df + else: + self.output['parameters'] = data_dict({ + 'fixed': fixed_pars, + 'varied': df + }) + + ## START EXPERIMENT ## + if display: print( f"Scheduled runs: {n_runs}" ) t0 = datetime.now() # Time-Stamp Start - temp_output = {} + combined_output = {} for i, parameters in enumerate(runs): for scenario in scenarios: # Run model for current parameters & scenario - output = self.model(parameters, run_id = i, scenario = scenario).run(display=False) + single_output = self.model(parameters, run_id = i, scenario = scenario).run(display = False) # Append results to experiment output - for key,value in output.items(): + for key,value in single_output.items(): - # Skip vars? - if not self.output_vars and 'vars' in key: + # Skip parameters & log + if key in ['parameters','log']: continue - # Initiate new key - if key not in temp_output: - temp_output[key] = [] + # Handle variables + if self.record and key == 'variables' and isinstance(value,data_dict): + + if key not in combined_output: + combined_output[key] = {} + + for var_key,value in single_output[key].items(): + + if var_key not in combined_output[key]: + combined_output[key][var_key] = [] + + combined_output[key][var_key].append(value) - # Append output - temp_output[key].append(value) + # Handle other output types + else: + if key not in combined_output: + combined_output[key] = [] + combined_output[key].append(value) if display: td = ( datetime.now() - t0 ).total_seconds() @@ -120,9 +150,13 @@ def run(self, display=True): print( f"\rCompleted: {i+1}, estimated time remaining: {te}" , end='' ) # Combine dataframes - for key,values in temp_output.items(): + for key,values in combined_output.items(): if values and all([isinstance(value,pd.DataFrame) for value in values]): self.output[key] = pd.concat(values) + elif isinstance(values,dict): + self.output[key] = data_dict() + for sk,sv in values.items(): + self.output[key][sk] = pd.concat(sv) elif key != 'log': self.output[key] = values @@ -131,78 +165,3 @@ def run(self, display=True): if display: print(f"\nRun time: {ct}\nSimulation finished") return self.output - - - - -def create_sample_discrete(parameter_ranges): - - """ Creates a parameter_sample out of all possible combinations given in parameter tuples """ - - def make_tuple(v): - if isinstance(v,tuple): return v - else: return (v,) - - param_ranges_values = [ make_tuple(v) for k,v in parameter_ranges.items() ] - parameter_combinations = list(itertools.product(*param_ranges_values)) - parameter_sample = [ { k:v for k,v in zip(parameter_ranges.keys(),parameters) } for parameters in parameter_combinations ] - - return parameter_sample - - -def saltelli(param_ranges,N,**kwargs): - - """ Creates saltelli parameter sample with the SALib Package (https://salib.readthedocs.io/) """ - - # STEP 1 - Convert param_ranges to SALib Format (https://salib.readthedocs.io/) - - param_ranges_tuples = { k:v for k,v in param_ranges.items() if isinstance(v,tuple) } - - param_ranges_salib = { - 'num_vars': len( param_ranges_tuples ), - 'names': list( param_ranges_tuples.keys() ), - 'bounds': [] - } - - for var_key, var_range in param_ranges_tuples.items(): - - param_ranges_salib['bounds'].append( [ var_range[0] , var_range[1] ] ) - - # STEP 2 - Create SALib Sample - - salib_sample = SALibSaltelli.sample(param_ranges_salib,N,**kwargs) - - # STEP 3 - Convert back to Agentpy Parameter Dict List - - ap_sample = [] - - for param_instance in salib_sample: - - parameters = {} - parameters.update( param_ranges ) - - for i, key in enumerate(param_ranges_tuples.keys()): - parameters[key] = param_instance[i] - - ap_sample.append( parameters ) - - return ap_sample - - -def sample(param_ranges,mode='discrete',**kwargs): - - """ - Returns parameter sample (list of dict) - - Arguments: - param_ranges(dict) - mode(str): Sampling method. Options are: - 'discrete' - tuples are given of style (value1,value2,...); - 'saltelli' - tuples are given of style (min_value,max_value) - """ - - if mode == 'discrete': parameters = create_sample_discrete(param_ranges,**kwargs) - elif mode == 'saltelli': parameters = saltelli(param_ranges,**kwargs) - else: raise ValueError(f"mode '{mode}' does not exist.") - - return parameters \ No newline at end of file diff --git a/agentpy/framework.py b/agentpy/framework.py index fef976f..96e0a8f 100644 --- a/agentpy/framework.py +++ b/agentpy/framework.py @@ -10,13 +10,14 @@ import pandas as pd import networkx as nx -import random +import random as rd import operator import warnings from datetime import datetime +from itertools import product from .output import data_dict -from .tools import make_list, attr_dict, attr_list, AgentpyError +from .tools import make_list, attr_dict, obj_list, nested_list, AgentpyError ### Tools for all classes ### @@ -27,12 +28,14 @@ def record(self, var_keys, value = None): Arguments: var_keys(str or list): Names of the variables value(optional): Value to be recorded. - If no value is given, keys are used to look up the objects attribute values. + If no value is given, var_keys have to refer to existing object attributes. """ for var_key in make_list(var_keys): - # Create empty list if var_key is new + # Create empty lists + if 't' not in self.log: + self.log['t'] = [] if var_key not in self.log: self.log[var_key] = [None] * len(self.log['t']) @@ -95,7 +98,7 @@ def __init__(self,model,envs=None): self.envs = env_dict(model) if envs: self.envs.update(envs) - self.log = {'t':[]} + self.log = {} self.type = type(self).__name__ self.setup() # Custom initialization @@ -108,6 +111,17 @@ def __repr__(self): def __hash__(self): return self.id # Necessary for networkx + def pos(self,key=None): + + # Select network + if key == None: + for k,v in self.envs.items(): + if v.topology == 'grid': + key = k + break + + return self.envs[key].pos[self] + def neighbors(self,key=None): """ Returns the agents' neighbor's in a network @@ -120,13 +134,13 @@ def neighbors(self,key=None): # Select network if key == None: for k,v in self.envs.items(): - if v.topology == 'network': + if v.topology == 'network' or v.topology == 'grid': key = k - break + break elif key not in self.envs.keys(): AgentpyError(f"Agent {self.id} has no network '{key}'") - if key == None: AgentpyError(f"No network found for agent {self.id}") + if key == None: AgentpyError(f"No network found for agent {self.id}") # (!) Faulty logic - return agent_list([n for n in self.envs[key].neighbors(self)]) + return agent_list(self.envs[key].neighbors(self)) def leave(self,keys=None): @@ -148,18 +162,19 @@ def leave(self,keys=None): del self.envs[key] -class agent_list(attr_list): +class agent_list(obj_list): """ List of agents. + Attribute access (e.g. `agent_list.x`) is forwarded to each agent and returns a list of attribute values. This also works for method calls (e.g. `agent_list.x()`) , which returns a list of method return values. Assignments of attributes (e.g. `agent_list.x = 1`) are forwarded to each agent in the list. Basic operators can also be used (e.g. `agent_list.x += 1` or `agent_list.x = agent_list.y * 2`). Comparison operators (e.g. `agent_list.x == 1`) return a new agent_list with agents that fulfill the condition. - See :func:`attr_list` for more information. + See :func:`obj_list` for more information. Arguments: - agents(agent or list, optional): Initial list entries + agents(agent or list of agent, optional): Initial agents of the list """ def __init__(self, agents = None): @@ -167,17 +182,17 @@ def __init__(self, agents = None): if agents: self.extend( make_list(agents) ) def __repr__(self): - return f"" + return f"" - def do(self, method, *args, order=None, return_value=False, **kwargs): + def do(self, method, *args, shuffle = False, **kwargs): """ - Calls a method for all agents in the list + Calls ``method(*args,**kwargs)`` for every agent. Arguments: method (str): Name of the method - order (str, optional): If 'random', order in which agents are called will be shuffled *args: Will be forwarded to the agents method + random (bool, optional): Whether to shuffle order in which agents are called (default False) **kwargs: Will be forwarded to the agents method """ @@ -185,20 +200,20 @@ def do(self, method, *args, order=None, return_value=False, **kwargs): if order == 'random': agents = list( self ) # Copy - random.shuffle( agents ) + rd.shuffle( agents ) for agent in agents: getattr( agent, method )(*args,**kwargs) def select(self,var_key,value=True,relation="=="): - """ Returns a new `agent_list` of selected agents. + """ Returns a new :class:`agent_list` of selected agents. Arguments: var_key (str): Variable for selection - value (optional): Value for selection, default `True` - relation (str, optional): Relation between variable and value - Options are '=='(default),'!=','<','<=','>','>=' """ + value (optional): Value for selection (default True) + relation (str, optional): Relation between variable and value (default '=='). + Options are '==','!=','<','<=','>','>=' """ relations = {'==':operator.eq,'!=':operator.ne, '<':operator.lt,'<=':operator.le, @@ -208,21 +223,21 @@ def select(self,var_key,value=True,relation="=="): def assign(self,var_key,value): - """ Assigns value to var_key for each agent.""" + """ Assigns ``value`` to ``var_key`` for each agent.""" for agent in self: setattr(agent,var_key,value) def of_type(self,agent_type): - """ Returns a new `agent_list` with agents of agent_type """ + """ Returns a new :class:`agent_list` with agents of type ``agent_type``. """ return self.select('type',agent_type) def random(self,n=1): - """ Returns a new `agent_list` with n random agents """ + """ Returns a new :class:`agent_list` with ``n`` random agents (default 1).""" - return agent_list(random.sample(self,n)) + return agent_list(rd.sample(self,n)) ### Level 2 - Environment class ### @@ -271,7 +286,7 @@ def _env_init(self,model,key): self.key = key self.type = type(self).__name__ self.t0 = model.t - self.log = {'t':[]} + self.log = {} class environment(attr_dict): @@ -359,7 +374,13 @@ def add_agents(self, agents, agent_class=agent, map_to_nodes=False, **kwargs): # Add agents to graph as new nodes for agent in new_agents: self.graph.add_node( agent ) + + def neighbors(self,agent): + + """ Returns :class:`agent_list` of agents that are connected to the passed agent. """ + return agent_list([n for n in self.graph.neighbors(agent)]) + def __getattr__(self, method_name): # Forward unknown method call to self.graph @@ -370,7 +391,129 @@ def method(*args, **kwargs): try: return method except AttributeError: raise AttributeError(f"module {__name__} has no attribute {name}") + +class grid(environment): + + """ Grid environment that contains agents with a spatial topology. + Inherits attributes and methods from :class:`environment`. + + Attributes: + pos(dict): Agent positions + + Arguments: + model (model): The environments' model + key (dict or env_dict, optional): The environments' name + dim(int, optional): Number of dimensions (default 2). + size(int or tuple): Size of the grid. + If int, the same length is assigned to each dimension. + If tuple, one int item is required per dimension. + """ + + def __init__(self,model,env_key,shape): + + _env_init(self,model,env_key) + + self.topology = 'grid' + self.grid = nested_list( make_list(shape) , lambda:agent_list() ) + self.shape = shape + self.pos = {} + + self.setup() + + def neighbors(self,agent,shape='diamond'): + + """ Return agent neighbors """ + + if shape == 'diamond': return self._get_neighbors4(self.pos[agent],self.grid) + elif shape == 'square': return self._get_neighbors8(self.pos[agent],self.grid) + + def area(self,area): + + return agent_list( self._get_area(area,self.grid) ) + + def _get_area(self,area,grid): + + """ Return agents in area of style: [(x_min,x_max),(y_min,y_max),...]""" + + subgrid = grid[area[0][0]:area[0][1]+1] + + if isinstance(subgrid[0],agent_list): # Detect last row (must have agent_lists) + return [y for x in subgrid for y in x] # Flatten list of agent_lists to list of agents + + objects = [] + for row in subgrid: + objects.extend( self._get_area(area[1:],row) ) + + return objects + + def _get_neighbors4(self,pos,grid,dist=1): + """ Return agents in diamond-shaped area around pos """ + + subgrid = grid[max(0,pos[0]-dist):pos[0]+dist+1] + + if len(pos) == 1: + return [y for x in subgrid for y in x] # flatten list + + objects = [] + for row,dist in zip(subgrid,[0,1,0]): + objects.extend( self._get_neighbors4(pos[1:],row,dist) ) + + return objects + + def _get_neighbors8(self,pos,grid): + + subgrid = grid[max(0,pos[0]-1):pos[0]+2] + + if len(pos) == 1: + return [y for x in subgrid for y in x] # flatten list + + objects = [] + for row in subgrid: + objects.extend( self._get_neighbors8(pos[1:],row) ) + + return objects + + def _get_pos(self,grid,pos): + if len(pos) == 1: return grid[pos[0]] + return self._get_pos(grid[pos[0]],pos[1:]) + + def get_pos(self,pos): + return self._get_pos(self.grid,pos) + + def change_pos(self,agent,new_position): + self.get_pos(self.position[agent],self.grid).drop(agent) # Remove from old position + self.get_pos(new_position,self.grid).append(agent) # Add to new position + self.pos[agent] = position # Log Position + + def add_agents(self, agents, agent_class=agent, positions=None, random = False, map_to_grid=False, **kwargs): + + """ Adds agents to the grid environment.""" + + # (!) unfinished + + # Standard adding + new_agents = add_agents(self,agents,agent_class,**kwargs) + + # Extra grid features + if map_to_grid: + pass #(!) + elif positions: + for agent,pos in zip(new_agents,positions): + self.pos[agent] = pos + elif random: + positions = list(product(*[ range(i) for i in self.shape ])) + sample = rd.sample(positions,agents) + + for agent,pos in zip(new_agents,sample): + self.pos[agent] = pos # Log Position + self.get_pos(pos).append(agent) # Add to new position + + else: + for agent in new_agents: + self.pos[agent] = [0] * self.dim + + class env_dict(dict): """ Dictionary for environments @@ -383,10 +526,14 @@ def __init__(self,model): super().__init__() self.model = model - + + def __repr__(self): + + return f"" + def of_type(self,env_type): - """ Returns an env_dict with selected environments """ + """ Returns :class:`env_dict` of selected environments. """ new_dict = env_dict(self.model) selection = {k : v for k, v in self.items() if v.type == env_type} @@ -395,13 +542,13 @@ def of_type(self,env_type): def do(self,method,*args,**kwargs): - """ Calls method for all environments """ + """ Calls ``method(*args,**kwargs)`` for all environments. """ for env in self.values(): getattr(env, method)(*args, **kwargs) def add_agents(self,*args,**kwargs): - """ Adds agents to all environments """ + """ Calls :meth:`environment.add_agents` with `*args,**kwargs` and forwards new agents to all environments """ for i,env in enumerate(self.values()): if i == 0: new_agents = env.add_agents(*args,**kwargs) @@ -451,6 +598,7 @@ def __init__(self, self.agents = agent_list() self.p = attr_dict() if parameters: self.p.update(parameters) + if 'steps' in parameters: self.stop_if = self.stop_if_steps self.type = type(self).__name__ self.run_id = run_id @@ -461,9 +609,10 @@ def __init__(self, self._id_counter = 0 # Recording - self.log = {'t':[]} + self.log = {} self.measure_log = {} self.output = data_dict() + self.output.log = {} self.output.log['name'] = self.type self.output.log['time_stamp'] = str(datetime.now()) @@ -475,6 +624,21 @@ def _get_id(self): self._id_counter += 1 return self._id_counter - 1 + def __repr__(self): + + rep = "Agent-based model {" + ignore = ['model','log','measure_log','stop_if','key','output'] + + for k,v in self.items(): + + if k not in ignore and not k[0] == '_': + rep += f"\n'{k}': {v}" + + if k == 'output': + rep += f"\n'{k}': data_dict with {len(v.keys())} entries" + + return rep + ' }' + def __getattr__(self, name): try: # Access environments @@ -496,7 +660,14 @@ def add_network(self, env_key, graph = None, env_class=network , **kwargs): """ Creates a new network environment """ for env_key in make_list(env_key): - self.add_env(env_key, env_class=network, graph=graph, **kwargs) + self.add_env(env_key, env_class=env_class, graph=graph, **kwargs) + + def add_grid(self, env_key, env_class=grid , **kwargs): + + """ Creates a new spacial grid environment """ + + for env_key in make_list(env_key): + self.add_env(env_key, env_class=env_class, **kwargs) # Recording functions @@ -536,9 +707,13 @@ def end(self): def stop_if(self): """ - Stops :meth:`model.run` during an active simulation if it returns `False`. + Stops :meth:`model.run` during an active simulation if it returns `True`. Can be overwritten with a custom function. - + """ + return False + + def stop_if_steps(self): + """ Returns: bool: Whether time-step `t` has reached the parameter `model.p.steps`. """ @@ -591,61 +766,70 @@ def create_output(self): """ Generates an 'output' dictionary out of object logs """ - def create_df(obj,c2): - - df = pd.DataFrame(obj.log) - df['obj_id'] = obj[c2] # obj_id - return df - def output_from_obj_list(self,obj_list,c1,c2,columns): - """ Create output for agent_list or env_dict """ - keys = [] + # Aggregate logs per object type obj_types = {} - for obj in obj_list: - if obj.log != {'t':[]}: + + if obj.log: # Check for variables - # Category (obj_type) - obj_type = f'{obj.type}_vars' + # Add id to object log obj.log['obj_id'] = [obj[c2]] * len(obj.log['t']) - if obj_type not in obj_types.keys(): - obj_types[obj_type] = {} + # Initiate object type if new + if obj.type not in obj_types.keys(): + obj_types[obj.type] = {} + # Add object log to aggr. log for k,v in obj.log.items(): - if k not in obj_types[obj_type]: - obj_types[obj_type][k] = [] - obj_types[obj_type][k].extend(v) - - # Once per type + if k not in obj_types[obj.type]: + obj_types[obj.type][k] = [] + obj_types[obj.type][k].extend(v) + + # Transform logs into dataframes for obj_type, log in obj_types.items(): df = pd.DataFrame( log ) - for k,v in columns.items(): df[k]=v + for k,v in columns.items(): df[k]=v # Set additional index columns df = df.set_index(list(columns.keys())+['obj_id','t']) - self.output[obj_type] = df + self.output['variables'][obj_type] = df - # Additional columns + # 0 - Document parameters + if self.p: self.output['parameters'] = self.p + + # 1 - Define additional index columns columns = {} if self.run_id is not None: columns['run_id'] = self.run_id if self.scenario is not None: columns['scenario'] = self.scenario - # Create object var output + # 2 - Create measure output + if self.measure_log: + d = self.measure_log + for key,value in columns.items(): d[key] = value + df = pd.DataFrame(d) + if columns: df = df.set_index(list(columns.keys())) + self.output['measures'] = df + + # 3 - Create variable output + self.output['variables'] = data_dict() + + # Create variable output for objects output_from_obj_list(self, self.agents, 'agent', 'id', columns) output_from_obj_list(self, self.envs.values(), 'env', 'key', columns) - # Create model var output - if self.log != {'t':[]}: + # Create variable output for model + if self.log: + df = pd.DataFrame(self.log) df['obj_id'] = 'model' for k,v in columns.items(): df[k]=v df = df.set_index(list(columns.keys())+['obj_id','t']) - self.output['model_vars'] = df + + if self.output['variables']: self.output['variables']['model'] = df + else: self.output['variables'] = df # No subdict if only model vars + + # Remove variable dict if empty + elif not self.output['variables']: + del self.output['variables'] + - # Create measure output - if self.measure_log != {}: - d = self.measure_log - for key,value in columns.items(): d[key] = value - df = pd.DataFrame(d) - if columns: df = df.set_index(list(columns.keys())) - self.output['measures'] = df \ No newline at end of file diff --git a/agentpy/interactive.py b/agentpy/interactive.py deleted file mode 100644 index 6f0c23e..0000000 --- a/agentpy/interactive.py +++ /dev/null @@ -1,103 +0,0 @@ -""" - -Agentpy -Interactive Module - -Copyright (c) 2020 Joël Foramitti - -""" - -import ipywidgets as widgets -import matplotlib.pyplot as plt - -from matplotlib import animation - -def animate(model, parameters, fig, axs, plot, skip_t0 = False, **kwargs): - - """ Returns an animation of the model simulation """ - - m = model(parameters) - m._stop = False - m.setup() - m.update() - - step0 = True - step00 = not skip_t0 - - def frames(): - - nonlocal m, step0, step00 - - while not m.stop_if() and not m._stop: - - if step0: step0 = False - elif step00: step00 = False - else: - m.t += 1 - m.step() - m.update() - m.create_output() - yield m.t - - def update(t, m, axs): - - for ax in axs: - ax.clear() - - if m.t == 0 and skip_t0: pass - else: plot( m, axs ) - - ani = animation.FuncAnimation(fig, update, frames=frames, fargs=(m, axs), **kwargs) - plt.close() # Don't display static plot - - return ani - - -def interactive(model,param_ranges,output_function,*args,**kwargs): - - """ - - Returns 'output_function' as an interactive ipywidget - More infos at https://ipywidgets.readthedocs.io/ - - """ - - def make_param(param_updates): - - parameters = dict(param_ranges) # Copy - i = 0 - - for key, value in parameters.items(): - - if isinstance(v,tuple): - parameters[key] = param_updates[i] - i += 1 - - def var_run(**param_updates): - - parameters = dict(param_ranges) - parameters.update(param_updates)#make_param(param_updates) - temp_model = model(parameters) - temp_model.run(display=False) - - output_function(temp_model.output,*args,**kwargs) - - # Create widget dict - widget_dict = {} - param_ranges_tuples = { k:v for k,v in param_ranges.items() if isinstance(v,tuple) } - for var_key, var_range in param_ranges_tuples.items(): - - widget_dict[var_key] = widgets.FloatSlider( - description=var_key, - value = (var_range[1] - var_range[0]) / 2 , - min = var_range[0], - max = var_range[1], - step = (var_range[1] - var_range[0]) / 10 , - style = dict(description_width='initial') , - layout = {'width': '300px'} ) - - out = widgets.interactive_output(var_run, widget_dict) - - return widgets.HBox([ widgets.VBox(list(widget_dict.values())), out ]) - - diff --git a/agentpy/output.py b/agentpy/output.py index a8f695a..af6f168 100644 --- a/agentpy/output.py +++ b/agentpy/output.py @@ -11,116 +11,200 @@ from os import listdir, makedirs import json +#import networkx as nx from .tools import attr_dict, make_list +import numpy as np + +class NpEncoder(json.JSONEncoder): + + # By Jie Yang https://stackoverflow.com/a/57915246 + + def default(self, obj): + if isinstance(obj, np.integer): + return int(obj) + elif isinstance(obj, np.floating): + return float(obj) + elif isinstance(obj, np.ndarray): + return obj.tolist() + else: + return super(NpEncoder, self).default(obj) + + class data_dict(attr_dict): - """ Dictionary for recorded data from :class:`model` and :class:`experiment` simulations. """ + """ Dictionary for recorded simulation data. + Can be generated from :class:`model`, :class:`experiment`, or :func:`load`. + Subclass of :class:`attr_dict`, which means that attributes can be accessed as items. - def __init__(self): - super().__init__() - self.log = {} + Attributes: + log (dict): Meta-data of the simulation (e.g. name, time-stamps, settings, etc.) + parameters (dict of dict and pandas.DataFrame): Parameters that have been used for the simulation. + variables (dict of pandas.DataFrame): Dynamic variables, seperated per object type, which can be recorded once per time-step with :func:`record`. + measures (pandas.DataFrame): Evaluation measures, which can be recorded once per run with :func:`measure`. + """ - def __repr__(self): + def __repr__(self,indent=False): rep = "data_dict {" + i = ' ' if indent else '' for k,v in self.items(): - + rep += f"\n{i}'{k}': " if isinstance(v,pd.DataFrame): lv = len(list(v.columns)) rv = len(list(v.index)) - rep += f"\n'{k}': DataFrame with {lv} variable{'s' if lv!=1 else ''} and {rv} row{'s' if rv!=1 else ''}" + rep += f"DataFrame with {lv} variable{'s' if lv!=1 else ''} and {rv} row{'s' if rv!=1 else ''}" + elif isinstance(v,data_dict): + rep += f"{v.__repr__(indent=True)}" elif isinstance(v,dict): lv = len(list(v.keys())) - rep += f"\n'{k}': Dictionary with {lv} key{'s' if lv!=1 else ''}" + rep += f"Dictionary with {lv} key{'s' if lv!=1 else ''}" elif isinstance(v,list): lv = len(v) - rep += f"\n'{k}': List with {lv} entr{'ies' if lv!=1 else 'y'}" + rep += f"List with {lv} entr{'ies' if lv!=1 else 'y'}" else: - rep += f"\n'{k}': Object of type {type(v)}" - - return rep + "\n}" - - - def ps_keys(self): - - """ Returns the keys of varied parameters """ - - df = self.parameter_sample - p_list = [] + rep += f"Object of type {type(v)}" - # creating a list of dataframe columns - columns = list(df) + return rep + " }" - for i in columns: + def _combine_vars(self,obj_types=None,var_keys=None): - # printing the third element of the column - if len(df[i].unique()) > 1: - p_list.append(i) - - return p_list + """ Returns pandas dataframe with variables """ + + # Select dataframes + df_dict = self['variables'] + + # If 'variables' is a dataframe + if isinstance(df_dict,pd.DataFrame): + return self['variables'] + + # If 'variables' is a dictionary + # (!) introduce checks & errors + + # Select object types + if var_keys is not None: + df_dict = { k:v for k,v in df_dict.items() if any(x in v.columns for x in make_list(var_keys) )} + if obj_types is not None: + df_dict = { k:v for k,v in df_dict.items() if k in obj_types} + + # Create dataframe + df = pd.concat( df_dict ) + df.index = df.index.set_names('obj_type',level=0) # Name new index column + if var_keys: df = df[var_keys] # Select var_keys + return df - def get_pars(self, parameters = None): + def _combine_pars(self): """ Returns pandas dataframe with parameters and run_id """ - dfp = pd.concat( [self.parameter_sample] * self.log['iterations'] ) + # Combine fixed & varied parameters + if isinstance(self.parameters,data_dict): + dfp0 = self.parameters.varied + for k,v in self.parameters.fixed.items(): + dfp0[k]=v + + # Take either fixed or varied parameters + elif isinstance(self.parameters,dict): + dfp0 = pd.DataFrame({k: [v] for k, v in self.parameters.items()}) + elif isinstance(self.parameters,pd.DataFrame): + dfp0 = self.parameters + else: + raise AgentpyError("Parameters must be of type dict, data_dict, or pandas.DataFrame") + + # Multiply for iterations + dfp = pd.concat( [dfp0] * self.log['iterations'] ) dfp = dfp.reset_index(drop=True) dfp.index.name = 'run_id' - if parameters is not None and not True: dfp = dfp[parameters] # Select parameters return dfp - - def get_measures(self,measures=None,parameters=None,reset_index=True): - - """ Returns pandas dataframe with measures """ + def arrange(self, + data_keys=None, + var_keys=None, + obj_types=None, + measure_keys=None, + param_keys=None, + scenarios=None, + index=False): - df = self.measures + """ Combines and/or filters data based on passed arguments, and returns a new dataframe. - # Add parameters - if parameters: - dfp = self.get_pars(parameters) - if isinstance(df.index, pd.MultiIndex): dfp = dfp.reindex(df.index,level='run_id') - df = pd.concat([df,dfp],axis=1) + Arguments: + data_keys (str or list of str, optional): + Keys from the data_dict to include in the new dataframe. + If none are given, all are selected. + obj_types (str or list of str, optional): + Agent and/or environment types to include in the new dataframe. + Only takes effect if data_keys include 'variables'. + If none are given, all are selected. + var_keys (str or list of str, optional): + Dynamic variables to include in the new dataframe. + Only takes effect if data_keys include 'variables'. + If none are given, all are selected. + param_keys (str or list of str, optional): + Parameters to include in the new dataframe. + Only takes effect if data_keys include 'parameters'. + If none are given, all are selected. + scenarios (str or list of str, optional): + Scenarios to include in the new dataframe. + If none are given, all are selected. + index (bool, optional): + Whether to keep original multi-index structure (default False). - return df - - - def get_vars(self,var_keys=None,obj_types=None,parameters=None,reset_index=True): - - """ Returns pandas dataframe with variables """ + """ - var_keys = make_list(var_keys) - if parameters is not True: - parameters = make_list(parameters) - df_dict = self + dfv = None # Dataframe for return - # Select variable dataframes - df_dict = { k[:-5]:v for k,v in df_dict.items() if 'vars' in k} + # Select all if no keys are given + if data_keys is None: + data_keys = self.keys() #[k for k in self.keys() if k in supported_keys or 'vars' in k] - # Select object types - if var_keys: df_dict = { k:v for k,v in df_dict.items() if any(x in v.columns for x in var_keys)} - if obj_types: df_dict = { k:v for k,v in df_dict.items() if k in obj_types} + # Reformat passed keys to list + else: data_keys = make_list(data_keys) - # Create dataframe - df = pd.concat( df_dict ) - df.index = df.index.set_names('obj_type',level=0) - if var_keys: df = df[var_keys] # Select var_keys - - # Add parameters - if parameters: - dfp = self.get_pars(parameters) - dfp = dfp.reindex(df.index,level='run_id') - df = pd.concat([df,dfp],axis=1) - + # Check keys + #for key in data_keys: + # if key not in self.keys(): + # raise KeyError(f"Key '{key}' not found") + + # Process 'variables' + if 'variables' in data_keys: + dfv = self._combine_vars(obj_types,var_keys) + + # Process 'measures' + if 'measures' in data_keys: + dfm = self.measures + if measure_keys: dfm = dfm[measure_keys] + if dfv is None: dfv = dfm + else: + # Combine vars & measures + index_keys = dfv.index.names + dfm = dfm.reset_index() + dfv = dfv.reset_index() + dfv = pd.concat([dfm,dfv]) + dfv = dfv.set_index(index_keys) + + # Process 'parameters' + if 'parameters' in data_keys: + dfp = self._combine_pars() + if param_keys: dfp = dfp[param_keys] + if isinstance(dfv.index, pd.MultiIndex): dfp = dfp.reindex(dfv.index,level='run_id') + dfv = pd.concat([dfv,dfp],axis=1) + + # Select scenarios + if scenarios: + scenarios = make_list(scenarios) + dfv = dfv.query("scenario in @scenarios") + # Reset index - if reset_index: df = df.reset_index() - - return df + if index == False: + dfv = dfv.reset_index() + + return dfv + def _last_exp_id(self, name, path): @@ -134,6 +218,7 @@ def _last_exp_id(self, name, path): exp_id = max(ids) return exp_id + def save(self, exp_name = None, exp_id = None, path = 'ap_output', display = True): """ Writes output data to directory ``{path}/{exp_name}_{exp_id}/``. @@ -168,13 +253,23 @@ def save(self, exp_name = None, exp_id = None, path = 'ap_output', display = Tru t = type(output) - if t == pd.DataFrame: + if isinstance(output,pd.DataFrame): output.to_csv(f'{path}/{key}.csv') - elif t == dict: - with open(f'{path}/{key}.json', 'w') as fp: - json.dump(output, fp) - elif t == nx.Graph: - nx.write_graphml(output, f'{path}/{key}.graphml') + + if isinstance(output,data_dict): + for k,o in output.items(): + + if isinstance(o,pd.DataFrame): + o.to_csv(f'{path}/{key}_{k}.csv') + elif isinstance(o,dict): + with open(f'{path}/{key}_{k}.json', 'w') as fp: + json.dump(o, fp, cls=NpEncoder) + + elif isinstance(output,dict): + with open(f'{path}/{key}.json', 'w') as fp: json.dump(output, fp, cls=NpEncoder) + + #elif t == nx.Graph: + # nx.write_graphml(output, f'{path}/{key}.graphml') if display: print(f"Data saved to {path}") @@ -194,9 +289,9 @@ def load(self, exp_name='experiment', exp_id=None, path='ap_output', display = T def load_file(self, path, file, display): - print(f'Loading {file} - ',end='') + if display: print(f'Loading {file} - ',end='') - i_cols = ['sample_id','run_id','scenario','env_key','agent_id','t'] + i_cols = ['sample_id','run_id','scenario','env_key','agent_id','obj_id','t'] ext = file.split(".")[-1] key = file[:-(len(ext)+1)] @@ -204,36 +299,53 @@ def load_file(self, path, file, display): path = path + file try: - + if ext == 'csv': - self[key] = df = pd.read_csv( path ) - index = [i for i in i_cols if i in df.columns] - if index: self[key] = df.set_index(index) + obj = pd.read_csv( path ) + index = [i for i in i_cols if i in obj.columns] + if index: obj = obj.set_index(index) elif ext == 'json': with open(path , 'r') as fp: - self[key] = json.load(fp) - elif ext == 'graphml': - self[key] = nx.read_graphml(path) + obj = json.load(fp) + #elif ext == 'graphml': + # self[key] = nx.read_graphml(path) else: if display: print(f"Error: File type '{ext}' not supported") return if display: print('Successful') - except Exception as e: print(f'Error: {e}') + except Exception as e: print(f'Error: {e}') + + return obj # Prepare for loading exp_name = exp_name.replace(" ", "_") if not exp_id: exp_id = self._last_exp_id(exp_name, path) if exp_id == 0: - raise ExperimentError(f"No experiment found with name '{exp_name}' in path '{path}'") + raise AgentpyError(f"No experiment found with name '{exp_name}' in path '{path}'") path = f'{path}/{exp_name}_{exp_id}/' if display: print(f'Loading from directory {path}') # Loading data - for file in listdir(path): load_file(self,path,file,display) - + for file in listdir(path): + if 'variables_' in file: + if 'variables' not in self: + self['variables'] = data_dict() + ext = file.split(".")[-1] + key = file[:-(len(ext)+1)].replace('variables_','') + self['variables'][key] = load_file(self,path,file,display) + elif 'parameters_' in file: + ext = file.split(".")[-1] + key = file[:-(len(ext)+1)].replace('parameters_','') + if 'parameters' not in self: + self['parameters'] = data_dict() + self['parameters'][key] = load_file(self,path,file,display) + else: + ext = file.split(".")[-1] + key = file[:-(len(ext)+1)] + self[key] = load_file(self,path,file,display) return self diff --git a/agentpy/sample.py b/agentpy/sample.py new file mode 100644 index 0000000..599a1ac --- /dev/null +++ b/agentpy/sample.py @@ -0,0 +1,91 @@ +""" + +Agentpy +Sampling Module + +Copyright (c) 2020 Joël Foramitti + +""" + +import itertools +import numpy as np + +from SALib.sample import saltelli as SALibSaltelli + +def sample(parameter_ranges,N): + + """ Creates a parameter_sample out of all possible combinations given in parameter tuples + uses np.arange(), tuples are given of style (min_value,max_value)""" + + def make_tuple(v): + if isinstance(v,tuple): return v + else: return (v,) + + for k,v in parameter_ranges.items(): + if isinstance(v,tuple): + parameter_ranges[k] = np.arange(v[0],v[1],(v[1]-v[0])/N) + else: + parameter_ranges[k] = [v] + + parameter_combinations = list(itertools.product(*parameter_ranges.values())) + parameter_sample = [ { k:v for k,v in zip(parameter_ranges.keys(),parameters) } for parameters in parameter_combinations ] + + return parameter_sample + + +def sample_discrete(parameter_ranges): + + """ Creates a parameter_sample out of all possible combinations given in parameter tuples + uses SALib.saltelli(), tuples are given of style (min_value,max_value)""" + + def make_tuple(v): + if isinstance(v,tuple): return v + else: return (v,) + + param_ranges_values = [ make_tuple(v) for k,v in parameter_ranges.items() ] + parameter_combinations = list(itertools.product(*param_ranges_values)) + parameter_sample = [ { k:v for k,v in zip(parameter_ranges.keys(),parameters) } for parameters in parameter_combinations ] + + return parameter_sample + + +def sample_saltelli(param_ranges,N,**kwargs): + + """ Creates saltelli parameter sample with the SALib Package (https://salib.readthedocs.io/) + 'discrete' - tuples are given of style (value1,value2,...) """ + + # STEP 1 - Convert param_ranges to SALib Format (https://salib.readthedocs.io/) + + param_ranges_tuples = { k:v for k,v in param_ranges.items() if isinstance(v,tuple) } + + param_ranges_salib = { + 'num_vars': len( param_ranges_tuples ), + 'names': list( param_ranges_tuples.keys() ), + 'bounds': [] + } + + for var_key, var_range in param_ranges_tuples.items(): + + param_ranges_salib['bounds'].append( [ var_range[0] , var_range[1] ] ) + + # STEP 2 - Create SALib Sample + + salib_sample = SALibSaltelli.sample(param_ranges_salib,N,**kwargs) + + # STEP 3 - Convert back to Agentpy Parameter Dict List + + ap_sample = [] + + for param_instance in salib_sample: + + parameters = {} + parameters.update( param_ranges ) + + for i, key in enumerate(param_ranges_tuples.keys()): + parameters[key] = param_instance[i] + + ap_sample.append( parameters ) + + return ap_sample + + diff --git a/agentpy/tools.py b/agentpy/tools.py index 4d3277d..51488d7 100644 --- a/agentpy/tools.py +++ b/agentpy/tools.py @@ -7,6 +7,8 @@ """ +from numpy import ndarray + class AgentpyError(Exception): pass @@ -23,70 +25,73 @@ def __init__(self,*args,**kwargs): self.__dict__ = self def __repr__(self): - return f"" + return f"attr_dict {dict.__repr__(self)}" - +def nested_list(shape, value_creator): + + """ Returns a nested list matrix with given shape and value. """ + + # With help from Thierry Lathuille https://stackoverflow.com/a/64467230/ + + if len(shape) == 1: + return [value_creator() for _ in range(shape[0])] + return [nested_list(shape[1:], value_creator) for _ in range(shape[0])] + def make_list(element,keep_none=False): - """ Turns `element` into a list of itself if it is not of type list or tuple. """ + """ Turns element into a list of itself if it is not of type list or tuple. """ if element is None and not keep_none: element = [] # Convert none to empty list - if not isinstance(element, (list, tuple)): element = [element] + if not isinstance(element, (list, tuple, ndarray)): element = [element] + elif isinstance(element,tuple): element = list(element) return element -class func_list(list): +class obj_attr_list(list): - """ List that distributes calls to its members and returns list of return values """ + """ List of object attributes that distributes calls to its members and returns list of return values """ def __init__(self,_super,_name,*args): super().__init__(*args) self._super = _super self._name = _name - def __call__(self,*args,**kwargs): - - try: return [ func_obj(*args,**kwargs) for func_obj in self ] + def __call__(self,*args,**kwargs): + try: return obj_attr_list(self._super,self._name,[ func_obj(*args,**kwargs) for func_obj in self ]) except TypeError: raise TypeError(f"Not all objects in '{type(self._super).__name__}' are callable.") - def __eq__(self, other): - + def __eq__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x == other]) - def __ne__(self, other): - + def __ne__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x != other]) - def __lt__(self, other): - + def __lt__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x < other]) - def __le__(self, other): - + def __le__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x <= other]) - def __gt__(self, other): - + def __gt__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x >= other]) - def __ge__(self, other): - + def __ge__(self, other): return type(self._super)([obj for obj,x in zip(self._super,self) if x > other]) def __add__(self,v): - return func_list(self._super,self._name,[x+v for x in self]) + return obj_attr_list(self._super,self._name,[x+v for x in self]) def __sub__(self,v): - return func_list(self._super,self._name,[x-v for x in self]) + return obj_attr_list(self._super,self._name,[x-v for x in self]) def __mul__(self,v): - return func_list(self._super,self._name,[x*v for x in self]) + return obj_attr_list(self._super,self._name,[x*v for x in self]) def __truediv__(self,v): - return func_list(self._super,self._name,[x/v for x in self]) + return obj_attr_list(self._super,self._name,[x/v for x in self]) def __iadd__(self,v): return self + v @@ -100,13 +105,16 @@ def __imul__(self,v): def __itruediv__(self,v): return self / v -class attr_list(list): + def __repr__(self): + return f"obj_attr_list {list.__repr__(self)}" + +class obj_list(list): """ A list that can access and assign attributes of it's entries like it's own """ def __setattr__(self, name, value): - if isinstance(value,func_list): + if isinstance(value,obj_attr_list): for obj,v in zip(self,value): setattr(obj, name, v) return @@ -117,6 +125,9 @@ def __setattr__(self, name, value): def __getattr__(self,name): try: - return func_list( self, name, [ getattr(obj,name) for obj in self ] ) + return obj_attr_list( self, name, [ getattr(obj,name) for obj in self ] ) except AttributeError: - raise AttributeError(f"Neither '{type(self).__name__}' object nor it's entries have attribute '{name}'") \ No newline at end of file + raise AttributeError(f"Neither '{type(self).__name__}' object nor it's entries have attribute '{name}'") + + def __repr__(self): + return f"obj_list {list.__repr__(self)}" \ No newline at end of file diff --git a/docs/Agentpy_Button_Network.ipynb b/docs/Agentpy_Button_Network.ipynb new file mode 100644 index 0000000..61ca5bb --- /dev/null +++ b/docs/Agentpy_Button_Network.ipynb @@ -0,0 +1,157 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Button Network\n", + "\n", + "This is a demonstration on how to create a model of randomly connecting buttons with the [agentpy](https://agentpy.readthedocs.io) package. It shows how to work with networks and how to visualize averaged time-series for discrete parameter samples. A similar model has been built by Wybo Wiersma in [Agentbase](http://agentbase.org/model.html?f4c4388138450bdf9732), allowing for a comparison between the two frameworks. The idea for the model is based on the following analogy from [Stuart Kauffman](http://www.pbs.org/lifebeyondearth/resources/intkauffmanpop.html): \n", + "\n", + "> \"Suppose you take 10,000 buttons and spread them out on a hardwood floor. You have a large spool of red thread. Now, what you do is you pick up a random pair of buttons and you tie them together with a piece of red thread. Put them down and pick up another random pair of buttons and tie them together with a red thread, and you just keep doing this. Every now and then lift up a button and see how many buttons you've lifted with your first button. A connective cluster of buttons is called a cluster or a component. When you have 10,000 buttons and only a few threads that tie them together, most of the times you'd pick up a button you'll pick up a single button. \n", + ">\n", + ">As the ratio of threads to buttons increases, you're going to start to get larger clusters, three or four buttons tied together; then larger and larger clusters. At some point, you will have a number of intermediate clusters, and when you add a few more threads, you'll have linked up the intermediate-sized clusters into one giant cluster.\n", + ">\n", + ">So that if you plot on an axis, the ratio of threads to buttons: 10,000 buttons and no threads; 10,000 buttons and 5,000 threads; and so on, you'll get a curve that is flat, and then all of a sudden it shoots up when you get this giant cluster. This steep curve is in fact evidence of a phase transition.\n", + ">\n", + ">If there were an infinite number of threads and an infinite number of buttons and one just tuned the ratios, this would be a step function; it would come up in a sudden jump. So it's a phase transition like ice freezing.\n", + ">\n", + ">Now, the image you should take away from this is if you connect enough buttons all of a sudden they all go connected. To think about the origin of life, we have to think about the same thing.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Import libraries\n", + "\n", + "import agentpy as ap\n", + "import networkx as nx\n", + "import seaborn as sns\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the model\n", + "\n", + "class button_model(ap.model):\n", + " \n", + " def setup(self):\n", + " \n", + " # Create a graph with n agents\n", + " self.add_network( 'buttons' )\n", + " self.buttons.add_agents( self.p.n )\n", + " self.threads = 0\n", + " \n", + " def update(self):\n", + " \n", + " # Record size of the biggest cluster\n", + " clusters = nx.connected_components(self.buttons.graph)\n", + " max_cluster_size = max([len(g) for g in clusters]) / self.p.n\n", + " self.record('max_cluster_size', max_cluster_size)\n", + " \n", + " # Record threads to button ratio\n", + " self.record('threads_to_button', self.threads / self.p.n)\n", + " \n", + " def step(self):\n", + " \n", + " # Create random edges based on parameters\n", + " for _ in range( int(self.p.n * self.p.speed) ): \n", + " self.buttons.add_edge(*self.agents.random(2))\n", + " self.threads += 1 " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Define parameter ranges\n", + "\n", + "parameter_ranges = {\n", + " 'steps': 30, # Number of simulation steps\n", + " 'speed': 0.05, # Speed of connections per step\n", + " 'n':(100,500,1000,10000) # Number of agents, given in a discrete range\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Scheduled runs: 100\n", + "Completed: 100, estimated time remaining: 0:00:00\n", + "Run time: 0:00:50.043113\n", + "Simulation finished\n" + ] + } + ], + "source": [ + "# Perform simulation\n", + "\n", + "sample = ap.sample_discrete(parameter_ranges) # Create sample for different values of n\n", + "exp = ap.exp(button_model, sample, iterations = 25, record = True) # Keep dynamic variables\n", + "results = exp.run() # Perform 100 seperate simulations ( 4 parameter combinations * 25 repetitions )" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Plot results\n", + "\n", + "data = results.arrange(('variables','parameters')) # Create plotting data\n", + "ax = sns.lineplot(data=data, x='threads_to_button', y='max_cluster_size', hue='n')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/Agentpy_Forest_Fire.ipynb b/docs/Agentpy_Forest_Fire.ipynb new file mode 100644 index 0000000..238d1d9 --- /dev/null +++ b/docs/Agentpy_Forest_Fire.ipynb @@ -0,0 +1,21105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forest Fire" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a demonstration on how to simulate a forest fire with the [agentpy](https://agentpy.readthedocs.io) package. It shows how to to work with a spacial grid, create animations, and perform a parameter sweep. The [original model](http://ccl.northwestern.edu/netlogo/models/FireSimple) has been designed in Netlogo by Uri Wilensky and William Rand (2015), who describe it as follows:\n", + "\n", + "> \"This model simulates the spread of a fire through a forest. It shows that the fire's chance of reaching the right edge of the forest depends critically on the density of trees. This is an example of a common feature of complex systems, the presence of a non-linear threshold or critical parameter. [...] \n", + ">\n", + "> The fire starts on the left edge of the forest, and spreads to neighboring trees. The fire spreads in four directions: north, east, south, and west.\n", + ">\n", + ">The model assumes there is no wind. So, the fire must have trees along its path in order to advance. That is, the fire cannot skip over an unwooded area (patch), so such a patch blocks the fire's motion in that direction.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Import libraries\n", + "\n", + "import agentpy as ap\n", + "import matplotlib.pyplot as plt # Plotting library\n", + "import seaborn as sns # Statistical data visualization\n", + "from IPython.display import HTML # To display animations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model definition" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class forest_model(ap.model):\n", + " \n", + " def setup(self):\n", + " \n", + " # Create grid (forest)\n", + " x = y = self.p.size # Access parameter\n", + " self.add_grid('forest', shape=(x,y))\n", + " \n", + " # Create agents (trees) \n", + " n_trees = int(self.p.density * x * y)\n", + " self.forest.add_agents(n_trees, random=True)\n", + " \n", + " # Initiate a dynamic variable for all trees\n", + " # Condition 0: Alive, 1: Burning, 2: Burned\n", + " self.agents.condition = 0 \n", + " \n", + " # Start a fire from the left side of the grid\n", + " unfortunate_trees = self.forest.area([(0,x),(0,0)])\n", + " unfortunate_trees.condition = 1 \n", + " \n", + " def step(self):\n", + " \n", + " # Select burning trees\n", + " burning_trees = self.agents.condition == 1\n", + "\n", + " # Spread fire \n", + " for agent in burning_trees:\n", + " for neighbor in agent.neighbors().condition == 0:\n", + " neighbor.condition = 1 # Neighbor starts burning\n", + " agent.condition = 2 # Tree burns out \n", + " \n", + " # Stop simulation if no fire is left\n", + " if len(burning_trees) == 0: self.stop()\n", + " \n", + " def end(self):\n", + " \n", + " # Document a measure at the end of the simulation\n", + " burned_trees = len(self.agents.condition == 2) / len(self.agents)\n", + " self.measure('Percentage of burned trees', burned_trees )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single-run animation" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Define parameters\n", + "\n", + "parameters = {\n", + " 'density': 0.6, # Percentage of grid covered by trees\n", + " 'size': 100 # Height and length of the grid\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create single-run animation\n", + "\n", + "def animation_plot(model,ax):\n", + " \n", + " color_dict = { 'empty': 'w', 0:'g' , 1:'r', 2:'grey'}\n", + " ap.gridplot(model,ax,'forest','condition',color_dict)\n", + "\n", + "fig, ax = plt.subplots(figsize=(7,7)) \n", + "animation = ap.animate(forest_model, parameters, fig, ax, animation_plot)\n", + "HTML(animation.to_jshtml())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parameter sweep" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare parameter sample\n", + "# Aranges 50 values for density from 0.1 to 1\n", + "\n", + "parameter_ranges = {\n", + " 'density': (0.1,1), \n", + " 'size': 100 \n", + " }\n", + "\n", + "sample = ap.sample(parameter_ranges, N = 50)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Scheduled runs: 2500\n", + "Completed: 2500, estimated time remaining: 0:00:00\n", + "Run time: 0:16:35.308748\n", + "Simulation finished\n", + "Data saved to ap_output/forest_model_4\n" + ] + } + ], + "source": [ + "# Perform experiment\n", + "# Repeats simulation 50 times for each value of density\n", + "\n", + "exp = ap.exp(forest_model, sample, iterations = 50)\n", + "results = exp.run()\n", + "results.save()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading from directory ap_output/forest_model_4/\n", + "Loading log.json - Successful\n", + "Loading measures.csv - Successful\n", + "Loading parameters_fixed.json - Successful\n", + "Loading parameters_varied.csv - Successful\n" + ] + } + ], + "source": [ + "# Load saved output data\n", + "\n", + "results = ap.load('forest_model')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Plot sensitivity\n", + "# Every point shows average over 50 runs\n", + "\n", + "data = results.arrange(('measures','parameters')) # Create plotting data\n", + "ax = sns.lineplot(data=data, x='density', y='Percentage of burned trees')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "Wilensky, U. & Rand, W. (2015). Introduction to Agent-Based Modeling: Modeling Natural, Social and Engineered Complex Systems with NetLogo. Cambridge, MA. MIT Press." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/Agentpy_Virus_Spread.ipynb b/docs/Agentpy_Virus_Spread.ipynb new file mode 100644 index 0000000..d7b50ee --- /dev/null +++ b/docs/Agentpy_Virus_Spread.ipynb @@ -0,0 +1,39228 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Virus Spread" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a demonstration on how to use the [agentpy](https://agentpy.readthedocs.io) package to build and analyze an agent-based model that simulates the propagation of a disease. It shows how to create and visualize networks, plot interactive output, and perform a sobol sensitivity analysis. \n", + "\n", + "In the model, agents can be in one of three conditions: susceptible to the disease (S), infected (I), or recovered (R). They are connected to other agents through a small-world network of peers. At every time-step, infected agents can infect their peers or recover from the disease based on random chance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To start, we import agentpy as follows (recommended):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import agentpy as ap\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We define a new agent type `human` by creating a subclass of `ap.agent`. The method `setup()` is called automatically when an agent is created, and initializes our variable `condition`. The custom method `being_sick()` causes the agent to spread the disease to its peers and/or recover based on random chance. There are three tools that are used within this class:\n", + "\n", + "- `agent.p` is used to access model parameters\n", + "- `agent.neighbors()` returns a list of the agents' peers in the network\n", + "- `random()` returns a uniform random draw between 0 and 1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from random import random\n", + "\n", + "class human(ap.agent):\n", + " \n", + " def setup(self): \n", + " \n", + " self.condition = 0 # Susceptible = 0, Infected = 1, Recovered = 2\n", + " \n", + " def being_sick(self):\n", + " \n", + " # Infect susceptible peers if succesfull\n", + " for peer in self.neighbors('peer_network'): \n", + " if peer.condition == 0 and self.p.infection_chance > random():\n", + " peer.condition = 1\n", + " \n", + " # Recover if succesfull\n", + " if self.p.recovery_chance > random(): \n", + " self.condition = 2 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we define our model `virus_model` by creating subclass of `model` with five methods:\n", + "\n", + "- `model.setup()` initializes the agents and network of the model \n", + "- `model.step()` and defines the models' events per simulation step\n", + "- `model.update()` records variables after setup and each step\n", + "- `model.stop_if()` stops the simulation if it returns `True`\n", + "- `model.end()` records evaluation measures at the end" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import networkx as nx\n", + "\n", + "class virus_model(ap.model):\n", + " \n", + " def setup(self):\n", + " \n", + " # Transform integer parameters to `int`\n", + " n = self.p.population = int(self.p.population)\n", + " nn = self.p.number_of_neighbors = int(self.p.number_of_neighbors)\n", + " \n", + " # Create a small-world network with a networkx generator function\n", + " small_world = nx.watts_strogatz_graph(n, nn, self.p.network_randomness)\n", + " self.add_network('peer_network', graph = small_world ) \n", + " \n", + " # Add human agents to network and map them to existing nodes\n", + " self.peer_network.add_agents(n, human, map_to_nodes = True)\n", + " \n", + " # Infect a random share of the population\n", + " I0 = int(self.p.initial_infections * n)\n", + " self.agents.random(I0).condition = 1 \n", + "\n", + " def update(self): \n", + " \n", + " # Susceptible, Infected, Recovered\n", + " conditions = ('S','I','R')\n", + " \n", + " # Count agents with each condition\n", + " for i,c in enumerate(conditions):\n", + " self[c] = len( self.agents.condition == i ) / self.p.population\n", + " \n", + " # Record variables for later analysis\n", + " self.record(conditions) \n", + " \n", + " def step(self): \n", + " \n", + " # Call 'being_sick' for infected agents\n", + " (self.agents.condition == 1).being_sick()\n", + " \n", + " def stop_if(self): \n", + " \n", + " # Stop simulation if disease is gone\n", + " return self.I == 0\n", + " \n", + " def end(self): \n", + " \n", + " # Record final evaluation measures\n", + " self.measure( 'Total Infection Rate', self.I + self.R ) \n", + " self.measure( 'Peak Infection Rate', max(self.log['I']) )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The formulation in `step()` uses the operator functions of `attr_list` that are inhereted by `agent_list`. The first element returns a list of agents where the condition is true, while the second one forwards the method call to all agents in the list.\n", + "\n", + " (self.agents.condition == 1).being_sick()\n", + "\n", + "One could achieve the same effect with the two following alternatives:\n", + "\n", + " self.agents.select('condition',1).do('being_sick')\n", + "\n", + " for agent in self.agents:\n", + " if agent.condition == 1:\n", + " agent.being_sick()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running a simulation\n", + "\n", + "To run our model, we define a dictionary with our `parameters`. We then create a new instance of our model, passing the parameters as an argument, and use the method `model.run()` to perform the simulation and return it's `output`. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Completed: 64 steps\n", + "Run time: 0:00:00.157910\n", + "Simulation finished\n" + ] + } + ], + "source": [ + "parameters = {\n", + " \n", + " 'population':1000,\n", + " 'infection_chance':0.3,\n", + " 'recovery_chance':0.1,\n", + " 'initial_infections':0.1,\n", + " 'number_of_neighbors':2,\n", + " 'network_randomness':0.5\n", + " \n", + "}\n", + "\n", + "model = virus_model(parameters)\n", + "results = model.run() # returns model.output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analyzing results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results are given as a dictionary of recorded data and pandas dataframes:\n", + "\n", + "- `log` holds information about the model and simulation performance.\n", + "- `model_vars` holds a dataframe with dynamic model variables that are recorded at different time-steps.\n", + "- `measures` holds a dataframe with evaluation measures that are recoreded only once per simulation." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "data_dict {\n", + "'log': Dictionary with 4 keys\n", + "'parameters': Dictionary with 6 keys\n", + "'measures': DataFrame with 2 variables and 1 row\n", + "'variables': DataFrame with 3 variables and 65 rows }" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To visualize the evolution of our `model_vars`, we create a matplotlib plot function `virus_stackplot()`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def virus_stackplot(data,ax):\n", + " \n", + " x = data.index.get_level_values('t')\n", + " y = [data[var] for var in ['I','S','R']]\n", + " \n", + " ax.stackplot(x, y, labels=['Infected','Susceptible','Recovered'], colors = ['r','b','g']) \n", + " \n", + " ax.legend()\n", + " ax.grid(False)\n", + " ax.set_xlim(0,max(1,len(x)-1))\n", + " ax.set_ylim(0,1)\n", + " ax.set_xlabel(\"Time steps\")\n", + " ax.set_ylabel(\"Percentage of population\")\n", + "\n", + "sns.set() # Set seaborn style\n", + "fig, ax = plt.subplots() # Initialize plot\n", + "virus_stackplot(results.variables, ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Animating a simulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also observe the development of our model over time. We define a function `animation_plot()` that takes a model instance and displays the previous stackplot together with a network graph for a passed model instance. The function `animate()` will call this plot function for every time-step and return a matplotlib animation object." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def animation_plot(m,axs):\n", + " \n", + " ax1,ax2 = axs\n", + " \n", + " # Plot stackplot on first axis\n", + " virus_stackplot(m.output.variables, ax1)\n", + " \n", + " # Plot network on second axis\n", + " color_dict = { 0 : 'b', 1 : 'r', 2 : 'g' }\n", + " colors = [ color_dict[c] for c in m.agents.condition ]\n", + " nx.draw_circular(m.peer_network.graph, node_color=colors, node_size=50, ax=ax2)\n", + "\n", + "sns.set() # Set seaborn style\n", + "fig, axs = plt.subplots(1,2,figsize=(8,4)) # Prepare figure \n", + "parameters['population'] = 50 # Reduce population for better visibility \n", + "animation = ap.animate(virus_model, parameters, fig, axs, animation_plot)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In Jupyter, we can use `.to_jshtml()` and `HTML` to display the animation object directly." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import HTML\n", + "HTML(animation.to_jshtml()) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interactive parameter variation\n", + "\n", + "To experiment with different parameter values, we define a dictionary `param_ranges`, where we declare tuples with a minimum and maximum value for parameters that we want to vary, as well as a function `interactive_plot()` that takes the model output and displays both our measures and variables." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "param_ranges = {\n", + " \n", + " 'population':(100,1000),\n", + " 'infection_chance':(0,1),\n", + " 'recovery_chance':(0,1),\n", + " 'initial_infections':0.1,\n", + " 'number_of_neighbors':2,\n", + " 'network_randomness':(0,1)\n", + " \n", + "}\n", + "\n", + "def interactive_plot(output):\n", + " \n", + " # Display measures\n", + " print(output.measures)\n", + " \n", + " # Display model_vars\n", + " fig,ax = plt.subplots()\n", + " virus_stackplot(output.variables,ax) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In Jupyter, we can then use the function `interactive()` to display our plot with a widget to change parameter values." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b57aa65d8e954ac5ab36b1f3d42c02dd", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(VBox(children=(FloatSlider(value=450.0, description='population', layout=Layout(width='300px'),…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ap.interactive(virus_model,param_ranges,interactive_plot)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment with multiple runs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To look into parameter variations in more detail, we use `sample_saltelli()` create a sample of different parameter combinations. We then use `exp()` to perform an experiment that runs our model repeatedly over the whole sample." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Scheduled runs: 10000\n", + "Completed: 10000, estimated time remaining: 0:00:00\n", + "Run time: 0:21:12.066373\n", + "Simulation finished\n" + ] + } + ], + "source": [ + "sample = ap.sample_saltelli(param_ranges, N=1000)\n", + "exp = ap.experiment(virus_model, sample)\n", + "results = exp.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can save and load agentpy output data with `save()` and `load()`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data saved to ap_output/virus_model_1\n" + ] + } + ], + "source": [ + "results.save() # Alternative: ap.save(results)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading from directory ap_output/virus_model_1/\n", + "Loading log.json - Successful\n", + "Loading measures.csv - Successful\n", + "Loading parameters_fixed.json - Successful\n", + "Loading parameters_varied.csv - Successful\n", + "Loading variables.csv - Successful\n" + ] + } + ], + "source": [ + "results = ap.load('virus_model') # Load data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The measures in our data_dict now hold one row for each simulation run." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "data_dict {\n", + "'log': Dictionary with 5 keys\n", + "'measures': DataFrame with 2 variables and 10000 rows\n", + "'parameters': data_dict {\n", + " 'fixed': Dictionary with 2 keys\n", + " 'varied': DataFrame with 4 variables and 10000 rows }\n", + "'variables': DataFrame with 3 variables and 645901 rows }" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use standard functions of the pandas library like `pandas.DataFrame.hist()` to look at summary statistics." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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jo4GoHv37mzfvr3OfZ07d46vvvqKl156yb5+aGgoH330EUOHDsXFxQVfX197LD/99FOd71XVZHT06FGmTJnCoEGD6N27d6POy4cffsiRI0eYMGGCvezs2bP89NNPXHfddXXuW5quuLiYkJAQACoqKrj++ut5+umn6dWrl/3zqu3ziI6O5tNPP+X5558nOzubEydOcO7cOft6jzzyCD179sRqtTYojsGDB9OzZ0/gQl179913a6xzufqxZ88eFi1ahLOzM87OzmzcuBG4cPVRm48++ohXX30VJycnOnbsyIQJE/jP//xPpk+fDmBvUh08eDClpaWcO3eu2tVPlaomo9OnT/PII49w44038pvf/AaAYcOG8ctf/pLNmzdz9OhRPv/8c7p161bjPaquRB5++GF7mZOTEz/88EObN9spIdTCZrPh5ORUbbm8vLzGen/9619JTU0lIiICq9XKdddd1+gbYZ07d7b/7OTkhKllaCmbzVbtMra0tNSeOCwWS7VYjx49yvXXX1/ncV36/hcfm4uLCx06dLDH0hC9e/dm1apVTJ48maFDh3Lbbbc1+LzYbDZCQkLsCc1ms3HixAl705M0v0vvIVzscp9HVFQUFRUVjB49mnvvvZfjx49Xq0sJCQn85S9/4eWXX2bq1KkNiqPK5ep9XfFcWu+PHz9e7T1re6/L/U5X/fGvWqe2eC7Wo0cPnnnmGYKDgxk2bBh+fn58+OGHLF++nClTpnD//ffTr18/MjIyao3F29ubZ555plr87u7ul91na7imnzKqyz333MPrr79u/waUkpLCHXfcQceOHXF2drZXpE8++YQHHniA8ePH07dvX3bt2kVFRUWzx3P33XezdetWCgsLAXj22WdZsGABAN7e3rz++usAFBQU8NBDD5GdnV0tziqurq4MHTqUTZs22ddPS0vjd7/73RXF96//+q+MHTuWJ554ApvNdtnzcnFcd999N2+99RYnTpwA4NVXX+Whhx66olik6S73eXzyySfMmjWLwMBAAPbt21etrnt6erJy5UrWr1/PP/7xjxaPx9vbm23btmGz2SgtLWXu3LlkZWXVWu+r3mvjxo0YYygtLSU1NfWK633v3r2ZMWMGy5cv59y5c3z66aeMGDGC8PBwhgwZwnvvvVdrvff29ubTTz/lu+++A2D37t2MGTOmSU8TNjddIdRi3Lhx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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "results.measures.hist()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sensitivity analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `sensitivity()` calculates sobol sensitivity indices for the passed results and parameter ranges, using the [SAlib]( https://salib.readthedocs.io/en/latest/basics.html) package. This adds two new categories to our results:\n", + "\n", + "- `sensitivity` - first-order sobol sensitivity indices\n", + "- `sensitivity_conf` - confidence ranges for the above indices" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
S1ST
measureparameter
Total Infection Ratepopulation-0.0048690.019708
infection_chance0.7140470.805062
recovery_chance0.1947950.271377
network_randomness-0.0058120.020597
Peak Infection Ratepopulation-0.0070280.016434
infection_chance0.1952670.329308
recovery_chance0.6496900.789410
network_randomness0.0038610.021419
\n", + "
" + ], + "text/plain": [ + " S1 ST\n", + "measure parameter \n", + "Total Infection Rate population -0.004869 0.019708\n", + " infection_chance 0.714047 0.805062\n", + " recovery_chance 0.194795 0.271377\n", + " network_randomness -0.005812 0.020597\n", + "Peak Infection Rate population -0.007028 0.016434\n", + " infection_chance 0.195267 0.329308\n", + " recovery_chance 0.649690 0.789410\n", + " network_randomness 0.003861 0.021419" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ap.sensitivity( results, param_ranges )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use the pandas functionalities to create a bar plot `plot_sobol_indices()` that visualizes our sensitivities." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlUAAAGtCAYAAAA/L4FbAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAABGz0lEQVR4nO3dfXzO9f////sxa7M5pzlJ8qGwChEh8RbKyTg2QyWiUk7KUsrJu0JyUs76Efoqvp28IyLnS28JpbLSeXrLSN7Ota3mfJudHM/fH76Oj2Vs47XjdRx73a6Xi4u9Xq/jOF6P17HjuO/xOncZY4wAAABwRYLsLgAAAKA4oKkCAACwAE0VAACABWiqAAAALEBTBQAAYAGaKgAAAAvQVAWQgwcP6sYbb1RMTIz3X3R0tJYtW3ZFr1uvXj2lpqZe8jFbt25V165d832tHTt26K677lL37t118ODBQteybds2jR07VpL0yy+/aOjQoYV+jYvp27ev2rVr533v3G63OnbsqFWrVhWqLgCXZ+LEid7vX/369dWxY0fvcEZGRp7P+eyzz/Tqq6/m+9p9+/bVunXrLhi/YsUKDRo0KN/nf/nll2rbtq169ux50Vou5fw6N27cqIkTJxb6NS6mXbt23veqW7duioqKUteuXfX5558Xqi4UvWC7C0DhlCxZUqtXr/YOJyUlqWvXrqpfv74iIyNtrOysjRs3qnnz5po0adJlPX/37t1KSkqSJDVo0ECzZs2ysjyNHDlSnTp18g7/8ssvuv/++3XXXXepdOnSBaoLwOUZPXq09+d27dpp+vTpatCgwSWf88svv+j48eNFXZrWrl2re+65R48//vhlPf/8Otu3b6/27dtbWd4F79W6dev03HPP6csvvyxwXSh6NFUBrkqVKqpZs6b27t2ryMhIffDBB1q8eLE8Ho/Kly+vMWPG6Prrr9d///tfjR8/XqdPn1ZKSooiIyM1c+ZMhYaGel8rJSVFDz/8sO6//3716dPnovNcsWKFPvnkEwUFBWnfvn0qWbKkpkyZou3bt2vx4sXKyclRRkaGXnnllYvWc/r0aU2cOFE//PCDSpQoobvuukv333+/Zs2apZMnT+rZZ59Vt27dNGHCBH344Yc6efKkXnzxRSUmJsrlcql169Z6+umnFRwcrAYNGmjgwIHasmWLkpOT9eijj6p3794Fev8OHDig8PBwhYSEyOPx6KWXXtLPP/+s06dPyxijiRMn6pprrslV18svv6xNmzZp7ty5ysrKUsmSJTVq1Cg1btz4in+fgFO99tprWrt2rUqUKKFatWppzJgxOnz4sN5//33l5OSoTJkyGjRokMaNG6d9+/bp2LFjKlWqlKZPn67atWsXaB6zZ8/WoUOHlJKSokOHDqlKlSqaNm2a1qxZo40bNyo0NFQnT57UqFGjNHfuXK1fv14ej0fVq1fXCy+8oCpVqiglJUUvvPCC9uzZo6CgIPXq1Uu33HJLrjpr1qypjz/+WG+88Yb++OMPjRs3TocOHZIxRt26ddOjjz6qgwcP6qGHHlKbNm30888/68SJExoxYoTuvvvufJfDGKODBw+qXLlykqS0tLQ835eTJ0/mqmvYsGEXzWRYxCBgHDhwwDRq1CjXuB9++MHcdttt5vDhw2br1q2md+/eJi0tzRhjzBdffGE6depkjDFm8uTJZtWqVcYYYzIzM03Xrl3NunXrjDHG1K1b1/z6668mKirKrF69Os95f/3116ZLly7GGGOWL19umjRpYo4cOWKMMWb8+PFm5MiRxhhjZs2aZV588UVjjLlkPS+99JIZNmyYyc7ONmfOnDF9+vQxX3/9tVm+fLkZOHDgBfMcOXKkmTBhgvF4PObMmTOmf//+5o033vDWv2DBAmOMMb/88oupX7++ycjIuGAZHnjgAdO2bVsTHR1t7rzzTnP77bebYcOGme3bt3vfyyeeeMLk5OQYY4x54403zKBBg7zLfK6u//73v6Zr164mNTXVGGPMrl27zB133GFOnz59qV8fgPO0bdvWbNu2zRhjzLJly8x9993n/Q7NmjXL9O/f3/vzuUz597//bSZMmOB9jTFjxpjx48cbY85+v//9739fMJ/zv7uzZs0y7du3NydPnjTGGDNo0CDz6quvGmOMGTVqlPm///f/GmOMWblypXnqqadMVlaWMcaY999/3zz66KPGGGOGDBlipkyZYowx5sSJE6ZLly5m7969ueo8f559+vQxb731lvfxbrfbfPjhh+bAgQOmbt26ZtOmTcYYY9atW2fuvPPOi75XHTp0MG6327Ru3dq0bt3aPPvss2b//v35vi8FzWRYgy1VASYjI0MxMTGSpJycHFWoUEHTpk1TtWrVtGDBAu3bt0+9evXyPv7EiRM6duyYRowY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OTO/JDIsEPKL3X8OFB0drTVr1uQaFxMT471abXGV13LHxsbmu1uhODl06FCe46tXr+7jSoDL49T8ksiwQMgvx+7+e/zxx3XrrbeqWbNmGj58uFJTU+0uy2dcLpd27tzpHf79999znUlRXJUsWVKbN2/2DickJCgsLMzGinzvmmuu0ebNmzVlyhRNmjRJGzduVLVq1ewuC4VAfjkzvyQyLBDyy5Fbqnr37q2oqCh169ZNHo9HK1as0JYtWzR//ny7S/OJhIQEjRgxQlWqVJEkHT16VNOmTVPTpk1trqxoJSYmavjw4UpJSZHL5VLVqlU1bdo01alTx+7SfGbKlCnat2+fevToIWOMVqxYoerVq+v555+3uzQUEPnlzPySyLCAyC/jQG63u0DjirMzZ86YX375xezYscOcOXPGO/7999+3sSrfSE1NNSdPnsw1btasWTZV41tut9vk5OR4h7OyskynTp1srAiFRX45O7+McW6GBUJ+OXL3X+PGjXPtf//ss89000032ViR74WEhKh+/fqKjIzMdRG9999/38aqfKNChQoqXbp0rnGbNm2yqRrfysnJUXZ2dq7h86/1A/9Hfjk7vyTnZlgg5JczdkT/zSeffKIlS5Zo7NixCgoKUnp6uiRp1apVcrlc2rFjh80V2sc4b2+wJOcst9vtVr9+/bxnEK1du9b7MwID+XVxTvke58UJyx4I+eXIpiohIcHuEvxWXre/cAKnLPfgwYN100036auvvpIxRoMHD9add95pd1koBPLr4pzyPc6LE5Y9EPLLkU1Venq65syZo6+++ko5OTlq0aKFnnzySYWHh9tdGlAkvv32W+/PYWFhateuXa5p/nTvLFwa+QWnCaT8cmRTNX78eIWFhemll16SJC1dulQvvPCCpk2bZnNlQNGYNWuWJOnYsWM6cOCAGjdurKCgIP3444+qW7euY45FKQ7ILzhNIOWXI5uq7du357qA2tixYxUVFWVjRf6jTJkydpdQZFJSUhQREZHntOuvv97H1fjWggULJEkDBgzQnDlzVLNmTUlnL6Y3duxYO0tDIZFfF1ec80tyboYFUn45sqkyxujEiRPeO7ufOHHC784gKEonTpxQfHy8jh07luvgxri4OL+627fVHnjgAdWsWVOxsbFq3759rrOGpk+fbmNlvnP48GFvIElnL6Z3+PBhGytCYZFf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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def plot_sobol_indices(results):\n", + " \n", + " sns.set()\n", + " fig, axs = plt.subplots(1,2,figsize=(10,5))\n", + "\n", + " SI = results.sensitivity.groupby(by='measure')\n", + " SIT = results.sensitivity_conf.groupby(by='measure')\n", + "\n", + " for (key,si),(_,err),ax in zip(SI, SIT, axs):\n", + "\n", + " si = si.droplevel('measure')\n", + " err = err.droplevel('measure')\n", + " si.plot.bar(yerr=err,title=key,ax=ax)\n", + " \n", + "plot_sobol_indices(results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, we can also display the sensitivities by plotting average evaluation measures over our parameter variations. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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cP78eahUKrRu3RqTJk2CRqNB7dq1MXLkSPz666/IycnBpEmTEBkZiR07diA2NharV68GgEde59u+fTu2bNkCo9GIjIwMjBw5EoMGDcJ7770HvV6Pnj17YseOHahduzYOHz6McuXKYdWqVfj222/h4+ODqlWr4v3330doaCiGDh2KZ555BidOnMCdO3fQvHlzzJs3D2o183QiserVq4cVK1bY/czOnTsRFRWFnj17urSP48eP4+mnnwYADBw40KVtEOVjQlPC/fnnn5gyZQqee+45bNq0CZMnT8bTTz+N4OBg7NmzB0ajEW+++SY+++wzvPHGGzCbzQgICMCOHTtw/vx5DBkyBM8995xT+8rOzsa2bduwZs0ahISE4I8//sCrr76KQYMGYcGCBejevTt27dpVYJ2vvvoKv/76K7Zv347AwECsXLmywC2s69evY/369cjJyUHnzp0RHx+PZs2aSf49EZU0R44cwbx581C3bl0EBQXhwoULSEhIQI0aNbBo0SIsX74cp0+fxs2bN5GWloZBgwZhyZIlOHr0KMxmM2rXro0ZM2YgKCgIf//9N2bOnInU1FSo1Wq8+eab0Gq1+PHHH3Hw4EH4+/sjNTUVaWlpmDlzJi5duoS5c+ciPT0dKpUKr732Gnr16oUjR44gJiYGlStXxqVLl2AymTBnzhw0atTIbllOnjyJDz74ADqdDlqtFu+++6510t+VK1fi5MmTSE9Px4gRIzB48GDk5ORg9uzZuHbtGtLT01GqVCksWbIE1apVs3sh9dNPP2HZsmWwWCwIDAzEnDlzULNmTZw4cQJLliyBTqeDWq3GuHHj8MILLxTHz1ii8FK2hKtZs6Y1Ienbty/OnTuHb775BkOGDIFKpYKvry8GDBiAX375xbrOkCFDrOtWr14dR48edWpfpUqVwn//+1/8/PPPWLZsGf773/8iJyfH7jq//PIL+vTpg8DAQADAsGHD8NtvvyE3NxdA3nxQarUaQUFBqFKlCjIyMor8HRCRfX/++SfWrl2L7777Drdu3cLevXsxbdo01K1bF++++y6GDx+ONWvWwMfHBzt27MDu3bsRFhaGJUuWAAAmTZqETp064dtvv8WaNWuwdOlSNG/eHO3atcPw4cMxePBg675MJhPefPNNDB06FHv27MEnn3yCpUuX4vfffwcAnDp1Cq+99hp27tyJPn36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Sz1TkuoYTk5JREREisc+NERERKR4TGiIiIhI8ZjQEBERkeIxoSEiIiLFY0JDREREiseEhjzWypUrMXfuXLnDICIiBWBCQ0RENm3btg0bNmyQfLtHjhxBt27dJN9uUa1duxZTp06VOwySiEbuAKjojhw5gujoaAQGBiI7OxtvvfUWVq9eDaPRCH9/f0yZMgUNGzaEyWTChx9+iP3798PHxwcNGzbErFmzoFKpsHDhQhw+fBg+Pj6oX78+3nvvPfzxxx9YtGgR9uzZAwC4d+8eXnzxRezbtw96vR5z587FnTt3YDQa0bVrV4wePRo3b97E4MGD8eSTT+LWrVvo1asXLl++jI8++ggAcOzYMXzwwQfYuXNnoeUpLE4AuHLlCoYOHYrk5GRUqFABS5cuRVhYGH766SesXr0aubm5SE1NRa9evTBx4kQcOXIEMTExqFy5Mi5dugSTyYQ5c+agUaNGyM7OxgcffIATJ07Ax8cH7du3R1RUFIxGI5YsWYKjR4/CbDajdu3amDFjBoKCgtz+WxJ5suPHj+Ppp5+WOwwipzChUahLly5h3759MBqNGD9+PL744guEhITg0qVLePXVVxEXF4ft27fjzJkz2LVrF3x9fTFp0iR89913uH79OpKSkrBr1y74+Phg+vTpWLx4MebMmYPs7GycPn0a9erVwzfffIO2bduibNmyGD9+PIYPH4527drBYDBg5MiR+Ne//oX69esjISEBH330EZ577jncvXsXkZGRSE9PR3BwMLZu3epw9taNGzfajBMAbty4gW3btqFcuXIYM2YMtm3bhjFjxuCzzz7DwoUL8cQTTyAxMREvvPAChg0bBgA4deoUZs2ahVq1auGzzz5DTEwMvvzyS6xYsQIGgwHfffcdzGYzXnvtNcTHx+Po0aPw8fHBjh07oFKpsHTpUixZsgSzZ892989IVGwKS/br1atnM6E/fPgwfvzxRxw8eBC5ublYs2YNDh48iMDAQMycORNXrlzBl19+CQCIjIzEf/7zH1gsFsydOxfp6elQqVR47bXX0KtXr0cuwt59911rXMeOHcM777yDpUuX4tlnny00/qlTpyI9PR03btzA888/j379+mHu3LnIzs5GcnIyatasiWXLlsHPzw/16tXDG2+8gYMHDyIpKQmvv/46Bg0aBKPRiA8++ACHDh1C+fLlUb58eZQuXRoAkJCQgNmzZ+PWrVsQBAG9evXC66+/jps3b+KVV15By5Yt8eeff8JsNmPChAnYsmULrly5grp162Lp0qW4ffs2hg8fjrZt2+LkyZO4d+8eJk+ebJ0t/T//+Q/i4uJgsVhQqVIlzJo1C+Hh4YiLi8N//vMfqFQq+Pj44N1330Xjxo0LfZ/skHmkYnLBb7/9Zh02/MsvvxSaNGlSYGj0Vq1aCefOnRNGjRolbN269ZH1+/btKxw4cMD6+syZM8Lzzz8vCIIg/Pvf/xbmzJlj/dyRI0eE7OxsoWbNmgX20b59e+Gjjz4Sbty4IdSuXVswGo3W7U2aNElYt26ddZ6QrKwsu+UpLM4VK1YI77//vvX18uXLhblz5wqCIAhZWVnCN998I6xcuVKYOHGiULNmTeHmzZvCb7/9Jrz44ovWdQ4fPix069ZNEARB6Natm3Dw4EGb30dkZKS1bJ07d1b0fCZEtvz2229CrVq1hLNnzwqCIAhr164VBg8eLKxcuVJYuHChYLFYBEEQhI8++kiYNWuWIAiCMGXKFOHTTz8VBEEQhg4dKvz444+CIAhCZGSk0KJFCyErK0u4dOmS0LlzZ8FoNAovvviiEBsbKwhC3jxCrVu3Fk6cOCH89ttv1r/R/Fi6du0qHD58WGjfvr11TiR7pkyZIrzyyivW1wsXLhR27twpCIIg5ObmCt26dRP27t0rCIIgVK9eXVi/fr0gCIJw+vRpoW7duoJerxfWrVsnDBs2TDAYDEJ2drbQu3dvYcqUKYIgCMLgwYOFzz77TBAEQbh3757QvXt34ZtvvhFu3LghVK9eXdi3b58gCIIwc+ZM4YUXXhAyMzMFvV4vtGzZUjh+/Lj1c/nf0d69e6316tdffy1MnDjRWk9u3rxZeP311wVBEIQXX3xR+P333wVBEIRff/1VWLlypd33qXBsoVGowMBAAHnTwzdv3hzLli2zLrtz5w7CwsKg0RT8eVNSUmCxWB6ZUt5iscBoNAIA+vXrh969e6N///7IzMxEkyZNkJWVBUEQsHnzZgQEBAAAUlNT4efnh7S0NPj6+hbY1+DBgzF79mxoNBpERkaiVKlSdstSWJwPL1OpVBAEATk5Oejduzfat2+P5557Dn379sW+ffsg3J/Fw9/f/5F18rf1YLnv3LkDf39/WCwWTJs2DW3btgUAZGdnw2Aw2I2ZSIkqVqyIWrVqAQBq166Nr7/+Gvv370dmZiYOHToEADAajShfvvwj63bo0AG//PIL/vWvfyE8PBzVq1fH0aNHceHCBURGRuLq1aswGAyIjIwEAISHhyMyMhK//vormjZtisceewyVKlWybi8hIQGjR4/GwIEDUbNmTafib9SokfX/J0+ejIMHD+KTTz7B1atXkZSUhJycHOvyF198EQBQp04d5ObmIicnB4cPH0a3bt3g6+sLX19fdO/eHRcuXEBOTg5OnDiBzz77DABQunRp9OnTB7/88gsaNGgArVaLdu3aAQD+9a9/oWHDhtZb0mFhYcjIyEBYWBi0Wq21HqlduzbS09MBAD/99BNOnz6Nvn37Asirc3U6HQCga9euGDduHNq2bYuWLVti5MiRdt+nwrFTsMI1b94cBw8exF9//QUA+Pnnn9GjRw/o9Xo0b94c33zzDXJzc2GxWDB79mx8++23aN26NTZt2gSj0QiLxYINGzagZcuWAPIqofr162PmzJno168fACAoKAjPPPMMPv/8cwB5fWsGDhyIH374wWZMzz77LNRqNdauXevwdlN+GWzFWZhr164hKysLEydORLt27XDkyBHruo728/XXX8NisSA3NxcTJkzA0aNH0apVK2zYsMG6jffffx9Lly51GDeR0thK9vMT+l27dmHXrl3Ytm0bli9f/si6+QnNgQMH0LJlS7Ro0QIHDhzAjz/+iE6dOsFsNhe4YAAAQRBgMpkA/HMRls/HxwefffYZvv76a5w8edKp+B/cxqRJk7B161ZUqlQJw4cPR506dawXLwDg5+dnLWd+LA/z8fEBkJdgPLzcYrFYY9dqtQXKptVqbcan1WqhVqsL7Dd/W6+//rr1O/7qq6+wadMmAEBUVBQ2btyIunXrYseOHRg8eLDd96lwTGgU7qmnnsLcuXMxadIk9OjRA8uXL8d//vMflCpVCgMGDECdOnXQp08fdO/eHaGhoRg6dCjefPNNVKhQAb169ULnzp1hMpkwffp06zb79++Pc+fOoXfv3tb3lixZgpMnT6J79+7o378/unXrhh49ehQaV58+fRAWFubUlVdhcRamRo0aeP7559G5c2d07twZP/30E5566ilcu3bN7n7GjRsHrVaLnj17olevXmjbti0iIyMxZswYVKpUCb1790aXLl0gCAKffKASw15C7+PjYz2pR0REICQkBJs3b0bLli3RqlUrxMXFIT09HTVr1kS1atWg0WgQFxcHAEhMTERsbCxatGhhc7+hoaF49tlnMWXKFLz77rvWFgtnHThwAGPHjkWXLl0AACdPnoTZbLa7TuvWrbFz504YDAZrfzog76KtQYMG1ie6MjMzsXPnzkJjL6pWrVph+/btyMrKAgAsX74c7777LkwmE9q1awedToeBAwdi1qxZuHDhAnJzcwt9nwrHW04K1LRpU3zzzTfW1/kn9ofldyR7sAMekHfrJf8pIltefPFF/PnnnwXee/zxx7F69epHPvv444/j999/L/CeyWTCoUOHrJ10HSkszvHjxxf6ev78+YVu78Hv5sHvKjAwENHR0Y983t/f3+73QeTNxowZg0WLFqF3794wm82oVauWNaFv06YNFi5cCAAYNWoUOnTogM8++wy1a9eGWq2Gv78/2rdvDyCvdeLjjz/GBx98gJUrV8JsNmPs2LFo1qwZjhw5Uuj+e/fujdjYWCxcuBBz5sxxOu6oqCiMHTsWgYGBCAoKQuPGjXH9+nW76wwYMADXr19Ht27dEBwcjCpVqliXLVmyBHPnzsWOHTuQm5uL7t27o0+fPrh165bTMRWmf//+SExMxEsvvQSVSoXHHnsMCxcuhEajwbRp0/DOO+9Yb4nPnz8fvr6+hb5PhVMJttrhiFx0+fJlDBw4EO3bt0d0dDTUajWysrIKbS4tVaoUNm7cWMxREhGRt2FCQ0REsrty5QqioqJsLqtatWqBBx+IbGFCQ0RERIrHTsFERESkeExoiIiISPGY0BAREZHiMaEhIiIixWNCQ0RERIr3/3S/bk6igmXCAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def virus_scatterplot(results):\n", + " \n", + " fig, axs = plt.subplots(2,2,figsize=(8,8))\n", + " \n", + " data = results.arrange(('measures','parameters'))\n", + " params = results.parameters.varied.keys() # List of varied parameter keys\n", + " axs = [i for j in axs for i in j] # Flatten axis list\n", + " \n", + " for x,ax in zip(params,axs):\n", + " for y in results.measures.columns:\n", + " sns.regplot(x=x, y=y, data=data, ax = ax, ci=99, x_bins=15, fit_reg=False, label=y)\n", + " \n", + " ax.set_ylim(0,1)\n", + " ax.set_ylabel('')\n", + " ax.legend()\n", + " \n", + " plt.tight_layout()\n", + "\n", + "virus_scatterplot(results)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/Agentpy_Wealth_Transfer.ipynb b/docs/Agentpy_Wealth_Transfer.ipynb new file mode 100644 index 0000000..1fead42 --- /dev/null +++ b/docs/Agentpy_Wealth_Transfer.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Wealth Transfer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is a demonstration on how to create a simple agent-based model with the [agentpy](https://agentpy.readthedocs.io) package. It shows how to create a custom agent and model class, run a model, and visualize output data. The model explores the distribution of wealth under a trading population of agents. The [original version of this model](https://mesa.readthedocs.io/en/master/tutorials/intro_tutorial.html) has been written in [MESA](https://mesa.readthedocs.io/) by the Project Mesa Team. This is an adaption of the same model to agentpy, allowing for a comparison between the two frameworks." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To start, we import agentpy as follows (recommended):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import agentpy as ap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each agent is set up as an object of `agent`. These objects hold the variables and possible actions of each agent. \n", + "\n", + "For our demonstration, we define a new agent type `wealth_agent`. Each agent starts with one unit of `wealth`. When `wealth_transfer` is called, the agent selects another agent at random and gives them one unit of their own wealth if they have one to spare." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class wealth_agent(ap.agent):\n", + " \n", + " \"\"\" An agent with wealth \"\"\"\n", + " \n", + " def setup(self):\n", + "\n", + " self.wealth = 1\n", + "\n", + " def wealth_transfer(self):\n", + " \n", + " if self.wealth > 0:\n", + " \n", + " partner = self.model.agents.random()\n", + " partner.wealth += 1\n", + " self.wealth -= 1 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For later analysis, we define a method `gini` that can measure the inequality of a passed list." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def gini(x):\n", + " \n", + " \"\"\" Calculate Gini Coefficient \"\"\"\n", + " # By Warren Weckesser https://stackoverflow.com/a/39513799\n", + " \n", + " mad = np.abs(np.subtract.outer(x, x)).mean() # Mean absolute difference\n", + " rmad = mad/np.mean(x) # Relative mean absolute difference\n", + " return 0.5 * rmad " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All agents are contained within an object of the class `model`. The model initializes with the above defined agents, the number of which will be taken from the models' parameters. In each time-step, `wealth_transfer` is activated for all agents in a random order, after which a model variable `Gini Coefficient` are recorded. At the end of the simulation, each agents `wealth` is also recorded." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "class wealth_model(ap.model):\n", + " \n", + " \"\"\" A simple model of random wealth transfers \"\"\"\n", + " \n", + " def setup(self):\n", + " \n", + " self.add_agents(self.p.agents, wealth_agent)\n", + " \n", + " def step(self):\n", + " \n", + " self.agents.do('wealth_transfer', order='random')\n", + " \n", + " def update(self):\n", + " \n", + " self.record('Gini Coefficient', gini(self.agents.wealth))\n", + " \n", + " def end(self):\n", + " \n", + " self.agents.record('wealth')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To run a simulation, we define a dictionary of parameters that defines the number of agents and the number of steps that the model will run." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "parameters = {\n", + " 'agents': 100,\n", + " 'steps': 100\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To perform a simulation, we initialize our model with these parameters and call the method `model.run`, which returns a `data_dict` of recorded data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Completed: 100 steps\n", + "Run time: 0:00:00.326001\n", + "Simulation finished\n" + ] + } + ], + "source": [ + "model = wealth_model(parameters)\n", + "results = model.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To visualize the evolution of our Gini Coefficient, we can use `pandas.DataFrame.plot`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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hx9+iIjw05/+lFtvvRWAp556in//+980NDRwww038Itf/IKFCxeSn5/Ptddey9e//nVeeuklSktLyczM5O233+bb3/42Tz/9NFlZWaxYsYL/9//+HzabjcTERD799FNeeeUVsrOzee655ygtLWXhwoUUFhYC8MwzzzB79uxWz++xxx4jLy+PzMxMLr30Up566qku/U4CMKA7cugRmkNX6ittz549TJkyxef99+/fz6pVq6ipqWH06NE88MADrdZg7969m6lTp3rd9s4775CTk8OOHTsoKytj2rRpzJs3j127dnHo0CE2b96MMYZrr72WNWvWsGjRIlasWMGqVatITExkxowZrg8ad6Wlpdx7772sWbOGtLQ0KioqWrz2I488wve+9z3mzJlDYWEhl19+Ofv27Wv1/J544gl2795NTk6Ozz+ntgRcQD9b5aIBXSlftdWT/rI8+OCDrFu3joiICLZs2dJi+1VXXUVkZCSRkZEkJydz8uRJUlI6fmfLdevWcdtttxEaGkr//v05//zz2bJlC2vWrOHjjz9m8uTJAJw+fZpDhw4xb948n467ceNG5s2b56oLj4+Pb7HPypUrPfLt1dXV1NTUtHp+/uZTQBeR+cAfcdwkeokx5olm28W5/UqgDrjLGLPNz20Fzk7915SLUl9t48eP5+2333Y9fv755ykrKyMrK8vr/pGRka6vQ0NDsVqtbR77rbfe8rrNkWb2/vyPf/xj7r//fl+a7/X72ysltNvtbNiwgV69erXY1pHz66x2u7kiEgo8D1wBjANuE5FxzXa7Ahjp/Hcf8KKf2+nS1EPXlItSX20XXXQR9fX1vPji2XBQV1fnt2M3NDTw0ksvuZ7bsmULq1evZt68ebzxxhvYbDZKS0tZs2YN06dP5/LLL+fll1925eOPHj1KSUmJz685c+ZMVq9ezeHDhwG8plwuu+wynnvuOdfj9lIpvXv3dvXg/cGXqDgdyDXG5BtjGoHXgeua7XMd8HfnIOxGIE5EBvqtlW6acujaQ1fqq01EeO+991i9ejVpaWlMnz6dO++8k9/+9rd+Ofa7777LJ598Qnp6OuPHj+fxxx9n0KBB3HDDDUyaNImMjAwuuuginnzySQYMGMBll13G7bffzsyZM5k4cSI333xzh4JpUlISixcv5sYbbyQjI8M10Oru2WefJTs7m0mTJjFu3DgWLVrU5jETEhKYPXs2EyZM4Ac/+EGHfw7NSWuXJ64dRG4G5htj7nE+/gYwwxjzkNs+y4AnjDHrnI8/BX5kjMludqz7cPTgGTJkyNSCgoION3hrQQV/WXeY/7l6HAP7trysUUo57Nu3j7Fjx/Z0M1QXePsdishWY4zXvJUvOXRvXeHmnwK+7IMxZjGwGCArK6vtT5JWTB0az9ShLQcjlFLqXOdLyqUYSHV7nAIc68Q+SimlupEvAX0LMFJE0kQkAlgALG22z1Lgm+JwHnDKGHPcz21VSnVQeylV9dXVmd9duykXY4xVRB4CPsJRtviyMWaPiCx0bl8ELMdRspiLo2zxWx1uiVLKr6KioigvL9cldAOQca6HHhUV1aHva3dQtLtkZWWZ7Ozs9ndUSnWK3rEosLV2x6KuDooqpQJQeHh4h+52owKfzs5RSqkgoQFdKaWChAZ0pZQKEj02KCoipUDHp4o6JAJlfmxOINBzPjfoOZ8bunLOQ40xSd429FhA7woRyW5tlDdY6TmfG/Sczw3ddc6aclFKqSChAV0ppYJEoAb0xT3dgB6g53xu0HM+N3TLOQdkDl0ppVRLgdpDV0op1YwGdKWUChIBF9BFZL6IHBCRXBF5rKfb0x1EJFVEVonIPhHZIyKPOJ+PF5FPROSQ8/9+Pd1WfxKRUBHZ7rwD1rlwvnEi8paI7Hf+rmeeA+f8Ped7ereI/EtEooLtnEXkZREpEZHdbs+1eo4i8mNnPDsgIpd35bUDKqD7eMPqYGAFvm+MGQucBzzoPM/HgE+NMSOBT52Pg8kjwD63x8F+vn8EVhhjxgAZOM49aM9ZRAYDDwNZxpgJOJbjXkDwnfMrwPxmz3k9R+ff9QJgvPN7XnDGuU4JqICObzesDnjGmOPGmG3Or2tw/KEPxnGuf3Pu9jfg+h5pYDcQkRTgKmCJ29PBfL59gHnAXwCMMY3GmCqC+JydwoBeIhIGROO4s1lQnbMxZg1Q0ezp1s7xOuB1Y0yDMeYwjntKTO/sawdaQB8MFLk9LnY+F7REZBgwGdgE9G+6E5Tz/+QebJq/PQP8ELC7PRfM5zscKAX+6kwzLRGRGIL4nI0xR4GngULgOI47m31MEJ+zm9bO0a8xLdACuk83ow4WIhILvA08aoyp7un2dBcRuRooMcZs7em2fInCgCnAi8aYyUAtgZ9qaJMzb3wdkAYMAmJE5Os926oe59eYFmgB/Zy5GbWIhOMI5v80xrzjfPqkiAx0bh8IlPRU+/xsNnCtiBzBkUa7SEReJXjPFxzv5WJjzCbn47dwBPhgPudLgMPGmFJjjAV4B5hFcJ9zk9bO0a8xLdACui83rA544rgB5F+AfcaY37ttWgrc6fz6TuD9L7tt3cEY82NjTIoxZhiO3+lnxpivE6TnC2CMOQEUicho51MXA3sJ4nPGkWo5T0Sine/xi3GMDwXzOTdp7RyXAgtEJFJE0oCRwOZOv4oxJqD+4bgZ9UEgD/hJT7enm85xDo7Lrp1AjvPflUACjhHyQ87/43u6rd1w7hcAy5xfB/X5AplAtvP3/B7Q7xw4518A+4HdwD+AyGA7Z+BfOMYILDh64N9u6xyBnzjj2QHgiq68tk79V0qpIBFoKRellFKt0ICulFJBQgO6UkoFCQ3oSikVJDSgK6VUkNCArpQb5wqI3+npdijVGRrQlfIUB2hAVwFJA7pSnp4A0kUkR0Se6unGKNUROrFIKTfO1S2XGcd63UoFFO2hK6VUkNCArpRSQUIDulKeaoDePd0IpTpDA7pSbowx5cAXzpsY66CoCig6KKqUUkFCe+hKKRUkNKArpVSQ0ICulFJBQgO6UkoFCQ3oSikVJDSgK6VUkNCArpRSQeL/A9cpfsCSathYAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "data = results.variables.model\n", + "ax = data.xs('model').plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To visualize the final distribution of wealth, we can use `pandas.DataFrame.hist`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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4LIrruHSXQcUd6wuOunxNnm3bV7LYv9fxmF6erpN3k5ZoVfeTllhZfKOchbK0cORN0kOA14HfB+4Fjh/WO184JplTSZJ2q9RDhHXA9uTrGG28K+qquuSfB24Ksk7gbuAt6xgTknSXhYs8Kr6LvDifSz/OXDaSoSSJC3MKzElqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqakFCzzJsUluTHJ7ktuSXDgsPzLJ9UnuGO6PWPm4kqQnjHIE/jhwUVW9AHgF8N4kLwQuBm6oquOBG4Z5SdJBsmCBV9V9VXXzMP0IcDvwbOAsYPuw2nbg7BXKKEnah1TV6CsnG4CvACcCd1XVujmPPVBVTxpGSbIF2AIwOTl5yo4dOxYVdM/9D7H7sUVtOhaTh9Em73GHr2FiYmLcMUY2MzPTJm+nrNArb6essLS8mzZt2llVU3svP2TUHSSZAK4G3l9VDycZabuq2gpsBZiamqqNGzeO+pT/z2VXXMOlu0aOO3YXnfR4m7zbNq9lsf8v4zA9Pd0mb6es0Ctvp6ywMnlHOgslyaHMlvcVVfX5YfHuJOuHx9cDe5Y1mSRpv0Y5CyXAJ4Dbq+ojcx66Fjh/mD4fuGb540mS5jPK3/inAucBu5LcOiz7EHAJcFWSdwJ3AW9ZkYSSpH1asMCr6mvAfAPepy1vHEnSqLwSU5KassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqaken7akFbXr3od4+8VfHHeMkW3bvHbcEaRVwSNwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWpqwQJP8skke5J8b86yI5Ncn+SO4f6IlY0pSdrbKEfg24DNey27GLihqo4HbhjmJUkH0YIFXlVfAe7fa/FZwPZhejtw9vLGkiQtJFW18ErJBuC6qjpxmH+wqtbNefyBqtrnMEqSLcAWgMnJyVN27NixqKB77n+I3Y8tatOxmDyMNnk7ZQU47vA1TExMjDvGSGZmZtpkhV55O2WFpeXdtGnTzqqa2nv5IUtOtYCq2gpsBZiamqqNGzcuaj+XXXENl+5a8bjL5qKTHm+Tt1NWgG2b17LYn6ODbXp6uk1W6JW3U1ZYmbyLPQtld5L1AMP9nuWLJEkaxWIL/Frg/GH6fOCa5YkjSRrVKKcRXgl8HTghyT1J3glcApye5A7g9GFeknQQLTjwWVXnzvPQacucRZJ0ALwSU5KassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqSkLXJKassAlqalDxh1AOlC77n2It1/8xXHHGMm2zWvHHUG/xjwCl6SmLHBJasoCl6SmLHBJaso3MaUV1OkNV/BN1248ApekpixwSWrKApekpixwSWrKApekpixwSWrKApekpixwSWrKC3kk/Z9OFx5ddNLjbbLCylwk5RG4JDW1pAJPsjnJD5L8KMnFyxVKkrSwRRd4kjXAx4E3AC8Ezk3ywuUKJknav6Ucgb8M+FFV3VlVvwB2AGctTyxJ0kJSVYvbMHkzsLmq3jXMnwe8vKou2Gu9LcCWYfYE4AeLzHoU8LNFbjsOnfJ2ygq98nbKCr3ydsoKS8v7nKo6eu+FSzkLJftY9qTfBlW1Fdi6hOeZfbLk21U1tdT9HCyd8nbKCr3ydsoKvfJ2ygork3cpQyj3AMfOmT8G+MnS4kiSRrWUAv8WcHyS45I8FTgHuHZ5YkmSFrLoIZSqejzJBcCXgDXAJ6vqtmVL9mRLHoY5yDrl7ZQVeuXtlBV65e2UFVYg76LfxJQkjZdXYkpSUxa4JDXVosA7XbKf5JNJ9iT53rizLCTJsUluTHJ7ktuSXDjuTPNJ8rQk30zynSHrh8edaSFJ1iS5Jcl1486ykCQ/TrIrya1Jvj3uPAtJsi7J55J8f/j5feW4M+1LkhOGf9Mnbg8nef+y7X+1j4EPl+z/EDid2VMXvwWcW1X/NtZg80jyamAG+LuqOnHcefYnyXpgfVXdnOQZwE7g7NX4b5skwNqqmklyKPA14MKq+tcxR5tXkj8ApoBnVtWZ486zP0l+DExVVYsLY5JsB75aVZcPZ8E9vaoeHHOs/Rq67F5mL3j8j+XYZ4cj8FaX7FfVV4D7x51jFFV1X1XdPEw/AtwOPHu8qfatZs0Ms4cOt1V79JHkGOAM4PJxZ/l1k+SZwKuBTwBU1S9We3kPTgP+fbnKG3oU+LOBu+fM38MqLZnOkmwAXgx8Y8xR5jUMSdwK7AGur6pVmxX4KPAB4FdjzjGqAv45yc7h4y9Ws+cC/wl8ahiiujzJ8n/Y9vI7B7hyOXfYocBHumRfi5dkArgaeH9VPTzuPPOpql9W1cnMXvX7siSrcogqyZnAnqraOe4sB+DUqnoJs58u+t5hKHC1OgR4CfA3VfVi4FFgtb839lTgTcBnl3O/HQrcS/ZX0DCefDVwRVV9ftx5RjH8uTwNbB5vknmdCrxpGFfeAbw2yafHG2n/quonw/0e4AvMDl2uVvcA98z5C+xzzBb6avYG4Oaq2r2cO+1Q4F6yv0KGNwY/AdxeVR8Zd579SXJ0knXD9GHA64DvjzXUPKrqg1V1TFVtYPbn9ctV9bYxx5pXkrXDm9gMQxGvB1btWVRV9VPg7iQnDItOA1bdG+97OZdlHj6BBt+JOYZL9pckyZXARuCoJPcAf1JVnxhvqnmdCpwH7BrGlgE+VFX/OL5I81oPbB/eyX8KcFVVrfrT85qYBL4w+/ucQ4DPVNU/jTfSgt4HXDEc1N0JvGPMeeaV5OnMnkX3+8u+79V+GqEkad86DKFIkvbBApekpixwSWrKApekpixwSWrKApekpixwSWrqfwH2i/o8BzKsvAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "data = results.variables.wealth_agent\n", + "ax = data.hist(bins=range(data.wealth.max()+1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What we get is a Boltzmann distribution. For those interested to understand this result, you can read more about it here: http://www.phys.ufl.edu/~meisel/Boltzmann.pdf" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/T01_Wealth_Transfer.ipynb b/docs/T01_Wealth_Transfer.ipynb deleted file mode 100644 index 1e0f0a2..0000000 --- a/docs/T01_Wealth_Transfer.ipynb +++ /dev/null @@ -1,251 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Wealth Transfer Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this tutorial, we will create a simple agent-based model that explores the distribution of wealth under a trading population of agents. The [original version of this model](https://mesa.readthedocs.io/en/master/tutorials/intro_tutorial.html) has been designed by the Project Mesa Team for the [MESA](https://mesa.readthedocs.io/) package. This is an adaption of the same model for the [agentpy](https://mesa.readthedocs.io) package to allow for a comparison between the two frameworks." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To start, we import agentpy as follows (recommended):" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import agentpy as ap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each agent is set up as an object of `agent`. These objects hold the variables and possible actions of each agent. \n", - "\n", - "For our demonstration, we define a new agent type `wealth_agent`. Each agent starts with one unit of `wealth`. When `wealth_transfer` is called, the agent selects another agent at random and gives them one unit of their own wealth if they have one to spare." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class wealth_agent(ap.agent):\n", - " \n", - " \"\"\" An agent with wealth \"\"\"\n", - " \n", - " def setup(self):\n", - "\n", - " self.wealth = 1\n", - "\n", - " def wealth_transfer(self):\n", - " \n", - " if self.wealth > 0:\n", - " \n", - " partner = self.model.agents.random()\n", - " partner.wealth += 1\n", - " self.wealth -= 1 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For later analysis, we define a method `gini` that can measure the inequality of a passed list." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "def gini(x):\n", - " \n", - " \"\"\" Calculate Gini Coefficient \"\"\"\n", - " # By Warren Weckesser https://stackoverflow.com/a/39513799\n", - " \n", - " mad = np.abs(np.subtract.outer(x, x)).mean() # Mean absolute difference\n", - " rmad = mad/np.mean(x) # Relative mean absolute difference\n", - " return 0.5 * rmad " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "All agents are contained within an object of the class `model`. The model initializes with the above defined agents, the number of which will be taken from the models' parameters. In each time-step, `wealth_transfer` is activated for all agents in a random order, after which a model variable `Gini Coefficient` are recorded. At the end of the simulation, each agents `wealth` is also recorded." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "class wealth_model(ap.model):\n", - " \n", - " \"\"\" A simple model of random wealth transfers \"\"\"\n", - " \n", - " def setup(self):\n", - " \n", - " self.add_agents(self.p.agents, wealth_agent)\n", - " \n", - " def step(self):\n", - " \n", - " self.agents.do('wealth_transfer', order='random')\n", - " \n", - " def update(self):\n", - " \n", - " self.record('Gini Coefficient', gini(self.agents.wealth))\n", - " \n", - " def end(self):\n", - " \n", - " self.agents.record('wealth')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To run a simulation, we define a dictionary of parameters that defines the number of agents and the number of steps that the model will run." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "parameters = {\n", - " 'agents': 100,\n", - " 'steps': 100\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To perform a simulation, we initialize our model with these parameters and call the method `model.run`, which returns a `data_dict` of recorded data." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Completed: 100 steps\n", - "Run time: 0:00:00.204882\n", - "Simulation finished\n" - ] - } - ], - "source": [ - "model = wealth_model(parameters)\n", - "results = model.run()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To visualize the distribution of wealth, we can use `pandas.DataFrame.hist`." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df = results.wealth_agent_vars\n", - "ax = df.hist(bins=range(df.wealth.max()+1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To visualize the evolution of our Gini Coefficient, we can use `pandas.DataFrame.plot`" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "ax = results.model_vars.xs('model').plot()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/T02_Virus_Spread.ipynb b/docs/T02_Virus_Spread.ipynb deleted file mode 100644 index 516ca10..0000000 --- a/docs/T02_Virus_Spread.ipynb +++ /dev/null @@ -1,51387 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Virus Spread Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This tutorial shows how to build and analyze a simple agent-based model that simulates the propagation of a disease. In the model, agents can be in one of three conditions: susceptible to the disease (S), infected (I), or recovered (R). They are connected to other agents through a small-world network of peers. At every time-step, infected agents can infect their peers or recover from the disease based on random chance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To start, we import agentpy as follows (recommended):" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import agentpy as ap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Defining the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We define a new agent type `human` by creating a subclass of `ap.agent`. The method `setup()` is called automatically when an agent is created, and initializes our variable `condition`. The custom method `being_sick()` causes the agent to spread the disease to its peers and/or recover based on random chance. There are three tools that are used within this class:\n", - "\n", - "- `agent.p` is used to access model parameters\n", - "- `agent.neighbors()` returns a list of the agents' peers in the network\n", - "- `random()` returns a uniform random draw between 0 and 1" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from random import random\n", - "\n", - "class human(ap.agent):\n", - " \n", - " def setup(self): \n", - " \n", - " self.condition = 0 # Susceptible = 0, Infected = 1, Recovered = 2\n", - " \n", - " def being_sick(self):\n", - " \n", - " # Infect susceptible peers if succesfull\n", - " for peer in self.neighbors('peer_network'): \n", - " if peer.condition == 0 and self.p.infection_chance > random():\n", - " peer.condition = 1\n", - " \n", - " # Recover if succesfull\n", - " if self.p.recovery_chance > random(): \n", - " self.condition = 2 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we define our model `virus_model` by creating subclass of `model` with five methods:\n", - "\n", - "- `model.setup()` initializes the agents and network of the model \n", - "- `model.step()` and defines the models' events per simulation step\n", - "- `model.update()` records variables after setup and each step\n", - "- `model.stop_if()` stops the simulation if it returns `True`\n", - "- `model.end()` records evaluation measures at the end" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import networkx as nx\n", - "\n", - "class virus_model(ap.model):\n", - " \n", - " def setup(self):\n", - " \n", - " # Transform integer parameters to `int`\n", - " n = self.p.population = int(self.p.population)\n", - " nn = self.p.number_of_neighbors = int(self.p.number_of_neighbors)\n", - " \n", - " # Create a small-world network with a networkx generator function\n", - " small_world = nx.watts_strogatz_graph(n, nn, self.p.network_randomness)\n", - " self.add_network('peer_network', graph = small_world ) \n", - " \n", - " # Add human agents to network and map them to existing nodes\n", - " self.peer_network.add_agents(n, human, map_to_nodes = True)\n", - " \n", - " # Infect a random share of the population\n", - " I0 = int(self.p.initial_infections * n)\n", - " self.agents.random(I0).condition = 1 \n", - "\n", - " def update(self): \n", - " \n", - " # Susceptible, Infected, Recovered\n", - " conditions = ('S','I','R')\n", - " \n", - " # Count agents with each condition\n", - " for i,c in enumerate(conditions):\n", - " self[c] = len( self.agents.condition == i ) / self.p.population\n", - " \n", - " # Record variables for later analysis\n", - " self.record(conditions) \n", - " \n", - " def step(self): \n", - " \n", - " # Call 'being_sick' for infected agents\n", - " (self.agents.condition == 1).being_sick()\n", - " \n", - " def stop_if(self): \n", - " \n", - " # Stop simulation if disease is gone\n", - " return self.I == 0\n", - " \n", - " def end(self): \n", - " \n", - " # Record final evaluation measures\n", - " self.measure( 'Total Infection Rate', self.I + self.R ) \n", - " self.measure( 'Peak Infection Rate', max(self.log['I']) )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The formulation in `step()` uses the operator functions of `attr_list` that are inhereted by `agent_list`. The first element returns a list of agents where the condition is true, while the second one forwards the method call to all agents in the list.\n", - "\n", - " (self.agents.condition == 1).being_sick()\n", - "\n", - "One could achieve the same effect with the two following alternatives:\n", - "\n", - " self.agents.select('condition',1).do('being_sick')\n", - "\n", - " for agent in self.agents:\n", - " if agent.condition == 1:\n", - " agent.being_sick()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Running a simulation\n", - "\n", - "To run our model, we define a dictionary with our `parameters`. We then create a new instance of our model, passing the parameters as an argument, and use the method `model.run()` to perform the simulation and return it's `output`. " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Completed: 76 steps\n", - "Run time: 0:00:00.215856\n", - "Simulation finished\n" - ] - } - ], - "source": [ - "parameters = {\n", - " \n", - " 'population':1000,\n", - " 'infection_chance':0.3,\n", - " 'recovery_chance':0.1,\n", - " 'initial_infections':0.1,\n", - " 'number_of_neighbors':2,\n", - " 'network_randomness':0.5\n", - " \n", - "}\n", - "\n", - "model = virus_model(parameters)\n", - "results = model.run() # returns model.output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyzing results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The results are given as a dictionary of recorded data and pandas dataframes:\n", - "\n", - "- `log` holds information about the model and simulation performance.\n", - "- `model_vars` holds a dataframe with dynamic model variables that are recorded at different time-steps.\n", - "- `measures` holds a dataframe with evaluation measures that are recoreded only once per simulation." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "data_dict {\n", - "'log': Dictionary with 4 keys\n", - "'model_vars': DataFrame with 3 variables and 77 rows\n", - "'measures': DataFrame with 2 variables and 1 row\n", - "}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To visualize the evolution of our `model_vars`, we create a matplotlib plot function `virus_stackplot()`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "\n", - "def virus_stackplot(data,ax):\n", - " \n", - " x = data.index.get_level_values('t')\n", - " y = [data[var] for var in ['I','S','R']]\n", - " \n", - " ax.stackplot(x, y, labels=['Infected','Susceptible','Recovered'], colors = ['r','b','g']) \n", - " \n", - " ax.legend()\n", - " ax.grid(False)\n", - " ax.set_xlim(0,max(1,len(x)-1))\n", - " ax.set_ylim(0,1)\n", - " ax.set_xlabel(\"Time steps\")\n", - " ax.set_ylabel(\"Percentage of population\")\n", - "\n", - "sns.set() # Set seaborn style\n", - "fig, ax = plt.subplots() # Initialize plot\n", - "virus_stackplot(results.model_vars, ax)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Animating a simulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also observe the development of our model over time. We define a function `animation_plot()` that takes a model instance and displays the previous stackplot together with a network graph for a passed model instance. The function `animate()` will call this plot function for every time-step and return a matplotlib animation object." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def animation_plot(m,axs):\n", - " \n", - " ax1,ax2 = axs\n", - " \n", - " # Plot stackplot on first axis\n", - " virus_stackplot(m.output.model_vars, ax1)\n", - " \n", - " # Plot network on second axis\n", - " color_dict = { 0 : 'b', 1 : 'r', 2 : 'g' }\n", - " colors = [ color_dict[c] for c in m.agents.condition ]\n", - " nx.draw_circular(m.peer_network.graph, node_color=colors, node_size=50, ax=ax2)\n", - "\n", - "sns.set() # Set seaborn style\n", - "fig, axs = plt.subplots(1,2,figsize=(8,4)) # Prepare figure \n", - "parameters['population'] = 50 # Reduce population for better visibility \n", - "animation = ap.animate(virus_model, parameters, fig, axs, animation_plot)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In Jupyter, we can use `.to_jshtml()` and `HTML` to display the animation object directly." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - " \n", - "
\n", - " \n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - "
\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import HTML\n", - "HTML(animation.to_jshtml()) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Interactive parameter variation\n", - "\n", - "To experiment with different parameter values, we define a dictionary `param_ranges`, where we declare tuples with a minimum and maximum value for parameters that we want to vary, as well as a function `interactive_plot()` that takes the model output and displays both our measures and variables." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "param_ranges = {\n", - " \n", - " 'population':(100,1000),\n", - " 'infection_chance':(0,1),\n", - " 'recovery_chance':(0,1),\n", - " 'initial_infections':0.1,\n", - " 'number_of_neighbors':2,\n", - " 'network_randomness':(0,1)\n", - " \n", - "}\n", - "\n", - "def interactive_plot(output):\n", - " \n", - " # Display measures\n", - " print(output.measures)\n", - " \n", - " # Display model_vars\n", - " fig,ax = plt.subplots()\n", - " virus_stackplot(output.model_vars,ax) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In Jupyter, we can then use the function `interactive()` to display our plot with a widget to change parameter values." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7d940ddb1d584bb9b48e2cab48e494a2", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(VBox(children=(FloatSlider(value=450.0, description='population', layout=Layout(width='300px'),…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "ap.interactive(virus_model,param_ranges,interactive_plot)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Experiment with multiple runs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To look into parameter variations in more detail, we use `sample()` create a sample of different parameter combinations. We then use `exp()` to perform an experiment that runs our model repeatedly over the whole sample." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Scheduled runs: 10000\n", - "Completed: 10000, estimated time remaining: 0:00:00\n", - "Run time: 0:19:20.971894\n", - "Simulation finished\n" - ] - } - ], - "source": [ - "parameters = ap.sample(param_ranges, mode='saltelli', N=1000)\n", - "\n", - "exp = ap.experiment(virus_model,parameters)\n", - "results = exp.run()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The measures in our data_dict now hold one row for each simulation run." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "data_dict {\n", - "'log': Dictionary with 5 keys\n", - "'parameter_sample': DataFrame with 6 variables and 10000 rows\n", - "'measures': DataFrame with 2 variables and 10000 rows\n", - "}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use standard functions of the pandas library like `pandas.DataFrame.hist()` to look at summary statistics." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYMAAAEJCAYAAAB2T0usAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAtg0lEQVR4nO3de1xVdb7/8RcC3sKZsgPq4cfDScfS0VInmiITsxQQ2GKkHYG0ZEw0L0ecwRRJEjXNTMujmNNtzlEnI4pLRtjFyTK6qJ2D2niaTomBGOItLspt7+/vDx/uREC5s9H38/HwIWvttfb+rLW/8N57Xb5fJ2OMQURErmkd2roAERFpewoDERFRGIiIiMJARERQGIiICAoDERHhGg+DZcuWERISQkhICIMGDcLf398+XVZWVus6H3/8MS+88MIVn3vSpElkZmbWmP/2228TFRV1xfV3797NyJEjGT9+fJ21XM7FdX700UcsW7aswc9Rl/vuu8++r8aNG0dgYCDBwcF88sknDapLWldeXh4DBgywt/GQkBDGjh1LcnJyk573lltu4dSpU5dd5ssvvyQ4OPiKz3Xo0CFGjRpFaGgoeXl5Da5l//79LF68GIADBw4wZ86cBj9HXSZNmsR9991n33cWiwV/f39SU1MbVJejcmnrAtpSXFyc/ef77ruP1atXc+utt152nQMHDvDzzz+3dGm8++67TJgwgccff7xR619c5/3338/999/fnOXV2FeZmZnExsaye/fuetclra9z586kpaXZpwsKCggODmbQoEH079+/DSs776OPPuLOO+9k+fLljVr///7v/ygoKADg1ltvZd26dc1ZHvPnzycgIMA+feDAAcLCwhg1ahRubm71qstRXdNhcDkbNmzg3XffxdnZmZtuuoknn3yS/Px8tm3bhtVqpVu3bkRFRfHUU09x5MgRzpw5w3XXXcfq1avp06dPvV7jP/7jPzh69CiFhYUcPXqUHj168Oyzz5Kens5HH31Ep06dKC4u5oknnmDjxo28//772Gw2PD09iY+Pp0ePHhQWFhIfH88PP/xAhw4dmDhxIoMHD65WZ+/evdmxYwebNm3ip59+4qmnnuLo0aMYYxg3bhxTp04lLy+PRx99lBEjRpCdnU1RURExMTGMHj36itthjCEvL49f//rXAJw9e7bW/VJcXFytrujoaN58801e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PMkmIo6totLa4A9cZeVVFBeda6GKpKU0OgyOHTvGzJkzWbt2LT4+PsD5P/5PP/00d911F127duWNN97ggQceqHaS7fbbb7efZBMRx9bR1RnLn9IatM47z4XgWBdeSn00OgxeeeUVysvLWblypX3exIkTmTZtGmFhYVRVVeHn50dwcDBw/iRbXFwcJSUlDBw40H6STURE2l6jwyAuLo64uLhaH4uIiKgxr66TbCIi0vbUN5GIiCgMREREYSAiIigMREQEhYGIiKAwEBERFAYiIoIGtxGRZqYuLNonhYGINCt1YdE+6TCRiIgoDERERGEgIiIoDEREBJ1AFhEHcKUrkGp7TFcgNS+FgYi0OV2B1PZ0mEhERPTNQETaJ93c1rwUBiLSLunQUvNSGIjINaMx3ybg2vhGoTAQkWtGY75NALy1MrhaiNQnUNpbgLRqGLzzzjts3LiRqqoqHnnkESIiIlrz5UVandr81eFaOCTVamFQUFDA2rVrefvtt+nYsSMTJ07kzjvv5Le//W1rlSDSqtTmr23t7QR3q4VBVlYWd911F9dffz0A/v7+ZGZmMmvWrHqt36GDU52PedzQpcH1tNY6rflajrxOa75WbW3lcu2npVwNbb41X8uR12nMeh1dnfnjsvcbtM7GJ+5vcICUl1cBNdtLQ9u8kzHGNGiNRtq0aRNnz54lOjoagDfffJP9+/ezdOnS1nh5kVanNi/tSavddGaz2XBy+iWpjDHVpkWuNmrz0p60Whj07NmTwsJC+3RhYSEeHh6t9fIirU5tXtqTVguDu+++m88//5xTp05x7tw53n//fXx9fVvr5UVandq8tCetdgK5R48eREdHM3nyZCorKxk/fjy33XZba728SKtTm5f2pNVOIIuIiONSr6UiIqIwEBERhYGIiKAwEBERruEweOeddwgMDMTPz4+tW7fWePzDDz8kJCSEsWPH8vjjj/Pzzz8DkJKSwj333ENISAghISGsXbu2VepZv349I0eOtL/uhWXy8/OJiIggICCAGTNmUFpa2uL1HDp0yF5HSEgIw4cPJzg4GGi5/XNBSUkJwcHB5OXl1Xjs0KFDhIaG4u/vz6JFi6iqOn+bfkvto/bG0dp8fWpSu2/FNm+uQT/99JMZOXKkOX36tCktLTUWi8V899139seLi4vNsGHDzE8//WSMMeb55583S5cuNcYYk5CQYN55551WrccYY6KioszXX39dY91p06aZ7du3G2OMWb9+vVm1alWr1HPB2bNnTVBQkNmzZ48xpmX2zwX/8z//Y4KDg83AgQNNbm5ujceDgoLMf//3fxtjjFm4cKHZunWrMaZl9lF742htvj41GaN235pt/pr8ZnBxB2Jdu3a1dyB2QWVlJfHx8fTo0QOAW265hWPHjgFw4MABUlJSsFgs/PnPf7Z/emrJegAOHjzIpk2bsFgsJCQkUF5eTmVlJXv27MHf3x+A0NDQGuu1VD0XbNq0iTvuuANvb2+gZfbPBUlJScTHx9d6F+/Ro0cpKytjyJAhwC/7oqX2UXvjaG2+PjWB2n1rtvlrMgyOHz+Ou7u7fdrDw4OCggL79A033MDo0aMBKCsr4y9/+QujRo0CwN3dnccff5z09HR69epFQkJCi9dTWlrKgAEDiImJISUlhaKiIhITEzl9+jRubm64uLjYa7t4vZaq54Li4mKSkpKq9cLZEvvnguXLl9t/+a5U84V90VL7qL1xtDZfn5rU7lu3zV+TYVDfDsSKi4uZNm0a/fv354EHHgBgw4YN3H777Tg5OTF16lQ+/fTTFq/nuuuu46WXXqJv3764uLgQGRnJrl27aq27OTpCq+/+SU9PZ9SoUdx44432eS2xf5pSc0vto/bG0dp8fWpSu29cvY3dP9dkGNSnA7Hjx48THh7OLbfcwvLly4Hzvyh//etf7csYY3B2dm7xevLz80lOTq72ui4uLnTv3p3i4mKsVmud29ES9Vzw4YcfEhgYaJ9uqf1TH5fWfOLECTw8PFpsH7U3jtbm61OT2v3lNXebvybD4EodiFmtVqZPn86YMWNYtGiRPVW7du3Kyy+/THZ2NgBbtmyxf7VuyXo6d+7Ms88+S25uLsYYtm7dyujRo3F1dcXb25uMjAwAUlNTm6UjtPp0sGaM4ZtvvmHo0KH2eS21f+rD09OTTp06sW/fPgDS0tLw9fVtsX3U3jham69PTWr3l9fsbb4xZ7ivBunp6SYoKMj4+fmZv/zlL8YYY6ZOnWr2799v3n//fXPLLbeYsWPH2v/FxsYaY4zZs2ePGTdunAkICDDTp083RUVFLV6PMcZkZmbaH1+wYIEpLy83xhiTl5dnHn74YTNmzBgTGRlpzpw50yr1nDhxwtx999011mup/XOxkSNH2q+suLimQ4cOmQcffND4+/ubefPmtfg+am8crc1fqSZj1O4vaI02r47qRETk2jxMJCIi1SkMREREYSAiIgoDERFBYSAiIigMREQEhYGIiKAwEBER4P8D2IdsWRFhZc8AAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "results.measures.hist()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can save and load agentpy output data with `save()` and `load()`." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data saved to ap_output/virus_model_2\n" - ] - } - ], - "source": [ - "results.save() # Alternative: ap.save(results)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loading from directory ap_output/virus_model_2/\n", - "Loading log.json - Successful\n", - "Loading measures.csv - Successful\n", - "Loading parameter_sample.csv - Successful\n" - ] - } - ], - "source": [ - "results = ap.load('virus_model') # Load data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Sensitivity analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function `sensitivity()` calculates sobol sensitivity indices for the passed results and parameter ranges, using the [SAlib]( https://salib.readthedocs.io/en/latest/basics.html) package. This adds two new categories to our results:\n", - "\n", - "- `sensitivity` - first-order sobol sensitivity indices\n", - "- `sensitivity_conf` - confidence ranges for the above indices" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
S1ST
measureparameter
Total Infection Ratepopulation0.0074910.019762
infection_chance0.7157520.805777
recovery_chance0.2071610.272250
network_randomness0.0035720.023464
Peak Infection Ratepopulation0.0074880.016675
infection_chance0.2016990.326066
recovery_chance0.6585060.790709
network_randomness0.0149580.021635
\n", - "
" - ], - "text/plain": [ - " S1 ST\n", - "measure parameter \n", - "Total Infection Rate population 0.007491 0.019762\n", - " infection_chance 0.715752 0.805777\n", - " recovery_chance 0.207161 0.272250\n", - " network_randomness 0.003572 0.023464\n", - "Peak Infection Rate population 0.007488 0.016675\n", - " infection_chance 0.201699 0.326066\n", - " recovery_chance 0.658506 0.790709\n", - " network_randomness 0.014958 0.021635" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ap.sensitivity( results, param_ranges )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the pandas functionalities to create a bar plot `plot_sobol_indices()` that visualizes our sensitivities." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlUAAAGtCAYAAAA/L4FbAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAABGO0lEQVR4nO3de5xNdf///+cec83JmWaQ5JMuTIWIkEgoh2FjohKRlEOZlBIdkBxyvD6EforvVV1RUs6TLgnRYURnLpkkl+NoZmqcZ8Yc9vv3h6/9NRlmhjV77T3rcb/d3Mxaa++9Xmvv2c95rbPLGGMEAACAKxJkdwEAAAAlAU0VAACABWiqAAAALEBTBQAAYAGaKgAAAAvQVAEAAFiApiqAHDp0SDfccIO6devm/de1a1ctXbr0il63bt26SktLu+Rjtm7dqi5duhT4Wrt27dJdd92le+65R4cOHSpyLdu3b9fYsWMlSTt27NCwYcOK/BoX07dvX7Vt29b73rndbnXo0EErV64sUl0ALs/EiRO937969eqpQ4cO3uHMzMx8n7Np0ya9+uqrBb523759tXbt2gvGL1++XIMHDy7w+V9++aXatGmjnj17XrSWSzm/zg0bNmjixIlFfo2Ladu2rfe96t69u2JiYtSlSxd9/vnnRaoLxS/Y7gJQNGFhYVq1apV3ODk5WV26dFG9evUUHR1tY2VnbdiwQc2aNdOkSZMu6/l79uxRcnKyJKl+/fqaPXu2leVp5MiR6tixo3d4x44deuCBB3TXXXepTJkyhaoLwOUZPXq09+e2bdtqxowZql+//iWfs2PHDh0/fry4S9OaNWt077336vHHH7+s559fZ7t27dSuXTsry7vgvVq7dq1eeOEFffnll4WuC8WPpirAValSRTVr1tS+ffsUHR2tDz/8UIsXL5bH41GFChU0ZswYXX/99frvf/+r8ePH6/Tp00pNTVV0dLRmzZql0NBQ72ulpqbq4Ycf1gMPPKA+ffpcdJ7Lly/Xp59+qqCgIO3fv19hYWGaOnWqdu7cqcWLFys3N1eZmZn6xz/+cdF6Tp8+rYkTJ+r7779XqVKldNddd+mBBx7Q7NmzdfLkST3//PPq3r27JkyYoI8++kgnT57Uyy+/rMTERLlcLrVq1UpPP/20goODVb9+fQ0aNEhfffWVUlJS9Oijj6p3796Fev8OHjyoiIgIhYSEyOPx6JVXXtFPP/2k06dPyxijiRMn6uqrr85T1+TJk7Vx40bNmzdP2dnZCgsL06hRo9SoUaMr/jwBp3rttde0Zs0alSpVStddd53GjBmjpKQkvf/++8rNzVXZsmU1ePBgjRs3Tvv379exY8dUunRpzZgxQ7Vq1SrUPObMmaPDhw8rNTVVhw8fVpUqVTR9+nStXr1aGzZsUGhoqE6ePKlRo0Zp3rx5WrdunTwej6pXr66XXnpJVapUUWpqql566SXt3btXQUFB6tWrl26++eY8ddasWVOffPKJ3njjDf3+++8aN26cDh8+LGOMunfvrkcffVSHDh1S//791bp1a/300086ceKEnn32Wd19990FLocxRocOHVL58uUlSenp6fm+LydPnsxT1/Dhwy+aybCIQcA4ePCgadiwYZ5x33//vbn11ltNUlKS2bp1q+ndu7dJT083xhjzxRdfmI4dOxpjjJkyZYpZuXKlMcaYrKws06VLF7N27VpjjDF16tQxP//8s4mJiTGrVq3Kd95ff/216dy5szHGmGXLlpnGjRubI0eOGGOMGT9+vBk5cqQxxpjZs2ebl19+2RhjLlnPK6+8YoYPH25ycnLMmTNnTJ8+fczXX39tli1bZgYNGnTBPEeOHGkmTJhgPB6POXPmjBkwYIB54403vPUvXLjQGGPMjh07TL169UxmZuYFy/Dggw+aNm3amK5du5o777zT3HbbbWb48OFm586d3vfyiSeeMLm5ucYYY9544w0zePBg7zKfq+u///2v6dKli0lLSzPGGLN7925z++23m9OnT1/q4wNwnjZt2pjt27cbY4xZunSpuf/++73fodmzZ5sBAwZ4fz6XKf/+97/NhAkTvK8xZswYM378eGPM2e/3v//97wvmc/53d/bs2aZdu3bm5MmTxhhjBg8ebF599VVjjDGjRo0y/+f//B9jjDErVqwwTz31lMnOzjbGGPP++++bRx991BhjzNChQ83UqVONMcacOHHCdO7c2ezbty9PnefPs0+fPubNN9/0Pt7tdpuPPvrIHDx40NSpU8ds3LjRGGPM2rVrzZ133nnR96p9+/bG7XabVq1amVatWpnnn3/eHDhwoMD3pbCZDGuwpSrAZGZmqlu3bpKk3NxcVaxYUdOnT1e1atW0cOFC7d+/X7169fI+/sSJEzp27JieffZZffXVV1qwYIH27dunlJQUpaenex83cOBAVa1aVW63u1B13HTTTapataok6cYbb9Snn356wWM2bdp00XoSEhL0/PPPq1SpUipVqpQWLVok6exWsPx8/vnnWrx4sVwul0JCQtSrVy/961//0qBBgyTJu6n9pptuUlZWltLT0/NshTvn3O6/tLQ0DRw4UFWqVNGNN94oSWrUqJHKly+v999/XwcPHtTWrVtVunTpC17j3Bax/v37e8e5XC4dOHDAL3bBAoHm888/1z333KOIiAhJUr9+/fT6668rKysrz+M6duyoGjVqeLNu27ZtRd5C3LRpU++u/htvvDHfXWOfffaZduzYoR49ekiSPB6PMjIyJEkJCQl69tlnJUlly5bVRx99dNF5paen6/vvv9ebb77pffw999yjzz//XDfffLP+9re/qXXr1t5ajh07dtHXOrf77+DBg3r44Yd1ww03qEaNGkV6Xy6VyRUqVLjovFF4NFUB5q/HVJ3P4/GoW7du3i+8x+NRSkqKypcvr+HDhys3N1edOnXSnXfeqSNHjsicd9vH8ePH6/XXX9dbb72lAQMGFKqOc1wuV57XKkw9wcHBcrlc3sceOXIkz2vm91rnP97j8SgnJ8c7fK6BOveY/Oo5X6VKlTRr1ix16dJFjRo1Uvv27bVp0yZNmjRJDz/8sNq1a6datWpp9erV+dZy2223adasWXnqj4qKuuQ8AeSvoO/3Oe+9954++OAD9enTR263WxUqVCjyCTGFza7zDyPIysryNl9/za6DBw+qYsWKF12uv77++cv2t7/9TUFBQd5aCqNGjRqaNm2a+vXrp5tvvlkNGjQo9PtyqUyGNTj7rwRp2bKl1qxZo5SUFEnS4sWL9dBDD0k6e2bL0KFDFRMTI0n66aeflJub631uw4YNNWXKFM2bN0+7d+8u9npuu+02rVixQh6PR1lZWRo2bJi++eYblSpVKt8wbdmypRYtWiRjjLKysvTBBx+oRYsWV1RfjRo1NGTIEE2aNEnp6en66quv1KZNG/Xu3Vv16tXT+vXrve/R+XXddttt+uqrr/Tbb79JkjZv3qyuXbte1hlDAKRWrVpp2bJl3q3nCxcu1K233qqQkJA8370vv/xSsbGxuvfee3Xddddp48aNeXLMKi1bttTSpUt16tQpSdKrr76qkSNHSjr7/V+2bJkk6eTJk3rooYe0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fLzdZdgmMzNTa9eu1cyZM703pB07dqwaNmxod2nFLisrS4sWLdK2bdskSS1atFDv3r0dc5uPkoD8cnZ+Sc7NsEDIL0c2VaNGjdKZM2fUtWtXeTwerVq1SlWrVvWvS90Xo5EjR2rAgAGKjo62uxSf2rJli1auXKmEhAS1bt1a99xzj2655Rb98ssvGjhwoD7//HO7S/SJU6dO6eTJk3l2nVx99dU2VoSiIL+cmV8SGSb5f375T3vnQz/99JPWrl3rHW7btq26dOliY0W+9euvvyo2NlaVK1dWaGiojDFyuVzasGGD3aUVq7lz56pnz54aN25cnpuQ1q1bVwMGDLCxMt95/fXXNX/+fFWoUEEul8sxn31JQn45M78kMiwQ8suRTdU111yj/fv3ew94++OPP7w353SCuXPn2l2CLUJDQxUbG5vvtP79+/u2GJssXbpU69evV6VKlewuBZeJ/HJmfklkWCDklyObqpycHHXr1k1NmjRRcHCwvvvuO0VGRqpfv36SVOLPILn66qu1ePFiff3118rJyVHz5s314IMP2l1WsTtz5oyOHDmiatWq2V2KbapVq6by5cvbXQauAPnlzPySyLBAyC9HHlN17iC3i2natKmPKrHH1KlTtX//fvXo0UPGGC1fvlzVq1cv8cdkdOzYUfv373fkboNzxowZo927d6tZs2Z5TseOi4uzsSoUBfnlzPySyLBAyC9Hbqkq6aFTkK+++korV65UUFCQJOnOO++U2+22uari989//tPuEmxXpUoVR+0qKonIL2fml0SGBUJ+ObKpcrrc3Fzl5OR4O/3c3FxHXDywevXqio+P1549ezRkyBB98skn6t69u91l+dRf1+iMMTp06JBN1QBF59T8ksiwQMgvmioHcrvd6tevnzp37ixJWrNmjffnkmzGjBn6/ffftXPnTg0cOFDLli1TYmKinnvuObtL85klS5Zo6tSpysjI8I675ppr9Omnn9pYFVB4Ts0viQwLhPwKsrsA+N6QIUP0+OOPKykpSYcPH9aQIUP02GOP2V1Wsfvyyy81ffp0hYaGqkyZMnrrrbcccV2X873xxhtatWqVYmJi9Omnn2r06NFq0KCB3WUBhebU/JLIsEDIL5oqB9m5c6ck6ZtvvlF4eLjatm2rdu3aqXTp0vrmm29srq74nTsG49yVp7OysrzjnKJy5cqqUaOG6tatq927d6tPnz765Zdf7C4LKJDT80siwwIhv9j95yCLFy/WxIkTNXv27AumuVyuEn8qdseOHfXUU0/p+PHjevvtt7V69WpHXTRRksLDw/X111+rbt26Wr9+verXr6/MzEy7ywIK5PT8ksiwQMgvR15Swel2796tOnXq5Bn3448/lvj7RknSF198oYSEBHk8HjVv3lxt2rSxuySf+vXXX7V06VKNGjVKTz75pLZs2aK4uDhHXDgQJYOT80tydoYFQn7RVDnId999J4/Ho9GjR2vSpEneeyfl5ORo3Lhx+uSTT2yusHgNHTpUXbt2VZs2bfJc48RJZs6cqeHDh9tdBlBkTs8viQwLhPyiqXKQOXPmaNu2bfrPf/6jevXqeccHBwerVatWJf7eUZ999pnWrFmjb7/9Vi1btlTXrl0dd82frl27atWqVd5jMoBA4fT8ksiwQMgvmioHWrlypbp06aLg4GBlZ2crOztbERERdpflM2fOnNFnn32m+fPn6+jRo/rss8/sLsln+vXrp+TkZN10000KDQ31jp88ebKNVQGF5/T8kpybYYGQXxyo7kAhISGKjY1VfHy8jhw5or59+2rMmDG666677C6t2O3Zs0dr1qzR2rVrVa1aNe/90pziYjdjBQKFk/NLcnaGBUJ+saXKgdxut9566y1dddVVkqQ///xTAwYM0KpVq2yurHi53W6VKlVKbrdbbrdbUVFRdpfkV2JjY7VixQq7ywAuyan5JZFhl+Iv+cWWKgfKzs72BpJ09tofTuitZ8yYobp16+rUqVPyeDx2l+N3nPA7gMDn1PySyLBL8ZffAZoqB2rcuLGefvppud1uuVwuffzxx444HTk8PFw9e/bUwYMH5fF4VL16dc2cOVPXXXed3aX5BX8++BM4x6n5JZFhl+Iv+cXuPwfKysrSwoUL9c033yg4OFhNmjRR7969S/wpug8//LDuv/9+dezYUZL08ccfa/HixVq4cKHNlfkHf9l8DlyKU/NLIsMuxV/yiy1VDhQSEqIOHTro+uuvV8uWLXXkyBFHBNLRo0e9YSRJMTExmjdvno0VASgqp+aXRIYFAufcNAheH3/8sR577DFNmjRJx48fV69evRxxkGdISIj3/mGS9J///Efh4eE2VuRf2GiNQODU/JLIsEvxm/wycJzu3bubkydPmm7duhljjElOTjYxMTH2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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def plot_sobol_indices(results):\n", - " \n", - " sns.set()\n", - " fig, axs = plt.subplots(1,2,figsize=(10,5))\n", - "\n", - " SI = results.sensitivity.groupby(by='measure')\n", - " SIT = results.sensitivity_conf.groupby(by='measure')\n", - "\n", - " for (key,si),(_,err),ax in zip(SI, SIT, axs):\n", - "\n", - " si = si.droplevel('measure')\n", - " err = err.droplevel('measure')\n", - " si.plot.bar(yerr=err,title=key,ax=ax)\n", - " \n", - "plot_sobol_indices(results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, we can also display the sensitivities by plotting average evaluation measures over our parameter variations. " - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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2Sz06tVLWLlypYxRusZZWe539epVnx2w56wcer1eaNGihXDq1ClBEARh1apVwptvvilXuA45K0tmZqbQrFkz4a+//hIEQRC2b98uDB48WK5wyQNYx/gmf6lnSlodw4TGie3btwtdunQRYmNjhdWrVwuCIAivvPKKcOzYMSExMVGoUaOG0L17d9t/U6dOlTniojkqy/18vbJxVo4//vhD6NOnj9C5c2fh5ZdfFtLS0uQM1yFnZdm9e7fQrVs3oWvXrsKLL74oXLlyRc5wyQNYx/gmf6lnSlIdw8UpiYiISPE4hoaIiIgUjwkNERERKR4TGiIiIlI8JjRERESkeExoiIiISPGY0JDPWrFiBebMmSN3GEREpABMaIiIyK7Nmzdj3bp1kh/3wIED6Nq1q+THLa6PPvoIU6ZMkTsMkkig3AFQ8R04cADx8fEIDQ3F3bt38frrr2PVqlUwmUzQarWYPHkyGjRoALPZjPfeew+7d+9GQEAAGjRogJkzZ0KlUmHBggXYv38/AgICEBMTg7fffht//PEHFi5ciB07dgAA7ty5g+effx7ff/89DAYD5syZg5s3b8JkMqFLly4YNWoUrl27hkGDBuHxxx/H9evX0bNnT5w/fx7vv/8+AODQoUN49913sXXr1iLLU1ScAHDhwgUMGTIEqampqFChApYsWYLIyEj89NNPWLVqFfLy8pCeno6ePXtiwoQJOHDgABISElC5cmWcO3cOZrMZs2fPRsOGDXH37l28++67OHLkCAICAtCuXTvExcXBZDJh8eLFOHjwICwWC2rXro3p06cjLCzM458lkS87fPgwnnzySbnDIHIJExqFOnfuHL7//nuYTCaMGzcOn376KSIiInDu3Dm89NJLSExMxJYtW3DixAls27YNQUFBmDhxIr799ltcuXIFKSkp2LZtGwICAjBt2jQsWrQIs2fPxt27d3H8+HHUq1cPX3/9Ndq0aYOyZcti3LhxGDZsGNq2bQuj0YgRI0bgH//4B2JiYnDr1i28//77eOaZZ3D79m3ExsYiMzMT4eHh2LRpk9PVW9evX283TgC4evUqNm/ejHLlymH06NHYvHkzRo8ejY8//hgLFizAY489huTkZDz33HMYOnQoAODYsWOYOXMmatWqhY8//hgJCQn47LPPsHz5chiNRnz77bewWCx4+eWXkZSUhIMHDyIgIABffvklVCoVlixZgsWLF2PWrFme/hiJvKaoZL9evXp2E/r9+/fjxx9/xN69e5GXl4fVq1dj7969CA0NxYwZM3DhwgV89tlnAIDY2Fj85z//gdVqxZw5c5CZmQmVSoWXX34ZPXv2fOhL2FtvvWWL69ChQ3jzzTexZMkSPP3000XGP2XKFGRmZuLq1at49tln0bdvX8yZMwd3795FamoqatasiaVLlyI4OBj16tXDq6++ir179yIlJQWvvPIKBg4cCJPJhHfffRf79u1D+fLlUb58eZQuXRoAcOvWLcyaNQvXr1+HIAjo2bMnXnnlFVy7dg0vvvgiWrRogT///BMWiwXjx4/Hxo0bceHCBdStWxdLlizBjRs3MGzYMLRp0wZHjx7FnTt3MGnSJNtq6f/5z3+QmJgIq9WKSpUqYebMmYiKikJiYiL+85//QKVSISAgAG+99RYaNWpU5M/JAZlnKiY3/Pbbb7Zpwz/77DOhcePGhaZGb9mypXDq1Clh5MiRwqZNmx7av0+fPsKePXtsr0+cOCE8++yzgiAIwr/+9S9h9uzZtvcdOHBAuHv3rlCzZs1C52jXrp3w/vvvC1evXhVq164tmEwm2/EmTpworFmzxrZOSE5OjsPyFBXn8uXLhXfeecf2etmyZcKcOXMEQRCEnJwc4euvvxZWrFghTJgwQahZs6Zw7do14bfffhOef/552z779+8XunbtKgiCIHTt2lXYu3ev3d9HbGysrWydOnVS9HomRPb89ttvQq1atYSTJ08KgiAIH330kTBo0CBhxYoVwoIFCwSr1SoIgiC8//77wsyZMwVBEITJkycLH374oSAIgjBkyBDhxx9/FARBEGJjY4XmzZsLOTk5wrlz54ROnToJJpNJeP7554Vdu3YJgpC/jlCrVq2EI0eOCL/99pvtb7Qgli5dugj79+8X2rVrZ1sTyZHJkycLL774ou31ggULhK1btwqCIAh5eXlC165dhZ07dwqCIAjVq1cX1q5dKwiCIBw/flyoW7euYDAYhDVr1ghDhw4VjEajcPfuXaFXr17C5MmTBUEQhEGDBgkff/yxIAiCcOfOHaFbt27C119/LVy9elWoXr268P333wuCIAgzZswQnnvuOSE7O1swGAxCixYthMOHD9veV/A72rlzp61e/eqrr4QJEybY6skNGzYIr7zyiiAIgvD8888Lv//+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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def virus_scatterplot(results):\n", - " \n", - " fig, axs = plt.subplots(2,2,figsize=(8,8))\n", - " \n", - " data = results.get_measures(parameters=True)\n", - " params = results.ps_keys() # List of varied parameter keys\n", - " axs = [i for j in axs for i in j] # Flatten axis list\n", - " \n", - " for x,ax in zip(params,axs):\n", - " for y in results.measures.columns:\n", - " sns.regplot(x=x, y=y, data=data, ax = ax, ci=99, x_bins=15, fit_reg=False, label=y)\n", - " \n", - " ax.set_ylim(0,1)\n", - " ax.set_ylabel('')\n", - " ax.legend()\n", - " \n", - " plt.tight_layout()\n", - "\n", - "virus_scatterplot(results)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/conf.py b/docs/conf.py index ff56fc5..a7131a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,6 +40,11 @@ 'nbsphinx' # Support jupyter notebooks ] +# Remove blank pages +latex_elements = { + 'extraclassoptions': 'openany,oneside' +} + # Define master file master_doc = 'index' @@ -54,12 +59,13 @@ You can download this tutorial as a Jupyter Notebook :download:`here<{{ env.doc2path(env.docname,base=None) }}>` """ -# Intersphinx mapping +# Connect to other docs intersphinx_mapping = { "https://docs.python.org/3/": None } -add_module_names = False # (!) what does this do? +# Remove module name before elements +add_module_names = False # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/index.rst b/docs/index.rst index 2891558..ff042b8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,35 +4,49 @@ Agentpy - Agent-based modeling in Python ======================================== -Agentpy is a package for the development and analysis of agent-based models in Python. The packages is still in a very early stage of development. If you need help using Agentpy or want to contribute, feel free to write me via joel.foramitti@uab.cat. +.. raw:: latex -Main features -############# + \chapter{Introduction} +Agentpy is a package for the development and analysis of agent-based models in Python. The project is still in an early stage of development. If you need help or want to contribute, feel free to write me via joel.foramitti@uab.cat. + +.. rubric:: Main features + +- Design of agent-based models with complex procedures. - Creation of custom agent types, environments, and networks. -- Design of models with complex procedures and multiple environments. -- Standard operators can be used on whole groups of agents simultaneously. +- Container classes for operations on groups of agents and environments. - Experiments with repeated iterations, large parameter samples, and distinct scenarios. -- Output data that can be saved, loaded, and re-arranged for further analysis. -- Tools for sensitivity analysis, interactive output, animations, and visualization. +- Output data that can be saved, loaded, and transformed for further analysis. +- Tools for sensitivity analysis, interactive output, animations, and plots. + +.. only:: html -Contents -######## + .. rubric:: Table of contents -.. :caption: Contents: .. toctree:: - :maxdepth: 3 + :maxdepth: 2 installation - tutorials + overview + models reference -.. license, gallery +.. only:: html + + .. rubric:: Indices and tables + + * :ref:`genindex` + * :ref:`search` + +.. only:: html + + .. rubric:: Alternatives + + There are numerous other frameworks for agent-based modeling, each with their own focus and advantages. An overview can be found in `Abar et al. (2017) `_. The main alternative to agentpy in Python is `Mesa `_, which could be more suited for users who are looking for a stronger similarity to Netlogo with spacial simulations and live visualization. -Indices and tables -################## +.. **References:** +.. Abar, S., Theodoropoulos, G. K., Lemarinier, P., & O’Hare, G. M. (2017). Agent Based Modelling and Simulation tools: A review of the state-of-art software. Computer Science Review, 24, 13-33. -* :ref:`genindex` -* :ref:`search` +.. * :ref:`modindex` -.. * :ref:`modindex` \ No newline at end of file +.. license, gallery \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst index e20ea3b..844474e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -4,13 +4,13 @@ Installation ============ -For full functionality, agentpy has the following requirements: Python 3.7 or higher, NumPy, scipy, matplotlib, networkx, pandas, SALib, and ipywidgets. +Agentpy has the following requirements: Python 3.7 or higher, NumPy, scipy, matplotlib, networkx, pandas, SALib, and ipywidgets. To install the package, run the following command on your console:: $ pip install agentpy -Alternatively, you can also clone the latest version from GitHub:: +Please note that the latest release on pip refers to the `stable `_ documentation, while the `latest `_ documentation refers to the latest version on Github, which can be cloned with the following command:: $ git clone https://github.com/JoelForamitti/agentpy.git @@ -18,3 +18,5 @@ The agentpy packet can then be imported as follows (recommended):: import agentpy as ap + + diff --git a/docs/models.rst b/docs/models.rst new file mode 100644 index 0000000..99a68a7 --- /dev/null +++ b/docs/models.rst @@ -0,0 +1,12 @@ +============= +Model Library +============= + +.. :caption: Contents: +.. toctree:: + :maxdepth: 3 + + Agentpy_Wealth_Transfer + Agentpy_Virus_Spread + Agentpy_Forest_Fire + Agentpy_Button_Network \ No newline at end of file diff --git a/docs/overview.rst b/docs/overview.rst new file mode 100644 index 0000000..913fc5d --- /dev/null +++ b/docs/overview.rst @@ -0,0 +1,20 @@ +.. currentmodule:: agentpy + +======== +Overview +======== + +The main framework for agent-based models consists of four levels: + +- :class:`experiment`, which holds a model, parameter sample, & settings +- :class:`model`, which holds agents, environments, parameters, & procedures +- :class:`environment`, :class:`grid` and :class:`network`, which hold agents +- :class:`agent` + +Agents are identified by a unique ID (str), while environments have unique keys (str). Groups of agents and environments are held within the classes :class:`agent_list` and :class:`env_dict`. + +Both :class:`model` and :class:`experiment` can be used to run a simulation, which will yield a :class:`data_dict` with output data. The function :func:`sample` can be used to create parameter samples for an experiment, and :func:`sensitivity` can be used to analyze the sensitivity of varied parameters. + +In addition to the experiments, a model can also be passed to the functions :func:`interactive` and :func:`animate` to generate interactive or animated output. + +To design a custom model, these classes can be used as parent classes and be expanded with custom attributes and methods. See :doc:`models` for examples. diff --git a/docs/reference.rst b/docs/reference.rst index ab9b4be..93ec776 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1,32 +1,11 @@ .. currentmodule:: agentpy -========= -Reference -========= - -Overview -######## - -The main framework for agent-based models consists of four levels: - -- :class:`experiment`, which holds a model, parameter sample, & settings -- :class:`model`, which holds agents, environments, parameters, & procedures -- :class:`environment` and :class:`network`, which hold agents -- :class:`agent` - -Agents are identified by a unique ID (str), while environments have unique keys (str). Groups of agents and environments are held within the classes :class:`agent_list` and :class:`env_dict`. - -Both :class:`model` and :class:`experiment` can be used to run a simulation, which will yield a :class:`data_dict` with output data. The function :func:`sample` can be used to create parameter samples for an experiment, and :func:`sensitivity` can be used to analyze the sensitivity of varied parameters. - -In addition to the experiments, a model can also be passed to the functions :func:`interactive` and :func:`animate` to generate interactive or animated output. - -To design a custom model, these classes can be used as parent classes and be expanded with custom attributes and methods. See :doc:`tutorials` for model examples. - -Model framework -############### +============= +API Reference +============= Agents ------- +###### .. autoclass:: agent :members: @@ -35,69 +14,89 @@ Agents :members: Environments ------------- +############ .. autoclass:: environment :members: +.. autoclass:: env_dict + :members: + +Networks +-------- + .. autoclass:: network :members: -.. autoclass:: env_dict +Spacial grids +------------- + +.. autoclass:: grid :members: + Agent-based models ------------------- +################## .. autoclass:: model :members: -Experiments -########### Parameter sampling ------------------- +################## .. autofunction:: sample -Experiment class ----------------- +.. autofunction:: sample_discrete -.. autoclass:: experiment - :members: +.. autofunction:: sample_saltelli + + +Experiments +########### .. class:: exp() Alias of :class:`experiment` -Output & Analysis -################# +.. autoclass:: experiment + :members: + Output data ------------ +########### .. autoclass:: data_dict :members: -Sensitivity analysis --------------------- - -.. autofunction:: sensitivity +Analysis +######## Interactive output ------------------ .. autofunction:: interactive + +Sobol sensitivity +----------------- + +.. autofunction:: sensitivity + Animations ---------- .. autofunction:: animate +Plots +----- + +.. autofunction:: gridplot + Base classes ############ .. autoclass:: attr_dict -.. autoclass:: attr_list \ No newline at end of file +.. autoclass:: obj_list \ No newline at end of file diff --git a/docs/tutorials.rst b/docs/tutorials.rst deleted file mode 100644 index c3b9f23..0000000 --- a/docs/tutorials.rst +++ /dev/null @@ -1,10 +0,0 @@ -========= -Tutorials -========= - -.. :caption: Contents: -.. toctree:: - :maxdepth: 3 - - T01_Wealth_Transfer - T02_Virus_Spread \ No newline at end of file diff --git a/setup.py b/setup.py index 3f8a0d5..bebcd37 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,16 @@ setuptools.setup( name="agentpy", - version="0.0.2", + version="0.0.3", author="Joël Foramitti", description="Agent-based modeling in Python", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/JoelForamitti/agentpy", + url="https://agentpy.readthedocs.io/", packages=setuptools.find_packages(), classifiers=[ - "Programming Language :: Python :: 3" - ] + "Programming Language :: Python :: 3", + ], + #setup_requires=['pytest-runner'], + #tests_require=['pytest', 'pytest-cov'], ) \ No newline at end of file