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.
|
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).
|
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:
|
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::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 0
|
||||||
:caption: Learn more about soil:
|
:caption: Learn more about soil:
|
||||||
|
|
||||||
installation
|
installation
|
||||||
quickstart
|
quickstart
|
||||||
Tutorial - Spreading news
|
Tutorial <soil_tutorial>
|
||||||
|
|
||||||
|
..
|
||||||
|
|
||||||
|
|
||||||
.. Indices and tables
|
.. 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 enum import Enum
|
||||||
from random import random, choice
|
from random import random, choice
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
@ -11,7 +11,7 @@ class Genders(Enum):
|
|||||||
female = 'female'
|
female = 'female'
|
||||||
|
|
||||||
|
|
||||||
class RabbitModel(NetworkAgent, FSM):
|
class RabbitModel(FSM):
|
||||||
|
|
||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class RabbitModel(NetworkAgent, FSM):
|
|||||||
life_expectancy = 365 * 3
|
life_expectancy = 365 * 3
|
||||||
gestation = 33
|
gestation = 33
|
||||||
pregnancy = -1
|
pregnancy = -1
|
||||||
max_females = 2
|
max_females = 5
|
||||||
|
|
||||||
@default_state
|
@default_state
|
||||||
@state
|
@state
|
||||||
@ -71,6 +71,7 @@ class RabbitModel(NetworkAgent, FSM):
|
|||||||
self.debug('Pregnancy: {}'.format(self['pregnancy']))
|
self.debug('Pregnancy: {}'.format(self['pregnancy']))
|
||||||
if self['pregnancy'] >= self.gestation:
|
if self['pregnancy'] >= self.gestation:
|
||||||
number_of_babies = int(8+4*random())
|
number_of_babies = int(8+4*random())
|
||||||
|
self.info('Having {} babies'.format(number_of_babies))
|
||||||
for i in range(number_of_babies):
|
for i in range(number_of_babies):
|
||||||
state = {}
|
state = {}
|
||||||
state['gender'] = choice(list(Genders)).value
|
state['gender'] = choice(list(Genders)).value
|
||||||
@ -79,7 +80,7 @@ class RabbitModel(NetworkAgent, FSM):
|
|||||||
self.env.add_edge(self['mate'], child.id)
|
self.env.add_edge(self['mate'], child.id)
|
||||||
# self.add_edge()
|
# self.add_edge()
|
||||||
self.debug('A BABY IS COMING TO LIFE')
|
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.debug('Rabbits alive: {}'.format(self.env['rabbits_alive']))
|
||||||
self['offspring'] += 1
|
self['offspring'] += 1
|
||||||
self.env.get_agent(self['mate'])['offspring'] += 1
|
self.env.get_agent(self['mate'])['offspring'] += 1
|
||||||
@ -98,12 +99,12 @@ class RabbitModel(NetworkAgent, FSM):
|
|||||||
|
|
||||||
class RandomAccident(BaseAgent):
|
class RandomAccident(BaseAgent):
|
||||||
|
|
||||||
level = logging.INFO
|
level = logging.DEBUG
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
rabbits_total = self.global_topology.number_of_nodes()
|
rabbits_total = self.global_topology.number_of_nodes()
|
||||||
rabbits_alive = self.env.get('rabbits_alive', rabbits_total)
|
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))
|
self.debug('Killing some rabbits with prob={}!'.format(prob_death))
|
||||||
for i in self.env.network_agents:
|
for i in self.env.network_agents:
|
||||||
if i.state['id'] == i.dead.id:
|
if i.state['id'] == i.dead.id:
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
load_module: rabbit_agents
|
load_module: rabbit_agents
|
||||||
name: rabbits_example
|
name: rabbits_example
|
||||||
max_time: 1500
|
max_time: 1200
|
||||||
interval: 1
|
interval: 1
|
||||||
seed: MySeed
|
seed: MySeed
|
||||||
agent_type: RabbitModel
|
agent_type: RabbitModel
|
||||||
environment_agents:
|
environment_agents:
|
||||||
- agent_type: RandomAccident
|
- agent_type: RandomAccident
|
||||||
environment_params:
|
environment_params:
|
||||||
prob_death: 0.0001
|
prob_death: 0.001
|
||||||
default_state:
|
default_state:
|
||||||
mating_prob: 0.01
|
mating_prob: 0.01
|
||||||
topology:
|
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 pdb
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
__version__ = "0.9.7"
|
__version__ = "0.10"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
basestring
|
basestring
|
||||||
except NameError:
|
except NameError:
|
||||||
basestring = str
|
basestring = str
|
||||||
|
|
||||||
logging.basicConfig()#format=FORMAT)
|
logging.basicConfig()
|
||||||
|
|
||||||
|
from . import agents
|
||||||
|
from . import simulation
|
||||||
|
from . import environment
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from . import analysis
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class BassModel(NetworkAgent):
|
class BassModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
innovation_prob
|
innovation_prob
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class BigMarketModel(NetworkAgent):
|
class BigMarketModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
Names:
|
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
|
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||||
in each step and adds it to its state.
|
in each step and adds it to its state.
|
||||||
@ -16,7 +16,7 @@ class CounterModel(NetworkAgent):
|
|||||||
self.state['total'] = total
|
self.state['total'] = total
|
||||||
|
|
||||||
|
|
||||||
class AggregatedCounter(NetworkAgent):
|
class AggregatedCounter(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||||
in each step and adds it to its state.
|
in each step and adds it to its state.
|
||||||
@ -28,4 +28,5 @@ class AggregatedCounter(NetworkAgent):
|
|||||||
neighbors = len(list(self.get_neighboring_agents()))
|
neighbors = len(list(self.get_neighboring_agents()))
|
||||||
self.state['times'] = self.state.get('times', 0) + 1
|
self.state['times'] = self.state.get('times', 0) + 1
|
||||||
self.state['neighbors'] = self.state.get('neighbors', 0) + neighbors
|
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 random
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class SpreadModelM2(NetworkAgent):
|
class SpreadModelM2(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
prob_neutral_making_denier
|
prob_neutral_making_denier
|
||||||
@ -104,7 +104,7 @@ class SpreadModelM2(NetworkAgent):
|
|||||||
neighbor.state['id'] = 2 # Cured
|
neighbor.state['id'] = 2 # Cured
|
||||||
|
|
||||||
|
|
||||||
class ControlModelM2(NetworkAgent):
|
class ControlModelM2(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
prob_neutral_making_denier
|
prob_neutral_making_denier
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import random
|
import random
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from . import FSM, NetworkAgent, state
|
from . import FSM, state
|
||||||
|
|
||||||
|
|
||||||
class SISaModel(FSM, NetworkAgent):
|
class SISaModel(FSM):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
neutral_discontent_spon_prob
|
neutral_discontent_spon_prob
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class SentimentCorrelationModel(NetworkAgent):
|
class SentimentCorrelationModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
outside_effects_prob
|
outside_effects_prob
|
||||||
|
@ -72,9 +72,10 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
interval = self.env.interval
|
||||||
while self.alive:
|
while self.alive:
|
||||||
res = self.step()
|
res = self.step()
|
||||||
yield res or self.env.timeout(self.env.interval)
|
yield res or self.env.timeout(interval)
|
||||||
|
|
||||||
def die(self, remove=False):
|
def die(self, remove=False):
|
||||||
self.alive = False
|
self.alive = False
|
||||||
@ -99,7 +100,10 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
count += 1
|
count += 1
|
||||||
return count
|
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:
|
if limit_neighbors:
|
||||||
agents = super().get_agents(state_id, limit_neighbors)
|
agents = super().get_agents(state_id, limit_neighbors)
|
||||||
else:
|
else:
|
||||||
@ -113,9 +117,13 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
return False
|
return False
|
||||||
return True
|
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)
|
message = "\t@{:>5}:\t{}".format(self.now, message)
|
||||||
for k, v in kwargs:
|
for k, v in kwargs:
|
||||||
message += " {k}={v} ".format(k, v)
|
message += " {k}={v} ".format(k, v)
|
||||||
@ -130,11 +138,6 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
def info(self, *args, **kwargs):
|
def info(self, *args, **kwargs):
|
||||||
return self.log(*args, level=logging.INFO, **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):
|
def state(func):
|
||||||
|
|
||||||
@ -150,7 +153,7 @@ def state(func):
|
|||||||
try:
|
try:
|
||||||
self.state['id'] = next_state.id
|
self.state['id'] = next_state.id
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise NotImplemented('State id %s is not valid.' % next_state)
|
raise ValueError('State id %s is not valid.' % next_state)
|
||||||
return when
|
return when
|
||||||
|
|
||||||
func_wrapper.id = func.__name__
|
func_wrapper.id = func.__name__
|
||||||
|
169
soil/analysis.py
@ -4,20 +4,175 @@ import glob
|
|||||||
import yaml
|
import yaml
|
||||||
from os.path import join
|
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):
|
for folder in glob.glob(pattern):
|
||||||
config_file = glob.glob(join(folder, '*.yml'))[0]
|
config_file = glob.glob(join(folder, '*.yml'))[0]
|
||||||
config = yaml.load(open(config_file))
|
config = yaml.load(open(config_file))
|
||||||
for trial_data in sorted(glob.glob(join(folder, '*.environment.csv'))):
|
df = None
|
||||||
df = pd.read_csv(trial_data)
|
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:
|
if process:
|
||||||
if attributes is not None:
|
df = process(df, **kwargs)
|
||||||
df = df[df['attribute'].isin(attributes)]
|
yield config_file, df, config
|
||||||
df = df.pivot_table(values='attribute', index='tstep', columns=['value'], aggfunc='count').fillna(0)
|
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
|
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):
|
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'])
|
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
|
# executed before network agents
|
||||||
self['SEED'] = seed or time.time()
|
self['SEED'] = seed or time.time()
|
||||||
random.seed(self['SEED'])
|
random.seed(self['SEED'])
|
||||||
|
self.process(self.save_state())
|
||||||
self.environment_agents = environment_agents or []
|
self.environment_agents = environment_agents or []
|
||||||
self.network_agents = network_agents or []
|
self.network_agents = network_agents or []
|
||||||
self.process(self.save_state())
|
|
||||||
if self.dump:
|
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:
|
else:
|
||||||
self._db_path = ":memory:"
|
self._db_path = ":memory:"
|
||||||
self.create_db(self._db_path)
|
self.create_db(self._db_path)
|
||||||
|
|
||||||
def create_db(self, db_path=None):
|
def create_db(self, db_path=None):
|
||||||
db_path = db_path or self._db_path
|
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)
|
self._db = sqlite3.connect(db_path)
|
||||||
with self._db:
|
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)''')
|
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)
|
return self.G.add_edge(agent1, agent2)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
self._save_state()
|
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
self._save_state()
|
|
||||||
|
|
||||||
def _save_state(self, now=None):
|
def _save_state(self, now=None):
|
||||||
# for agent in self.agents:
|
# for agent in self.agents:
|
||||||
# agent.save_state()
|
# agent.save_state()
|
||||||
|
utils.logger.debug('Saving state @{}'.format(self.now))
|
||||||
with self._db:
|
with self._db:
|
||||||
self._db.executemany("insert into history(agent_id, t_step, key, value, value_type) values (?, ?, ?, ?, ?)", self.state_to_tuples(now=now))
|
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):
|
def save_state(self):
|
||||||
|
self._save_state()
|
||||||
while self.peek() != simpy.core.Infinity:
|
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 = self.event()
|
||||||
ev._ok = True
|
ev._ok = True
|
||||||
# Schedule the event with minimum priority so
|
# Schedule the event with minimum priority so
|
||||||
# that it executes after all agents are done
|
# that it executes before all agents
|
||||||
self.schedule(ev, -1, self.peek())
|
self.schedule(ev, -999, delay)
|
||||||
yield ev
|
yield ev
|
||||||
self._save_state()
|
self._save_state()
|
||||||
|
|
||||||
@ -215,7 +219,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
|
|
||||||
with open(csv_name, 'w') as f:
|
with open(csv_name, 'w') as f:
|
||||||
cr = csv.writer(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():
|
for i in self.history_to_tuples():
|
||||||
cr.writerow(i)
|
cr.writerow(i)
|
||||||
|
|
||||||
@ -229,14 +233,16 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
if now is None:
|
if now is None:
|
||||||
now = self.now
|
now = self.now
|
||||||
for k, v in self.environment_params.items():
|
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 agent in self.agents:
|
||||||
for k, v in agent.state.items():
|
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):
|
def history_to_tuples(self):
|
||||||
with self._db:
|
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
|
yield from res
|
||||||
|
|
||||||
def history_to_graph(self):
|
def history_to_graph(self):
|
||||||
|
@ -67,7 +67,7 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
self.default_state = default_state or {}
|
self.default_state = default_state or {}
|
||||||
self.dir_path = dir_path or os.getcwd()
|
self.dir_path = dir_path or os.getcwd()
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.seed = seed
|
self.seed = str(seed) or str(time.time())
|
||||||
self.dump = dump
|
self.dump = dump
|
||||||
self.environment_params = environment_params or {}
|
self.environment_params = environment_params or {}
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||||
env = environment.SoilEnvironment(name=env_name,
|
env = environment.SoilEnvironment(name=env_name,
|
||||||
topology=self.topology.copy(),
|
topology=self.topology.copy(),
|
||||||
seed=self.seed,
|
seed=self.seed+env_name,
|
||||||
initial_time=0,
|
initial_time=0,
|
||||||
dump=self.dump,
|
dump=self.dump,
|
||||||
interval=self.interval,
|
interval=self.interval,
|
||||||
|
@ -13,7 +13,6 @@ from contextlib import contextmanager
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
logger.addHandler(logging.StreamHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def load_network(network_params, dir_path=None):
|
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))
|
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_):
|
def convert(value, type_):
|
||||||
import importlib
|
import importlib
|
||||||
try:
|
try:
|
||||||
|
@ -60,7 +60,7 @@ class TestMain(TestCase):
|
|||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
},
|
},
|
||||||
'agent_type': 'NetworkAgent',
|
'agent_type': 'BaseAgent',
|
||||||
'environment_params': {
|
'environment_params': {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ class TestMain(TestCase):
|
|||||||
|
|
||||||
def test_custom_agent(self):
|
def test_custom_agent(self):
|
||||||
"""Allow for search of neighbors with a certain state_id"""
|
"""Allow for search of neighbors with a certain state_id"""
|
||||||
class CustomAgent(agents.NetworkAgent):
|
class CustomAgent(agents.BaseAgent):
|
||||||
def step(self):
|
def step(self):
|
||||||
self.state['neighbors'] = self.count_agents(state_id=0,
|
self.state['neighbors'] = self.count_agents(state_id=0,
|
||||||
limit_neighbors=True)
|
limit_neighbors=True)
|
||||||
@ -208,7 +208,7 @@ class TestMain(TestCase):
|
|||||||
|
|
||||||
res = list(env.history_to_tuples())
|
res = list(env.history_to_tuples())
|
||||||
assert len(res) == len(env.environment_params)
|
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['test'] = 'second_value'
|
||||||
env._save_state(now=1)
|
env._save_state(now=1)
|
||||||
|