1
0
mirror of https://github.com/gsi-upm/soil synced 2024-11-22 11:12:29 +00:00

Improved docs

Fixed several bugs
Added convenience methods in soil.analysis
This commit is contained in:
J. Fernando Sánchez 2017-10-18 20:28:42 +02:00
parent 78364d89d5
commit a7c51742f6
69 changed files with 30969 additions and 3300 deletions

View File

@ -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:

File diff suppressed because it is too large Load Diff

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/output_54_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_54_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_55_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_55_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_55_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_55_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_55_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/output_55_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/output_55_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_55_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_55_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_55_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_56_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_56_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_61_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/output_63_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_66_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/output_67_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
docs/output_72_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/output_72_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/output_74_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/output_75_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/output_76_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

2612
docs/soil_tutorial.rst Normal file

File diff suppressed because it is too large Load Diff

View 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}

View File

@ -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

File diff suppressed because one or more lines are too long

View 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

View 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()

View File

@ -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,13 +80,13 @@ 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
del self['mate'] del self['mate']
self['pregnancy'] = -1 self['pregnancy'] = -1
return self.fertile return self.fertile
@state @state
def dead(self): def dead(self):
@ -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:

View File

@ -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:

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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():

View File

@ -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

View File

@ -1,8 +1,8 @@
import random import random
from . import NetworkAgent from . import BaseAgent
class BigMarketModel(NetworkAgent): class BigMarketModel(BaseAgent):
""" """
Settings: Settings:
Names: Names:

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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__

View File

@ -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:
if process: for trial_data in sorted(glob.glob(join(folder,
if attributes is not None: '*.environment.csv'))):
df = df[df['attribute'].isin(attributes)] df = read_csv(trial_data, convert_types=convert_types)
df = df.pivot_table(values='attribute', index='tstep', columns=['value'], aggfunc='count').fillna(0) if process:
yield config_file, df, config 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): 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)

View File

@ -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):

View File

@ -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,

View File

@ -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:

View File

@ -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)