From e860bdb922a22da2987fba07dffb81351c0272e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Sat, 22 May 2021 16:33:20 +0200 Subject: [PATCH] v0.15.2 See CHANGELOG.md for a complete list of changes --- CHANGELOG.md | 7 +++++++ soil/VERSION | 2 +- soil/__init__.py | 15 ++++++++++++--- soil/exporters.py | 23 ++++++++++++----------- soil/serialization.py | 10 ++++++++++ soil/simulation.py | 25 +++++++++++++++---------- tests/test_exporters.py | 8 ++++---- 7 files changed, 61 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5747552..d2d5bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.15.2] +### Fixed +* Pass the right known_modules and parameters to stats discovery in simulation +* The configuration file must exist when launching through the CLI. If it doesn't, an error will be logged +* Minor changes in the documentation of the CLI arguments +### Changed +* Stats are now exported by default ## [0.15.1] ### Added * read-only `History` diff --git a/soil/VERSION b/soil/VERSION index 8076af5..a12760e 100644 --- a/soil/VERSION +++ b/soil/VERSION @@ -1 +1 @@ -0.15.1 \ No newline at end of file +0.15.2 \ No newline at end of file diff --git a/soil/__init__.py b/soil/__init__.py index d05519d..3283dd8 100644 --- a/soil/__init__.py +++ b/soil/__init__.py @@ -23,13 +23,15 @@ def main(): import argparse from . import simulation - logging.info('Running SOIL version: {}'.format(__version__)) + logger.info('Running SOIL version: {}'.format(__version__)) parser = argparse.ArgumentParser(description='Run a SOIL simulation') parser.add_argument('file', type=str, nargs="?", default='simulation.yml', - help='python module containing the simulation configuration.') + help='Configuration file for the simulation (e.g., YAML or JSON)') + parser.add_argument('--version', action='store_true', + help='Show version info and exit') parser.add_argument('--module', '-m', type=str, help='file containing the code of any custom agents.') parser.add_argument('--dry-run', '--dry', action='store_true', @@ -52,12 +54,15 @@ def main(): args = parser.parse_args() logging.basicConfig(level=getattr(logging, (args.level or 'INFO').upper())) + if args.version: + return + if os.getcwd() not in sys.path: sys.path.append(os.getcwd()) if args.module: importlib.import_module(args.module) - logging.info('Loading config file: {}'.format(args.file)) + logger.info('Loading config file: {}'.format(args.file)) try: exporters = list(args.exporter or ['default', ]) @@ -68,6 +73,10 @@ def main(): exp_params = {} if args.dry_run: exp_params['copy_to'] = sys.stdout + + if not os.path.exists(args.file): + logger.error('Please, input a valid file') + return simulation.run_from_config(args.file, dry_run=args.dry_run, exporters=exporters, diff --git a/soil/exporters.py b/soil/exporters.py index f323a71..b526b60 100644 --- a/soil/exporters.py +++ b/soil/exporters.py @@ -14,15 +14,6 @@ from .utils import open_or_reuse, logger, timer from . import utils -def for_sim(simulation, names, *args, **kwargs): - '''Return the set of exporters for a simulation, given the exporter names''' - exporters = [] - for name in names: - mod = deserialize(name, known_modules=['soil.exporters']) - exporters.append(mod(simulation, *args, **kwargs)) - return exporters - - class DryRunner(BytesIO): def __init__(self, fname, *args, copy_to=None, **kwargs): super().__init__(*args, **kwargs) @@ -38,8 +29,12 @@ class DryRunner(BytesIO): super().write(bytes(txt, 'utf-8')) def close(self): - logger.info('**Not** written to {} (dry run mode):\n\n{}\n\n'.format(self.__fname, - self.getvalue().decode())) + content = '(binary data not shown)' + try: + content = self.getvalue().decode() + except UnicodeDecodeError: + pass + logger.info('**Not** written to {} (dry run mode):\n\n{}\n\n'.format(self.__fname, content)) super().close() @@ -99,6 +94,12 @@ class default(Exporter): with self.output('{}.sqlite'.format(env.name), mode='wb') as f: env.dump_sqlite(f) + def end(self, stats): + with timer('Dumping simulation {}\'s stats'.format(self.simulation.name)): + with self.output('{}.sqlite'.format(self.simulation.name), mode='wb') as f: + self.simulation.dump_sqlite(f) + + class csv(Exporter): '''Export the state of each environment (and its agents) in a separate CSV file''' diff --git a/soil/serialization.py b/soil/serialization.py index ccef06f..ac58f79 100644 --- a/soil/serialization.py +++ b/soil/serialization.py @@ -208,3 +208,13 @@ def deserialize(type_, value=None, **kwargs): if value is None: return des return des(value) + + +def deserialize_all(names, *args, known_modules=['soil'], **kwargs): + '''Return the set of exporters for a simulation, given the exporter names''' + exporters = [] + for name in names: + mod = deserialize(name, known_modules=known_modules) + exporters.append(mod(*args, **kwargs)) + return exporters + diff --git a/soil/simulation.py b/soil/simulation.py index 3ddff10..96e1bc2 100644 --- a/soil/simulation.py +++ b/soil/simulation.py @@ -15,7 +15,7 @@ import pickle from . import serialization, utils, basestring, agents from .environment import Environment from .utils import logger -from .exporters import default, for_sim as exporters_for_sim +from .exporters import default from .stats import defaultStats from .history import History @@ -133,7 +133,7 @@ class Simulation: self.topology) self._history = History(name=self.name, - backup=False) + backup=False) def run_simulation(self, *args, **kwargs): return self.run(*args, **kwargs) @@ -167,14 +167,16 @@ class Simulation: logger.setLevel(log_level) logger.info('Using exporters: %s', exporters or []) logger.info('Output directory: %s', outdir) - exporters = exporters_for_sim(self, - exporters, - dry_run=dry_run, - outdir=outdir, - **exporter_params) - stats = exporters_for_sim(self, - stats, - **stats_params) + exporters = serialization.deserialize_all(exporters, + simulation=self, + known_modules=['soil.exporters',], + dry_run=dry_run, + outdir=outdir, + **exporter_params) + stats = serialization.deserialize_all(simulation=self, + names=stats, + known_modules=['soil.stats',], + **stats_params) with utils.timer('simulation {}'.format(self.name)): for stat in stats: @@ -293,6 +295,9 @@ class Simulation: with utils.open_or_reuse(f, 'wb') as f: pickle.dump(self, f) + def dump_sqlite(self, f): + return self._history.dump(f) + def __getstate__(self): state={} for k, v in self.__dict__.items(): diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 56d734b..1583f23 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -74,10 +74,10 @@ class Exporters(TestCase): s = simulation.from_config(config) tmpdir = tempfile.mkdtemp() envs = s.run_simulation(exporters=[ - exporters.default, - exporters.csv, - exporters.gexf, - ], + exporters.default, + exporters.csv, + exporters.gexf, + ], stats=[distribution,], outdir=tmpdir, exporter_params={'copy_to': output})