mirror of https://github.com/gsi-upm/soil
WIP
parent
f49be3af68
commit
3041156f19
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
1.0.0rc3
|
||||
1.0.0rc6
|
||||
|
@ -1,58 +1,34 @@
|
||||
from . import BaseAgent
|
||||
from ..events import Message, Tell, Ask, TimedOut
|
||||
from .. import environment, events
|
||||
from functools import partial
|
||||
from collections import deque
|
||||
from types import coroutine
|
||||
|
||||
# from soilent import Scheduler
|
||||
|
||||
|
||||
class EventedAgent(BaseAgent):
|
||||
# scheduler_class = Scheduler
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._inbox = deque()
|
||||
self._processed = 0
|
||||
|
||||
def on_receive(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
@coroutine
|
||||
def received(self, expiration=None, timeout=60, delay=1, process=True):
|
||||
if not expiration:
|
||||
expiration = self.now + timeout
|
||||
while self.now < expiration:
|
||||
if self._inbox:
|
||||
msgs = self._inbox
|
||||
if process:
|
||||
self.process_messages()
|
||||
return msgs
|
||||
yield self.delay(delay)
|
||||
raise TimedOut("No message received")
|
||||
|
||||
def tell(self, msg, sender=None):
|
||||
self._inbox.append(Tell(timestamp=self.now, payload=msg, sender=sender))
|
||||
|
||||
@coroutine
|
||||
def ask(self, msg, expiration=None, timeout=None, delay=1):
|
||||
ask = Ask(timestamp=self.now, payload=msg, sender=self)
|
||||
self._inbox.append(ask)
|
||||
expiration = float("inf") if timeout is None else self.now + timeout
|
||||
while self.now < expiration:
|
||||
if ask.reply:
|
||||
return ask.reply
|
||||
yield self.delay(delay)
|
||||
raise TimedOut("No reply received")
|
||||
assert isinstance(self.model, environment.EventedEnvironment), "EventedAgent requires an EventedEnvironment"
|
||||
self.model.register(self)
|
||||
|
||||
def received(self, **kwargs):
|
||||
return self.model.received(self, **kwargs)
|
||||
|
||||
def tell(self, msg, **kwargs):
|
||||
return self.model.tell(msg, recipient=self, **kwargs)
|
||||
|
||||
def broadcast(self, msg, **kwargs):
|
||||
return self.model.broadcast(msg, sender=self, **kwargs)
|
||||
|
||||
def ask(self, msg, **kwargs):
|
||||
return self.model.ask(msg, recipient=self, **kwargs)
|
||||
|
||||
def process_messages(self):
|
||||
valid = list()
|
||||
for msg in self._inbox:
|
||||
self._processed += 1
|
||||
if msg.expired(self.now):
|
||||
continue
|
||||
valid.append(msg)
|
||||
reply = self.on_receive(msg.payload, sender=msg.sender)
|
||||
if isinstance(msg, Ask):
|
||||
msg.reply = reply
|
||||
self._inbox.clear()
|
||||
return valid
|
||||
return self.model.process_messages(self.model.inbox_for(self))
|
||||
|
||||
|
||||
Evented = EventedAgent
|
||||
|
@ -0,0 +1,87 @@
|
||||
from abc import ABCMeta
|
||||
from copy import copy
|
||||
from functools import wraps
|
||||
from .. import time
|
||||
|
||||
import types
|
||||
import inspect
|
||||
|
||||
def decorate_generator_step(func, name):
|
||||
@wraps(func)
|
||||
def decorated(self):
|
||||
if not self.alive:
|
||||
return time.INFINITY
|
||||
|
||||
if self._coroutine is None:
|
||||
self._coroutine = func(self)
|
||||
try:
|
||||
if self._last_except:
|
||||
val = self._coroutine.throw(self._last_except)
|
||||
else:
|
||||
val = self._coroutine.send(self._last_return)
|
||||
except StopIteration as ex:
|
||||
self._coroutine = None
|
||||
val = ex.value
|
||||
finally:
|
||||
self._last_return = None
|
||||
self._last_except = None
|
||||
return float(val) if val is not None else val
|
||||
return decorated
|
||||
|
||||
|
||||
def decorate_normal_step(func, name):
|
||||
@wraps(func)
|
||||
def decorated(self):
|
||||
# if not self.alive:
|
||||
# return time.INFINITY
|
||||
val = func(self)
|
||||
return float(val) if val is not None else val
|
||||
return decorated
|
||||
|
||||
|
||||
class MetaAgent(ABCMeta):
|
||||
def __new__(mcls, name, bases, namespace):
|
||||
defaults = {}
|
||||
|
||||
# Re-use defaults from inherited classes
|
||||
for i in bases:
|
||||
if isinstance(i, MetaAgent):
|
||||
defaults.update(i._defaults)
|
||||
|
||||
new_nmspc = {
|
||||
"_defaults": defaults,
|
||||
}
|
||||
|
||||
for attr, func in namespace.items():
|
||||
if attr == "step":
|
||||
if inspect.isgeneratorfunction(func) or inspect.iscoroutinefunction(func):
|
||||
func = decorate_generator_step(func, attr)
|
||||
new_nmspc.update({
|
||||
"_last_return": None,
|
||||
"_last_except": None,
|
||||
"_coroutine": None,
|
||||
})
|
||||
elif inspect.isasyncgenfunction(func):
|
||||
raise ValueError("Illegal step function: {}. It probably mixes both async/await and yield".format(func))
|
||||
elif inspect.isfunction(func):
|
||||
func = decorate_normal_step(func, attr)
|
||||
else:
|
||||
raise ValueError("Illegal step function: {}".format(func))
|
||||
new_nmspc[attr] = func
|
||||
elif (
|
||||
isinstance(func, types.FunctionType)
|
||||
or isinstance(func, property)
|
||||
or isinstance(func, classmethod)
|
||||
or attr[0] == "_"
|
||||
):
|
||||
new_nmspc[attr] = func
|
||||
elif attr == "defaults":
|
||||
defaults.update(func)
|
||||
elif inspect.isfunction(func):
|
||||
new_nmspc[attr] = func
|
||||
else:
|
||||
defaults[attr] = copy(func)
|
||||
|
||||
|
||||
# Add attributes for their use in the decorated functions
|
||||
return super().__new__(mcls, name, bases, new_nmspc)
|
@ -0,0 +1,136 @@
|
||||
from collections.abc import Mapping, Set
|
||||
from itertools import islice
|
||||
|
||||
|
||||
class AgentView(Mapping, Set):
|
||||
"""A lazy-loaded list of agents."""
|
||||
|
||||
__slots__ = ("_agents", "agents_by_type")
|
||||
|
||||
def __init__(self, agents, agents_by_type):
|
||||
self._agents = agents
|
||||
self.agents_by_type = agents_by_type
|
||||
|
||||
def __getstate__(self):
|
||||
return {"_agents": self._agents}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self._agents = state["_agents"]
|
||||
|
||||
# Mapping methods
|
||||
def __len__(self):
|
||||
return len(self._agents)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._agents.values()
|
||||
|
||||
def __getitem__(self, agent_id):
|
||||
if isinstance(agent_id, slice):
|
||||
raise ValueError(f"Slicing is not supported")
|
||||
if agent_id in self._agents:
|
||||
return self._agents[agent_id]
|
||||
raise ValueError(f"Agent {agent_id} not found")
|
||||
|
||||
def filter(self, agent_class=None, include_subclasses=True, **kwargs):
|
||||
if agent_class and self.agents_by_type:
|
||||
if not include_subclasses:
|
||||
return filter_agents(self.agents_by_type[agent_class],
|
||||
**kwargs)
|
||||
else:
|
||||
d = {}
|
||||
for k, v in self.agents_by_type.items():
|
||||
if (k == agent_class) or issubclass(k, agent_class):
|
||||
d.update(v)
|
||||
return filter_agents(d, **kwargs)
|
||||
return filter_agents(self._agents, agent_class=agent_class, **kwargs)
|
||||
|
||||
|
||||
def one(self, *args, **kwargs):
|
||||
try:
|
||||
return next(self.filter(*args, **kwargs))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return list(self.filter(*args, **kwargs))
|
||||
|
||||
def __contains__(self, agent_id):
|
||||
return agent_id in self._agents
|
||||
|
||||
def __str__(self):
|
||||
return str(list(unique_id for unique_id in self.keys()))
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self})"
|
||||
|
||||
|
||||
def filter_agents(
|
||||
agents: dict,
|
||||
*id_args,
|
||||
unique_id=None,
|
||||
state_id=None,
|
||||
agent_class=None,
|
||||
ignore=None,
|
||||
state=None,
|
||||
limit=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Filter agents given as a dict, by the criteria given as arguments (e.g., certain type or state id).
|
||||
"""
|
||||
assert isinstance(agents, dict)
|
||||
|
||||
ids = []
|
||||
|
||||
if unique_id is not None:
|
||||
if isinstance(unique_id, list):
|
||||
ids += unique_id
|
||||
else:
|
||||
ids.append(unique_id)
|
||||
|
||||
if id_args:
|
||||
ids += id_args
|
||||
|
||||
if ids:
|
||||
f = list(agents[aid] for aid in ids if aid in agents)
|
||||
else:
|
||||
f = agents.values()
|
||||
|
||||
if state_id is not None and not isinstance(state_id, (tuple, list)):
|
||||
state_id = tuple([state_id])
|
||||
|
||||
if ignore:
|
||||
f = filter(lambda x: x not in ignore, f)
|
||||
|
||||
if state_id is not None:
|
||||
f = filter(lambda agent: agent.get("state_id", None) in state_id, f)
|
||||
|
||||
if agent_class is not None:
|
||||
f = filter(lambda agent: isinstance(agent, agent_class), f)
|
||||
|
||||
state = state or dict()
|
||||
state.update(kwargs)
|
||||
|
||||
for k, vs in state.items():
|
||||
if not isinstance(vs, list):
|
||||
vs = [vs]
|
||||
f = filter(lambda agent: any(getattr(agent, k, None) == v for v in vs), f)
|
||||
|
||||
if limit is not None:
|
||||
f = islice(f, limit)
|
||||
|
||||
return AgentResult(f)
|
||||
|
||||
class AgentResult:
|
||||
def __init__(self, iterator):
|
||||
self.iterator = iterator
|
||||
|
||||
def limit(self, limit):
|
||||
self.iterator = islice(self.iterator, limit)
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.iterator)
|
||||
|
||||
def __next__(self):
|
||||
return next(self.iterator)
|
Loading…
Reference in New Issue