mirror of
https://github.com/gsi-upm/soil
synced 2024-11-14 15:32:29 +00:00
Release 0.20.6
This commit is contained in:
parent
dfb6d13649
commit
50cba751a6
@ -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
|
||||||
|
@ -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'):
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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
24
tests/test_agents.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user