Improved docs
Fixed several bugs Added convenience methods in soil.analysis
| @@ -3,7 +3,7 @@ | ||||
| Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks. | ||||
| Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io). | ||||
|  | ||||
| Follow our [tutorial](notebooks/soil_tutorial.ipynb) to develop your own agent models. | ||||
| Follow our [tutorial](examples/tutorial/soil_tutorial.ipynb) to develop your own agent models. | ||||
|  | ||||
| If you use Soil in your research, don't forget to cite this paper: | ||||
|  | ||||
|   | ||||
| @@ -34,13 +34,14 @@ If you use Soil in your research, do not forget to cite this paper: | ||||
|  | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|    :maxdepth: 0 | ||||
|    :caption: Learn more about soil: | ||||
|  | ||||
|    installation | ||||
|    quickstart | ||||
|    Tutorial - Spreading news | ||||
|    Tutorial <soil_tutorial> | ||||
|  | ||||
| .. | ||||
|  | ||||
|  | ||||
| .. Indices and tables | ||||
|   | ||||
| Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.0 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 17 KiB | 
| Before Width: | Height: | Size: 16 KiB | 
| Before Width: | Height: | Size: 15 KiB | 
| Before Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 12 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_54_0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_54_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_55_9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_56_9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_61_0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_63_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_66_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_67_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_72_0.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_72_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_74_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_75_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/output_76_1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										2612
									
								
								docs/soil_tutorial.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -1,17 +0,0 @@ | ||||
| default_state: {} | ||||
| environment_agents: [] | ||||
| environment_params: {prob_neighbor_spread: 0.0, prob_tv_spread: 0.01} | ||||
| interval: 1 | ||||
| max_time: 20 | ||||
| name: Sim_prob_0 | ||||
| network_agents: | ||||
| - agent_type: NewsSpread | ||||
|   state: {has_tv: false} | ||||
|   weight: 1 | ||||
| - agent_type: NewsSpread | ||||
|   state: {has_tv: true} | ||||
|   weight: 2 | ||||
| network_params: {generator: erdos_renyi_graph, n: 500, p: 0.1} | ||||
| num_trials: 1 | ||||
| states: | ||||
| - {has_tv: true} | ||||
| @@ -1,20 +0,0 @@ | ||||
| import soil | ||||
| import random | ||||
|  | ||||
| class NewsSpread(soil.agents.FSM): | ||||
|     @soil.agents.default_state | ||||
|     @soil.agents.state | ||||
|     def neutral(self): | ||||
|         r = random.random() | ||||
|         if self['has_tv'] and r < self.env['prob_tv_spread']: | ||||
|                 return self.infected | ||||
|         return | ||||
|  | ||||
|     @soil.agents.state | ||||
|     def infected(self): | ||||
|         prob_infect = self.env['prob_neighbor_spread'] | ||||
|         for neighbor in self.get_neighboring_agents(state_id=self.neutral.id): | ||||
|             r = random.random() | ||||
|             if r < prob_infect: | ||||
|                 neighbor.state['id'] = self.infected.id | ||||
|         return | ||||
							
								
								
									
										460
									
								
								examples/newsspread/NewsSpread.ipynb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										138
									
								
								examples/newsspread/NewsSpread.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,138 @@ | ||||
| --- | ||||
| default_state: {} | ||||
| load_module: newsspread | ||||
| environment_agents: [] | ||||
| environment_params: | ||||
|   prob_neighbor_spread: 0.0 | ||||
|   prob_tv_spread: 0.01 | ||||
| interval: 1 | ||||
| max_time: 30 | ||||
| name: Sim_all_dumb | ||||
| network_agents: | ||||
| - agent_type: DumbViewer | ||||
|   state: | ||||
|     has_tv: false | ||||
|   weight: 1 | ||||
| - agent_type: DumbViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|   weight: 1 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| num_trials: 50 | ||||
| --- | ||||
| default_state: {} | ||||
| load_module: newsspread | ||||
| environment_agents: [] | ||||
| environment_params: | ||||
|   prob_neighbor_spread: 0.0 | ||||
|   prob_tv_spread: 0.01 | ||||
| interval: 1 | ||||
| max_time: 30 | ||||
| name: Sim_half_herd | ||||
| network_agents: | ||||
| - agent_type: DumbViewer | ||||
|   state: | ||||
|     has_tv: false | ||||
|   weight: 1 | ||||
| - agent_type: DumbViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|   weight: 1 | ||||
| - agent_type: HerdViewer | ||||
|   state: | ||||
|     has_tv: false | ||||
|   weight: 1 | ||||
| - agent_type: HerdViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|   weight: 1 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| num_trials: 50 | ||||
| --- | ||||
| default_state: {} | ||||
| load_module: newsspread | ||||
| environment_agents: [] | ||||
| environment_params: | ||||
|   prob_neighbor_spread: 0.0 | ||||
|   prob_tv_spread: 0.01 | ||||
| interval: 1 | ||||
| max_time: 30 | ||||
| name: Sim_all_herd | ||||
| network_agents: | ||||
| - agent_type: HerdViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|     id: infected | ||||
|   weight: 1 | ||||
| - agent_type: HerdViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|     id: neutral | ||||
|   weight: 1 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| num_trials: 50 | ||||
| --- | ||||
| default_state: {} | ||||
| load_module: newsspread | ||||
| environment_agents: [] | ||||
| environment_params: | ||||
|   prob_neighbor_spread: 0.0 | ||||
|   prob_tv_spread: 0.01 | ||||
|   prob_neighbor_cure: 0.1 | ||||
| interval: 1 | ||||
| max_time: 30 | ||||
| name: Sim_wise_herd | ||||
| network_agents: | ||||
| - agent_type: HerdViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|     id: infected | ||||
|   weight: 1 | ||||
| - agent_type: WiseViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|   weight: 1 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| num_trials: 50 | ||||
| --- | ||||
| default_state: {} | ||||
| load_module: newsspread | ||||
| environment_agents: [] | ||||
| environment_params: | ||||
|   prob_neighbor_spread: 0.0 | ||||
|   prob_tv_spread: 0.01 | ||||
|   prob_neighbor_cure: 0.1 | ||||
| interval: 1 | ||||
| max_time: 30 | ||||
| name: Sim_all_wise | ||||
| network_agents: | ||||
| - agent_type: WiseViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|     id: infected | ||||
|   weight: 1 | ||||
| - agent_type: WiseViewer | ||||
|   state: | ||||
|     has_tv: true | ||||
|   weight: 1 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| network_params: | ||||
|   generator: barabasi_albert_graph | ||||
|   n: 500 | ||||
|   m: 5 | ||||
| num_trials: 50 | ||||
							
								
								
									
										79
									
								
								examples/newsspread/newsspread.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,79 @@ | ||||
| from soil.agents import BaseAgent,FSM, state, default_state | ||||
| import random | ||||
| import logging | ||||
|  | ||||
|  | ||||
| class DumbViewer(FSM): | ||||
|     ''' | ||||
|     A viewer that gets infected via TV (if it has one) and tries to infect | ||||
|     its neighbors once it's infected. | ||||
|     ''' | ||||
|     defaults = { | ||||
|         'prob_neighbor_spread': 0.5, | ||||
|         'prob_neighbor_cure': 0.25, | ||||
|     } | ||||
|  | ||||
|     @default_state | ||||
|     @state | ||||
|     def neutral(self): | ||||
|         r = random.random() | ||||
|         if self['has_tv'] and r < self.env['prob_tv_spread']: | ||||
|             self.infect() | ||||
|         return | ||||
|  | ||||
|     @state | ||||
|     def infected(self): | ||||
|         for neighbor in self.get_neighboring_agents(state_id=self.neutral.id): | ||||
|             prob_infect = self.env['prob_neighbor_spread'] | ||||
|             r = random.random() | ||||
|             if r < prob_infect: | ||||
|                 self.set_state(self.infected.id) | ||||
|                 neighbor.infect() | ||||
|         return | ||||
|  | ||||
|     def infect(self): | ||||
|         self.set_state(self.infected) | ||||
|  | ||||
| class HerdViewer(DumbViewer): | ||||
|     ''' | ||||
|     A viewer whose probability of infection depends on the state of its neighbors. | ||||
|     ''' | ||||
|  | ||||
|     level = logging.DEBUG | ||||
|   | ||||
|     def infect(self): | ||||
|         infected = self.count_neighboring_agents(state_id=self.infected.id) | ||||
|         total = self.count_neighboring_agents() | ||||
|         prob_infect = self.env['prob_neighbor_spread'] * infected/total | ||||
|         self.debug('prob_infect', prob_infect) | ||||
|         r = random.random() | ||||
|         if r < prob_infect: | ||||
|             self.set_state(self.infected.id) | ||||
|  | ||||
| class WiseViewer(HerdViewer): | ||||
|     ''' | ||||
|     A viewer that can change its mind. | ||||
|     ''' | ||||
|     @state | ||||
|     def cured(self): | ||||
|         prob_cure = self.env['prob_neighbor_cure'] | ||||
|         for neighbor in self.get_neighboring_agents(state_id=self.infected.id): | ||||
|             r = random.random() | ||||
|             if r < prob_cure: | ||||
|                 try: | ||||
|                     neighbor.cure() | ||||
|                 except AttributeError: | ||||
|                     self.debug('Viewer {} cannot be cured'.format(neighbor.id)) | ||||
|         return | ||||
|  | ||||
|     def cure(self): | ||||
|         self.set_state(self.cured.id) | ||||
|  | ||||
|     @state | ||||
|     def infected(self): | ||||
|         prob_cure = self.env['prob_neighbor_cure'] | ||||
|         r = random.random() | ||||
|         if r < prob_cure: | ||||
|             self.cure() | ||||
|             return | ||||
|         return super().infected() | ||||
| @@ -1,4 +1,4 @@ | ||||
| from soil.agents import NetworkAgent, FSM, state, default_state, BaseAgent | ||||
| from soil.agents import FSM, state, default_state, BaseAgent | ||||
| from enum import Enum | ||||
| from random import random, choice | ||||
| from itertools import islice | ||||
| @@ -11,7 +11,7 @@ class Genders(Enum): | ||||
|     female = 'female' | ||||
|  | ||||
|  | ||||
| class RabbitModel(NetworkAgent, FSM): | ||||
| class RabbitModel(FSM): | ||||
|  | ||||
|     level = logging.INFO | ||||
|  | ||||
| @@ -26,7 +26,7 @@ class RabbitModel(NetworkAgent, FSM): | ||||
|     life_expectancy = 365 * 3 | ||||
|     gestation = 33 | ||||
|     pregnancy = -1 | ||||
|     max_females = 2 | ||||
|     max_females = 5 | ||||
|  | ||||
|     @default_state | ||||
|     @state | ||||
| @@ -71,6 +71,7 @@ class RabbitModel(NetworkAgent, FSM): | ||||
|         self.debug('Pregnancy: {}'.format(self['pregnancy'])) | ||||
|         if self['pregnancy'] >= self.gestation: | ||||
|             number_of_babies = int(8+4*random()) | ||||
|             self.info('Having {} babies'.format(number_of_babies)) | ||||
|             for i in range(number_of_babies): | ||||
|                 state = {} | ||||
|                 state['gender'] = choice(list(Genders)).value | ||||
| @@ -79,13 +80,13 @@ class RabbitModel(NetworkAgent, FSM): | ||||
|                 self.env.add_edge(self['mate'], child.id) | ||||
|                 # self.add_edge() | ||||
|                 self.debug('A BABY IS COMING TO LIFE') | ||||
|                 self.env['rabbits_alive'] = self.env.get('rabbits_alive', 0)+1 | ||||
|                 self.env['rabbits_alive'] = self.env.get('rabbits_alive', self.global_topology.number_of_nodes())+1 | ||||
|                 self.debug('Rabbits alive: {}'.format(self.env['rabbits_alive'])) | ||||
|                 self['offspring'] += 1 | ||||
|                 self.env.get_agent(self['mate'])['offspring'] += 1 | ||||
|                 del self['mate'] | ||||
|                 self['pregnancy'] = -1 | ||||
|                 return self.fertile | ||||
|             del self['mate'] | ||||
|             self['pregnancy'] = -1 | ||||
|             return self.fertile | ||||
|  | ||||
|     @state | ||||
|     def dead(self): | ||||
| @@ -98,12 +99,12 @@ class RabbitModel(NetworkAgent, FSM): | ||||
|  | ||||
| class RandomAccident(BaseAgent): | ||||
|  | ||||
|     level = logging.INFO | ||||
|     level = logging.DEBUG | ||||
|  | ||||
|     def step(self): | ||||
|         rabbits_total = self.global_topology.number_of_nodes() | ||||
|         rabbits_alive = self.env.get('rabbits_alive', rabbits_total) | ||||
|         prob_death = self.env.get('prob_death', 1e-100)*math.log(max(1, rabbits_alive)) | ||||
|         prob_death = self.env.get('prob_death', 1e-100)*math.floor(math.log10(max(1, rabbits_alive))) | ||||
|         self.debug('Killing some rabbits with prob={}!'.format(prob_death)) | ||||
|         for i in self.env.network_agents: | ||||
|             if i.state['id'] == i.dead.id: | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| --- | ||||
| load_module: rabbit_agents | ||||
| name: rabbits_example | ||||
| max_time: 1500 | ||||
| max_time: 1200 | ||||
| interval: 1 | ||||
| seed: MySeed | ||||
| agent_type: RabbitModel | ||||
| environment_agents: | ||||
|     - agent_type: RandomAccident | ||||
| environment_params: | ||||
|   prob_death: 0.0001 | ||||
|   prob_death: 0.001 | ||||
| default_state: | ||||
|   mating_prob: 0.01 | ||||
| topology: | ||||
|   | ||||
							
								
								
									
										23569
									
								
								examples/tutorial/soil_tutorial.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										3867
									
								
								examples/tutorial/soil_tutorial.ipynb
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -4,15 +4,20 @@ import os | ||||
| import pdb | ||||
| import logging | ||||
|  | ||||
| __version__ = "0.9.7" | ||||
| __version__ = "0.10" | ||||
|  | ||||
| try: | ||||
|     basestring | ||||
| except NameError: | ||||
|     basestring = str | ||||
|  | ||||
| logging.basicConfig()#format=FORMAT) | ||||
| logging.basicConfig() | ||||
|  | ||||
| from . import agents | ||||
| from . import simulation | ||||
| from . import environment | ||||
| from . import utils | ||||
| from . import analysis | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import random | ||||
| from . import NetworkAgent | ||||
| from . import BaseAgent | ||||
|  | ||||
|  | ||||
| class BassModel(NetworkAgent): | ||||
| class BassModel(BaseAgent): | ||||
|     """ | ||||
|     Settings: | ||||
|         innovation_prob | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import random | ||||
| from . import NetworkAgent | ||||
| from . import BaseAgent | ||||
|  | ||||
|  | ||||
| class BigMarketModel(NetworkAgent): | ||||
| class BigMarketModel(BaseAgent): | ||||
|     """ | ||||
|     Settings: | ||||
|         Names: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from . import NetworkAgent | ||||
| from . import BaseAgent | ||||
|  | ||||
|  | ||||
| class CounterModel(NetworkAgent): | ||||
| class CounterModel(BaseAgent): | ||||
|     """ | ||||
|     Dummy behaviour. It counts the number of nodes in the network and neighbors | ||||
|     in each step and adds it to its state. | ||||
| @@ -16,7 +16,7 @@ class CounterModel(NetworkAgent): | ||||
|         self.state['total'] = total | ||||
|  | ||||
|  | ||||
| class AggregatedCounter(NetworkAgent): | ||||
| class AggregatedCounter(BaseAgent): | ||||
|     """ | ||||
|     Dummy behaviour. It counts the number of nodes in the network and neighbors | ||||
|     in each step and adds it to its state. | ||||
| @@ -28,4 +28,5 @@ class AggregatedCounter(NetworkAgent): | ||||
|         neighbors = len(list(self.get_neighboring_agents())) | ||||
|         self.state['times'] = self.state.get('times', 0) + 1 | ||||
|         self.state['neighbors'] = self.state.get('neighbors', 0) + neighbors | ||||
|         self.state['total'] = self.state.get('total', 0) + total | ||||
|         self.state['total'] = total = self.state.get('total', 0) + total | ||||
|         self.debug('Running for step: {}. Total: {}'.format(self.now, total)) | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import random | ||||
| import numpy as np | ||||
| from . import NetworkAgent | ||||
| from . import BaseAgent | ||||
|  | ||||
|  | ||||
| class SpreadModelM2(NetworkAgent): | ||||
| class SpreadModelM2(BaseAgent): | ||||
|     """ | ||||
|     Settings: | ||||
|         prob_neutral_making_denier | ||||
| @@ -104,7 +104,7 @@ class SpreadModelM2(NetworkAgent): | ||||
|                 neighbor.state['id'] = 2  # Cured | ||||
|  | ||||
|  | ||||
| class ControlModelM2(NetworkAgent): | ||||
| class ControlModelM2(BaseAgent): | ||||
|     """ | ||||
|     Settings: | ||||
|         prob_neutral_making_denier | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import random | ||||
| import numpy as np | ||||
| from . import FSM, NetworkAgent, state | ||||
| from . import FSM, state | ||||
|  | ||||
|  | ||||
| class SISaModel(FSM, NetworkAgent): | ||||
| class SISaModel(FSM): | ||||
|     """ | ||||
|     Settings: | ||||
|         neutral_discontent_spon_prob | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import random | ||||
| from . import NetworkAgent | ||||
| from . import BaseAgent | ||||
|  | ||||
|  | ||||
| class SentimentCorrelationModel(NetworkAgent): | ||||
| class SentimentCorrelationModel(BaseAgent): | ||||
|     """ | ||||
|     Settings: | ||||
|         outside_effects_prob | ||||
|   | ||||
| @@ -72,9 +72,10 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent): | ||||
|             return None | ||||
|  | ||||
|     def run(self): | ||||
|         interval = self.env.interval | ||||
|         while self.alive: | ||||
|             res = self.step() | ||||
|             yield res or self.env.timeout(self.env.interval) | ||||
|             yield res or self.env.timeout(interval) | ||||
|  | ||||
|     def die(self, remove=False): | ||||
|         self.alive = False | ||||
| @@ -99,7 +100,10 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent): | ||||
|             count += 1 | ||||
|         return count | ||||
|  | ||||
|     def get_agents(self, state_id=None, limit_neighbors=False, **kwargs): | ||||
|     def count_neighboring_agents(self, state_id=None): | ||||
|         return len(super().get_agents(state_id, limit_neighbors=True)) | ||||
|  | ||||
|     def get_agents(self, state_id=None, limit_neighbors=False, iterator=False, **kwargs): | ||||
|         if limit_neighbors: | ||||
|             agents = super().get_agents(state_id, limit_neighbors) | ||||
|         else: | ||||
| @@ -113,9 +117,13 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent): | ||||
|                     return False | ||||
|             return True | ||||
|  | ||||
|         return filter(matches_all, agents) | ||||
|         f = filter(matches_all, agents) | ||||
|         if iterator: | ||||
|             return f | ||||
|         return list(f) | ||||
|  | ||||
|     def log(self, message, level=logging.INFO, **kwargs): | ||||
|     def log(self, message, *args, level=logging.INFO, **kwargs): | ||||
|         message = message + " ".join(str(i) for i in args) | ||||
|         message = "\t@{:>5}:\t{}".format(self.now, message) | ||||
|         for k, v in kwargs: | ||||
|             message += " {k}={v} ".format(k, v) | ||||
| @@ -130,11 +138,6 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent): | ||||
|     def info(self, *args, **kwargs): | ||||
|         return self.log(*args, level=logging.INFO, **kwargs) | ||||
|  | ||||
| class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent): | ||||
|  | ||||
|     def count_neighboring_agents(self, state_id=None): | ||||
|         return self.count_agents(state_id, limit_neighbors=True) | ||||
|  | ||||
|  | ||||
| def state(func): | ||||
|  | ||||
| @@ -150,7 +153,7 @@ def state(func): | ||||
|             try: | ||||
|                 self.state['id'] = next_state.id | ||||
|             except AttributeError: | ||||
|                 raise NotImplemented('State id %s is not valid.' % next_state) | ||||
|                 raise ValueError('State id %s is not valid.' % next_state) | ||||
|         return when | ||||
|  | ||||
|     func_wrapper.id = func.__name__ | ||||
|   | ||||
							
								
								
									
										173
									
								
								soil/analysis.py
									
									
									
									
									
								
							
							
						
						| @@ -4,20 +4,175 @@ import glob | ||||
| import yaml | ||||
| from os.path import join | ||||
|  | ||||
| from . import utils | ||||
|  | ||||
| def get_data(pattern, process=True, attributes=None): | ||||
|  | ||||
| def read_data(*args, group=False, **kwargs): | ||||
|     iterable = _read_data(*args, **kwargs) | ||||
|     if group: | ||||
|         return group_trials(iterable) | ||||
|     else: | ||||
|         return list(iterable) | ||||
|  | ||||
|  | ||||
| def _read_data(pattern, keys=None, convert_types=False, | ||||
|                process=None, from_csv=False, **kwargs): | ||||
|     for folder in glob.glob(pattern): | ||||
|         config_file = glob.glob(join(folder, '*.yml'))[0] | ||||
|         config = yaml.load(open(config_file)) | ||||
|         for trial_data in sorted(glob.glob(join(folder, '*.environment.csv'))): | ||||
|             df = pd.read_csv(trial_data) | ||||
|             if process: | ||||
|                 if attributes is not None: | ||||
|                     df = df[df['attribute'].isin(attributes)] | ||||
|                 df = df.pivot_table(values='attribute', index='tstep', columns=['value'], aggfunc='count').fillna(0) | ||||
|             yield config_file, df, config | ||||
|         df = None | ||||
|         if from_csv: | ||||
|             for trial_data in sorted(glob.glob(join(folder, | ||||
|                                                     '*.environment.csv'))): | ||||
|                 df = read_csv(trial_data, convert_types=convert_types) | ||||
|                 if process: | ||||
|                     df = process(df, **kwargs) | ||||
|                 yield config_file, df, config | ||||
|         else: | ||||
|             for trial_data in sorted(glob.glob(join(folder, '*.db.sqlite'))): | ||||
|                 df = read_sql(trial_data, convert_types=convert_types, | ||||
|                               keys=keys) | ||||
|                 if process: | ||||
|                     df = process(df, **kwargs) | ||||
|                 yield config_file, df, config | ||||
|  | ||||
|  | ||||
| def read_csv(filename, keys=None, convert_types=False, **kwargs): | ||||
|     ''' | ||||
|     Read a CSV in canonical form: :: | ||||
|  | ||||
|         <agent_id, t_step, key, value, value_type> | ||||
|  | ||||
|     ''' | ||||
|     df = pd.read_csv(filename) | ||||
|     if convert_types: | ||||
|         df = convert_types_slow(df) | ||||
|     if keys: | ||||
|         df = df[df['key'].isin(keys)] | ||||
|     return df | ||||
|  | ||||
|  | ||||
| def read_sql(filename, keys=None, convert_types=False, limit=-1): | ||||
|     condition = '' | ||||
|     if keys: | ||||
|         k = map(lambda x: "\'{}\'".format(x), keys) | ||||
|         condition = 'where key in ({})'.format(','.join(k)) | ||||
|     query = 'select * from history {} limit {}'.format(condition, limit) | ||||
|     df = pd.read_sql_query(query, 'sqlite:///{}'.format(filename)) | ||||
|     if convert_types: | ||||
|         df = convert_types_slow(df) | ||||
|     return df | ||||
|  | ||||
|  | ||||
| def convert_row(row): | ||||
|     row['value'] = utils.convert(row['value'], row['value_type']) | ||||
|     return row | ||||
|  | ||||
|  | ||||
| def convert_types_slow(df): | ||||
|     '''This is a slow operation.''' | ||||
|     dtypes = get_types(df) | ||||
|     for k, v in dtypes.items(): | ||||
|         t = df[df['key']==k] | ||||
|         t['value'] = t['value'].astype(v) | ||||
|     df = df.apply(convert_row, axis=1) | ||||
|     return df | ||||
|  | ||||
| def split_df(df): | ||||
|     ''' | ||||
|     Split a dataframe in two dataframes: one with the history of agents, | ||||
|     and one with the environment history | ||||
|     ''' | ||||
|     envmask = (df['agent_id'] == 'env') | ||||
|     n_env = envmask.sum() | ||||
|     if n_env == len(df): | ||||
|         return df, None | ||||
|     elif n_env == 0: | ||||
|         return None, df | ||||
|     agents, env = [x for _, x in df.groupby(envmask)] | ||||
|     return env, agents | ||||
|  | ||||
|  | ||||
| def process(df, **kwargs): | ||||
|     ''' | ||||
|     Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into | ||||
|     two dataframes with a column per key: one with the history of the agents, and one for the | ||||
|     history of the environment. | ||||
|     ''' | ||||
|     env, agents = split_df(df) | ||||
|     return process_one(env, **kwargs), process_one(agents, **kwargs) | ||||
|  | ||||
|  | ||||
| def get_types(df): | ||||
|     dtypes = df.groupby(by=['key'])['value_type'].unique() | ||||
|     return {k:v[0] for k,v in dtypes.iteritems()} | ||||
|  | ||||
|  | ||||
| def process_one(df, *keys, columns=['key'], values='value', | ||||
|                 index=['t_step', 'agent_id'], aggfunc='first', **kwargs): | ||||
|     ''' | ||||
|     Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into | ||||
|     a dataframe with a column per key | ||||
|     ''' | ||||
|     if df is None: | ||||
|         return df | ||||
|     if keys: | ||||
|         df = df[df['key'].isin(keys)] | ||||
|  | ||||
|     dtypes = get_types(df) | ||||
|  | ||||
|     df = df.pivot_table(values=values, index=index, columns=columns, | ||||
|                         aggfunc=aggfunc, **kwargs) | ||||
|     df = df.fillna(0).astype(dtypes) | ||||
|     return df | ||||
|  | ||||
|  | ||||
| def get_count_processed(df, *keys): | ||||
|     if keys: | ||||
|         df = df[list(keys)] | ||||
|     # p = df.groupby(level=0).apply(pd.Series.value_counts) | ||||
|     p = df.unstack().apply(pd.Series.value_counts, axis=1) | ||||
|     return p | ||||
|  | ||||
|  | ||||
| def get_count(df, *keys): | ||||
|     if keys: | ||||
|         df = df[df['key'].isin(keys)] | ||||
|     p = df.groupby(by=['t_step', 'key', 'value']).size().unstack(level=[1,2]).fillna(0) | ||||
|     return p | ||||
|  | ||||
|  | ||||
| def get_value(df, *keys, aggfunc='sum'): | ||||
|     if keys: | ||||
|         df = df[df['key'].isin(keys)] | ||||
|     p = process_one(df, *keys) | ||||
|     p = p.groupby(level='t_step').agg(aggfunc) | ||||
|     return p | ||||
|  | ||||
|  | ||||
| def plot_all(*args, **kwargs): | ||||
|     for config_file, df, config in sorted(get_data(*args, **kwargs)): | ||||
|     ''' | ||||
|     Read all the trial data and plot the result of applying a function on them. | ||||
|     ''' | ||||
|     dfs = do_all(*args, **kwargs) | ||||
|     ps = [] | ||||
|     for line in dfs: | ||||
|         f, df, config = line | ||||
|         df.plot(title=config['name']) | ||||
|         ps.append(df) | ||||
|     return ps | ||||
|  | ||||
| def do_all(pattern, func, *keys, include_env=False, **kwargs): | ||||
|     for config_file, df, config in read_data(pattern, keys=keys): | ||||
|         p = func(df, *keys, **kwargs) | ||||
|         p.plot(title=config['name']) | ||||
|         yield config_file, p, config | ||||
|  | ||||
|  | ||||
| def group_trials(trials, aggfunc=['mean', 'min', 'max', 'std']): | ||||
|     trials = list(trials) | ||||
|     trials = list(map(lambda x: x[1] if isinstance(x, tuple) else x, trials)) | ||||
|     return pd.concat(trials).groupby(level=0).agg(aggfunc).reorder_levels([2, 0,1] ,axis=1) | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -41,17 +41,20 @@ class SoilEnvironment(nxsim.NetworkEnvironment): | ||||
|         # executed before network agents | ||||
|         self['SEED'] = seed or time.time() | ||||
|         random.seed(self['SEED']) | ||||
|         self.process(self.save_state()) | ||||
|         self.environment_agents = environment_agents or [] | ||||
|         self.network_agents = network_agents or [] | ||||
|         self.process(self.save_state()) | ||||
|         if self.dump: | ||||
|             self._db_path = os.path.join(self.get_path(), 'db.sqlite') | ||||
|             self._db_path = os.path.join(self.get_path(), '{}.db.sqlite'.format(self.name)) | ||||
|         else: | ||||
|             self._db_path = ":memory:" | ||||
|         self.create_db(self._db_path) | ||||
|  | ||||
|     def create_db(self, db_path=None): | ||||
|         db_path = db_path or self._db_path | ||||
|         if os.path.exists(db_path): | ||||
|             newname = db_path.replace('db.sqlite', 'backup{}.sqlite'.format(time.time())) | ||||
|             os.rename(db_path, newname) | ||||
|         self._db = sqlite3.connect(db_path) | ||||
|         with self._db: | ||||
|             self._db.execute('''CREATE TABLE IF NOT EXISTS history (agent_id text, t_step int, key text, value text, value_type text)''') | ||||
| @@ -118,24 +121,25 @@ class SoilEnvironment(nxsim.NetworkEnvironment): | ||||
|         return self.G.add_edge(agent1, agent2) | ||||
|  | ||||
|     def run(self, *args, **kwargs): | ||||
|         self._save_state() | ||||
|         super().run(*args, **kwargs) | ||||
|         self._save_state() | ||||
|  | ||||
|     def _save_state(self, now=None): | ||||
|         # for agent in self.agents: | ||||
|         #     agent.save_state() | ||||
|         utils.logger.debug('Saving state @{}'.format(self.now)) | ||||
|         with self._db: | ||||
|             self._db.executemany("insert into history(agent_id, t_step, key, value, value_type) values (?, ?, ?, ?, ?)", self.state_to_tuples(now=now)) | ||||
|  | ||||
|     def save_state(self): | ||||
|         self._save_state() | ||||
|         while self.peek() != simpy.core.Infinity: | ||||
|             utils.logger.info('Step: {}'.format(self.now)) | ||||
|             delay = max(self.peek() - self.now, self.interval) | ||||
|             utils.logger.debug('Step: {}'.format(self.now)) | ||||
|             ev = self.event() | ||||
|             ev._ok = True | ||||
|             # Schedule the event with minimum priority so | ||||
|             # that it executes after all agents are done | ||||
|             self.schedule(ev, -1, self.peek()) | ||||
|             # that it executes before all agents | ||||
|             self.schedule(ev, -999, delay) | ||||
|             yield ev | ||||
|             self._save_state() | ||||
|  | ||||
| @@ -215,7 +219,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment): | ||||
|  | ||||
|         with open(csv_name, 'w') as f: | ||||
|             cr = csv.writer(f) | ||||
|             cr.writerow(('agent_id', 'tstep', 'attribute', 'value')) | ||||
|             cr.writerow(('agent_id', 't_step', 'key', 'value', 'value_type')) | ||||
|             for i in self.history_to_tuples(): | ||||
|                 cr.writerow(i) | ||||
|  | ||||
| @@ -229,14 +233,16 @@ class SoilEnvironment(nxsim.NetworkEnvironment): | ||||
|         if now is None: | ||||
|             now = self.now | ||||
|         for k, v in self.environment_params.items(): | ||||
|             yield 'env', now, k, v, type(v).__name__ | ||||
|             v, v_t = utils.repr(v) | ||||
|             yield 'env', now, k, v, v_t | ||||
|         for agent in self.agents: | ||||
|             for k, v in agent.state.items(): | ||||
|                 yield agent.id, now, k, v, type(v).__name__ | ||||
|                 v, v_t = utils.repr(v) | ||||
|                 yield agent.id, now, k, v, v_t | ||||
|  | ||||
|     def history_to_tuples(self): | ||||
|         with self._db: | ||||
|             res = self._db.execute("select agent_id, t_step, key, value from history ").fetchall() | ||||
|             res = self._db.execute("select agent_id, t_step, key, value, value_type from history ").fetchall() | ||||
|         yield from res | ||||
|  | ||||
|     def history_to_graph(self): | ||||
|   | ||||
| @@ -67,7 +67,7 @@ class SoilSimulation(NetworkSimulation): | ||||
|         self.default_state = default_state or {} | ||||
|         self.dir_path = dir_path or os.getcwd() | ||||
|         self.interval = interval | ||||
|         self.seed = seed | ||||
|         self.seed = str(seed) or str(time.time()) | ||||
|         self.dump = dump | ||||
|         self.environment_params = environment_params or {} | ||||
|  | ||||
| @@ -168,7 +168,7 @@ class SoilSimulation(NetworkSimulation): | ||||
|         env_name = '{}_trial_{}'.format(self.name, trial_id) | ||||
|         env = environment.SoilEnvironment(name=env_name, | ||||
|                                           topology=self.topology.copy(), | ||||
|                                           seed=self.seed, | ||||
|                                           seed=self.seed+env_name, | ||||
|                                           initial_time=0, | ||||
|                                           dump=self.dump, | ||||
|                                           interval=self.interval, | ||||
|   | ||||
| @@ -13,7 +13,6 @@ from contextlib import contextmanager | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| logger.setLevel(logging.INFO) | ||||
| logger.addHandler(logging.StreamHandler()) | ||||
|  | ||||
|  | ||||
| def load_network(network_params, dir_path=None): | ||||
| @@ -86,6 +85,12 @@ def agent_from_distribution(distribution, value=-1): | ||||
|     raise Exception('Distribution for value {} not found in: {}'.format(value, distribution)) | ||||
|  | ||||
|  | ||||
| def repr(v): | ||||
|     if isinstance(v, bool): | ||||
|         v = "true" if v else "" | ||||
|         return v, bool.__name__ | ||||
|     return v, type(v).__name__ | ||||
|  | ||||
| def convert(value, type_): | ||||
|     import importlib | ||||
|     try: | ||||
|   | ||||
| @@ -60,7 +60,7 @@ class TestMain(TestCase): | ||||
|             'network_params': { | ||||
|                 'path': join(ROOT, 'test.gexf') | ||||
|             }, | ||||
|             'agent_type': 'NetworkAgent', | ||||
|             'agent_type': 'BaseAgent', | ||||
|             'environment_params': { | ||||
|             } | ||||
|         } | ||||
| @@ -119,7 +119,7 @@ class TestMain(TestCase): | ||||
|  | ||||
|     def test_custom_agent(self): | ||||
|         """Allow for search of neighbors with a certain state_id""" | ||||
|         class CustomAgent(agents.NetworkAgent): | ||||
|         class CustomAgent(agents.BaseAgent): | ||||
|             def step(self): | ||||
|                 self.state['neighbors'] = self.count_agents(state_id=0, | ||||
|                                                             limit_neighbors=True) | ||||
| @@ -208,7 +208,7 @@ class TestMain(TestCase): | ||||
|  | ||||
|         res = list(env.history_to_tuples()) | ||||
|         assert len(res) == len(env.environment_params) | ||||
|         assert ('env', 0, 'test', 'test_value') in res | ||||
|         assert ('env', 0, 'test', 'test_value', 'str') in res | ||||
|  | ||||
|         env['test'] = 'second_value' | ||||
|         env._save_state(now=1) | ||||
|   | ||||