mirror of
https://github.com/gsi-upm/soil
synced 2024-11-12 22:42:28 +00:00
Python3.7, testing and bug fixes
* Upgrade to python3.7 and pandas 0.3.4 because pandas has dropped support for python 3.4 -> There are some API changes in pandas, and I've update the code accordingly. * Set pytest as the default test runner
This commit is contained in:
parent
bd4700567e
commit
2e28b36f6e
@ -1,4 +1,11 @@
|
||||
FROM python:3.4-onbuild
|
||||
FROM python:3.7
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY test-requirements.txt requirements.txt /usr/src/app/
|
||||
RUN pip install --no-cache-dir -r test-requirements.txt -r requirements.txt
|
||||
|
||||
COPY ./ /usr/src/app
|
||||
|
||||
RUN pip install '.[web]'
|
||||
|
||||
|
@ -2,6 +2,8 @@ version: '3'
|
||||
services:
|
||||
dev:
|
||||
build: .
|
||||
environment:
|
||||
PYTHONDONTWRITEBYTECODE: 1
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
tty: true
|
||||
|
@ -4,6 +4,8 @@ from random import random, shuffle
|
||||
from itertools import islice
|
||||
import logging
|
||||
|
||||
import other_module
|
||||
|
||||
|
||||
class CityPubs(Environment):
|
||||
'''Environment with Pubs'''
|
||||
|
4
setup.cfg
Normal file
4
setup.cfg
Normal file
@ -0,0 +1,4 @@
|
||||
[aliases]
|
||||
test=pytest
|
||||
[tool:pytest]
|
||||
addopts = --verbose
|
@ -1 +1 @@
|
||||
0.12.0
|
||||
0.13.0
|
@ -16,7 +16,7 @@ class SentimentCorrelationModel(BaseAgent):
|
||||
disgust_prob
|
||||
"""
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
def __init__(self, environment, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.outside_effects_prob = environment.environment_params['outside_effects_prob']
|
||||
self.anger_prob = environment.environment_params['anger_prob']
|
||||
|
@ -324,15 +324,14 @@ def calculate_distribution(network_agents=None,
|
||||
return network_agents
|
||||
|
||||
|
||||
def serialize_agent_type(agent_type):
|
||||
def serialize_type(agent_type, known_modules=[], **kwargs):
|
||||
if isinstance(agent_type, str):
|
||||
return agent_type
|
||||
type_name = agent_type.__name__
|
||||
if type_name not in globals():
|
||||
type_name = utils.name(agent_type)
|
||||
return type_name
|
||||
known_modules += ['soil.agents']
|
||||
return utils.serialize(agent_type, known_modules=known_modules, **kwargs)[1] # Get the name of the class
|
||||
|
||||
def serialize_distribution(network_agents):
|
||||
|
||||
def serialize_distribution(network_agents, known_modules=[]):
|
||||
'''
|
||||
When serializing an agent distribution, remove the thresholds, in order
|
||||
to avoid cluttering the YAML definition file.
|
||||
@ -341,25 +340,23 @@ def serialize_distribution(network_agents):
|
||||
for v in d:
|
||||
if 'threshold' in v:
|
||||
del v['threshold']
|
||||
v['agent_type'] = serialize_agent_type(v['agent_type'])
|
||||
v['agent_type'] = serialize_type(v['agent_type'],
|
||||
known_modules=known_modules)
|
||||
return d
|
||||
|
||||
|
||||
def deserialize_type(agent_type, known_modules=[]):
|
||||
if not isinstance(agent_type, str):
|
||||
return agent_type
|
||||
if agent_type in globals():
|
||||
agent_type = globals()[agent_type]
|
||||
else:
|
||||
known = known_modules + ['soil.agents', 'soil.agents.custom' ]
|
||||
agent_type = utils.deserializer(agent_type, known_modules=known)
|
||||
known = known_modules + ['soil.agents', 'soil.agents.custom' ]
|
||||
agent_type = utils.deserializer(agent_type, known_modules=known)
|
||||
return agent_type
|
||||
|
||||
|
||||
def deserialize_distribution(ind):
|
||||
def deserialize_distribution(ind, **kwargs):
|
||||
d = deepcopy(ind)
|
||||
for v in d:
|
||||
v['agent_type'] = deserialize_type(v['agent_type'])
|
||||
v['agent_type'] = deserialize_type(v['agent_type'], **kwargs)
|
||||
return d
|
||||
|
||||
|
||||
@ -374,11 +371,11 @@ def _validate_states(states, topology):
|
||||
return states
|
||||
|
||||
|
||||
def _convert_agent_types(ind, to_string=False):
|
||||
def _convert_agent_types(ind, to_string=False, **kwargs):
|
||||
'''Convenience method to allow specifying agents by class or class name.'''
|
||||
if to_string:
|
||||
return serialize_distribution(ind)
|
||||
return deserialize_distribution(ind)
|
||||
return serialize_distribution(ind, **kwargs)
|
||||
return deserialize_distribution(ind, **kwargs)
|
||||
|
||||
|
||||
def _agent_from_distribution(distribution, value=-1):
|
||||
|
@ -123,7 +123,7 @@ def get_count(df, *keys):
|
||||
df = df[list(keys)]
|
||||
counts = pd.DataFrame()
|
||||
for key in df.columns.levels[0]:
|
||||
g = df[key].apply(pd.Series.value_counts, axis=1).fillna(0)
|
||||
g = df[[key]].apply(pd.Series.value_counts, axis=1).fillna(0)
|
||||
for value, series in g.iteritems():
|
||||
counts[key, value] = series
|
||||
counts.columns = pd.MultiIndex.from_tuples(counts.columns)
|
||||
|
@ -110,7 +110,7 @@ class Environment(nxsim.NetworkEnvironment):
|
||||
agent_type = self.default_state['agent_type']
|
||||
|
||||
if agent_type:
|
||||
agent_type = agents.deserialize_agent_type(agent_type)
|
||||
agent_type = agents.deserialize_type(agent_type)
|
||||
else:
|
||||
agent_type, state = agents._agent_from_distribution(agent_distribution)
|
||||
return self.set_agent(agent_id, agent_type, state)
|
||||
|
@ -3,7 +3,7 @@ import os
|
||||
import pandas as pd
|
||||
import sqlite3
|
||||
import copy
|
||||
from collections import UserDict, Iterable, namedtuple
|
||||
from collections import UserDict, namedtuple
|
||||
|
||||
from . import utils
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import os
|
||||
import time
|
||||
import imp
|
||||
import importlib
|
||||
import sys
|
||||
import yaml
|
||||
import traceback
|
||||
import networkx as nx
|
||||
from networkx.readwrite import json_graph
|
||||
from multiprocessing import Pool
|
||||
@ -78,6 +79,7 @@ class Simulation(NetworkSimulation):
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, topology=None, network_params=None,
|
||||
network_agents=None, agent_type=None, states=None,
|
||||
default_state=None, interval=1, dump=None, dry_run=False,
|
||||
@ -104,23 +106,21 @@ class Simulation(NetworkSimulation):
|
||||
self.seed = str(seed) or str(time.time())
|
||||
self.dump = dump
|
||||
self.dry_run = dry_run
|
||||
|
||||
sys.path += [self.dir_path, os.getcwd()]
|
||||
|
||||
self.environment_params = environment_params or {}
|
||||
self.environment_class = utils.deserialize(environment_class,
|
||||
known_modules=['soil.environment',]) or Environment
|
||||
|
||||
self._loaded_module = None
|
||||
|
||||
if load_module:
|
||||
path = sys.path + [self.dir_path, os.getcwd()]
|
||||
f, fp, desc = imp.find_module(load_module, path)
|
||||
self._loaded_module = imp.load_module('soil.agents.custom', f, fp, desc)
|
||||
known_modules=['soil.environment', ]) or Environment
|
||||
|
||||
environment_agents = environment_agents or []
|
||||
self.environment_agents = agents._convert_agent_types(environment_agents)
|
||||
self.environment_agents = agents._convert_agent_types(environment_agents,
|
||||
known_modules=[self.load_module])
|
||||
|
||||
distro = agents.calculate_distribution(network_agents,
|
||||
agent_type)
|
||||
self.network_agents = agents._convert_agent_types(distro)
|
||||
self.network_agents = agents._convert_agent_types(distro,
|
||||
known_modules=[self.load_module])
|
||||
|
||||
self.states = agents._validate_states(states,
|
||||
self.topology)
|
||||
@ -136,13 +136,17 @@ class Simulation(NetworkSimulation):
|
||||
p = Pool()
|
||||
with utils.timer('simulation {}'.format(self.name)):
|
||||
if parallel:
|
||||
func = partial(self.run_trial, dry_run=dry_run or self.dry_run,
|
||||
return_env=not parallel, **kwargs)
|
||||
func = partial(self.run_trial_exceptions, dry_run=dry_run or self.dry_run,
|
||||
return_env=True,
|
||||
**kwargs)
|
||||
for i in p.imap_unordered(func, range(self.num_trials)):
|
||||
if isinstance(i, Exception):
|
||||
logger.error('Trial failed:\n\t{}'.format(i.message))
|
||||
continue
|
||||
yield i
|
||||
else:
|
||||
for i in range(self.num_trials):
|
||||
yield self.run_trial(i, dry_run=dry_run or self.dry_run, **kwargs)
|
||||
yield self.run_trial(i, dry_run = dry_run or self.dry_run, **kwargs)
|
||||
if not (dry_run or self.dry_run):
|
||||
logger.info('Dumping results to {}'.format(self.dir_path))
|
||||
self.dump_pickle(self.dir_path)
|
||||
@ -150,9 +154,9 @@ class Simulation(NetworkSimulation):
|
||||
else:
|
||||
logger.info('NOT dumping results')
|
||||
|
||||
def get_env(self, trial_id=0, **kwargs):
|
||||
opts = self.environment_params.copy()
|
||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||
def get_env(self, trial_id = 0, **kwargs):
|
||||
opts=self.environment_params.copy()
|
||||
env_name='{}_trial_{}'.format(self.name, trial_id)
|
||||
opts.update({
|
||||
'name': env_name,
|
||||
'topology': self.topology.copy(),
|
||||
@ -167,10 +171,10 @@ class Simulation(NetworkSimulation):
|
||||
'dir_path': self.dir_path,
|
||||
})
|
||||
opts.update(kwargs)
|
||||
env = self.environment_class(**opts)
|
||||
env=self.environment_class(**opts)
|
||||
return env
|
||||
|
||||
def run_trial(self, trial_id=0, until=None, return_env=True, **opts):
|
||||
def run_trial(self, trial_id = 0, until = None, return_env = True, **opts):
|
||||
"""Run a single trial of the simulation
|
||||
|
||||
Parameters
|
||||
@ -178,16 +182,27 @@ class Simulation(NetworkSimulation):
|
||||
trial_id : int
|
||||
"""
|
||||
# Set-up trial environment and graph
|
||||
until = until or self.max_time
|
||||
env = self.get_env(trial_id=trial_id, **opts)
|
||||
until=until or self.max_time
|
||||
env=self.get_env(trial_id = trial_id, **opts)
|
||||
# Set up agents on nodes
|
||||
with utils.timer('Simulation {} trial {}'.format(self.name, trial_id)):
|
||||
env.run(until)
|
||||
if self.dump and not self.dry_run:
|
||||
with utils.timer('Dumping simulation {} trial {}'.format(self.name, trial_id)):
|
||||
env.dump(formats=self.dump)
|
||||
env.dump(formats = self.dump)
|
||||
if return_env:
|
||||
return env
|
||||
def run_trial_exceptions(self, *args, **kwargs):
|
||||
'''
|
||||
A wrapper for run_trial that catches exceptions and returns them.
|
||||
It is meant for async simulations
|
||||
'''
|
||||
try:
|
||||
return self.run_trial(*args, **kwargs)
|
||||
except Exception as ex:
|
||||
c = ex.__cause__
|
||||
c.message = ''.join(traceback.format_tb(c.__traceback__)[3:])
|
||||
return c
|
||||
|
||||
def to_dict(self):
|
||||
return self.__getstate__()
|
||||
@ -195,48 +210,53 @@ class Simulation(NetworkSimulation):
|
||||
def to_yaml(self):
|
||||
return yaml.dump(self.to_dict())
|
||||
|
||||
def dump_yaml(self, dir_path=None, file_name=None):
|
||||
dir_path = dir_path or self.dir_path
|
||||
def dump_yaml(self, dir_path = None, file_name = None):
|
||||
dir_path=dir_path or self.dir_path
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
if not file_name:
|
||||
file_name = os.path.join(dir_path,
|
||||
file_name=os.path.join(dir_path,
|
||||
'{}.dumped.yml'.format(self.name))
|
||||
with open(file_name, 'w') as f:
|
||||
f.write(self.to_yaml())
|
||||
|
||||
def dump_pickle(self, dir_path=None, pickle_name=None):
|
||||
dir_path = dir_path or self.dir_path
|
||||
def dump_pickle(self, dir_path = None, pickle_name = None):
|
||||
dir_path=dir_path or self.dir_path
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
if not pickle_name:
|
||||
pickle_name = os.path.join(dir_path,
|
||||
pickle_name=os.path.join(dir_path,
|
||||
'{}.simulation.pickle'.format(self.name))
|
||||
with open(pickle_name, 'wb') as f:
|
||||
pickle.dump(self, f)
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
state={}
|
||||
for k, v in self.__dict__.items():
|
||||
if k[0] != '_':
|
||||
state[k] = v
|
||||
state['topology'] = json_graph.node_link_data(self.topology)
|
||||
state['network_agents'] = agents.serialize_distribution(self.network_agents)
|
||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||
to_string=True)
|
||||
state['environment_class'] = utils.serialize(self.environment_class,
|
||||
known_modules=['soil.environment', ])[1] # func, name
|
||||
state[k]=v
|
||||
state['topology']=json_graph.node_link_data(self.topology)
|
||||
state['network_agents']=agents.serialize_distribution(self.network_agents,
|
||||
known_modules = [])
|
||||
state['environment_agents']=agents.serialize_distribution(self.environment_agents,
|
||||
known_modules = [])
|
||||
state['environment_class']=utils.serialize(self.environment_class,
|
||||
known_modules=['soil.environment'])[1] # func, name
|
||||
if state['load_module'] is None:
|
||||
del state['load_module']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self.load_module = getattr(self, 'load_module', None)
|
||||
if self.dir_path not in sys.path:
|
||||
sys.path += [self.dir_path, os.getcwd()]
|
||||
self.topology = json_graph.node_link_graph(state['topology'])
|
||||
self.network_agents = agents.calculate_distribution(agents._convert_agent_types(self.network_agents))
|
||||
self.environment_agents = agents._convert_agent_types(self.environment_agents)
|
||||
self.environment_agents = agents._convert_agent_types(self.environment_agents,
|
||||
known_modules=[self.load_module])
|
||||
self.environment_class = utils.deserialize(self.environment_class,
|
||||
known_modules=['soil.environment', ]) # func, name
|
||||
known_modules=[self.load_module, 'soil.environment', ]) # func, name
|
||||
return state
|
||||
|
||||
|
||||
|
@ -92,8 +92,10 @@ def name(value, known_modules=[]):
|
||||
return tname
|
||||
if known_modules and modname in known_modules:
|
||||
return tname
|
||||
for mod_name in known_modules:
|
||||
module = importlib.import_module(mod_name)
|
||||
for kmod in known_modules:
|
||||
if not kmod:
|
||||
continue
|
||||
module = importlib.import_module(kmod)
|
||||
if hasattr(module, tname):
|
||||
return tname
|
||||
return '{}.{}'.format(modname, tname)
|
||||
@ -124,21 +126,22 @@ def deserializer(type_, known_modules=[]):
|
||||
options = []
|
||||
|
||||
for mod in modules:
|
||||
options.append((mod, type_))
|
||||
if mod:
|
||||
options.append((mod, type_))
|
||||
|
||||
if '.' in type_: # Fully qualified module
|
||||
module, type_ = type_.rsplit(".", 1)
|
||||
options.append ((module, type_))
|
||||
|
||||
errors = []
|
||||
for module, name in options:
|
||||
for modname, tname in options:
|
||||
try:
|
||||
module = importlib.import_module(module)
|
||||
cls = getattr(module, name)
|
||||
module = importlib.import_module(modname)
|
||||
cls = getattr(module, tname)
|
||||
return getattr(cls, 'deserialize', cls)
|
||||
except (ImportError, AttributeError) as ex:
|
||||
errors.append((module, name, ex))
|
||||
raise Exception('Could not find module {}. Tried: {}'.format(type_, errors))
|
||||
errors.append((modname, tname, ex))
|
||||
raise Exception('Could not find type {}. Tried: {}'.format(type_, errors))
|
||||
|
||||
|
||||
def deserialize(type_, value=None, **kwargs):
|
||||
|
@ -0,0 +1 @@
|
||||
pytest
|
@ -129,7 +129,7 @@ class TestHistory(TestCase):
|
||||
backuppaths = glob(db_path + '.backup*.sqlite')
|
||||
assert len(backuppaths) == 1
|
||||
backuppath = backuppaths[0]
|
||||
assert newhistory._db_path == h._db_path
|
||||
assert newhistory.db_path == h.db_path
|
||||
assert os.path.exists(backuppath)
|
||||
assert not len(newhistory[None, None, None])
|
||||
|
||||
|
@ -12,6 +12,12 @@ from soil import simulation, Environment, agents, utils, history
|
||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
EXAMPLES = join(ROOT, '..', 'examples')
|
||||
|
||||
|
||||
class CustomAgent(agents.BaseAgent):
|
||||
def step(self):
|
||||
self.state['neighbors'] = self.count_agents(state_id=0,
|
||||
limit_neighbors=True)
|
||||
|
||||
class TestMain(TestCase):
|
||||
|
||||
def test_load_graph(self):
|
||||
@ -125,10 +131,6 @@ class TestMain(TestCase):
|
||||
|
||||
def test_custom_agent(self):
|
||||
"""Allow for search of neighbors with a certain state_id"""
|
||||
class CustomAgent(agents.BaseAgent):
|
||||
def step(self):
|
||||
self.state['neighbors'] = self.count_agents(state_id=0,
|
||||
limit_neighbors=True)
|
||||
config = {
|
||||
'dry_run': True,
|
||||
'network_params': {
|
||||
@ -261,6 +263,13 @@ class TestMain(TestCase):
|
||||
des = utils.deserialize(name, ser)
|
||||
assert i == des
|
||||
|
||||
def test_serialize_agent_type(self):
|
||||
'''A class from soil.agents should be serialized without the module part'''
|
||||
ser = agents.serialize_type(CustomAgent)
|
||||
assert ser == 'test_main.CustomAgent'
|
||||
ser = agents.serialize_type(agents.BaseAgent)
|
||||
assert ser == 'BaseAgent'
|
||||
|
||||
def test_deserialize_agent_distribution(self):
|
||||
agent_distro = [
|
||||
{
|
||||
@ -268,13 +277,13 @@ class TestMain(TestCase):
|
||||
'weight': 1
|
||||
},
|
||||
{
|
||||
'agent_type': 'BaseAgent',
|
||||
'agent_type': 'test_main.CustomAgent',
|
||||
'weight': 2
|
||||
},
|
||||
]
|
||||
converted = agents.deserialize_distribution(agent_distro)
|
||||
assert converted[0]['agent_type'] == agents.CounterModel
|
||||
assert converted[1]['agent_type'] == agents.BaseAgent
|
||||
assert converted[1]['agent_type'] == CustomAgent
|
||||
|
||||
def test_serialize_agent_distribution(self):
|
||||
agent_distro = [
|
||||
@ -283,13 +292,13 @@ class TestMain(TestCase):
|
||||
'weight': 1
|
||||
},
|
||||
{
|
||||
'agent_type': agents.BaseAgent,
|
||||
'agent_type': CustomAgent,
|
||||
'weight': 2
|
||||
},
|
||||
]
|
||||
converted = agents.serialize_distribution(agent_distro)
|
||||
assert converted[0]['agent_type'] == 'CounterModel'
|
||||
assert converted[1]['agent_type'] == 'BaseAgent'
|
||||
assert converted[1]['agent_type'] == 'test_main.CustomAgent'
|
||||
|
||||
def test_history(self):
|
||||
'''Test storing in and retrieving from history (sqlite)'''
|
||||
|
Loading…
Reference in New Issue
Block a user