Commit 50cba751 authored by J. Fernando Sánchez's avatar J. Fernando Sánchez
Browse files

Release 0.20.6

parent dfb6d136
Pipeline #3694 canceled with stage
in 2 minutes and 50 seconds
......@@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](, and this project adheres to [Semantic Versioning](
## [0.20.6]
### Fixed
* Agents now return `time.INFINITY` when dead, instead of 'inf'
* `soil.__init__` does not re-export built-in time (change in `soil.simulation`. It used to create subtle import conflicts when importing soil.time.
* Parallel simulations were broken because lambdas cannot be pickled properly, which is needed for multiprocessing.
### Changed
* Some internal simulation methods do not accept `*args` anymore, to avoid ambiguity and bugs.
## [0.20.5]
### Changed
* Defaults are now set in the agent __init__, not in the environment. This decouples both classes a bit more, and it is more intuitive
......@@ -145,6 +145,7 @@ class BaseAgent(Agent):
self.alive = False
if remove:
return time.INFINITY
def step(self):
if not self.alive:
......@@ -313,18 +314,16 @@ class FSM(NetworkAgent, metaclass=MetaFSM):
def step(self):
self.debug(f'Agent {self.unique_id} @ state {self.state_id}')
interval = super().step()
except DeadAgent:
return time.When('inf')
interval = super().step()
if 'id' not in self.state:
# if 'id' in self.state:
# self.set_state(self.state['id'])
if self.default_state:
raise Exception('{} has no valid state id or default state'.format(self))
return self.states[self.state_id](self) or interval
interval = self.states[self.state_id](self) or interval
if not self.alive:
return time.NEVER
return interval
def set_state(self, state):
if hasattr(state, 'id'):
import os
import time
import importlib
import sys
import yaml
import traceback
import logging
import networkx as nx
from time import strftime
from networkx.readwrite import json_graph
from multiprocessing import Pool
from functools import partial
......@@ -98,7 +99,7 @@ class Simulation:
self.network_params = network_params = name or 'Unnamed'
self.seed = str(seed or name)
self._id = '{}_{}'.format(, time.strftime("%Y-%m-%d_%H.%M.%S"))
self._id = '{}_{}'.format(, strftime("%Y-%m-%d_%H.%M.%S")) = group or ''
self.num_trials = num_trials
self.max_time = max_time
......@@ -142,10 +143,10 @@ class Simulation:
'''Run the simulation and return the list of resulting environments'''
return list(self.run_gen(*args, **kwargs))
def _run_sync_or_async(self, parallel=False, *args, **kwargs):
def _run_sync_or_async(self, parallel=False, **kwargs):
if parallel and not os.environ.get('SENPY_DEBUG', None):
p = Pool()
func = lambda x: self.run_trial_exceptions(trial_id=x, *args, **kwargs)
func = partial(self.run_trial_exceptions, **kwargs)
for i in p.imap_unordered(func, range(self.num_trials)):
if isinstance(i, Exception):
logger.error('Trial failed:\n\t%s', i.message)
......@@ -154,10 +155,9 @@ class Simulation:
for i in range(self.num_trials):
yield self.run_trial(trial_id=i,
def run_gen(self, *args, parallel=False, dry_run=False,
def run_gen(self, parallel=False, dry_run=False,
exporters=[default, ], stats=[], outdir=None, exporter_params={},
stats_params={}, log_level=None,
......@@ -183,8 +183,7 @@ class Simulation:
for exporter in exporters:
for env in self._run_sync_or_async(*args,
for env in self._run_sync_or_async(parallel=parallel,
......@@ -15,6 +15,8 @@ class When:
def abs(self, time):
return self._time
class Delta:
def __init__(self, delta):
from unittest import TestCase
import pytest
from soil import agents, environment
from soil import time as stime
class Dead(agents.FSM):
def only(self):
class TestMain(TestCase):
def test_die_raises_exception(self):
d = Dead(unique_id=0, model=environment.Environment())
with pytest.raises(agents.DeadAgent):
def test_die_returns_infinity(self):
d = Dead(unique_id=0, model=environment.Environment())
assert d.step().abs(0) == stime.INFINITY
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment