From 497c8a55db28938fb1af32b319b693f5f563d57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Fri, 16 Feb 2018 18:04:43 +0100 Subject: [PATCH] Add workaround for geometric models Closes soil/soil#4 --- soil/environment.py | 17 +++++++++++++++-- soil/simulation.py | 46 +++++++++++++++++++++++++-------------------- tests/test_main.py | 32 ++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/soil/environment.py b/soil/environment.py index c2f38f5..2809cb9 100644 --- a/soil/environment.py +++ b/soil/environment.py @@ -4,6 +4,7 @@ import time import csv import random import simpy +import tempfile from copy import deepcopy from networkx.readwrite import json_graph @@ -24,13 +25,16 @@ class SoilEnvironment(nxsim.NetworkEnvironment): seed=None, dry_run=False, dir_path=None, + topology=None, *args, **kwargs): self.name = name or 'UnnamedEnvironment' if isinstance(states, list): states = dict(enumerate(states)) self.states = deepcopy(states) if states else {} self.default_state = deepcopy(default_state) or {} - super().__init__(*args, **kwargs) + if not topology: + topology = nx.Graph() + super().__init__(*args, topology=topology, **kwargs) self._env_agents = {} self.dry_run = dry_run self.interval = interval @@ -41,7 +45,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment): self.process(self.save_state()) self.environment_agents = environment_agents or [] self.network_agents = network_agents or [] - self.dir_path = dir_path + self.dir_path = dir_path or tempfile.mkdtemp('soil-env') if self.dry_run: self._db_path = ":memory:" else: @@ -88,6 +92,8 @@ class SoilEnvironment(nxsim.NetworkEnvironment): @network_agents.setter def network_agents(self, network_agents): + if not network_agents: + return for ix in self.G.nodes(): i = ix node = self.G.node[i] @@ -223,6 +229,13 @@ class SoilEnvironment(nxsim.NetworkEnvironment): G = self.history_to_graph() graph_path = os.path.join(self.get_path(dir_path), self.name+".gexf") + # Workaround for geometric models + # See soil/soil#4 + for node in G.nodes(): + if 'pos' in G.node[node]: + G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}} + del (G.node[node]['pos']) + nx.write_gexf(G, graph_path, version="1.2draft") def dump(self, dir_path=None, formats=None): diff --git a/soil/simulation.py b/soil/simulation.py index f1b7acf..f53540f 100644 --- a/soil/simulation.py +++ b/soil/simulation.py @@ -92,40 +92,46 @@ class SoilSimulation(NetworkSimulation): def run(self, *args, **kwargs): return list(self.run_simulation_gen(*args, **kwargs)) - def run_simulation_gen(self, *args, parallel=False, **kwargs): + def run_simulation_gen(self, *args, parallel=False, dry_run=False, + **kwargs): p = Pool() with utils.timer('simulation'): if parallel: - func = partial(self.run_trial, return_env=not parallel) + func = partial(self.run_trial, dry_run=dry_run, + return_env=not parallel, **kwargs) for i in p.imap_unordered(func, range(self.num_trials)): yield i else: for i in range(self.num_trials): - yield self.run_trial(i) - if not self.dry_run: + yield self.run_trial(i, dry_run=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) self.dump_yaml(self.dir_path) else: logger.info('NOT dumping results') - def get_env(self, trial_id=0, dump=False, dir_path=None): + def get_env(self, trial_id=0, **kwargs): + opts = self.environment_params.copy() env_name = '{}_trial_{}'.format(self.name, trial_id) - env = environment.SoilEnvironment(name=env_name, - topology=self.topology.copy(), - seed=self.seed+env_name, - initial_time=0, - dry_run=self.dry_run, - interval=self.interval, - network_agents=self.network_agents, - states=self.states, - default_state=self.default_state, - environment_agents=self.environment_agents, - dir_path=dir_path or self.dir_path, - **self.environment_params) + opts.update({ + 'name': env_name, + 'topology': self.topology.copy(), + 'seed': self.seed+env_name, + 'initial_time': 0, + 'dry_run': self.dry_run, + 'interval': self.interval, + 'network_agents': self.network_agents, + 'states': self.states, + 'default_state': self.default_state, + 'environment_agents': self.environment_agents, + 'dir_path': self.dir_path, + }) + opts.update(kwargs) + env = environment.SoilEnvironment(**opts) return env - def run_trial(self, trial_id=0, dump=False, dir_path=None, until=None, return_env=False): + def run_trial(self, trial_id=0, until=None, return_env=True, **opts): """Run a single trial of the simulation Parameters @@ -134,13 +140,13 @@ class SoilSimulation(NetworkSimulation): """ # Set-up trial environment and graph until = until or self.max_time - env = self.get_env(trial_id=trial_id, dump=dump, dir_path=dir_path) + 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(dir_path, formats=self.dump) + env.dump(formats=self.dump) if return_env: return env diff --git a/tests/test_main.py b/tests/test_main.py index 94e4033..6b22f8e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2,6 +2,7 @@ from unittest import TestCase import os import yaml +import networkx as nx from functools import partial from os.path import join @@ -65,13 +66,14 @@ class TestMain(TestCase): } } s = simulation.from_config(config) - s.run_simulation() + s.run_simulation(dry_run=True) def test_counter_agent(self): """ The initial states should be applied to the agent and the agent should be able to update its state.""" config = { + 'name': 'CounterAgent', 'network_params': { 'path': join(ROOT, 'test.gexf') }, @@ -83,7 +85,7 @@ class TestMain(TestCase): } } s = simulation.from_config(config) - env = s.run_simulation()[0] + env = s.run_simulation(dry_run=True)[0] assert env.get_agent(0)['neighbors', 0] == 10 assert env.get_agent(0)['neighbors', 1] == 1 assert env.get_agent(1)['total', 0] == 12 @@ -94,6 +96,7 @@ class TestMain(TestCase): The evolution of the state should be recorded in the logging agent """ config = { + 'name': 'CounterAgent', 'network_params': { 'path': join(ROOT, 'test.gexf') }, @@ -108,7 +111,7 @@ class TestMain(TestCase): } } s = simulation.from_config(config) - env = s.run_simulation()[0] + env = s.run_simulation(dry_run=True)[0] for agent in env.network_agents: last = 0 assert len(agent[None, None]) == 11 @@ -138,7 +141,7 @@ class TestMain(TestCase): } } s = simulation.from_config(config) - env = s.run_simulation()[0] + env = s.run_simulation(dry_run=True)[0] assert env.get_agent(0).state['neighbors'] == 1 def test_torvalds_example(self): @@ -147,7 +150,7 @@ class TestMain(TestCase): config['network_params']['path'] = join(EXAMPLES, config['network_params']['path']) s = simulation.from_config(config) - env = s.run_simulation()[0] + env = s.run_simulation(dry_run=True)[0] for a in env.network_agents: skill_level = a.state['skill_level'] if a.id == 'Torvalds': @@ -177,6 +180,7 @@ class TestMain(TestCase): recovered = yaml.load(serial) with utils.timer('deleting'): del recovered['topology'] + del recovered['dry_run'] del recovered['load_module'] assert config == recovered @@ -188,9 +192,10 @@ class TestMain(TestCase): config = utils.load_file('examples/complete.yml')[0] s = simulation.from_config(config) for i in range(5): - s.run_simulation() + s.run_simulation(dry_run=True) nconfig = s.to_dict() del nconfig['topology'] + del nconfig['dry_run'] del nconfig['load_module'] assert config == nconfig @@ -202,7 +207,7 @@ class TestMain(TestCase): def test_row_conversion(self): sim = simulation.SoilSimulation() - env = environment.SoilEnvironment(simulation=sim) + env = environment.SoilEnvironment(dry_run=True) env['test'] = 'test_value' env._save_state(now=0) @@ -217,7 +222,14 @@ class TestMain(TestCase): assert env['env', 0, 'test' ] == 'test_value' assert env['env', 1, 'test' ] == 'second_value' - + def test_save_geometric(self): + """ + There is a bug in networkx that prevents it from creating a GEXF file + from geometric models. We should work around it. + """ + G = nx.random_geometric_graph(20,0.1) + env = environment.SoilEnvironment(topology=G, dry_run=True) + env.dump_gexf('/tmp/dump-gexf') def make_example_test(path, config): @@ -225,8 +237,10 @@ def make_example_test(path, config): root = os.getcwd() os.chdir(os.path.dirname(path)) s = simulation.from_config(config) - envs = s.run_simulation() + envs = s.run_simulation(dry_run=True) + assert envs for env in envs: + assert env try: n = config['network_params']['n'] assert len(env.get_agents()) == n