1
0
mirror of https://github.com/gsi-upm/soil synced 2025-09-18 22:22:20 +00:00
This commit is contained in:
J. Fernando Sánchez
2022-10-06 15:49:10 +02:00
parent 0a9c6d8b19
commit f811ee18c5
53 changed files with 856 additions and 774 deletions

View File

@@ -5,7 +5,7 @@ import math
import random
import logging
from typing import Dict
from typing import Any, Dict, Optional, Union
from collections import namedtuple
from time import time as current_time
from copy import deepcopy
@@ -17,20 +17,24 @@ import networkx as nx
from mesa import Model
from mesa.datacollection import DataCollector
from . import serialization, agents, analysis, utils, time, config, network
from . import serialization, analysis, utils, time, network
from .agents import AgentView, BaseAgent, NetworkAgent, from_config as agents_from_config
Record = namedtuple('Record', 'dict_id t_step key value')
class Environment(Model):
class BaseEnvironment(Model):
"""
The environment is key in a simulation. It contains the network topology,
a reference to network and environment agents, as well as the environment
params, which are used as shared state between agents.
The environment is key in a simulation. It controls how agents interact,
and what information is available to them.
This is an opinionated version of `mesa.Model` class, which adds many
convenience methods and abstractions.
The environment parameters and the state of every agent can be accessed
both by using the environment as a dictionary or with the environment's
both by using the environment as a dictionary and with the environment's
:meth:`soil.environment.Environment.get` method.
"""
@@ -40,67 +44,62 @@ class Environment(Model):
schedule=None,
dir_path=None,
interval=1,
agents: Dict[str, config.AgentConfig] = {},
topologies: Dict[str, config.NetConfig] = {},
agent_class=BaseAgent,
agents: [tuple[type, Dict[str, Any]]] = {},
agent_reporters: Optional[Any] = None,
model_reporters: Optional[Any] = None,
tables: Optional[Any] = None,
**env_params):
super().__init__()
super().__init__(seed=seed)
self.current_id = -1
self.seed = '{}_{}'.format(seed, env_id)
self.id = env_id
self.dir_path = dir_path or os.getcwd()
if schedule is None:
schedule = time.TimedActivation()
schedule = time.TimedActivation(self)
self.schedule = schedule
seed = seed or current_time()
self.agent_class = agent_class
random.seed(seed)
self.topologies = {}
self._node_ids = {}
for (name, cfg) in topologies.items():
self.set_topology(cfg=cfg,
graph=name)
self.agents = agents or {}
self.init_agents(agents)
self.env_params = env_params or {}
self.interval = interval
self['SEED'] = seed
self.logger = utils.logger.getChild(self.id)
self.datacollector = DataCollector(model_reporters, agent_reporters, tables)
self.datacollector = DataCollector(
model_reporters=model_reporters,
agent_reporters=agent_reporters,
tables=tables,
)
def __read_agent_tuple(self, tup):
cls = self.agent_class
args = tup
if isinstance(tup, tuple):
cls = tup[0]
args = tup[1]
return serialization.deserialize(cls)(unique_id=self.next_id(),
model=self, **args)
def init_agents(self, agents: [tuple[type, Dict[str, Any]]] = {}):
agents = [self.__read_agent_tuple(tup) for tup in agents]
self._agents = {'default': {agent.id: agent for agent in agents}}
@property
def topology(self):
return self.topologies['default']
def agents(self):
return AgentView(self._agents)
@property
def network_agents(self):
yield from self.agents(agent_class=agents.NetworkAgent)
@staticmethod
def from_config(conf: config.Config, trial_id, **kwargs) -> Environment:
'''Create an environment for a trial of the simulation'''
conf = conf
if kwargs:
conf = config.Config(**conf.dict(exclude_defaults=True), **kwargs)
seed = '{}_{}'.format(conf.general.seed, trial_id)
id = '{}_trial_{}'.format(conf.general.id, trial_id).replace('.', '-')
opts = conf.environment.params.copy()
dir_path = conf.general.dir_path
opts.update(conf)
opts.update(kwargs)
env = serialization.deserialize(conf.environment.environment_class)(env_id=id, seed=seed, dir_path=dir_path, **opts)
return env
def find_one(self, *args, **kwargs):
return AgentView(self._agents).one(*args, **kwargs)
def count_agents(self, *args, **kwargs):
return sum(1 for i in self.agents(*args, **kwargs))
@property
def now(self):
@@ -109,115 +108,42 @@ class Environment(Model):
raise Exception('The environment has not been scheduled, so it has no sense of time')
def topology_for(self, agent_id):
return self.topologies[self._node_ids[agent_id][0]]
# def init_agent(self, agent_id, agent_definitions, state=None):
# state = state or {}
def node_id_for(self, agent_id):
return self._node_ids[agent_id][1]
# agent_class = None
# if 'agent_class' in self.states.get(agent_id, {}):
# agent_class = self.states[agent_id]['agent_class']
# elif 'agent_class' in self.default_state:
# agent_class = self.default_state['agent_class']
def set_topology(self, cfg=None, dir_path=None, graph='default'):
topology = cfg
if not isinstance(cfg, nx.Graph):
topology = network.from_config(cfg, dir_path=dir_path or self.dir_path)
self.topologies[graph] = topology
@property
def agents(self):
return agents.AgentView(self._agents)
def count_agents(self, *args, **kwargs):
return sum(1 for i in self.find_all(*args, **kwargs))
def find_all(self, *args, **kwargs):
return agents.AgentView(self._agents).filter(*args, **kwargs)
def find_one(self, *args, **kwargs):
return agents.AgentView(self._agents).one(*args, **kwargs)
@agents.setter
def agents(self, agents_def: Dict[str, config.AgentConfig]):
self._agents = agents.from_config(agents_def, env=self)
for d in self._agents.values():
for a in d.values():
self.schedule.add(a)
def init_agent(self, agent_id, agent_definitions, graph='default'):
node = self.topologies[graph].nodes[agent_id]
init = False
state = dict(node)
agent_class = None
if 'agent_class' in self.states.get(agent_id, {}):
agent_class = self.states[agent_id]['agent_class']
elif 'agent_class' in node:
agent_class = node['agent_class']
elif 'agent_class' in self.default_state:
agent_class = self.default_state['agent_class']
if agent_class:
agent_class = agents.deserialize_type(agent_class)
elif agent_definitions:
agent_class, state = agents._agent_from_definition(agent_definitions, unique_id=agent_id)
else:
serialization.logger.debug('Skipping node {}'.format(agent_id))
return
return self.set_agent(agent_id, agent_class, state)
def agent_to_node(self, agent_id, graph_name='default', node_id=None, shuffle=False):
#TODO: test
if node_id is None:
G = self.topologies[graph_name]
candidates = list(G.nodes(data=True))
if shuffle:
random.shuffle(candidates)
for next_id, data in candidates:
if data.get('agent_id', None) is None:
node_id = next_id
data['agent_id'] = agent_id
break
self._node_ids[agent_id] = (graph_name, node_id)
print(self._node_ids)
# if agent_class:
# agent_class = agents.deserialize_type(agent_class)
# elif agent_definitions:
# agent_class, state = agents._agent_from_definition(agent_definitions, unique_id=agent_id)
# else:
# serialization.logger.debug('Skipping agent {}'.format(agent_id))
# return
# return self.add_agent(agent_id, agent_class, state)
def set_agent(self, agent_id, agent_class, state=None, graph='default'):
node = self.topologies[graph].nodes[agent_id]
def add_agent(self, agent_id, agent_class, state=None, graph='default'):
defstate = deepcopy(self.default_state) or {}
defstate.update(self.states.get(agent_id, {}))
defstate.update(node.get('state', {}))
if state:
defstate.update(state)
a = None
if agent_class:
state = defstate
a = agent_class(model=self,
unique_id=agent_id
)
unique_id=agent_id)
for (k, v) in state.items():
setattr(a, k, v)
node['agent'] = a
self.schedule.add(a)
return a
def add_node(self, agent_class, state=None, graph='default'):
agent_id = int(len(self.topologies[graph].nodes()))
self.topologies[graph].add_node(agent_id)
a = self.set_agent(agent_id, agent_class, state, graph=graph)
a['visible'] = True
return a
def add_edge(self, agent1, agent2, start=None, graph='default', **attrs):
if hasattr(agent1, 'id'):
agent1 = agent1.id
if hasattr(agent2, 'id'):
agent2 = agent2.id
start = start or self.now
return self.topologies[graph].add_edge(agent1, agent2, **attrs)
def log(self, message, *args, level=logging.INFO, **kwargs):
if not self.logger.isEnabledFor(level):
return
@@ -238,14 +164,6 @@ class Environment(Model):
self.schedule.step()
self.datacollector.collect(self)
def run(self, until, *args, **kwargs):
until = until or float('inf')
while self.schedule.next_time < until:
self.step()
utils.logger.debug(f'Simulation step {self.schedule.time}/{until}. Next: {self.schedule.next_time}')
self.schedule.time = until
def __contains__(self, key):
return key in self.env_params
@@ -289,5 +207,90 @@ class Environment(Model):
yield from self._agent_to_tuples(agent, now)
class AgentConfigEnvironment(BaseEnvironment):
SoilEnvironment = Environment
def __init__(self, *args,
agents: Dict[str, config.AgentConfig] = {},
**kwargs):
return super().__init__(*args, agents=agents, **kwargs)
def init_agents(self, agents: Union[Dict[str, config.AgentConfig], [tuple[type, Dict[str, Any]]]] = {}):
if not isinstance(agents, dict):
return BaseEnvironment.init_agents(self, agents)
self._agents = agents_from_config(agents,
env=self,
random=self.random)
for d in self._agents.values():
for a in d.values():
self.schedule.add(a)
class NetworkConfigEnvironment(BaseEnvironment):
def __init__(self, *args, topologies: Dict[str, config.NetConfig] = {}, **kwargs):
super().__init__(*args, **kwargs)
self.topologies = {}
self._node_ids = {}
for (name, cfg) in topologies.items():
self.set_topology(cfg=cfg, graph=name)
@property
def topology(self):
return self.topologies['default']
def set_topology(self, cfg=None, dir_path=None, graph='default'):
topology = cfg
if not isinstance(cfg, nx.Graph):
topology = network.from_config(cfg, dir_path=dir_path or self.dir_path)
self.topologies[graph] = topology
def topology_for(self, agent_id):
return self.topologies[self._node_ids[agent_id][0]]
@property
def network_agents(self):
yield from self.agents(agent_class=NetworkAgent)
def agent_to_node(self, agent_id, graph_name='default', node_id=None, shuffle=False):
node_id = network.agent_to_node(G=self.topologies[graph_name], agent_id=agent_id,
node_id=node_id, shuffle=shuffle,
random=self.random)
self._node_ids[agent_id] = (graph_name, node_id)
def add_node(self, agent_class, state=None, graph='default'):
agent_id = int(len(self.topologies[graph].nodes()))
self.topologies[graph].add_node(agent_id)
a = self.add_agent(agent_id, agent_class, state, graph=graph)
a['visible'] = True
return a
def add_edge(self, agent1, agent2, start=None, graph='default', **attrs):
if hasattr(agent1, 'id'):
agent1 = agent1.id
if hasattr(agent2, 'id'):
agent2 = agent2.id
start = start or self.now
return self.topologies[graph].add_edge(agent1, agent2, **attrs)
def add_agent(self, *args, state=None, graph='default', **kwargs):
node = self.topologies[graph].nodes[agent_id]
node_state = node.get('state', {})
if node_state:
node_state.update(state or {})
state = node_state
a = super().add_agent(*args, state=state, **kwargs)
node['agent'] = a
return a
def node_id_for(self, agent_id):
return self._node_ids[agent_id][1]
class Environment(AgentConfigEnvironment, NetworkConfigEnvironment):
def __init__(self, *args, **kwargs):
agents = kwargs.pop('agents', {})
NetworkConfigEnvironment.__init__(self, *args, **kwargs)
AgentConfigEnvironment.__init__(self, *args, agents=agents, **kwargs)