mirror of
https://github.com/gsi-upm/soil
synced 2025-09-14 04:02:21 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
09e14c6e84 | ||
|
8593ac999d | ||
|
90338c3549 | ||
|
1d532dacfe | ||
|
a1f8d8c9c5 | ||
|
de326eb331 | ||
|
04b4380c61 | ||
|
d70a0c865c | ||
|
625c28e4ee | ||
|
9749f4ca14 |
@@ -1,21 +1,28 @@
|
||||
image: python:3.7
|
||||
|
||||
steps:
|
||||
- build
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
|
||||
build:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
||||
# The skip-tls-verify flag is there because our registry certificate is self signed
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --skip-tls-verify --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
||||
only:
|
||||
- tags
|
||||
|
||||
|
||||
test:
|
||||
except:
|
||||
- tags # Avoid running tests for tags, because they are already run for the branch
|
||||
tags:
|
||||
- docker
|
||||
image: python:3.7
|
||||
stage: test
|
||||
script:
|
||||
python setup.py test
|
||||
- python setup.py test
|
@@ -26,7 +26,7 @@ But before that, let's import the soil module and networkx.
|
||||
%autoreload 2
|
||||
|
||||
%pylab inline
|
||||
# To display plots in the notebooed_
|
||||
# To display plots in the notebook_
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
@@ -2531,7 +2531,7 @@ Dealing with bigger data
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
267M ../rabbits/soil_output/rabbits_example/
|
||||
267M ../rabbits/soil_output/rabbits_example/
|
||||
|
||||
|
||||
If we tried to load the entire history, we would probably run out of
|
||||
|
17
examples/custom_generator/custom_generator.yml
Normal file
17
examples/custom_generator/custom_generator.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: custom-generator
|
||||
description: Using a custom generator for the network
|
||||
num_trials: 3
|
||||
dry_run: True
|
||||
max_time: 100
|
||||
interval: 1
|
||||
network_params:
|
||||
generator: mymodule.mygenerator
|
||||
# These are custom parameters
|
||||
n: 10
|
||||
n_edges: 5
|
||||
network_agents:
|
||||
- agent_type: CounterModel
|
||||
weight: 1
|
||||
state:
|
||||
id: 0
|
27
examples/custom_generator/mymodule.py
Normal file
27
examples/custom_generator/mymodule.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from networkx import Graph
|
||||
import networkx as nx
|
||||
from random import choice
|
||||
|
||||
def mygenerator(n=5, n_edges=5):
|
||||
'''
|
||||
Just a simple generator that creates a network with n nodes and
|
||||
n_edges edges. Edges are assigned randomly, only avoiding self loops.
|
||||
'''
|
||||
G = nx.Graph()
|
||||
|
||||
for i in range(n):
|
||||
G.add_node(i)
|
||||
|
||||
for i in range(n_edges):
|
||||
nodes = list(G.nodes)
|
||||
n_in = choice(nodes)
|
||||
nodes.remove(n_in) # Avoid loops
|
||||
n_out = choice(nodes)
|
||||
G.add_edge(n_in, n_out)
|
||||
return G
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
examples/programmatic/.gitignore
vendored
Normal file
1
examples/programmatic/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Programmatic*
|
38
examples/programmatic/programmatic.py
Normal file
38
examples/programmatic/programmatic.py
Normal file
@@ -0,0 +1,38 @@
|
||||
'''
|
||||
Example of a fully programmatic simulation, without definition files.
|
||||
'''
|
||||
from soil import Simulation, agents
|
||||
from networkx import Graph
|
||||
import logging
|
||||
|
||||
|
||||
def mygenerator():
|
||||
# Add only a node
|
||||
G = Graph()
|
||||
G.add_node(1)
|
||||
return G
|
||||
|
||||
|
||||
class MyAgent(agents.FSM):
|
||||
|
||||
@agents.default_state
|
||||
@agents.state
|
||||
def neutral(self):
|
||||
self.info('I am running')
|
||||
|
||||
|
||||
s = Simulation(name='Programmatic',
|
||||
network_params={'generator': mygenerator},
|
||||
num_trials=1,
|
||||
max_time=100,
|
||||
agent_type=MyAgent,
|
||||
dry_run=True)
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
envs = s.run()
|
||||
|
||||
s.dump_yaml()
|
||||
|
||||
for env in envs:
|
||||
env.dump_csv()
|
@@ -1 +1 @@
|
||||
0.13.1
|
||||
0.13.6
|
||||
|
@@ -11,8 +11,6 @@ try:
|
||||
except NameError:
|
||||
basestring = str
|
||||
|
||||
logging.basicConfig()
|
||||
|
||||
from . import agents
|
||||
from .simulation import *
|
||||
from .environment import Environment
|
||||
@@ -23,6 +21,9 @@ def main():
|
||||
import argparse
|
||||
from . import simulation
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.info('Running SOIL version: {}'.format(__version__))
|
||||
|
||||
parser = argparse.ArgumentParser(description='Run a SOIL simulation')
|
||||
parser.add_argument('file', type=str,
|
||||
nargs="?",
|
||||
@@ -62,7 +63,7 @@ def main():
|
||||
simulation.run_from_config(args.file,
|
||||
dry_run=args.dry_run,
|
||||
dump=dump,
|
||||
parallel=(not args.synchronous and not args.pdb),
|
||||
parallel=(not args.synchronous),
|
||||
results_dir=args.output)
|
||||
except Exception:
|
||||
if args.pdb:
|
||||
|
@@ -24,7 +24,7 @@ class BaseAgent(nxsim.BaseAgent):
|
||||
|
||||
defaults = {}
|
||||
|
||||
def __init__(self, environment, agent_id=None, state=None,
|
||||
def __init__(self, environment, agent_id, state=None,
|
||||
name='network_process', interval=None, **state_params):
|
||||
# Check for REQUIRED arguments
|
||||
assert environment is not None, TypeError('__init__ missing 1 required keyword argument: \'environment\'. '
|
||||
@@ -34,10 +34,6 @@ class BaseAgent(nxsim.BaseAgent):
|
||||
self.name = name
|
||||
self.state_params = state_params
|
||||
|
||||
# Global parameters
|
||||
self.global_topology = environment.G
|
||||
self.environment_params = environment.environment_params
|
||||
|
||||
# Register agent to environment
|
||||
self.env = environment
|
||||
|
||||
@@ -73,6 +69,18 @@ class BaseAgent(nxsim.BaseAgent):
|
||||
for k, v in value.items():
|
||||
self[k] = v
|
||||
|
||||
@property
|
||||
def global_topology(self):
|
||||
return self.env.G
|
||||
|
||||
@property
|
||||
def environment_params(self):
|
||||
return self.env.environment_params
|
||||
|
||||
@environment_params.setter
|
||||
def environment_params(self, value):
|
||||
self.env.environment_params = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, tuple):
|
||||
key, t_step = key
|
||||
@@ -126,9 +134,6 @@ class BaseAgent(nxsim.BaseAgent):
|
||||
def step(self):
|
||||
pass
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.state)
|
||||
|
||||
def count_agents(self, state_id=None, limit_neighbors=False):
|
||||
if limit_neighbors:
|
||||
agents = self.global_topology.neighbors(self.id)
|
||||
@@ -182,6 +187,26 @@ class BaseAgent(nxsim.BaseAgent):
|
||||
|
||||
def info(self, *args, **kwargs):
|
||||
return self.log(*args, level=logging.INFO, **kwargs)
|
||||
|
||||
def __getstate__(self):
|
||||
'''
|
||||
Serializing an agent will lose all its running information (you cannot
|
||||
serialize an iterator), but it keeps the state and link to the environment,
|
||||
so it can be used for inspection and dumping to a file
|
||||
'''
|
||||
state = {}
|
||||
state['id'] = self.id
|
||||
state['environment'] = self.env
|
||||
state['_state'] = self._state
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
'''
|
||||
Get back a serialized agent and try to re-compose it
|
||||
'''
|
||||
self.id = state['id']
|
||||
self._state = state['_state']
|
||||
self.env = state['environment']
|
||||
|
||||
|
||||
def state(func):
|
||||
@@ -336,7 +361,7 @@ def serialize_distribution(network_agents, known_modules=[]):
|
||||
When serializing an agent distribution, remove the thresholds, in order
|
||||
to avoid cluttering the YAML definition file.
|
||||
'''
|
||||
d = deepcopy(network_agents)
|
||||
d = deepcopy(list(network_agents))
|
||||
for v in d:
|
||||
if 'threshold' in v:
|
||||
del v['threshold']
|
||||
|
@@ -14,6 +14,14 @@ import nxsim
|
||||
|
||||
from . import utils, agents, analysis, history
|
||||
|
||||
# These properties will be copied when pickling/unpickling the environment
|
||||
_CONFIG_PROPS = [ 'name',
|
||||
'states',
|
||||
'default_state',
|
||||
'interval',
|
||||
'dry_run',
|
||||
'dir_path',
|
||||
]
|
||||
|
||||
class Environment(nxsim.NetworkEnvironment):
|
||||
"""
|
||||
@@ -318,21 +326,22 @@ class Environment(nxsim.NetworkEnvironment):
|
||||
G.add_node(agent.id, **attributes)
|
||||
|
||||
return G
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state = {}
|
||||
for prop in _CONFIG_PROPS:
|
||||
state[prop] = self.__dict__[prop]
|
||||
state['G'] = json_graph.node_link_data(self.G)
|
||||
state['network_agents'] = agents.serialize_distribution(self.network_agents)
|
||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||
to_string=True)
|
||||
state['environment_agents'] = self._env_agents
|
||||
state['history'] = self._history
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
for prop in _CONFIG_PROPS:
|
||||
self.__dict__[prop] = state[prop]
|
||||
self._env_agents = state['environment_agents']
|
||||
self.G = json_graph.node_link_graph(state['G'])
|
||||
self.network_agents = self.calculate_distribution(self._convert_agent_types(self.network_agents))
|
||||
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
||||
return state
|
||||
self._history = state['history']
|
||||
|
||||
|
||||
SoilEnvironment = Environment
|
||||
|
@@ -38,7 +38,7 @@ class History:
|
||||
def db(self):
|
||||
try:
|
||||
self._db.cursor()
|
||||
except sqlite3.ProgrammingError:
|
||||
except (sqlite3.ProgrammingError, AttributeError):
|
||||
self.db = None # Reset the database
|
||||
return self._db
|
||||
|
||||
@@ -207,6 +207,16 @@ class History:
|
||||
if t_steps:
|
||||
df_p = df_p.reindex(t_steps, method='ffill')
|
||||
return df_p.ffill()
|
||||
|
||||
def __getstate__(self):
|
||||
state = dict(**self.__dict__)
|
||||
del state['_db']
|
||||
del state['_dtypes']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self._dtypes = {}
|
||||
|
||||
|
||||
class Records():
|
||||
|
@@ -88,14 +88,8 @@ class Simulation(NetworkSimulation):
|
||||
environment_agents=None, environment_params=None,
|
||||
environment_class=None, **kwargs):
|
||||
|
||||
if topology is None:
|
||||
topology = utils.load_network(network_params,
|
||||
dir_path=dir_path)
|
||||
elif isinstance(topology, basestring) or isinstance(topology, dict):
|
||||
topology = json_graph.node_link_graph(topology)
|
||||
|
||||
self.seed = str(seed) or str(time.time())
|
||||
self.load_module = load_module
|
||||
self.topology = nx.Graph(topology)
|
||||
self.network_params = network_params
|
||||
self.name = name or 'UnnamedSimulation'
|
||||
self.num_trials = num_trials
|
||||
@@ -103,12 +97,19 @@ class Simulation(NetworkSimulation):
|
||||
self.default_state = default_state or {}
|
||||
self.dir_path = dir_path or os.getcwd()
|
||||
self.interval = interval
|
||||
self.seed = str(seed) or str(time.time())
|
||||
self.dump = dump
|
||||
self.dry_run = dry_run
|
||||
|
||||
sys.path += [self.dir_path, os.getcwd()]
|
||||
|
||||
if topology is None:
|
||||
topology = utils.load_network(network_params,
|
||||
dir_path=self.dir_path)
|
||||
elif isinstance(topology, basestring) or isinstance(topology, dict):
|
||||
topology = json_graph.node_link_graph(topology)
|
||||
self.topology = nx.Graph(topology)
|
||||
|
||||
|
||||
self.environment_params = environment_params or {}
|
||||
self.environment_class = utils.deserialize(environment_class,
|
||||
known_modules=['soil.environment', ]) or Environment
|
||||
@@ -201,7 +202,7 @@ class Simulation(NetworkSimulation):
|
||||
return self.run_trial(*args, **kwargs)
|
||||
except Exception as ex:
|
||||
c = ex.__cause__
|
||||
c.message = ''.join(traceback.format_tb(c.__traceback__)[3:])
|
||||
c.message = ''.join(traceback.format_exception(type(c), c, c.__traceback__)[:])
|
||||
return c
|
||||
|
||||
def to_dict(self):
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import ast
|
||||
import sys
|
||||
import yaml
|
||||
import logging
|
||||
import importlib
|
||||
@@ -36,9 +37,14 @@ def load_network(network_params, dir_path=None):
|
||||
return method(path, **kwargs)
|
||||
|
||||
net_args = network_params.copy()
|
||||
net_type = net_args.pop('generator')
|
||||
net_gen = net_args.pop('generator')
|
||||
|
||||
if dir_path not in sys.path:
|
||||
sys.path.append(dir_path)
|
||||
|
||||
method = deserializer(net_gen,
|
||||
known_modules=['networkx.generators',])
|
||||
|
||||
method = getattr(nx.generators, net_type)
|
||||
return method(**net_args)
|
||||
|
||||
|
||||
@@ -114,6 +120,8 @@ def serialize(v, known_modules=[]):
|
||||
return func(v), tname
|
||||
|
||||
def deserializer(type_, known_modules=[]):
|
||||
if type(type_) != str: # Already deserialized
|
||||
return type_
|
||||
if type_ == 'str':
|
||||
return lambda x='': x
|
||||
if type_ == 'None':
|
||||
|
@@ -19,7 +19,7 @@ from xml.etree.ElementTree import tostring
|
||||
from tornado.concurrent import run_on_executor
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from ..simulation import SoilSimulation
|
||||
from ..simulation import Simulation
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -168,7 +168,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
@run_on_executor
|
||||
def nonblocking(self, config):
|
||||
simulation = SoilSimulation(**config)
|
||||
simulation = Simulation(**config)
|
||||
return simulation.run()
|
||||
|
||||
@tornado.gen.coroutine
|
||||
|
@@ -2,6 +2,7 @@ from unittest import TestCase
|
||||
|
||||
import os
|
||||
import yaml
|
||||
import pickle
|
||||
import networkx as nx
|
||||
from functools import partial
|
||||
|
||||
@@ -248,12 +249,10 @@ class TestMain(TestCase):
|
||||
assert name == 'soil.agents.BaseAgent'
|
||||
assert ser == agents.BaseAgent
|
||||
|
||||
class CustomAgent(agents.BaseAgent):
|
||||
pass
|
||||
|
||||
ser, name = utils.serialize(CustomAgent)
|
||||
assert name == 'test_main.CustomAgent'
|
||||
assert ser == CustomAgent
|
||||
pickle.dumps(ser)
|
||||
|
||||
def test_serialize_builtin_types(self):
|
||||
|
||||
@@ -269,7 +268,8 @@ class TestMain(TestCase):
|
||||
assert ser == 'test_main.CustomAgent'
|
||||
ser = agents.serialize_type(agents.BaseAgent)
|
||||
assert ser == 'BaseAgent'
|
||||
|
||||
pickle.dumps(ser)
|
||||
|
||||
def test_deserialize_agent_distribution(self):
|
||||
agent_distro = [
|
||||
{
|
||||
@@ -284,6 +284,7 @@ class TestMain(TestCase):
|
||||
converted = agents.deserialize_distribution(agent_distro)
|
||||
assert converted[0]['agent_type'] == agents.CounterModel
|
||||
assert converted[1]['agent_type'] == CustomAgent
|
||||
pickle.dumps(converted)
|
||||
|
||||
def test_serialize_agent_distribution(self):
|
||||
agent_distro = [
|
||||
@@ -299,6 +300,20 @@ class TestMain(TestCase):
|
||||
converted = agents.serialize_distribution(agent_distro)
|
||||
assert converted[0]['agent_type'] == 'CounterModel'
|
||||
assert converted[1]['agent_type'] == 'test_main.CustomAgent'
|
||||
pickle.dumps(converted)
|
||||
|
||||
def test_pickle_agent_environment(self):
|
||||
env = Environment(name='Test')
|
||||
a = agents.BaseAgent(environment=env, agent_id=25)
|
||||
|
||||
a['key'] = 'test'
|
||||
|
||||
pickled = pickle.dumps(a)
|
||||
recovered = pickle.loads(pickled)
|
||||
|
||||
assert recovered.env.name == 'Test'
|
||||
assert recovered['key'] == 'test'
|
||||
assert recovered['key', 0] == 'test'
|
||||
|
||||
def test_history(self):
|
||||
'''Test storing in and retrieving from history (sqlite)'''
|
||||
|
Reference in New Issue
Block a user