Fix gephi representation. Add sqlite

history
J. Fernando Sánchez 7 years ago
parent af76f54a28
commit 78364d89d5

@ -4,6 +4,8 @@ dir_path: "/tmp/"
num_trials: 3 num_trials: 3
max_time: 100 max_time: 100
interval: 1 interval: 1
seed: "CompleteSeed!"
dump: false
network_params: network_params:
generator: complete_graph generator: complete_graph
n: 10 n: 10
@ -21,4 +23,4 @@ default_state:
incidents: 0 incidents: 0
states: states:
- name: 'The first node' - name: 'The first node'
- name: 'The second node' - name: 'The second node'

@ -1,10 +1,10 @@
import logging
from soil.agents import NetworkAgent, FSM, state, default_state, BaseAgent from soil.agents import NetworkAgent, 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
import logging
import math
logger = logging.getLogger(__name__)
class Genders(Enum): class Genders(Enum):
male = 'male' male = 'male'
@ -13,6 +13,8 @@ class Genders(Enum):
class RabbitModel(NetworkAgent, FSM): class RabbitModel(NetworkAgent, FSM):
level = logging.INFO
defaults = { defaults = {
'age': 0, 'age': 0,
'gender': Genders.male.value, 'gender': Genders.male.value,
@ -24,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 = 10 max_females = 2
@default_state @default_state
@state @state
@ -57,7 +59,7 @@ class RabbitModel(NetworkAgent, FSM):
whom['pregnancy'] = 0 whom['pregnancy'] = 0
whom['mate'] = self.id whom['mate'] = self.id
whom.set_state(whom.pregnant) whom.set_state(whom.pregnant)
logger.debug('{} impregnating: {}. {}'.format(self.id, whom.id, whom.state)) self.debug('{} impregnating: {}. {}'.format(self.id, whom.id, whom.state))
@state @state
def pregnant(self): def pregnant(self):
@ -66,38 +68,52 @@ class RabbitModel(NetworkAgent, FSM):
return self.dead return self.dead
self['pregnancy'] += 1 self['pregnancy'] += 1
logger.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())
state = {} for i in range(number_of_babies):
state['gender'] = choice(list(Genders)).value state = {}
child = self.env.add_node(self.__class__, state) state['gender'] = choice(list(Genders)).value
self.env.add_edge(self.id, child.id) child = self.env.add_node(self.__class__, state)
self.env.add_edge(self['mate'], child.id) self.env.add_edge(self.id, child.id)
# self.add_edge() self.env.add_edge(self['mate'], child.id)
logger.info("A rabbit has been born: {}. Total: {}".format(child.id, len(self.global_topology.nodes))) # self.add_edge()
self['offspring'] += 1 self.debug('A BABY IS COMING TO LIFE')
self.env.get_agent(self['mate'])['offspring'] += 1 self.env['rabbits_alive'] = self.env.get('rabbits_alive', 0)+1
del self['mate'] self.debug('Rabbits alive: {}'.format(self.env['rabbits_alive']))
self['pregnancy'] = -1 self['offspring'] += 1
return self.fertile self.env.get_agent(self['mate'])['offspring'] += 1
del self['mate']
self['pregnancy'] = -1
return self.fertile
@state @state
def dead(self): def dead(self):
logger.info('Agent {} is dying'.format(self.id)) self.info('Agent {} is dying'.format(self.id))
if 'pregnancy' in self and self['pregnancy'] > -1: if 'pregnancy' in self and self['pregnancy'] > -1:
logger.info('A mother has died carrying a baby!: {}!'.format(self.state)) self.info('A mother has died carrying a baby!!')
self.die() self.die()
return return
class RandomAccident(BaseAgent): class RandomAccident(BaseAgent):
level = logging.INFO
def step(self): def step(self):
logger.debug('Killing some rabbits!') rabbits_total = self.global_topology.number_of_nodes()
prob_death = self.env.get('prob_death', -1) rabbits_alive = self.env.get('rabbits_alive', rabbits_total)
prob_death = self.env.get('prob_death', 1e-100)*math.log(max(1, rabbits_alive))
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:
continue
r = random() r = random()
if r < prob_death: if r < prob_death:
logger.info('I killed a rabbit: {}'.format(i.id)) self.debug('I killed a rabbit: {}'.format(i.id))
rabbits_alive = self.env['rabbits_alive'] = rabbits_alive -1
self.log('Rabbits alive: {}'.format(self.env['rabbits_alive']))
i.set_state(i.dead) i.set_state(i.dead)
self.log('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
if self.count_agents(state_id=RabbitModel.dead.id) == self.global_topology.number_of_nodes():
self.die()

@ -1,14 +1,16 @@
--- ---
load_module: custom_agents load_module: rabbit_agents
name: custom_agent_example name: rabbits_example
max_time: 2500 max_time: 1500
interval: 1 interval: 1
seed: MySimulationSeed seed: MySeed
agent_type: RabbitModel agent_type: RabbitModel
environment_agents: environment_agents:
- agent_type: RandomAccident - agent_type: RandomAccident
environment_params:
prob_death: 0.0001
default_state: default_state:
mating_prob: 1 mating_prob: 0.01
topology: topology:
nodes: nodes:
- id: 1 - id: 1

@ -11,11 +11,8 @@ try:
except NameError: except NameError:
basestring = str basestring = str
from . import agents logging.basicConfig()#format=FORMAT)
from . import simulation
from . import environment
from . import utils from . import utils
from . import settings
def main(): def main():
@ -42,9 +39,8 @@ def main():
sys.path.append(os.getcwd()) sys.path.append(os.getcwd())
importlib.import_module(args.module) importlib.import_module(args.module)
logging.basicConfig(level=logging.INFO) logging.info('Loading config file: {}'.format(args.file, args.output))
logger = logging.getLogger(__name__)
logger.info('Loading config file: {}'.format(args.file, args.output))
try: try:
simulation.run_from_config(args.file, dump=(not args.dry_run), results_dir=args.output) simulation.run_from_config(args.file, dump=(not args.dry_run), results_dir=args.output)
except Exception as ex: except Exception as ex:

@ -9,8 +9,8 @@ class CounterModel(NetworkAgent):
def step(self): def step(self):
# Outside effects # Outside effects
total = len(self.get_all_agents()) total = len(list(self.get_all_agents()))
neighbors = len(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'] = neighbors self.state['neighbors'] = neighbors
self.state['total'] = total self.state['total'] = total
@ -24,8 +24,8 @@ class AggregatedCounter(NetworkAgent):
def step(self): def step(self):
# Outside effects # Outside effects
total = len(self.get_all_agents()) total = len(list(self.get_all_agents()))
neighbors = len(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'] = self.state.get('total', 0) + total

@ -6,11 +6,13 @@
import nxsim import nxsim
import logging
from collections import OrderedDict from collections import OrderedDict
from copy import deepcopy from copy import deepcopy
from functools import partial from functools import partial
import json import json
from functools import wraps from functools import wraps
@ -37,11 +39,16 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
state.update(kwargs.pop('state', {})) state.update(kwargs.pop('state', {}))
kwargs['state'] = state kwargs['state'] = state
super().__init__(**kwargs) super().__init__(**kwargs)
if not hasattr(self, 'level'):
self.level = logging.DEBUG
self.logger = logging.getLogger('Agent-{}'.format(self.id))
self.logger.setLevel(self.level)
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, tuple): if isinstance(key, tuple):
k, t_step = key k, t_step = key
return self.env[t_step, self.id, k] return self.env[self.id, t_step, k]
return self.state.get(key, None) return self.state.get(key, None)
def __delitem__(self, key): def __delitem__(self, key):
@ -78,7 +85,7 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
pass pass
def to_json(self): def to_json(self):
return json.dumps(self._history) return json.dumps(self.state)
def count_agents(self, state_id=None, limit_neighbors=False): def count_agents(self, state_id=None, limit_neighbors=False):
if limit_neighbors: if limit_neighbors:
@ -108,6 +115,20 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
return filter(matches_all, agents) return filter(matches_all, agents)
def log(self, message, level=logging.INFO, **kwargs):
message = "\t@{:>5}:\t{}".format(self.now, message)
for k, v in kwargs:
message += " {k}={v} ".format(k, v)
extra = {}
extra['now'] = self.now
extra['id'] = self.id
return self.logger.log(level, message, extra=extra)
def debug(self, *args, **kwargs):
return self.log(*args, level=logging.DEBUG, **kwargs)
def info(self, *args, **kwargs):
return self.log(*args, level=logging.INFO, **kwargs)
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent): class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):

@ -1,10 +1,11 @@
import os import os
import sqlite3
import time import time
import csv
import weakref import weakref
import csv
import random import random
import simpy
from copy import deepcopy from copy import deepcopy
from functools import partial
import networkx as nx import networkx as nx
import nxsim import nxsim
@ -22,15 +23,19 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
interval=1, interval=1,
seed=None, seed=None,
dump=False, dump=False,
simulation=None,
*args, **kwargs): *args, **kwargs):
self.name = name or 'UnnamedEnvironment' self.name = name or 'UnnamedEnvironment'
self.states = deepcopy(states) or {} if isinstance(states, list):
states = dict(enumerate(states))
self.states = deepcopy(states) if states else {}
self.default_state = deepcopy(default_state) or {} self.default_state = deepcopy(default_state) or {}
self.sim = weakref.ref(simulation)
if 'topology' not in kwargs and simulation:
kwargs['topology'] = self.sim().topology.copy()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._env_agents = {} self._env_agents = {}
self._history = {}
self.interval = interval self.interval = interval
self.logger = None
self.dump = dump self.dump = dump
# Add environment agents first, so their events get # Add environment agents first, so their events get
# executed before network agents # executed before network agents
@ -39,6 +44,17 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
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()) self.process(self.save_state())
if self.dump:
self._db_path = os.path.join(self.get_path(), 'db.sqlite')
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
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)''')
@property @property
def agents(self): def agents(self):
@ -48,7 +64,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
@property @property
def environment_agents(self): def environment_agents(self):
for ref in self._env_agents.values(): for ref in self._env_agents.values():
yield ref() yield ref
@environment_agents.setter @environment_agents.setter
def environment_agents(self, environment_agents): def environment_agents(self, environment_agents):
@ -60,7 +76,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
kwargs['agent_id'] = kwargs.get('agent_id', atype.__name__) kwargs['agent_id'] = kwargs.get('agent_id', atype.__name__)
kwargs['state'] = kwargs.get('state', {}) kwargs['state'] = kwargs.get('state', {})
a = atype(environment=self, **kwargs) a = atype(environment=self, **kwargs)
self._env_agents[a.id] = weakref.ref(a) self._env_agents[a.id] = a
@property @property
def network_agents(self): def network_agents(self):
@ -106,42 +122,72 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
super().run(*args, **kwargs) super().run(*args, **kwargs)
self._save_state() self._save_state()
def _save_state(self): def _save_state(self, now=None):
# for agent in self.agents: # for agent in self.agents:
# agent.save_state() # agent.save_state()
nowd = self._history[self.now] = {} with self._db:
nowd['env'] = deepcopy(self.environment_params) self._db.executemany("insert into history(agent_id, t_step, key, value, value_type) values (?, ?, ?, ?, ?)", self.state_to_tuples(now=now))
for agent in self.agents:
nowd[agent.id] = deepcopy(agent.state)
def save_state(self): def save_state(self):
while True: while self.peek() != simpy.core.Infinity:
utils.logger.info('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 after all agents are done
self.schedule(ev, -1, self.interval) self.schedule(ev, -1, self.peek())
yield ev yield ev
self._save_state() self._save_state()
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, tuple): if isinstance(key, tuple):
t_step, agent_id, k = key values = {"agent_id": key[0],
"t_step": key[1],
def key_or_dict(d, k, nfunc): "key": key[2],
if k is None: "value": None,
if d is None: "value_type": None
return {} }
return {k: nfunc(v) for k, v in d.items()}
if k in d: fields = list(k for k, v in values.items() if v is None)
return nfunc(d[k]) conditions = " and ".join("{}='{}'".format(k, v) for k, v in values.items() if v is not None)
return {}
query = """SELECT {fields} from history""".format(fields=",".join(fields))
f1 = partial(key_or_dict, k=k, nfunc=lambda x: x) if conditions:
f2 = partial(key_or_dict, k=agent_id, nfunc=f1) query = """{query} where {conditions}""".format(query=query,
return key_or_dict(self._history, t_step, f2) conditions=conditions)
with self._db:
rows = self._db.execute(query).fetchall()
utils.logger.debug(rows)
results = self.rows_to_dict(rows)
return results
return self.environment_params[key] return self.environment_params[key]
def rows_to_dict(self, rows):
if len(rows) < 1:
return None
level = len(rows[0])-2
if level == 0:
if len(rows) != 1:
raise ValueError('Cannot convert {} to dictionaries'.format(rows))
value, value_type = rows[0]
return utils.convert(value, value_type)
results = {}
for row in rows:
item = results
for i in range(level-1):
key = row[i]
if key not in item:
item[key] = {}
item = item[key]
key, value, value_type = row[level-1:]
item[key] = utils.convert(value, value_type)
return results
def __setitem__(self, key, value): def __setitem__(self, key, value):
self.environment_params[key] = value self.environment_params[key] = value
@ -179,23 +225,34 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
self.name+".gexf") self.name+".gexf")
nx.write_gexf(G, graph_path, version="1.2draft") nx.write_gexf(G, graph_path, version="1.2draft")
def state_to_tuples(self, now=None):
if now is None:
now = self.now
for k, v in self.environment_params.items():
yield 'env', now, k, v, type(v).__name__
for agent in self.agents:
for k, v in agent.state.items():
yield agent.id, now, k, v, type(v).__name__
def history_to_tuples(self): def history_to_tuples(self):
for tstep, states in self._history.items(): with self._db:
for a_id, state in states.items(): res = self._db.execute("select agent_id, t_step, key, value from history ").fetchall()
for attribute, value in state.items(): yield from res
yield (a_id, tstep, attribute, value)
def history_to_graph(self): def history_to_graph(self):
G = nx.Graph(self.G) G = nx.Graph(self.G)
for agent in self.agents: for agent in self.network_agents:
attributes = {'agent': str(agent.__class__)} attributes = {'agent': str(agent.__class__)}
lastattributes = {} lastattributes = {}
spells = [] spells = []
lastvisible = False lastvisible = False
laststep = None laststep = None
for t_step, state in reversed(list(self[None, agent.id, None].items())): history = self[agent.id, None, None]
if not history:
continue
for t_step, state in reversed(sorted(list(history.items()))):
for attribute, value in state.items(): for attribute, value in state.items():
if attribute == 'visible': if attribute == 'visible':
nowvisible = state[attribute] nowvisible = state[attribute]
@ -206,15 +263,20 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
lastvisible = nowvisible lastvisible = nowvisible
else: else:
if attribute not in lastattributes or lastattributes[attribute][0] != value: key = 'attr_' + attribute
laststep = lastattributes.get(attribute, if key not in attributes:
(None, None))[1] attributes[key] = list()
value = (state[attribute], t_step, laststep) if key not in lastattributes:
key = 'attr_' + attribute lastattributes[key] = (state[attribute], t_step)
elif lastattributes[key][0] != value:
last_value, laststep = lastattributes[key]
value = (last_value, t_step, laststep)
if key not in attributes: if key not in attributes:
attributes[key] = list() attributes[key] = list()
attributes[key].append(value) attributes[key].append(value)
lastattributes[attribute] = (state[attribute], t_step) lastattributes[key] = (state[attribute], t_step)
for k, v in lastattributes.items():
attributes[k].append((v[0], 0, v[1]))
if lastvisible: if lastvisible:
spells.append((laststep, None)) spells.append((laststep, None))
if spells: if spells:

@ -1,10 +1,8 @@
import weakref
import os import os
import time import time
import imp import imp
import sys import sys
import yaml import yaml
import logging
import networkx as nx import networkx as nx
from networkx.readwrite import json_graph from networkx.readwrite import json_graph
@ -15,8 +13,8 @@ import pickle
from nxsim import NetworkSimulation from nxsim import NetworkSimulation
from . import agents, utils, environment, basestring from . import agents, utils, environment, basestring
from .utils import logger
logger = logging.getLogger(__name__)
class SoilSimulation(NetworkSimulation): class SoilSimulation(NetworkSimulation):
""" """
@ -86,7 +84,7 @@ class SoilSimulation(NetworkSimulation):
self.network_agents = self._convert_agent_types(distro) self.network_agents = self._convert_agent_types(distro)
self.states = self.validate_states(states, self.states = self.validate_states(states,
topology) self.topology)
def calculate_distribution(self, def calculate_distribution(self,
network_agents=None, network_agents=None,
@ -178,9 +176,8 @@ class SoilSimulation(NetworkSimulation):
states=self.states, states=self.states,
default_state=self.default_state, default_state=self.default_state,
environment_agents=self.environment_agents, environment_agents=self.environment_agents,
simulation=self,
**self.environment_params) **self.environment_params)
env.sim = weakref.ref(self)
# Set up agents on nodes # Set up agents on nodes
logger.info('\tRunning') logger.info('\tRunning')
with utils.timer('trial'): with utils.timer('trial'):

@ -10,10 +10,15 @@ import networkx as nx
from contextlib import contextmanager from contextlib import contextmanager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
def load_network(network_params, dir_path=None): def load_network(network_params, dir_path=None):
if network_params is None:
return nx.Graph()
path = network_params.get('path', None) path = network_params.get('path', None)
if path: if path:
if dir_path and not os.path.isabs(path): if dir_path and not os.path.isabs(path):
@ -73,9 +78,23 @@ def agent_from_distribution(distribution, value=-1):
for d in distribution: for d in distribution:
threshold = d['threshold'] threshold = d['threshold']
if value >= threshold[0] and value < threshold[1]: if value >= threshold[0] and value < threshold[1]:
state = None state = {}
if 'state' in d: if 'state' in d:
state = deepcopy(d['state']) state = deepcopy(d['state'])
return d['agent_type'], state return d['agent_type'], state
raise Exception('Distribution for value {} not found in: {}'.format(value, distribution)) raise Exception('Distribution for value {} not found in: {}'.format(value, distribution))
def convert(value, type_):
import importlib
try:
# Check if it's a builtin type
module = importlib.import_module('builtins')
cls = getattr(module, type_)
except AttributeError:
# if not, separate module and class
module, type_ = type_.rsplit(".", 1)
module = importlib.import_module(module)
cls = getattr(module, type_)
return cls(value)

@ -5,7 +5,7 @@ import yaml
from functools import partial from functools import partial
from os.path import join from os.path import join
from soil import simulation, agents, utils from soil import simulation, environment, agents, utils
ROOT = os.path.abspath(os.path.dirname(__file__)) ROOT = os.path.abspath(os.path.dirname(__file__))
@ -198,6 +198,26 @@ class TestMain(TestCase):
""" """
Make sure all examples in the examples folder are correct Make sure all examples in the examples folder are correct
""" """
pass
def test_row_conversion(self):
sim = simulation.SoilSimulation()
env = environment.SoilEnvironment(simulation=sim)
env['test'] = 'test_value'
env._save_state(now=0)
res = list(env.history_to_tuples())
assert len(res) == len(env.environment_params)
assert ('env', 0, 'test', 'test_value') in res
env['test'] = 'second_value'
env._save_state(now=1)
res = list(env.history_to_tuples())
assert env['env', 0, 'test' ] == 'test_value'
assert env['env', 1, 'test' ] == 'second_value'
def make_example_test(path, config): def make_example_test(path, config):

Loading…
Cancel
Save