1
0
mirror of https://github.com/gsi-upm/soil synced 2025-01-06 23:01:27 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
J. Fernando Sánchez
50cba751a6 Release 0.20.6 2022-07-05 12:08:34 +02:00
J. Fernando Sánchez
dfb6d13649 version 0.20.5 2022-05-18 16:13:53 +02:00
7 changed files with 56 additions and 20 deletions

View File

@ -3,6 +3,17 @@ 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]
### 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
## [0.20.4] ## [0.20.4]
### Added ### Added
* Agents can now be given any kwargs, which will be used to set their state * Agents can now be given any kwargs, which will be used to set their state

View File

@ -1 +1 @@
0.20.4 0.20.5

View File

@ -54,9 +54,14 @@ class BaseAgent(Agent):
if hasattr(self, 'level'): if hasattr(self, 'level'):
self.logger.setLevel(self.level) self.logger.setLevel(self.level)
for (k, v) in self.defaults.items():
if not hasattr(self, k) or getattr(self, k) is None:
setattr(self, k, deepcopy(v))
for (k, v) in kwargs.items(): for (k, v) in kwargs.items():
setattr(self, k, v) setattr(self, k, v)
# TODO: refactor to clean up mesa compatibility # TODO: refactor to clean up mesa compatibility
@property @property
def id(self): def id(self):
@ -140,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:
@ -308,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

@ -175,10 +175,6 @@ class Environment(Model):
unique_id=agent_id unique_id=agent_id
) )
for (k, v) in getattr(a, 'defaults', {}).items():
if not hasattr(a, k) or getattr(a, k) is None:
setattr(a, k, deepcopy(v))
for (k, v) in state.items(): for (k, v) in state.items():
setattr(a, k, v) setattr(a, k, v)

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