mirror of
https://github.com/gsi-upm/soil
synced 2024-11-13 23:12:28 +00:00
Added rabbits
This commit is contained in:
parent
dbc182c6d0
commit
af76f54a28
@ -13,7 +13,7 @@ Here's an example (``example.yml``).
|
|||||||
name: MyExampleSimulation
|
name: MyExampleSimulation
|
||||||
max_time: 50
|
max_time: 50
|
||||||
num_trials: 3
|
num_trials: 3
|
||||||
timeout: 2
|
interval: 2
|
||||||
network_params:
|
network_params:
|
||||||
network_type: barabasi_albert_graph
|
network_type: barabasi_albert_graph
|
||||||
n: 100
|
n: 100
|
||||||
@ -34,6 +34,12 @@ Here's an example (``example.yml``).
|
|||||||
environment_params:
|
environment_params:
|
||||||
prob_infect: 0.075
|
prob_infect: 0.075
|
||||||
|
|
||||||
|
|
||||||
|
This example configuration will run three trials of a simulation containing a randomly generated network.
|
||||||
|
The 100 nodes in the network will be SISaModel agents, 10% of them will start in the content state, 10% in the discontent state, and the remaining 80% in the neutral state.
|
||||||
|
All agents will have access to the environment, which only contains one variable, ``prob_infected``.
|
||||||
|
The state of the agents will be updated every 2 seconds (``interval``).
|
||||||
|
|
||||||
Now run the simulation with the command line tool:
|
Now run the simulation with the command line tool:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
@ -41,7 +47,7 @@ Now run the simulation with the command line tool:
|
|||||||
soil example.yml
|
soil example.yml
|
||||||
|
|
||||||
Once the simulation finishes, its results will be stored in a folder named ``MyExampleSimulation``.
|
Once the simulation finishes, its results will be stored in a folder named ``MyExampleSimulation``.
|
||||||
Four types of objects are saved by default: a pickle of the simulation, a ``YAML`` representation of the simulation (to re-launch it), for every trial, a csv file with the content of the state of every network node and the environment parameters at every step of the simulation as well as the network in gephi format (``gexf``).
|
Four types of objects are saved by default: a pickle of the simulation; a ``YAML`` representation of the simulation (which can be used to re-launch it); and for every trial, a csv file with the content of the state of every network node and the environment parameters at every step of the simulation, as well as the network in gephi format (``gexf``).
|
||||||
|
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
@ -54,12 +60,6 @@ Four types of objects are saved by default: a pickle of the simulation, a ``YAML
|
|||||||
│ └── Sim_prob_0_trial_0.gexf
|
│ └── Sim_prob_0_trial_0.gexf
|
||||||
|
|
||||||
|
|
||||||
This example configuration will run three trials of a simulation containing a randomly generated network.
|
|
||||||
The 100 nodes in the network will be SISaModel agents, 10% of them will start in the content state, 10% in the discontent state, and the remaining 80% in the neutral state.
|
|
||||||
All agents will have access to the environment, which only contains one variable, ``prob_infected``.
|
|
||||||
The state of the agents will be updated every 2 seconds (``timeout``).
|
|
||||||
|
|
||||||
|
|
||||||
Network
|
Network
|
||||||
=======
|
=======
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ For example, the following configuration is equivalent to :code:`nx.complete_gra
|
|||||||
Environment
|
Environment
|
||||||
============
|
============
|
||||||
The environment is the place where the shared state of the simulation is stored.
|
The environment is the place where the shared state of the simulation is stored.
|
||||||
For instance, the probability of certain events.
|
For instance, the probability of disease outbreak.
|
||||||
The configuration file may specify the initial value of the environment parameters:
|
The configuration file may specify the initial value of the environment parameters:
|
||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
@ -103,14 +103,17 @@ The configuration file may specify the initial value of the environment paramete
|
|||||||
daily_probability_of_earthquake: 0.001
|
daily_probability_of_earthquake: 0.001
|
||||||
number_of_earthquakes: 0
|
number_of_earthquakes: 0
|
||||||
|
|
||||||
|
Any agent has unrestricted access to the environment.
|
||||||
|
However, for the sake of simplicity, we recommend limiting environment updates to environment agents.
|
||||||
|
|
||||||
Agents
|
Agents
|
||||||
======
|
======
|
||||||
Agents are a way of modelling behavior.
|
Agents are a way of modelling behavior.
|
||||||
Agents can be characterized with two variables: an agent type (``agent_type``) and its state.
|
Agents can be characterized with two variables: an agent type (``agent_type``) and its state.
|
||||||
Only one agent is executed at a time (generally, every ``timeout`` seconds), and it has access to its state and the environment parameters.
|
Only one agent is executed at a time (generally, every ``interval`` seconds), and it has access to its state and the environment parameters.
|
||||||
Through the environment, it can access the network topology and the state of other agents.
|
Through the environment, it can access the network topology and the state of other agents.
|
||||||
|
|
||||||
There are three three types of agents according to how they are added to the simulation: network agents, environment agent, and other agents.
|
There are three three types of agents according to how they are added to the simulation: network agents and environment agent.
|
||||||
|
|
||||||
Network Agents
|
Network Agents
|
||||||
##############
|
##############
|
||||||
@ -118,13 +121,13 @@ Network agents are attached to a node in the topology.
|
|||||||
The configuration file allows you to specify how agents will be mapped to topology nodes.
|
The configuration file allows you to specify how agents will be mapped to topology nodes.
|
||||||
|
|
||||||
The simplest way is to specify a single type of agent.
|
The simplest way is to specify a single type of agent.
|
||||||
Hence, every node in the network will have an associated agent of that type.
|
Hence, every node in the network will be associated to an agent of that type.
|
||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
|
|
||||||
agent_type: SISaModel
|
agent_type: SISaModel
|
||||||
|
|
||||||
It is also possible to add more than one type of agent to the simulation, and to control the ratio of each type (``weight``).
|
It is also possible to add more than one type of agent to the simulation, and to control the ratio of each type (using the ``weight`` property).
|
||||||
For instance, with following configuration, it is five times more likely for a node to be assigned a CounterModel type than a SISaModel type.
|
For instance, with following configuration, it is five times more likely for a node to be assigned a CounterModel type than a SISaModel type.
|
||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
|
21
examples/custom.yml
Normal file
21
examples/custom.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
load_module: custom_agents
|
||||||
|
name: custom_agent_example
|
||||||
|
max_time: 2500
|
||||||
|
interval: 1
|
||||||
|
seed: MySimulationSeed
|
||||||
|
agent_type: RabbitModel
|
||||||
|
environment_agents:
|
||||||
|
- agent_type: RandomAccident
|
||||||
|
default_state:
|
||||||
|
mating_prob: 1
|
||||||
|
topology:
|
||||||
|
nodes:
|
||||||
|
- id: 1
|
||||||
|
state:
|
||||||
|
gender: female
|
||||||
|
- id: 0
|
||||||
|
state:
|
||||||
|
gender: male
|
||||||
|
directed: true
|
||||||
|
links: []
|
103
examples/custom_agents.py
Normal file
103
examples/custom_agents.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import logging
|
||||||
|
from soil.agents import NetworkAgent, FSM, state, default_state, BaseAgent
|
||||||
|
from enum import Enum
|
||||||
|
from random import random, choice
|
||||||
|
from itertools import islice
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Genders(Enum):
|
||||||
|
male = 'male'
|
||||||
|
female = 'female'
|
||||||
|
|
||||||
|
|
||||||
|
class RabbitModel(NetworkAgent, FSM):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'age': 0,
|
||||||
|
'gender': Genders.male.value,
|
||||||
|
'mating_prob': 0.001,
|
||||||
|
'offspring': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
sexual_maturity = 4*30
|
||||||
|
life_expectancy = 365 * 3
|
||||||
|
gestation = 33
|
||||||
|
pregnancy = -1
|
||||||
|
max_females = 10
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def newborn(self):
|
||||||
|
self['age'] += 1
|
||||||
|
|
||||||
|
if self['age'] >= self.sexual_maturity:
|
||||||
|
return self.fertile
|
||||||
|
|
||||||
|
@state
|
||||||
|
def fertile(self):
|
||||||
|
self['age'] += 1
|
||||||
|
if self['age'] > self.life_expectancy:
|
||||||
|
return self.dead
|
||||||
|
|
||||||
|
if self['gender'] == Genders.female.value:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Males try to mate
|
||||||
|
females = self.get_agents(state_id=self.fertile.id, gender=Genders.female.value, limit_neighbors=False)
|
||||||
|
for f in islice(females, self.max_females):
|
||||||
|
r = random()
|
||||||
|
if r < self['mating_prob']:
|
||||||
|
self.impregnate(f)
|
||||||
|
break # Take a break
|
||||||
|
|
||||||
|
def impregnate(self, whom):
|
||||||
|
if self['gender'] == Genders.female.value:
|
||||||
|
raise NotImplementedError('Females cannot impregnate')
|
||||||
|
whom['pregnancy'] = 0
|
||||||
|
whom['mate'] = self.id
|
||||||
|
whom.set_state(whom.pregnant)
|
||||||
|
logger.debug('{} impregnating: {}. {}'.format(self.id, whom.id, whom.state))
|
||||||
|
|
||||||
|
@state
|
||||||
|
def pregnant(self):
|
||||||
|
self['age'] += 1
|
||||||
|
if self['age'] > self.life_expectancy:
|
||||||
|
return self.dead
|
||||||
|
|
||||||
|
self['pregnancy'] += 1
|
||||||
|
logger.debug('Pregnancy: {}'.format(self['pregnancy']))
|
||||||
|
if self['pregnancy'] >= self.gestation:
|
||||||
|
|
||||||
|
state = {}
|
||||||
|
state['gender'] = choice(list(Genders)).value
|
||||||
|
child = self.env.add_node(self.__class__, state)
|
||||||
|
self.env.add_edge(self.id, child.id)
|
||||||
|
self.env.add_edge(self['mate'], child.id)
|
||||||
|
# self.add_edge()
|
||||||
|
logger.info("A rabbit has been born: {}. Total: {}".format(child.id, len(self.global_topology.nodes)))
|
||||||
|
self['offspring'] += 1
|
||||||
|
self.env.get_agent(self['mate'])['offspring'] += 1
|
||||||
|
del self['mate']
|
||||||
|
self['pregnancy'] = -1
|
||||||
|
return self.fertile
|
||||||
|
|
||||||
|
@state
|
||||||
|
def dead(self):
|
||||||
|
logger.info('Agent {} is dying'.format(self.id))
|
||||||
|
if 'pregnancy' in self and self['pregnancy'] > -1:
|
||||||
|
logger.info('A mother has died carrying a baby!: {}!'.format(self.state))
|
||||||
|
self.die()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class RandomAccident(BaseAgent):
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
logger.debug('Killing some rabbits!')
|
||||||
|
prob_death = self.env.get('prob_death', -1)
|
||||||
|
for i in self.env.network_agents:
|
||||||
|
r = random()
|
||||||
|
if r < prob_death:
|
||||||
|
logger.info('I killed a rabbit: {}'.format(i.id))
|
||||||
|
i.set_state(i.dead)
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: torvalds_example
|
name: torvalds_example
|
||||||
max_time: 1
|
max_time: 10
|
||||||
interval: 2
|
interval: 2
|
||||||
agent_type: CounterModel
|
agent_type: CounterModel
|
||||||
default_state:
|
default_state:
|
||||||
@ -11,4 +11,4 @@ states:
|
|||||||
Torvalds:
|
Torvalds:
|
||||||
skill_level: 'God'
|
skill_level: 'God'
|
||||||
balkian:
|
balkian:
|
||||||
skill_level: 'developer'
|
skill_level: 'developer'
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import pdb
|
||||||
|
import logging
|
||||||
|
|
||||||
__version__ = "0.9.7"
|
__version__ = "0.9.7"
|
||||||
|
|
||||||
@ -29,6 +31,8 @@ def main():
|
|||||||
help='file containing the code of any custom agents.')
|
help='file containing the code of any custom agents.')
|
||||||
parser.add_argument('--dry-run', '--dry', action='store_true',
|
parser.add_argument('--dry-run', '--dry', action='store_true',
|
||||||
help='Do not store the results of the simulation.')
|
help='Do not store the results of the simulation.')
|
||||||
|
parser.add_argument('--pdb', action='store_true',
|
||||||
|
help='Use a pdb console in case of exception.')
|
||||||
parser.add_argument('--output', '-o', type=str,
|
parser.add_argument('--output', '-o', type=str,
|
||||||
help='folder to write results to. It defaults to the current directory.')
|
help='folder to write results to. It defaults to the current directory.')
|
||||||
|
|
||||||
@ -38,8 +42,16 @@ def main():
|
|||||||
sys.path.append(os.getcwd())
|
sys.path.append(os.getcwd())
|
||||||
importlib.import_module(args.module)
|
importlib.import_module(args.module)
|
||||||
|
|
||||||
print('Loading config file: {}'.format(args.file, args.output))
|
logging.basicConfig(level=logging.INFO)
|
||||||
simulation.run_from_config(args.file, dump=(not args.dry_run), results_dir=args.output)
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info('Loading config file: {}'.format(args.file, args.output))
|
||||||
|
try:
|
||||||
|
simulation.run_from_config(args.file, dump=(not args.dry_run), results_dir=args.output)
|
||||||
|
except Exception as ex:
|
||||||
|
if args.pdb:
|
||||||
|
pdb.post_mortem()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
|
import logging
|
||||||
from . import simulation
|
from . import simulation
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import nxsim
|
import nxsim
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from functools import partial
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@ -27,28 +28,33 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
A special simpy BaseAgent that keeps track of its state history.
|
A special simpy BaseAgent that keeps track of its state history.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
defaults = {}
|
||||||
self._history = OrderedDict()
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
self._neighbors = None
|
self._neighbors = None
|
||||||
super().__init__(*args, **kwargs)
|
self.alive = True
|
||||||
|
state = deepcopy(self.defaults)
|
||||||
|
state.update(kwargs.pop('state', {}))
|
||||||
|
kwargs['state'] = state
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, tuple):
|
if isinstance(key, tuple):
|
||||||
k, t_step = key
|
k, t_step = key
|
||||||
if k is not None:
|
return self.env[t_step, self.id, k]
|
||||||
if t_step is not None:
|
return self.state.get(key, None)
|
||||||
return self._history[t_step][k]
|
|
||||||
else:
|
def __delitem__(self, key):
|
||||||
return {tt: tv.get(k, None) for tt, tv in self._history.items()}
|
del self.state[key]
|
||||||
else:
|
|
||||||
return self._history[t_step]
|
def __contains__(self, key):
|
||||||
return self.state[key]
|
return key in self.state
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
self.state[key] = value
|
self.state[key] = value
|
||||||
|
|
||||||
def save_state(self):
|
def get(self, key, default=None):
|
||||||
self._history[self.now] = deepcopy(self.state)
|
return self[key] if key in self else default
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def now(self):
|
def now(self):
|
||||||
@ -59,19 +65,21 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while self.alive:
|
||||||
res = self.step()
|
res = self.step()
|
||||||
yield res or self.env.timeout(self.env.interval)
|
yield res or self.env.timeout(self.env.interval)
|
||||||
|
|
||||||
|
def die(self, remove=False):
|
||||||
|
self.alive = False
|
||||||
|
if remove:
|
||||||
|
super().die()
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return json.dumps(self._history)
|
return json.dumps(self._history)
|
||||||
|
|
||||||
|
|
||||||
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
|
||||||
|
|
||||||
def count_agents(self, state_id=None, limit_neighbors=False):
|
def count_agents(self, state_id=None, limit_neighbors=False):
|
||||||
if limit_neighbors:
|
if limit_neighbors:
|
||||||
agents = self.global_topology.neighbors(self.id)
|
agents = self.global_topology.neighbors(self.id)
|
||||||
@ -84,6 +92,25 @@ class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
|||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
def get_agents(self, state_id=None, limit_neighbors=False, **kwargs):
|
||||||
|
if limit_neighbors:
|
||||||
|
agents = super().get_agents(state_id, limit_neighbors)
|
||||||
|
else:
|
||||||
|
agents = filter(lambda x: state_id is None or x.state.get('id', None) == state_id,
|
||||||
|
self.env.agents)
|
||||||
|
|
||||||
|
def matches_all(agent):
|
||||||
|
state = agent.state
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
if state.get(k, None) != v:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
return filter(matches_all, agents)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
||||||
|
|
||||||
def count_neighboring_agents(self, state_id=None):
|
def count_neighboring_agents(self, state_id=None):
|
||||||
return self.count_agents(state_id, limit_neighbors=True)
|
return self.count_agents(state_id, limit_neighbors=True)
|
||||||
|
|
||||||
@ -155,6 +182,13 @@ class FSM(BaseAgent, metaclass=MetaFSM):
|
|||||||
raise Exception('{} is not a valid id for {}'.format(next_state, self))
|
raise Exception('{} is not a valid id for {}'.format(next_state, self))
|
||||||
self.states[next_state](self)
|
self.states[next_state](self)
|
||||||
|
|
||||||
|
def set_state(self, state):
|
||||||
|
if hasattr(state, 'id'):
|
||||||
|
state = state.id
|
||||||
|
if state not in self.states:
|
||||||
|
raise ValueError('{} is not a valid state'.format(state))
|
||||||
|
self.state['id'] = state
|
||||||
|
|
||||||
|
|
||||||
from .BassModel import *
|
from .BassModel import *
|
||||||
from .BigMarketModel import *
|
from .BigMarketModel import *
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import csv
|
import csv
|
||||||
import weakref
|
import weakref
|
||||||
from random import random
|
import random
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import nxsim
|
import nxsim
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
class SoilEnvironment(nxsim.NetworkEnvironment):
|
class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||||
|
|
||||||
@ -16,6 +20,8 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
states=None,
|
states=None,
|
||||||
default_state=None,
|
default_state=None,
|
||||||
interval=1,
|
interval=1,
|
||||||
|
seed=None,
|
||||||
|
dump=False,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
self.name = name or 'UnnamedEnvironment'
|
self.name = name or 'UnnamedEnvironment'
|
||||||
self.states = deepcopy(states) or {}
|
self.states = deepcopy(states) or {}
|
||||||
@ -25,8 +31,11 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
self._history = {}
|
self._history = {}
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.logger = None
|
self.logger = None
|
||||||
|
self.dump = dump
|
||||||
# Add environment agents first, so their events get
|
# Add environment agents first, so their events get
|
||||||
# executed before network agents
|
# executed before network agents
|
||||||
|
self['SEED'] = seed or time.time()
|
||||||
|
random.seed(self['SEED'])
|
||||||
self.environment_agents = environment_agents or []
|
self.environment_agents = environment_agents or []
|
||||||
self.network_agents = network_agents or []
|
self.network_agents = network_agents or []
|
||||||
self.process(self.save_state())
|
self.process(self.save_state())
|
||||||
@ -65,26 +74,32 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
for ix in self.G.nodes():
|
for ix in self.G.nodes():
|
||||||
i = ix
|
i = ix
|
||||||
node = self.G.node[i]
|
node = self.G.node[i]
|
||||||
v = random()
|
agent, state = utils.agent_from_distribution(network_agents)
|
||||||
found = False
|
self.set_agent(i, agent_type=agent, state=state)
|
||||||
for d in network_agents:
|
|
||||||
threshold = d['threshold']
|
def set_agent(self, agent_id, agent_type, state=None):
|
||||||
if v >= threshold[0] and v < threshold[1]:
|
node = self.G.nodes[agent_id]
|
||||||
agent = d['agent_type']
|
defstate = deepcopy(self.default_state)
|
||||||
state = None
|
defstate.update(self.states.get(agent_id, {}))
|
||||||
if 'state' in d:
|
if state:
|
||||||
state = deepcopy(d['state'])
|
defstate.update(state)
|
||||||
else:
|
state = defstate
|
||||||
try:
|
state.update(node.get('state', {}))
|
||||||
state = self.states[i]
|
a = agent_type(environment=self,
|
||||||
except (IndexError, KeyError):
|
agent_id=agent_id,
|
||||||
state = deepcopy(self.default_state)
|
state=state)
|
||||||
node['agent'] = agent(environment=self,
|
node['agent'] = a
|
||||||
agent_id=i,
|
return a
|
||||||
state=state)
|
|
||||||
found = True
|
def add_node(self, agent_type, state=None):
|
||||||
break
|
agent_id = int(len(self.G.nodes()))
|
||||||
assert found
|
self.G.add_node(agent_id)
|
||||||
|
a = self.set_agent(agent_id, agent_type, state)
|
||||||
|
a['visible'] = True
|
||||||
|
return a
|
||||||
|
|
||||||
|
def add_edge(self, agent1, agent2, attrs=None):
|
||||||
|
return self.G.add_edge(agent1, agent2)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
self._save_state()
|
self._save_state()
|
||||||
@ -92,9 +107,12 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
self._save_state()
|
self._save_state()
|
||||||
|
|
||||||
def _save_state(self):
|
def _save_state(self):
|
||||||
|
# for agent in self.agents:
|
||||||
|
# agent.save_state()
|
||||||
|
nowd = self._history[self.now] = {}
|
||||||
|
nowd['env'] = deepcopy(self.environment_params)
|
||||||
for agent in self.agents:
|
for agent in self.agents:
|
||||||
agent.save_state()
|
nowd[agent.id] = deepcopy(agent.state)
|
||||||
self._history[self.now] = deepcopy(self.environment_params)
|
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
while True:
|
while True:
|
||||||
@ -107,11 +125,32 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
self._save_state()
|
self._save_state()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, tuple):
|
||||||
|
t_step, agent_id, k = key
|
||||||
|
|
||||||
|
def key_or_dict(d, k, nfunc):
|
||||||
|
if k is None:
|
||||||
|
if d is None:
|
||||||
|
return {}
|
||||||
|
return {k: nfunc(v) for k, v in d.items()}
|
||||||
|
if k in d:
|
||||||
|
return nfunc(d[k])
|
||||||
|
return {}
|
||||||
|
|
||||||
|
f1 = partial(key_or_dict, k=k, nfunc=lambda x: x)
|
||||||
|
f2 = partial(key_or_dict, k=agent_id, nfunc=f1)
|
||||||
|
return key_or_dict(self._history, t_step, f2)
|
||||||
return self.environment_params[key]
|
return self.environment_params[key]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
self.environment_params[key] = value
|
self.environment_params[key] = value
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.environment_params
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self[key] if key in self else default
|
||||||
|
|
||||||
def get_path(self, dir_path=None):
|
def get_path(self, dir_path=None):
|
||||||
dir_path = dir_path or self.sim().dir_path
|
dir_path = dir_path or self.sim().dir_path
|
||||||
if not os.path.exists(dir_path):
|
if not os.path.exists(dir_path):
|
||||||
@ -141,13 +180,10 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
nx.write_gexf(G, graph_path, version="1.2draft")
|
nx.write_gexf(G, graph_path, version="1.2draft")
|
||||||
|
|
||||||
def history_to_tuples(self):
|
def history_to_tuples(self):
|
||||||
for tstep, state in self._history.items():
|
for tstep, states in self._history.items():
|
||||||
for attribute, value in state.items():
|
for a_id, state in states.items():
|
||||||
yield ('env', tstep, attribute, value)
|
|
||||||
for agent in self.agents:
|
|
||||||
for tstep, state in agent._history.items():
|
|
||||||
for attribute, value in state.items():
|
for attribute, value in state.items():
|
||||||
yield (agent.id, tstep, attribute, value)
|
yield (a_id, tstep, attribute, value)
|
||||||
|
|
||||||
def history_to_graph(self):
|
def history_to_graph(self):
|
||||||
G = nx.Graph(self.G)
|
G = nx.Graph(self.G)
|
||||||
@ -159,7 +195,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
spells = []
|
spells = []
|
||||||
lastvisible = False
|
lastvisible = False
|
||||||
laststep = None
|
laststep = None
|
||||||
for t_step, state in reversed(list(agent._history.items())):
|
for t_step, state in reversed(list(self[None, agent.id, None].items())):
|
||||||
for attribute, value in state.items():
|
for attribute, value in state.items():
|
||||||
if attribute == 'visible':
|
if attribute == 'visible':
|
||||||
nowvisible = state[attribute]
|
nowvisible = state[attribute]
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import weakref
|
import weakref
|
||||||
import os
|
import os
|
||||||
import csv
|
|
||||||
import time
|
import time
|
||||||
|
import imp
|
||||||
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
import logging
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
from networkx.readwrite import json_graph
|
from networkx.readwrite import json_graph
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from random import random
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ from nxsim import NetworkSimulation
|
|||||||
|
|
||||||
from . import agents, utils, environment, basestring
|
from . import agents, utils, environment, basestring
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class SoilSimulation(NetworkSimulation):
|
class SoilSimulation(NetworkSimulation):
|
||||||
"""
|
"""
|
||||||
@ -47,9 +48,9 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, name=None, topology=None, network_params=None,
|
def __init__(self, name=None, topology=None, network_params=None,
|
||||||
network_agents=None, agent_type=None, states=None,
|
network_agents=None, agent_type=None, states=None,
|
||||||
default_state=None, interval=1,
|
default_state=None, interval=1, dump=False,
|
||||||
dir_path=None, num_trials=3, max_time=100,
|
dir_path=None, num_trials=1, max_time=100,
|
||||||
agent_module=None,
|
agent_module=None, load_module=None, seed=None,
|
||||||
environment_agents=None, environment_params=None):
|
environment_agents=None, environment_params=None):
|
||||||
|
|
||||||
if topology is None:
|
if topology is None:
|
||||||
@ -58,6 +59,8 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
elif isinstance(topology, basestring) or isinstance(topology, dict):
|
elif isinstance(topology, basestring) or isinstance(topology, dict):
|
||||||
topology = json_graph.node_link_graph(topology)
|
topology = json_graph.node_link_graph(topology)
|
||||||
|
|
||||||
|
|
||||||
|
self.load_module = load_module
|
||||||
self.topology = nx.Graph(topology)
|
self.topology = nx.Graph(topology)
|
||||||
self.network_params = network_params
|
self.network_params = network_params
|
||||||
self.name = name or 'UnnamedSimulation'
|
self.name = name or 'UnnamedSimulation'
|
||||||
@ -66,8 +69,15 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
self.default_state = default_state or {}
|
self.default_state = default_state or {}
|
||||||
self.dir_path = dir_path or os.getcwd()
|
self.dir_path = dir_path or os.getcwd()
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
|
self.seed = seed
|
||||||
|
self.dump = dump
|
||||||
self.environment_params = environment_params or {}
|
self.environment_params = environment_params or {}
|
||||||
|
|
||||||
|
if load_module:
|
||||||
|
path = sys.path + [self.dir_path]
|
||||||
|
f, fp, desc = imp.find_module(load_module, path)
|
||||||
|
imp.load_module('soil.agents.custom', f, fp, desc)
|
||||||
|
|
||||||
environment_agents = environment_agents or []
|
environment_agents = environment_agents or []
|
||||||
self.environment_agents = self._convert_agent_types(environment_agents)
|
self.environment_agents = self._convert_agent_types(environment_agents)
|
||||||
|
|
||||||
@ -132,12 +142,23 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
def run(self):
|
def run(self):
|
||||||
return list(self.run_simulation_gen())
|
return list(self.run_simulation_gen())
|
||||||
|
|
||||||
def run_simulation_gen(self):
|
def run_simulation_gen(self, *args, **kwargs):
|
||||||
with utils.timer('simulation'):
|
with utils.timer('simulation'):
|
||||||
for i in range(self.num_trials):
|
for i in range(self.num_trials):
|
||||||
yield self.run_trial(i)
|
res = self.run_trial(i)
|
||||||
|
if self.dump:
|
||||||
|
res.dump_gexf(self.dir_path)
|
||||||
|
res.dump_csv(self.dir_path)
|
||||||
|
yield res
|
||||||
|
|
||||||
def run_trial(self, trial_id=0):
|
if self.dump:
|
||||||
|
logger.info('Dumping results to {}'.format(self.dir_path))
|
||||||
|
self.dump_pickle(self.dir_path)
|
||||||
|
self.dump_yaml(self.dir_path)
|
||||||
|
else:
|
||||||
|
logger.info('NOT dumping results')
|
||||||
|
|
||||||
|
def run_trial(self, trial_id=0, dump=False, dir_path=None):
|
||||||
"""Run a single trial of the simulation
|
"""Run a single trial of the simulation
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -145,11 +166,13 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
trial_id : int
|
trial_id : int
|
||||||
"""
|
"""
|
||||||
# Set-up trial environment and graph
|
# Set-up trial environment and graph
|
||||||
print('Trial: {}'.format(trial_id))
|
logger.info('Trial: {}'.format(trial_id))
|
||||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||||
env = environment.SoilEnvironment(name=env_name,
|
env = environment.SoilEnvironment(name=env_name,
|
||||||
topology=self.topology.copy(),
|
topology=self.topology.copy(),
|
||||||
|
seed=self.seed,
|
||||||
initial_time=0,
|
initial_time=0,
|
||||||
|
dump=self.dump,
|
||||||
interval=self.interval,
|
interval=self.interval,
|
||||||
network_agents=self.network_agents,
|
network_agents=self.network_agents,
|
||||||
states=self.states,
|
states=self.states,
|
||||||
@ -159,7 +182,7 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
|
|
||||||
env.sim = weakref.ref(self)
|
env.sim = weakref.ref(self)
|
||||||
# Set up agents on nodes
|
# Set up agents on nodes
|
||||||
print('\tRunning')
|
logger.info('\tRunning')
|
||||||
with utils.timer('trial'):
|
with utils.timer('trial'):
|
||||||
env.run(until=self.max_time)
|
env.run(until=self.max_time)
|
||||||
return env
|
return env
|
||||||
@ -221,7 +244,7 @@ def run_from_config(*configs, dump=True, results_dir=None, timestamp=False):
|
|||||||
for config_def in configs:
|
for config_def in configs:
|
||||||
for config, cpath in utils.load_config(config_def):
|
for config, cpath in utils.load_config(config_def):
|
||||||
name = config.get('name', 'unnamed')
|
name = config.get('name', 'unnamed')
|
||||||
print("Using config(s): {name}".format(name=name))
|
logger.info("Using config(s): {name}".format(name=name))
|
||||||
|
|
||||||
sim = SoilSimulation(**config)
|
sim = SoilSimulation(**config)
|
||||||
if timestamp:
|
if timestamp:
|
||||||
@ -229,13 +252,7 @@ def run_from_config(*configs, dump=True, results_dir=None, timestamp=False):
|
|||||||
time.strftime("%Y-%m-%d_%H:%M:%S"))
|
time.strftime("%Y-%m-%d_%H:%M:%S"))
|
||||||
else:
|
else:
|
||||||
sim_folder = sim.name
|
sim_folder = sim.name
|
||||||
dir_path = os.path.join(results_dir,
|
sim.dir_path = os.path.join(results_dir, sim_folder)
|
||||||
sim_folder)
|
sim.dump = dump
|
||||||
|
logger.info('Dumping results to {} : {}'.format(sim.dir_path, dump))
|
||||||
results = sim.run_simulation()
|
results = sim.run_simulation()
|
||||||
|
|
||||||
if dump:
|
|
||||||
sim.dump_pickle(dir_path)
|
|
||||||
sim.dump_yaml(dir_path)
|
|
||||||
for env in results:
|
|
||||||
env.dump_gexf(dir_path)
|
|
||||||
env.dump_csv(dir_path)
|
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import logging
|
||||||
from time import time
|
from time import time
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
from random import random
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def load_network(network_params, dir_path=None):
|
def load_network(network_params, dir_path=None):
|
||||||
path = network_params.get('path', None)
|
path = network_params.get('path', None)
|
||||||
@ -51,7 +56,7 @@ def load_config(config):
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def timer(name='task', pre="", function=print, to_object=None):
|
def timer(name='task', pre="", function=logger.info, to_object=None):
|
||||||
start = time()
|
start = time()
|
||||||
yield start
|
yield start
|
||||||
end = time()
|
end = time()
|
||||||
@ -59,3 +64,18 @@ def timer(name='task', pre="", function=print, to_object=None):
|
|||||||
if to_object:
|
if to_object:
|
||||||
to_object.start = start
|
to_object.start = start
|
||||||
to_object.end = end
|
to_object.end = end
|
||||||
|
|
||||||
|
|
||||||
|
def agent_from_distribution(distribution, value=-1):
|
||||||
|
"""Find the agent """
|
||||||
|
if value < 0:
|
||||||
|
value = random()
|
||||||
|
for d in distribution:
|
||||||
|
threshold = d['threshold']
|
||||||
|
if value >= threshold[0] and value < threshold[1]:
|
||||||
|
state = None
|
||||||
|
if 'state' in d:
|
||||||
|
state = deepcopy(d['state'])
|
||||||
|
return d['agent_type'], state
|
||||||
|
|
||||||
|
raise Exception('Distribution for value {} not found in: {}'.format(value, distribution))
|
||||||
|
@ -111,7 +111,7 @@ class TestMain(TestCase):
|
|||||||
env = s.run_simulation()[0]
|
env = s.run_simulation()[0]
|
||||||
for agent in env.network_agents:
|
for agent in env.network_agents:
|
||||||
last = 0
|
last = 0
|
||||||
assert len(agent._history) == 11
|
assert len(agent[None, None]) == 11
|
||||||
for step, total in agent['total', None].items():
|
for step, total in agent['total', None].items():
|
||||||
if step > 0:
|
if step > 0:
|
||||||
assert total == last + 2
|
assert total == last + 2
|
||||||
@ -177,6 +177,7 @@ class TestMain(TestCase):
|
|||||||
recovered = yaml.load(serial)
|
recovered = yaml.load(serial)
|
||||||
with utils.timer('deleting'):
|
with utils.timer('deleting'):
|
||||||
del recovered['topology']
|
del recovered['topology']
|
||||||
|
del recovered['load_module']
|
||||||
assert config == recovered
|
assert config == recovered
|
||||||
|
|
||||||
def test_configuration_changes(self):
|
def test_configuration_changes(self):
|
||||||
@ -190,6 +191,7 @@ class TestMain(TestCase):
|
|||||||
s.run_simulation()
|
s.run_simulation()
|
||||||
nconfig = s.to_dict()
|
nconfig = s.to_dict()
|
||||||
del nconfig['topology']
|
del nconfig['topology']
|
||||||
|
del nconfig['load_module']
|
||||||
assert config == nconfig
|
assert config == nconfig
|
||||||
|
|
||||||
def test_examples(self):
|
def test_examples(self):
|
||||||
@ -205,9 +207,11 @@ def make_example_test(path, config):
|
|||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
envs = s.run_simulation()
|
envs = s.run_simulation()
|
||||||
for env in envs:
|
for env in envs:
|
||||||
n = config['network_params'].get('n', None)
|
try:
|
||||||
if n is not None:
|
n = config['network_params']['n']
|
||||||
assert len(env.get_agents()) == n
|
assert len(env.get_agents()) == n
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
os.chdir(root)
|
os.chdir(root)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user