1
0
mirror of https://github.com/gsi-upm/soil synced 2024-11-13 23:12:28 +00:00

Release 0.20.6

This commit is contained in:
J. Fernando Sánchez 2022-07-05 12:08:34 +02:00
parent dfb6d13649
commit 50cba751a6
5 changed files with 47 additions and 15 deletions

View File

@ -3,6 +3,14 @@ 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). 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).
## [UNRELEASED]
## [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] ## [0.20.5]
### Changed ### 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 * Defaults are now set in the agent __init__, not in the environment. This decouples both classes a bit more, and it is more intuitive

View File

@ -145,6 +145,7 @@ class BaseAgent(Agent):
self.alive = False self.alive = False
if remove: if remove:
self.remove_node(self.id) self.remove_node(self.id)
return time.INFINITY
def step(self): def step(self):
if not self.alive: if not self.alive:
@ -313,18 +314,16 @@ class FSM(NetworkAgent, metaclass=MetaFSM):
def step(self): def step(self):
self.debug(f'Agent {self.unique_id} @ state {self.state_id}') self.debug(f'Agent {self.unique_id} @ state {self.state_id}')
try: interval = super().step()
interval = super().step()
except DeadAgent:
return time.When('inf')
if 'id' not in self.state: if 'id' not in self.state:
# if 'id' in self.state:
# self.set_state(self.state['id'])
if self.default_state: if self.default_state:
self.set_state(self.default_state.id) self.set_state(self.default_state.id)
else: else:
raise Exception('{} has no valid state id or default state'.format(self)) 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): def set_state(self, state):
if hasattr(state, 'id'): if hasattr(state, 'id'):

View File

@ -1,11 +1,12 @@
import os import os
import time
import importlib import importlib
import sys import sys
import yaml import yaml
import traceback import traceback
import logging import logging
import networkx as nx import networkx as nx
from time import strftime
from networkx.readwrite import json_graph from networkx.readwrite import json_graph
from multiprocessing import Pool from multiprocessing import Pool
from functools import partial from functools import partial
@ -98,7 +99,7 @@ class Simulation:
self.network_params = network_params self.network_params = network_params
self.name = name or 'Unnamed' self.name = name or 'Unnamed'
self.seed = str(seed or name) self.seed = str(seed or name)
self._id = '{}_{}'.format(self.name, time.strftime("%Y-%m-%d_%H.%M.%S")) self._id = '{}_{}'.format(self.name, strftime("%Y-%m-%d_%H.%M.%S"))
self.group = group or '' self.group = group or ''
self.num_trials = num_trials self.num_trials = num_trials
self.max_time = max_time self.max_time = max_time
@ -142,10 +143,10 @@ class Simulation:
'''Run the simulation and return the list of resulting environments''' '''Run the simulation and return the list of resulting environments'''
return list(self.run_gen(*args, **kwargs)) 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): if parallel and not os.environ.get('SENPY_DEBUG', None):
p = Pool() 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)): for i in p.imap_unordered(func, range(self.num_trials)):
if isinstance(i, Exception): if isinstance(i, Exception):
logger.error('Trial failed:\n\t%s', i.message) logger.error('Trial failed:\n\t%s', i.message)
@ -154,10 +155,9 @@ class Simulation:
else: else:
for i in range(self.num_trials): for i in range(self.num_trials):
yield self.run_trial(trial_id=i, yield self.run_trial(trial_id=i,
*args,
**kwargs) **kwargs)
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={}, exporters=[default, ], stats=[], outdir=None, exporter_params={},
stats_params={}, log_level=None, stats_params={}, log_level=None,
**kwargs): **kwargs):
@ -183,8 +183,7 @@ class Simulation:
for exporter in exporters: for exporter in exporters:
exporter.start() exporter.start()
for env in self._run_sync_or_async(*args, for env in self._run_sync_or_async(parallel=parallel,
parallel=parallel,
log_level=log_level, log_level=log_level,
**kwargs): **kwargs):

View File

@ -15,6 +15,8 @@ class When:
def abs(self, time): def abs(self, time):
return self._time return self._time
NEVER = When(INFINITY)
class Delta: class Delta:
def __init__(self, delta): def __init__(self, delta):

24
tests/test_agents.py Normal file
View File

@ -0,0 +1,24 @@
from unittest import TestCase
import pytest
from soil import agents, environment
from soil import time as stime
class Dead(agents.FSM):
@agents.default_state
@agents.state
def only(self):
self.die()
class TestMain(TestCase):
def test_die_raises_exception(self):
d = Dead(unique_id=0, model=environment.Environment())
d.step()
with pytest.raises(agents.DeadAgent):
d.step()
def test_die_returns_infinity(self):
d = Dead(unique_id=0, model=environment.Environment())
assert d.step().abs(0) == stime.INFINITY