Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d89827ccf | ||
|
|
fc48ed7e09 | ||
|
|
73c90887e8 | ||
|
|
497c8a55db | ||
|
|
7d1c800490 | ||
|
|
a4b32afa2f | ||
|
|
a7c51742f6 | ||
|
|
78364d89d5 | ||
|
|
af76f54a28 | ||
|
|
dbc182c6d0 | ||
|
|
eafecc9e5e | ||
|
|
e8988015e2 | ||
|
|
ccc8e43416 | ||
|
|
347d295b09 |
3
Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM python:3.4-onbuild
|
||||||
|
|
||||||
|
ENTRYPOINT ["python", "-m", "soil"]
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks.
|
Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks.
|
||||||
Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io).
|
Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io).
|
||||||
|
|
||||||
Follow our [tutorial](notebooks/soil_tutorial.ipynb) to develop your own agent models.
|
Follow our [tutorial](examples/tutorial/soil_tutorial.ipynb) to develop your own agent models.
|
||||||
|
|
||||||
If you use Soil in your research, don't forget to cite this paper:
|
If you use Soil in your research, don't forget to cite this paper:
|
||||||
|
|
||||||
|
|||||||
8
docker-compose.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
dev:
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/app
|
||||||
|
tty: true
|
||||||
|
entrypoint: /bin/bash
|
||||||
@@ -34,13 +34,14 @@ If you use Soil in your research, do not forget to cite this paper:
|
|||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 0
|
||||||
:caption: Learn more about soil:
|
:caption: Learn more about soil:
|
||||||
|
|
||||||
installation
|
installation
|
||||||
quickstart
|
quickstart
|
||||||
Tutorial - Spreading news
|
Tutorial <soil_tutorial>
|
||||||
|
|
||||||
|
..
|
||||||
|
|
||||||
|
|
||||||
.. Indices and tables
|
.. Indices and tables
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The easiest way to install Soil is through pip:
|
The easiest way to install Soil is through pip, with Python >= 3.4:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
BIN
docs/output_54_0.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_54_1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_0.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_3.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_55_5.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_55_6.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_7.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_8.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_9.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_56_0.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_1.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_2.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_4.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_5.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_6.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_7.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_8.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_9.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_61_0.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_63_1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_66_1.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_67_1.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
docs/output_72_0.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/output_72_1.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/output_74_1.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_75_1.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/output_76_1.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
@@ -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
|
||||||
|
|||||||
2612
docs/soil_tutorial.rst
Normal file
334
examples/NewsSpread.ipynb
Normal file
@@ -4,6 +4,8 @@ dir_path: "/tmp/"
|
|||||||
num_trials: 3
|
num_trials: 3
|
||||||
max_time: 100
|
max_time: 100
|
||||||
interval: 1
|
interval: 1
|
||||||
|
seed: "CompleteSeed!"
|
||||||
|
dump: false
|
||||||
network_params:
|
network_params:
|
||||||
generator: complete_graph
|
generator: complete_graph
|
||||||
n: 10
|
n: 10
|
||||||
@@ -21,4 +23,4 @@ default_state:
|
|||||||
incidents: 0
|
incidents: 0
|
||||||
states:
|
states:
|
||||||
- name: 'The first node'
|
- name: 'The first node'
|
||||||
- name: 'The second node'
|
- name: 'The second node'
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params: {prob_neighbor_spread: 0.0, prob_tv_spread: 0.01}
|
|
||||||
interval: 1
|
|
||||||
max_time: 20
|
|
||||||
name: Sim_prob_0
|
|
||||||
network_agents:
|
|
||||||
- agent_type: NewsSpread
|
|
||||||
state: {has_tv: false}
|
|
||||||
weight: 1
|
|
||||||
- agent_type: NewsSpread
|
|
||||||
state: {has_tv: true}
|
|
||||||
weight: 2
|
|
||||||
network_params: {generator: erdos_renyi_graph, n: 500, p: 0.1}
|
|
||||||
num_trials: 1
|
|
||||||
states:
|
|
||||||
- {has_tv: true}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import soil
|
|
||||||
import random
|
|
||||||
|
|
||||||
class NewsSpread(soil.agents.FSM):
|
|
||||||
@soil.agents.default_state
|
|
||||||
@soil.agents.state
|
|
||||||
def neutral(self):
|
|
||||||
r = random.random()
|
|
||||||
if self['has_tv'] and r < self.env['prob_tv_spread']:
|
|
||||||
return self.infected
|
|
||||||
return
|
|
||||||
|
|
||||||
@soil.agents.state
|
|
||||||
def infected(self):
|
|
||||||
prob_infect = self.env['prob_neighbor_spread']
|
|
||||||
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
|
|
||||||
r = random.random()
|
|
||||||
if r < prob_infect:
|
|
||||||
neighbor.state['id'] = self.infected.id
|
|
||||||
return
|
|
||||||
767
examples/newsspread/NewsSpread.ipynb
Normal file
138
examples/newsspread/NewsSpread.yml
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
default_state: {}
|
||||||
|
load_module: newsspread
|
||||||
|
environment_agents: []
|
||||||
|
environment_params:
|
||||||
|
prob_neighbor_spread: 0.0
|
||||||
|
prob_tv_spread: 0.01
|
||||||
|
interval: 1
|
||||||
|
max_time: 30
|
||||||
|
name: Sim_all_dumb
|
||||||
|
network_agents:
|
||||||
|
- agent_type: DumbViewer
|
||||||
|
state:
|
||||||
|
has_tv: false
|
||||||
|
weight: 1
|
||||||
|
- agent_type: DumbViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
weight: 1
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
num_trials: 50
|
||||||
|
---
|
||||||
|
default_state: {}
|
||||||
|
load_module: newsspread
|
||||||
|
environment_agents: []
|
||||||
|
environment_params:
|
||||||
|
prob_neighbor_spread: 0.0
|
||||||
|
prob_tv_spread: 0.01
|
||||||
|
interval: 1
|
||||||
|
max_time: 30
|
||||||
|
name: Sim_half_herd
|
||||||
|
network_agents:
|
||||||
|
- agent_type: DumbViewer
|
||||||
|
state:
|
||||||
|
has_tv: false
|
||||||
|
weight: 1
|
||||||
|
- agent_type: DumbViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
weight: 1
|
||||||
|
- agent_type: HerdViewer
|
||||||
|
state:
|
||||||
|
has_tv: false
|
||||||
|
weight: 1
|
||||||
|
- agent_type: HerdViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
weight: 1
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
num_trials: 50
|
||||||
|
---
|
||||||
|
default_state: {}
|
||||||
|
load_module: newsspread
|
||||||
|
environment_agents: []
|
||||||
|
environment_params:
|
||||||
|
prob_neighbor_spread: 0.0
|
||||||
|
prob_tv_spread: 0.01
|
||||||
|
interval: 1
|
||||||
|
max_time: 30
|
||||||
|
name: Sim_all_herd
|
||||||
|
network_agents:
|
||||||
|
- agent_type: HerdViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
id: neutral
|
||||||
|
weight: 1
|
||||||
|
- agent_type: HerdViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
id: neutral
|
||||||
|
weight: 1
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
num_trials: 50
|
||||||
|
---
|
||||||
|
default_state: {}
|
||||||
|
load_module: newsspread
|
||||||
|
environment_agents: []
|
||||||
|
environment_params:
|
||||||
|
prob_neighbor_spread: 0.0
|
||||||
|
prob_tv_spread: 0.01
|
||||||
|
prob_neighbor_cure: 0.1
|
||||||
|
interval: 1
|
||||||
|
max_time: 30
|
||||||
|
name: Sim_wise_herd
|
||||||
|
network_agents:
|
||||||
|
- agent_type: HerdViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
id: neutral
|
||||||
|
weight: 1
|
||||||
|
- agent_type: WiseViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
weight: 1
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
num_trials: 50
|
||||||
|
---
|
||||||
|
default_state: {}
|
||||||
|
load_module: newsspread
|
||||||
|
environment_agents: []
|
||||||
|
environment_params:
|
||||||
|
prob_neighbor_spread: 0.0
|
||||||
|
prob_tv_spread: 0.01
|
||||||
|
prob_neighbor_cure: 0.1
|
||||||
|
interval: 1
|
||||||
|
max_time: 30
|
||||||
|
name: Sim_all_wise
|
||||||
|
network_agents:
|
||||||
|
- agent_type: WiseViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
id: neutral
|
||||||
|
weight: 1
|
||||||
|
- agent_type: WiseViewer
|
||||||
|
state:
|
||||||
|
has_tv: true
|
||||||
|
weight: 1
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 500
|
||||||
|
m: 5
|
||||||
|
num_trials: 50
|
||||||
81
examples/newsspread/newsspread.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from soil.agents import FSM, state, default_state, prob
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class DumbViewer(FSM):
|
||||||
|
'''
|
||||||
|
A viewer that gets infected via TV (if it has one) and tries to infect
|
||||||
|
its neighbors once it's infected.
|
||||||
|
'''
|
||||||
|
defaults = {
|
||||||
|
'prob_neighbor_spread': 0.5,
|
||||||
|
'prob_tv_spread': 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def neutral(self):
|
||||||
|
if self['has_tv']:
|
||||||
|
if prob(self.env['prob_tv_spread']):
|
||||||
|
self.set_state(self.infected)
|
||||||
|
|
||||||
|
@state
|
||||||
|
def infected(self):
|
||||||
|
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
|
||||||
|
if prob(self.env['prob_neighbor_spread']):
|
||||||
|
neighbor.infect()
|
||||||
|
|
||||||
|
def infect(self):
|
||||||
|
self.set_state(self.infected)
|
||||||
|
|
||||||
|
|
||||||
|
class HerdViewer(DumbViewer):
|
||||||
|
'''
|
||||||
|
A viewer whose probability of infection depends on the state of its neighbors.
|
||||||
|
'''
|
||||||
|
|
||||||
|
level = logging.DEBUG
|
||||||
|
|
||||||
|
def infect(self):
|
||||||
|
infected = self.count_neighboring_agents(state_id=self.infected.id)
|
||||||
|
total = self.count_neighboring_agents()
|
||||||
|
prob_infect = self.env['prob_neighbor_spread'] * infected/total
|
||||||
|
self.debug('prob_infect', prob_infect)
|
||||||
|
if prob(prob_infect):
|
||||||
|
self.set_state(self.infected.id)
|
||||||
|
|
||||||
|
|
||||||
|
class WiseViewer(HerdViewer):
|
||||||
|
'''
|
||||||
|
A viewer that can change its mind.
|
||||||
|
'''
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'prob_neighbor_spread': 0.5,
|
||||||
|
'prob_neighbor_cure': 0.25,
|
||||||
|
'prob_tv_spread': 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
@state
|
||||||
|
def cured(self):
|
||||||
|
prob_cure = self.env['prob_neighbor_cure']
|
||||||
|
for neighbor in self.get_neighboring_agents(state_id=self.infected.id):
|
||||||
|
if prob(prob_cure):
|
||||||
|
try:
|
||||||
|
neighbor.cure()
|
||||||
|
except AttributeError:
|
||||||
|
self.debug('Viewer {} cannot be cured'.format(neighbor.id))
|
||||||
|
|
||||||
|
def cure(self):
|
||||||
|
self.set_state(self.cured.id)
|
||||||
|
|
||||||
|
@state
|
||||||
|
def infected(self):
|
||||||
|
cured = max(self.count_neighboring_agents(self.cured.id),
|
||||||
|
1.0)
|
||||||
|
infected = max(self.count_neighboring_agents(self.infected.id),
|
||||||
|
1.0)
|
||||||
|
prob_cure = self.env['prob_neighbor_cure'] * (cured/infected)
|
||||||
|
if prob(prob_cure):
|
||||||
|
return self.cure()
|
||||||
|
return self.set_state(super().infected)
|
||||||
120
examples/rabbits/rabbit_agents.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from soil.agents import FSM, state, default_state, BaseAgent
|
||||||
|
from enum import Enum
|
||||||
|
from random import random, choice
|
||||||
|
from itertools import islice
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class Genders(Enum):
|
||||||
|
male = 'male'
|
||||||
|
female = 'female'
|
||||||
|
|
||||||
|
|
||||||
|
class RabbitModel(FSM):
|
||||||
|
|
||||||
|
level = logging.INFO
|
||||||
|
|
||||||
|
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 = 5
|
||||||
|
|
||||||
|
@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)
|
||||||
|
self.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
|
||||||
|
self.debug('Pregnancy: {}'.format(self['pregnancy']))
|
||||||
|
if self['pregnancy'] >= self.gestation:
|
||||||
|
number_of_babies = int(8+4*random())
|
||||||
|
self.info('Having {} babies'.format(number_of_babies))
|
||||||
|
for i in range(number_of_babies):
|
||||||
|
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()
|
||||||
|
self.debug('A BABY IS COMING TO LIFE')
|
||||||
|
self.env['rabbits_alive'] = self.env.get('rabbits_alive', self.global_topology.number_of_nodes())+1
|
||||||
|
self.debug('Rabbits alive: {}'.format(self.env['rabbits_alive']))
|
||||||
|
self['offspring'] += 1
|
||||||
|
self.env.get_agent(self['mate'])['offspring'] += 1
|
||||||
|
del self['mate']
|
||||||
|
self['pregnancy'] = -1
|
||||||
|
return self.fertile
|
||||||
|
|
||||||
|
@state
|
||||||
|
def dead(self):
|
||||||
|
self.info('Agent {} is dying'.format(self.id))
|
||||||
|
if 'pregnancy' in self and self['pregnancy'] > -1:
|
||||||
|
self.info('A mother has died carrying a baby!!')
|
||||||
|
self.die()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class RandomAccident(BaseAgent):
|
||||||
|
|
||||||
|
level = logging.DEBUG
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
rabbits_total = self.global_topology.number_of_nodes()
|
||||||
|
rabbits_alive = self.env.get('rabbits_alive', rabbits_total)
|
||||||
|
prob_death = self.env.get('prob_death', 1e-100)*math.floor(math.log10(max(1, rabbits_alive)))
|
||||||
|
self.debug('Killing some rabbits with prob={}!'.format(prob_death))
|
||||||
|
for i in self.env.network_agents:
|
||||||
|
if i.state['id'] == i.dead.id:
|
||||||
|
continue
|
||||||
|
r = random()
|
||||||
|
if r < prob_death:
|
||||||
|
self.debug('I killed a rabbit: {}'.format(i.id))
|
||||||
|
rabbits_alive = self.env['rabbits_alive'] = rabbits_alive -1
|
||||||
|
self.log('Rabbits alive: {}'.format(self.env['rabbits_alive']))
|
||||||
|
i.set_state(i.dead)
|
||||||
|
self.log('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
|
||||||
|
if self.count_agents(state_id=RabbitModel.dead.id) == self.global_topology.number_of_nodes():
|
||||||
|
self.die()
|
||||||
23
examples/rabbits/rabbits.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
load_module: rabbit_agents
|
||||||
|
name: rabbits_example
|
||||||
|
max_time: 1200
|
||||||
|
interval: 1
|
||||||
|
seed: MySeed
|
||||||
|
agent_type: RabbitModel
|
||||||
|
environment_agents:
|
||||||
|
- agent_type: RandomAccident
|
||||||
|
environment_params:
|
||||||
|
prob_death: 0.001
|
||||||
|
default_state:
|
||||||
|
mating_prob: 0.01
|
||||||
|
topology:
|
||||||
|
nodes:
|
||||||
|
- id: 1
|
||||||
|
state:
|
||||||
|
gender: female
|
||||||
|
- id: 0
|
||||||
|
state:
|
||||||
|
gender: male
|
||||||
|
directed: true
|
||||||
|
links: []
|
||||||
@@ -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'
|
||||||
|
|||||||
23569
examples/tutorial/soil_tutorial.html
Normal file
1350
examples/tutorial/soil_tutorial.ipynb
Normal file
596
models_org.py
@@ -1,596 +0,0 @@
|
|||||||
from nxsim import BaseNetworkAgent
|
|
||||||
import numpy as np
|
|
||||||
import random
|
|
||||||
import settings
|
|
||||||
|
|
||||||
settings.init()
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# Variables initialization #
|
|
||||||
##############################
|
|
||||||
def init():
|
|
||||||
global networkStatus
|
|
||||||
networkStatus = {} # Dict that will contain the status of every agent in the network
|
|
||||||
|
|
||||||
sentimentCorrelationNodeArray=[]
|
|
||||||
for x in range(0, settings.number_of_nodes):
|
|
||||||
sentimentCorrelationNodeArray.append({'id':x})
|
|
||||||
# Initialize agent states. Let's assume everyone is normal.
|
|
||||||
init_states = [{'id': 0, } for _ in range(settings.number_of_nodes)] # add keys as as necessary, but "id" must always refer to that state category
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# Available models #
|
|
||||||
####################
|
|
||||||
|
|
||||||
class BaseBehaviour(BaseNetworkAgent):
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
self._attrs = {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attrs(self):
|
|
||||||
now = self.env.now
|
|
||||||
if now not in self._attrs:
|
|
||||||
self._attrs[now] = {}
|
|
||||||
return self._attrs[now]
|
|
||||||
|
|
||||||
@attrs.setter
|
|
||||||
def attrs(self, value):
|
|
||||||
self._attrs[self.env.now] = value
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while True:
|
|
||||||
self.step(self.env.now)
|
|
||||||
yield self.env.timeout(settings.timeout)
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
networkStatus['agent_%s'% self.id] = self.to_json()
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
final = {}
|
|
||||||
for stamp, attrs in self._attrs.items():
|
|
||||||
for a in attrs:
|
|
||||||
if a not in final:
|
|
||||||
final[a] = {}
|
|
||||||
final[a][stamp] = attrs[a]
|
|
||||||
return final
|
|
||||||
|
|
||||||
class ControlModelM2(BaseBehaviour):
|
|
||||||
#Init infected
|
|
||||||
init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}
|
|
||||||
init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}
|
|
||||||
|
|
||||||
# Init beacons
|
|
||||||
init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}
|
|
||||||
init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
|
|
||||||
self.prob_neutral_making_denier = np.random.normal(settings.prob_neutral_making_denier, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_infect = np.random.normal(settings.prob_infect, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_cured_healing_infected = np.random.normal(settings.prob_cured_healing_infected, settings.standard_variance)
|
|
||||||
self.prob_cured_vaccinate_neutral = np.random.normal(settings.prob_cured_vaccinate_neutral, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_vaccinated_healing_infected = np.random.normal(settings.prob_vaccinated_healing_infected, settings.standard_variance)
|
|
||||||
self.prob_vaccinated_vaccinate_neutral = np.random.normal(settings.prob_vaccinated_vaccinate_neutral, settings.standard_variance)
|
|
||||||
self.prob_generate_anti_rumor = np.random.normal(settings.prob_generate_anti_rumor, settings.standard_variance)
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
|
|
||||||
if self.state['id'] == 0: #Neutral
|
|
||||||
self.neutral_behaviour()
|
|
||||||
elif self.state['id'] == 1: #Infected
|
|
||||||
self.infected_behaviour()
|
|
||||||
elif self.state['id'] == 2: #Cured
|
|
||||||
self.cured_behaviour()
|
|
||||||
elif self.state['id'] == 3: #Vaccinated
|
|
||||||
self.vaccinated_behaviour()
|
|
||||||
elif self.state['id'] == 4: #Beacon-off
|
|
||||||
self.beacon_off_behaviour()
|
|
||||||
elif self.state['id'] == 5: #Beacon-on
|
|
||||||
self.beacon_on_behaviour()
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
|
|
||||||
def neutral_behaviour(self):
|
|
||||||
|
|
||||||
# Infected
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
if len(infected_neighbors)>0:
|
|
||||||
if random.random() < self.prob_neutral_making_denier:
|
|
||||||
self.state['id'] = 3 # Vaccinated making denier
|
|
||||||
|
|
||||||
def infected_behaviour(self):
|
|
||||||
|
|
||||||
# Neutral
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_infect:
|
|
||||||
neighbor.state['id'] = 1 # Infected
|
|
||||||
|
|
||||||
def cured_behaviour(self):
|
|
||||||
|
|
||||||
# Vaccinate
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
|
|
||||||
# Cure
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors:
|
|
||||||
if random.random() < self.prob_cured_healing_infected:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
def vaccinated_behaviour(self):
|
|
||||||
|
|
||||||
# Cure
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors:
|
|
||||||
if random.random() < self.prob_cured_healing_infected:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
# Vaccinate
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
|
|
||||||
# Generate anti-rumor
|
|
||||||
infected_neighbors_2 = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors_2:
|
|
||||||
if random.random() < self.prob_generate_anti_rumor:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
def beacon_off_behaviour(self):
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
if len(infected_neighbors) > 0:
|
|
||||||
self.state['id'] == 5 #Beacon on
|
|
||||||
|
|
||||||
def beacon_on_behaviour(self):
|
|
||||||
|
|
||||||
# Cure (M2 feature added)
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors:
|
|
||||||
if random.random() < self.prob_generate_anti_rumor:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
neutral_neighbors_infected = neighbor.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors_infected:
|
|
||||||
if random.random() < self.prob_generate_anti_rumor:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
infected_neighbors_infected = neighbor.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors_infected:
|
|
||||||
if random.random() < self.prob_generate_anti_rumor:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
# Vaccinate
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
|
|
||||||
|
|
||||||
class SpreadModelM2(BaseBehaviour):
|
|
||||||
init_states[random.randint(0,settings.number_of_nodes)] = {'id':1}
|
|
||||||
init_states[random.randint(0,settings.number_of_nodes)] = {'id':1}
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
|
|
||||||
self.prob_neutral_making_denier = np.random.normal(settings.prob_neutral_making_denier, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_infect = np.random.normal(settings.prob_infect, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_cured_healing_infected = np.random.normal(settings.prob_cured_healing_infected, settings.standard_variance)
|
|
||||||
self.prob_cured_vaccinate_neutral = np.random.normal(settings.prob_cured_vaccinate_neutral, settings.standard_variance)
|
|
||||||
|
|
||||||
self.prob_vaccinated_healing_infected = np.random.normal(settings.prob_vaccinated_healing_infected, settings.standard_variance)
|
|
||||||
self.prob_vaccinated_vaccinate_neutral = np.random.normal(settings.prob_vaccinated_vaccinate_neutral, settings.standard_variance)
|
|
||||||
self.prob_generate_anti_rumor = np.random.normal(settings.prob_generate_anti_rumor, settings.standard_variance)
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
|
|
||||||
if self.state['id'] == 0: #Neutral
|
|
||||||
self.neutral_behaviour()
|
|
||||||
elif self.state['id'] == 1: #Infected
|
|
||||||
self.infected_behaviour()
|
|
||||||
elif self.state['id'] == 2: #Cured
|
|
||||||
self.cured_behaviour()
|
|
||||||
elif self.state['id'] == 3: #Vaccinated
|
|
||||||
self.vaccinated_behaviour()
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
|
|
||||||
def neutral_behaviour(self):
|
|
||||||
|
|
||||||
# Infected
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
if len(infected_neighbors)>0:
|
|
||||||
if random.random() < self.prob_neutral_making_denier:
|
|
||||||
self.state['id'] = 3 # Vaccinated making denier
|
|
||||||
|
|
||||||
def infected_behaviour(self):
|
|
||||||
|
|
||||||
# Neutral
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_infect:
|
|
||||||
neighbor.state['id'] = 1 # Infected
|
|
||||||
|
|
||||||
def cured_behaviour(self):
|
|
||||||
|
|
||||||
# Vaccinate
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
|
|
||||||
# Cure
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors:
|
|
||||||
if random.random() < self.prob_cured_healing_infected:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
def vaccinated_behaviour(self):
|
|
||||||
|
|
||||||
# Cure
|
|
||||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors:
|
|
||||||
if random.random() < self.prob_cured_healing_infected:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
# Vaccinate
|
|
||||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
|
||||||
for neighbor in neutral_neighbors:
|
|
||||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
|
||||||
neighbor.state['id'] = 3 # Vaccinated
|
|
||||||
|
|
||||||
# Generate anti-rumor
|
|
||||||
infected_neighbors_2 = self.get_neighboring_agents(state_id=1)
|
|
||||||
for neighbor in infected_neighbors_2:
|
|
||||||
if random.random() < self.prob_generate_anti_rumor:
|
|
||||||
neighbor.state['id'] = 2 # Cured
|
|
||||||
|
|
||||||
|
|
||||||
class SISaModel(BaseBehaviour):
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
|
|
||||||
self.neutral_discontent_spon_prob = np.random.normal(settings.neutral_discontent_spon_prob, settings.standard_variance)
|
|
||||||
self.neutral_discontent_infected_prob = np.random.normal(settings.neutral_discontent_infected_prob,settings.standard_variance)
|
|
||||||
self.neutral_content_spon_prob = np.random.normal(settings.neutral_content_spon_prob,settings.standard_variance)
|
|
||||||
self.neutral_content_infected_prob = np.random.normal(settings.neutral_content_infected_prob,settings.standard_variance)
|
|
||||||
|
|
||||||
self.discontent_neutral = np.random.normal(settings.discontent_neutral,settings.standard_variance)
|
|
||||||
self.discontent_content = np.random.normal(settings.discontent_content,settings.variance_d_c)
|
|
||||||
|
|
||||||
self.content_discontent = np.random.normal(settings.content_discontent,settings.variance_c_d)
|
|
||||||
self.content_neutral = np.random.normal(settings.content_neutral,settings.standard_variance)
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
|
|
||||||
if self.state['id'] == 0:
|
|
||||||
self.neutral_behaviour()
|
|
||||||
if self.state['id'] == 1:
|
|
||||||
self.discontent_behaviour()
|
|
||||||
if self.state['id'] == 2:
|
|
||||||
self.content_behaviour()
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
|
|
||||||
def neutral_behaviour(self):
|
|
||||||
|
|
||||||
#Spontaneus effects
|
|
||||||
if random.random() < self.neutral_discontent_spon_prob:
|
|
||||||
self.state['id'] = 1
|
|
||||||
if random.random() < self.neutral_content_spon_prob:
|
|
||||||
self.state['id'] = 2
|
|
||||||
|
|
||||||
#Infected
|
|
||||||
discontent_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
if random.random() < len(discontent_neighbors)*self.neutral_discontent_infected_prob:
|
|
||||||
self.state['id'] = 1
|
|
||||||
content_neighbors = self.get_neighboring_agents(state_id=2)
|
|
||||||
if random.random() < len(content_neighbors)*self.neutral_content_infected_prob:
|
|
||||||
self.state['id'] = 2
|
|
||||||
|
|
||||||
def discontent_behaviour(self):
|
|
||||||
|
|
||||||
#Healing
|
|
||||||
if random.random() < self.discontent_neutral:
|
|
||||||
self.state['id'] = 0
|
|
||||||
|
|
||||||
#Superinfected
|
|
||||||
content_neighbors = self.get_neighboring_agents(state_id=2)
|
|
||||||
if random.random() < len(content_neighbors)*self.discontent_content:
|
|
||||||
self.state['id'] = 2
|
|
||||||
|
|
||||||
def content_behaviour(self):
|
|
||||||
|
|
||||||
#Healing
|
|
||||||
if random.random() < self.content_neutral:
|
|
||||||
self.state['id'] = 0
|
|
||||||
|
|
||||||
#Superinfected
|
|
||||||
discontent_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
if random.random() < len(discontent_neighbors)*self.content_discontent:
|
|
||||||
self.state['id'] = 1
|
|
||||||
|
|
||||||
|
|
||||||
class BigMarketModel(BaseBehaviour):
|
|
||||||
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
self.enterprises = settings.enterprises
|
|
||||||
self.type = ""
|
|
||||||
self.number_of_enterprises = len(settings.enterprises)
|
|
||||||
|
|
||||||
if self.id < self.number_of_enterprises: #Enterprises
|
|
||||||
self.state['id']=self.id
|
|
||||||
self.type="Enterprise"
|
|
||||||
self.tweet_probability = settings.tweet_probability_enterprises[self.id]
|
|
||||||
else: #normal users
|
|
||||||
self.state['id']=self.number_of_enterprises
|
|
||||||
self.type="User"
|
|
||||||
self.tweet_probability = settings.tweet_probability_users
|
|
||||||
self.tweet_relevant_probability = settings.tweet_relevant_probability
|
|
||||||
self.tweet_probability_about = settings.tweet_probability_about #List
|
|
||||||
self.sentiment_about = settings.sentiment_about #List
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
|
|
||||||
if(self.id < self.number_of_enterprises): # Ennterprise
|
|
||||||
self.enterpriseBehaviour()
|
|
||||||
else: # Usuario
|
|
||||||
self.userBehaviour()
|
|
||||||
for i in range(self.number_of_enterprises): # So that it never is set to 0 if there are not changes (logs)
|
|
||||||
self.attrs['sentiment_enterprise_%s'% self.enterprises[i]] = self.sentiment_about[i]
|
|
||||||
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
def enterpriseBehaviour(self):
|
|
||||||
|
|
||||||
if random.random()< self.tweet_probability: #Tweets
|
|
||||||
aware_neighbors = self.get_neighboring_agents(state_id=self.number_of_enterprises) #Nodes neighbour users
|
|
||||||
for x in aware_neighbors:
|
|
||||||
if random.uniform(0,10) < 5:
|
|
||||||
x.sentiment_about[self.id] += 0.1 #Increments for enterprise
|
|
||||||
else:
|
|
||||||
x.sentiment_about[self.id] -= 0.1 #Decrements for enterprise
|
|
||||||
|
|
||||||
# Establecemos limites
|
|
||||||
if x.sentiment_about[self.id] > 1:
|
|
||||||
x.sentiment_about[self.id] = 1
|
|
||||||
if x.sentiment_about[self.id]< -1:
|
|
||||||
x.sentiment_about[self.id] = -1
|
|
||||||
|
|
||||||
x.attrs['sentiment_enterprise_%s'% self.enterprises[self.id]] = x.sentiment_about[self.id]
|
|
||||||
|
|
||||||
|
|
||||||
def userBehaviour(self):
|
|
||||||
|
|
||||||
if random.random() < self.tweet_probability: #Tweets
|
|
||||||
if random.random() < self.tweet_relevant_probability: #Tweets something relevant
|
|
||||||
#Tweet probability per enterprise
|
|
||||||
for i in range(self.number_of_enterprises):
|
|
||||||
random_num = random.random()
|
|
||||||
if random_num < self.tweet_probability_about[i]:
|
|
||||||
#The condition is fulfilled, sentiments are evaluated towards that enterprise
|
|
||||||
if self.sentiment_about[i] < 0:
|
|
||||||
#NEGATIVO
|
|
||||||
self.userTweets("negative",i)
|
|
||||||
elif self.sentiment_about[i] == 0:
|
|
||||||
#NEUTRO
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
#POSITIVO
|
|
||||||
self.userTweets("positive",i)
|
|
||||||
|
|
||||||
def userTweets(self,sentiment,enterprise):
|
|
||||||
aware_neighbors = self.get_neighboring_agents(state_id=self.number_of_enterprises) #Nodes neighbours users
|
|
||||||
for x in aware_neighbors:
|
|
||||||
if sentiment == "positive":
|
|
||||||
x.sentiment_about[enterprise] +=0.003
|
|
||||||
elif sentiment == "negative":
|
|
||||||
x.sentiment_about[enterprise] -=0.003
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Establecemos limites
|
|
||||||
if x.sentiment_about[enterprise] > 1:
|
|
||||||
x.sentiment_about[enterprise] = 1
|
|
||||||
if x.sentiment_about[enterprise] < -1:
|
|
||||||
x.sentiment_about[enterprise] = -1
|
|
||||||
|
|
||||||
x.attrs['sentiment_enterprise_%s'% self.enterprises[enterprise]] = x.sentiment_about[enterprise]
|
|
||||||
|
|
||||||
class SentimentCorrelationModel(BaseBehaviour):
|
|
||||||
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
self.outside_effects_prob = settings.outside_effects_prob
|
|
||||||
self.anger_prob = settings.anger_prob
|
|
||||||
self.joy_prob = settings.joy_prob
|
|
||||||
self.sadness_prob = settings.sadness_prob
|
|
||||||
self.disgust_prob = settings.disgust_prob
|
|
||||||
self.time_awareness=[]
|
|
||||||
for i in range(4): #In this model we have 4 sentiments
|
|
||||||
self.time_awareness.append(0) #0-> Anger, 1-> joy, 2->sadness, 3 -> disgust
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
|
||||||
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
self.behaviour()
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
def behaviour(self):
|
|
||||||
|
|
||||||
angry_neighbors_1_time_step=[]
|
|
||||||
joyful_neighbors_1_time_step=[]
|
|
||||||
sad_neighbors_1_time_step=[]
|
|
||||||
disgusted_neighbors_1_time_step=[]
|
|
||||||
|
|
||||||
|
|
||||||
angry_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for x in angry_neighbors:
|
|
||||||
if x.time_awareness[0] > (self.env.now-500):
|
|
||||||
angry_neighbors_1_time_step.append(x)
|
|
||||||
num_neighbors_angry = len(angry_neighbors_1_time_step)
|
|
||||||
|
|
||||||
|
|
||||||
joyful_neighbors = self.get_neighboring_agents(state_id=2)
|
|
||||||
for x in joyful_neighbors:
|
|
||||||
if x.time_awareness[1] > (self.env.now-500):
|
|
||||||
joyful_neighbors_1_time_step.append(x)
|
|
||||||
num_neighbors_joyful = len(joyful_neighbors_1_time_step)
|
|
||||||
|
|
||||||
|
|
||||||
sad_neighbors = self.get_neighboring_agents(state_id=3)
|
|
||||||
for x in sad_neighbors:
|
|
||||||
if x.time_awareness[2] > (self.env.now-500):
|
|
||||||
sad_neighbors_1_time_step.append(x)
|
|
||||||
num_neighbors_sad = len(sad_neighbors_1_time_step)
|
|
||||||
|
|
||||||
|
|
||||||
disgusted_neighbors = self.get_neighboring_agents(state_id=4)
|
|
||||||
for x in disgusted_neighbors:
|
|
||||||
if x.time_awareness[3] > (self.env.now-500):
|
|
||||||
disgusted_neighbors_1_time_step.append(x)
|
|
||||||
num_neighbors_disgusted = len(disgusted_neighbors_1_time_step)
|
|
||||||
|
|
||||||
|
|
||||||
anger_prob= settings.anger_prob+(len(angry_neighbors_1_time_step)*settings.anger_prob)
|
|
||||||
joy_prob= settings.joy_prob+(len(joyful_neighbors_1_time_step)*settings.joy_prob)
|
|
||||||
sadness_prob = settings.sadness_prob+(len(sad_neighbors_1_time_step)*settings.sadness_prob)
|
|
||||||
disgust_prob = settings.disgust_prob+(len(disgusted_neighbors_1_time_step)*settings.disgust_prob)
|
|
||||||
outside_effects_prob= settings.outside_effects_prob
|
|
||||||
|
|
||||||
|
|
||||||
num = random.random()
|
|
||||||
|
|
||||||
|
|
||||||
if(num<outside_effects_prob):
|
|
||||||
self.state['id'] = random.randint(1,4)
|
|
||||||
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=self.state['id'] #It is stored when it has been infected for the dynamic network
|
|
||||||
self.time_awareness[self.state['id']-1] = self.env.now
|
|
||||||
self.attrs['sentiment'] = self.state['id']
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(num<anger_prob):
|
|
||||||
|
|
||||||
self.state['id'] = 1
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
|
||||||
self.time_awareness[self.state['id']-1] = self.env.now
|
|
||||||
elif (num<joy_prob+anger_prob and num>anger_prob):
|
|
||||||
|
|
||||||
self.state['id'] = 2
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=2
|
|
||||||
self.time_awareness[self.state['id']-1] = self.env.now
|
|
||||||
elif (num<sadness_prob+anger_prob+joy_prob and num>joy_prob+anger_prob):
|
|
||||||
|
|
||||||
|
|
||||||
self.state['id'] = 3
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=3
|
|
||||||
self.time_awareness[self.state['id']-1] = self.env.now
|
|
||||||
elif (num<disgust_prob+sadness_prob+anger_prob+joy_prob and num>sadness_prob+anger_prob+joy_prob):
|
|
||||||
|
|
||||||
|
|
||||||
self.state['id'] = 4
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=4
|
|
||||||
self.time_awareness[self.state['id']-1] = self.env.now
|
|
||||||
|
|
||||||
self.attrs['sentiment'] = self.state['id']
|
|
||||||
|
|
||||||
|
|
||||||
class BassModel(BaseBehaviour):
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
self.innovation_prob = settings.innovation_prob
|
|
||||||
self.imitation_prob = settings.imitation_prob
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
|
||||||
|
|
||||||
def step(self, now):
|
|
||||||
self.behaviour()
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
def behaviour(self):
|
|
||||||
#Outside effects
|
|
||||||
if random.random() < settings.innovation_prob:
|
|
||||||
if self.state['id'] == 0:
|
|
||||||
self.state['id'] = 1
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
return
|
|
||||||
|
|
||||||
#Imitation effects
|
|
||||||
if self.state['id'] == 0:
|
|
||||||
aware_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
num_neighbors_aware = len(aware_neighbors)
|
|
||||||
if random.random() < (settings.imitation_prob*num_neighbors_aware):
|
|
||||||
self.state['id'] = 1
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
|
||||||
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
|
|
||||||
|
|
||||||
class IndependentCascadeModel(BaseBehaviour):
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
|
||||||
self.innovation_prob = settings.innovation_prob
|
|
||||||
self.imitation_prob = settings.imitation_prob
|
|
||||||
self.time_awareness = 0
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
|
||||||
|
|
||||||
def step(self,now):
|
|
||||||
self.behaviour()
|
|
||||||
super().step(now)
|
|
||||||
|
|
||||||
def behaviour(self):
|
|
||||||
aware_neighbors_1_time_step=[]
|
|
||||||
#Outside effects
|
|
||||||
if random.random() < settings.innovation_prob:
|
|
||||||
if self.state['id'] == 0:
|
|
||||||
self.state['id'] = 1
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
|
||||||
self.time_awareness = self.env.now #To know when they have been infected
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
return
|
|
||||||
|
|
||||||
#Imitation effects
|
|
||||||
if self.state['id'] == 0:
|
|
||||||
aware_neighbors = self.get_neighboring_agents(state_id=1)
|
|
||||||
for x in aware_neighbors:
|
|
||||||
if x.time_awareness == (self.env.now-1):
|
|
||||||
aware_neighbors_1_time_step.append(x)
|
|
||||||
num_neighbors_aware = len(aware_neighbors_1_time_step)
|
|
||||||
if random.random() < (settings.imitation_prob*num_neighbors_aware):
|
|
||||||
self.state['id'] = 1
|
|
||||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.attrs['status'] = self.state['id']
|
|
||||||
return
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
nxsim
|
nxsim
|
||||||
simpy
|
simpy
|
||||||
networkx
|
networkx>=2.0
|
||||||
numpy
|
numpy
|
||||||
matplotlib
|
matplotlib
|
||||||
pyyaml
|
pyyaml
|
||||||
|
pandas
|
||||||
|
|||||||
40
setup.py
@@ -1,20 +1,21 @@
|
|||||||
import pip
|
import os
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
|
||||||
from pip.req import parse_requirements
|
|
||||||
from soil import __version__
|
|
||||||
|
|
||||||
try:
|
|
||||||
install_reqs = parse_requirements(
|
|
||||||
"requirements.txt", session=pip.download.PipSession())
|
|
||||||
test_reqs = parse_requirements(
|
|
||||||
"test-requirements.txt", session=pip.download.PipSession())
|
|
||||||
except AttributeError:
|
|
||||||
install_reqs = parse_requirements("requirements.txt")
|
|
||||||
test_reqs = parse_requirements("test-requirements.txt")
|
|
||||||
|
|
||||||
install_reqs = [str(ir.req) for ir in install_reqs]
|
with open(os.path.join('soil', 'VERSION')) as f:
|
||||||
test_reqs = [str(ir.req) for ir in test_reqs]
|
__version__ = f.readlines()[0].strip()
|
||||||
|
assert __version__
|
||||||
|
|
||||||
|
|
||||||
|
def parse_requirements(filename):
|
||||||
|
""" load requirements from a pip requirements file """
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
lineiter = list(line.strip() for line in f)
|
||||||
|
return [line for line in lineiter if line and not line.startswith("#")]
|
||||||
|
|
||||||
|
|
||||||
|
install_reqs = parse_requirements("requirements.txt")
|
||||||
|
test_reqs = parse_requirements("test-requirements.txt")
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
@@ -28,7 +29,16 @@ setup(
|
|||||||
download_url='https://github.com/gsi-upm/soil/archive/{}.tar.gz'.format(
|
download_url='https://github.com/gsi-upm/soil/archive/{}.tar.gz'.format(
|
||||||
__version__),
|
__version__),
|
||||||
keywords=['agent', 'social', 'simulator'],
|
keywords=['agent', 'social', 'simulator'],
|
||||||
classifiers=[],
|
classifiers=[
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Environment :: Console',
|
||||||
|
'Intended Audience :: End Users/Desktop',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: Apache Software License',
|
||||||
|
'Operating System :: MacOS :: MacOS X',
|
||||||
|
'Operating System :: Microsoft :: Windows',
|
||||||
|
'Operating System :: POSIX',
|
||||||
|
'Programming Language :: Python :: 3'],
|
||||||
install_requires=install_reqs,
|
install_requires=install_reqs,
|
||||||
tests_require=test_reqs,
|
tests_require=test_reqs,
|
||||||
setup_requires=['pytest-runner', ],
|
setup_requires=['pytest-runner', ],
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
name: ControlModelM2_sim
|
|
||||||
max_time: 50
|
|
||||||
num_trials: 1
|
|
||||||
timeout: 2
|
|
||||||
network_params:
|
|
||||||
generator: barabasi_albert_graph
|
|
||||||
n: 100
|
|
||||||
m: 2
|
|
||||||
agent_distribution:
|
|
||||||
- agent_type: ControlModelM2
|
|
||||||
weight: 0.1
|
|
||||||
state:
|
|
||||||
id: 1
|
|
||||||
- agent_type: ControlModelM2
|
|
||||||
weight: 0.9
|
|
||||||
state:
|
|
||||||
id: 0
|
|
||||||
environment_params:
|
|
||||||
prob_neutral_making_denier: 0.035
|
|
||||||
prob_infect: 0.075
|
|
||||||
prob_cured_healing_infected: 0.035
|
|
||||||
prob_cured_vaccinate_neutral: 0.035
|
|
||||||
prob_vaccinated_healing_infected: 0.035
|
|
||||||
prob_vaccinated_vaccinate_neutral: 0.035
|
|
||||||
prob_generate_anti_rumor: 0.035
|
|
||||||
standard_variance: 0.055
|
|
||||||
---
|
|
||||||
name: SISA_sm
|
|
||||||
max_time: 50
|
|
||||||
num_trials: 2
|
|
||||||
timeout: 2
|
|
||||||
network_params:
|
|
||||||
generator: erdos_renyi_graph
|
|
||||||
n: 10000
|
|
||||||
p: 0.05
|
|
||||||
#other_agents:
|
|
||||||
# - agent_type: DrawingAgent
|
|
||||||
agent_distribution:
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: content
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: neutral
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: discontent
|
|
||||||
environment_params:
|
|
||||||
neutral_discontent_spon_prob: 0.04
|
|
||||||
neutral_discontent_infected_prob: 0.04
|
|
||||||
neutral_content_spon_prob: 0.18
|
|
||||||
neutral_content_infected_prob: 0.02
|
|
||||||
discontent_neutral: 0.13
|
|
||||||
discontent_content: 0.07
|
|
||||||
variance_d_c: 0.02
|
|
||||||
content_discontent: 0.009
|
|
||||||
variance_c_d: 0.003
|
|
||||||
content_neutral: 0.088
|
|
||||||
standard_variance: 0.055
|
|
||||||
1
soil/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.11.1
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import pdb
|
||||||
|
import logging
|
||||||
|
|
||||||
__version__ = "0.9.2"
|
from .version import __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
basestring
|
basestring
|
||||||
except NameError:
|
except NameError:
|
||||||
basestring = str
|
basestring = str
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
|
||||||
from . import agents
|
from . import agents
|
||||||
from . import simulation
|
from . import simulation
|
||||||
from . import environment
|
from . import environment
|
||||||
from . import utils
|
from . import utils
|
||||||
from . import settings
|
from . import analysis
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -27,6 +31,18 @@ def main():
|
|||||||
help='python module containing the simulation configuration.')
|
help='python module containing the simulation configuration.')
|
||||||
parser.add_argument('--module', '-m', type=str,
|
parser.add_argument('--module', '-m', type=str,
|
||||||
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',
|
||||||
|
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('--graph', '-g', action='store_true',
|
||||||
|
help='Dump GEXF graph. Defaults to false.')
|
||||||
|
parser.add_argument('--csv', action='store_true',
|
||||||
|
help='Dump history in CSV format. Defaults to false.')
|
||||||
|
parser.add_argument('--output', '-o', type=str, default="soil_output",
|
||||||
|
help='folder to write results to. It defaults to the current directory.')
|
||||||
|
parser.add_argument('--synchronous', action='store_true',
|
||||||
|
help='Run trials serially and synchronously instead of in parallel. Defaults to false.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -34,8 +50,25 @@ 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))
|
logging.info('Loading config file: {}'.format(args.file, args.output))
|
||||||
simulation.run_from_config(args.file)
|
|
||||||
|
try:
|
||||||
|
dump = []
|
||||||
|
if not args.dry_run:
|
||||||
|
if args.csv:
|
||||||
|
dump.append('csv')
|
||||||
|
if args.graph:
|
||||||
|
dump.append('gexf')
|
||||||
|
simulation.run_from_config(args.file,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
dump=dump,
|
||||||
|
parallel=(not args.synchronous and not args.pdb),
|
||||||
|
results_dir=args.output)
|
||||||
|
except Exception as ex:
|
||||||
|
if args.pdb:
|
||||||
|
pdb.post_mortem()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
4
soil/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from . import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import nxsim
|
|
||||||
from collections import OrderedDict
|
|
||||||
from copy import deepcopy
|
|
||||||
import json
|
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAgent(nxsim.BaseAgent):
|
|
||||||
"""
|
|
||||||
A special simpy BaseAgent that keeps track of its state history.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self._history = OrderedDict()
|
|
||||||
self._neighbors = None
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._history[None] = deepcopy(self.state)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def now(self):
|
|
||||||
try:
|
|
||||||
return self.env.now
|
|
||||||
except AttributeError:
|
|
||||||
# No environment
|
|
||||||
return None
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while True:
|
|
||||||
res = self.step()
|
|
||||||
self._history[self.env.now] = deepcopy(self.state)
|
|
||||||
yield res or self.env.timeout(self.env.interval)
|
|
||||||
|
|
||||||
def step(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return json.dumps(self._history)
|
|
||||||
|
|
||||||
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
|
||||||
|
|
||||||
def count_agents(self, state_id=None, limit_neighbors=False):
|
|
||||||
if limit_neighbors:
|
|
||||||
agents = self.global_topology.neighbors(self.id)
|
|
||||||
else:
|
|
||||||
agents = self.global_topology.nodes()
|
|
||||||
count = 0
|
|
||||||
for agent in agents:
|
|
||||||
if state_id and state_id != self.global_topology.node[agent]['agent'].state['id']:
|
|
||||||
continue
|
|
||||||
count += 1
|
|
||||||
return count
|
|
||||||
|
|
||||||
def count_neighboring_agents(self, state_id=None):
|
|
||||||
return self.count_agents(state_id, limit_neighbors=True)
|
|
||||||
|
|
||||||
|
|
||||||
def state(func):
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def func_wrapper(self):
|
|
||||||
when = None
|
|
||||||
next_state = func(self)
|
|
||||||
try:
|
|
||||||
next_state, when = next_state
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
if next_state:
|
|
||||||
try:
|
|
||||||
self.state['id'] = next_state.id
|
|
||||||
except AttributeError:
|
|
||||||
raise NotImplemented('State id %s is not valid.' % next_state)
|
|
||||||
return when
|
|
||||||
|
|
||||||
func_wrapper.id = func.__name__
|
|
||||||
func_wrapper.is_default = False
|
|
||||||
return func_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def default_state(func):
|
|
||||||
func.is_default = True
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
class MetaFSM(type):
|
|
||||||
def __init__(cls, name, bases, nmspc):
|
|
||||||
super(MetaFSM, cls).__init__(name, bases, nmspc)
|
|
||||||
states = {}
|
|
||||||
# Re-use states from inherited classes
|
|
||||||
default_state = None
|
|
||||||
for i in bases:
|
|
||||||
if isinstance(i, MetaFSM):
|
|
||||||
for state_id, state in i.states.items():
|
|
||||||
if state.is_default:
|
|
||||||
default_state = state
|
|
||||||
states[state_id] = state
|
|
||||||
|
|
||||||
# Add new states
|
|
||||||
for name, func in nmspc.items():
|
|
||||||
if hasattr(func, 'id'):
|
|
||||||
if func.is_default:
|
|
||||||
default_state = func
|
|
||||||
states[func.id] = func
|
|
||||||
cls.default_state = default_state
|
|
||||||
cls.states = states
|
|
||||||
|
|
||||||
|
|
||||||
class FSM(BaseAgent, metaclass=MetaFSM):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(FSM, self).__init__(*args, **kwargs)
|
|
||||||
if 'id' not in self.state:
|
|
||||||
self.state['id'] = self.default_state.id
|
|
||||||
|
|
||||||
def step(self):
|
|
||||||
if 'id' in self.state:
|
|
||||||
next_state = self.state['id']
|
|
||||||
elif self.default_state:
|
|
||||||
next_state = self.default_state.id
|
|
||||||
else:
|
|
||||||
raise Exception('{} has no valid state id or default state'.format(self))
|
|
||||||
if next_state not in self.states:
|
|
||||||
raise Exception('{} is not a valid id for {}'.format(next_state, self))
|
|
||||||
self.states[next_state](self)
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class BassModel(NetworkAgent):
|
class BassModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
innovation_prob
|
innovation_prob
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class BigMarketModel(NetworkAgent):
|
class BigMarketModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
Names:
|
Names:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class CounterModel(NetworkAgent):
|
class CounterModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||||
in each step and adds it to its state.
|
in each step and adds it to its state.
|
||||||
@@ -9,14 +9,14 @@ class CounterModel(NetworkAgent):
|
|||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
# Outside effects
|
# Outside effects
|
||||||
total = len(self.get_all_agents())
|
total = len(list(self.get_all_agents()))
|
||||||
neighbors = len(self.get_neighboring_agents())
|
neighbors = len(list(self.get_neighboring_agents()))
|
||||||
self.state['times'] = self.state.get('times', 0) + 1
|
self['times'] = self.get('times', 0) + 1
|
||||||
self.state['neighbors'] = neighbors
|
self['neighbors'] = neighbors
|
||||||
self.state['total'] = total
|
self['total'] = total
|
||||||
|
|
||||||
|
|
||||||
class AggregatedCounter(NetworkAgent):
|
class AggregatedCounter(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||||
in each step and adds it to its state.
|
in each step and adds it to its state.
|
||||||
@@ -24,8 +24,9 @@ class AggregatedCounter(NetworkAgent):
|
|||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
# Outside effects
|
# Outside effects
|
||||||
total = len(self.get_all_agents())
|
total = len(list(self.get_all_agents()))
|
||||||
neighbors = len(self.get_neighboring_agents())
|
neighbors = len(list(self.get_neighboring_agents()))
|
||||||
self.state['times'] = self.state.get('times', 0) + 1
|
self['times'] = self.get('times', 0) + 1
|
||||||
self.state['neighbors'] = self.state.get('neighbors', 0) + neighbors
|
self['neighbors'] = self.get('neighbors', 0) + neighbors
|
||||||
self.state['total'] = self.state.get('total', 0) + total
|
self['total'] = total = self.get('total', 0) + total
|
||||||
|
self.debug('Running for step: {}. Total: {}'.format(self.now, total))
|
||||||
|
|||||||
@@ -15,4 +15,4 @@ class DrawingAgent(BaseAgent):
|
|||||||
# Outside effects
|
# Outside effects
|
||||||
f = plt.figure()
|
f = plt.figure()
|
||||||
nx.draw(self.env.G, node_size=10, width=0.2, pos=nx.spring_layout(self.env.G, scale=100), ax=f.add_subplot(111))
|
nx.draw(self.env.G, node_size=10, width=0.2, pos=nx.spring_layout(self.env.G, scale=100), ax=f.add_subplot(111))
|
||||||
f.savefig(os.path.join(self.env.sim().dir_path, "graph-"+str(self.env.now)+".png"))
|
f.savefig(os.path.join(self.env.get_path(), "graph-"+str(self.env.now)+".png"))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import random
|
import random
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class SpreadModelM2(NetworkAgent):
|
class SpreadModelM2(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
prob_neutral_making_denier
|
prob_neutral_making_denier
|
||||||
@@ -104,7 +104,7 @@ class SpreadModelM2(NetworkAgent):
|
|||||||
neighbor.state['id'] = 2 # Cured
|
neighbor.state['id'] = 2 # Cured
|
||||||
|
|
||||||
|
|
||||||
class ControlModelM2(NetworkAgent):
|
class ControlModelM2(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
prob_neutral_making_denier
|
prob_neutral_making_denier
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
from . import NetworkAgent
|
from . import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
class SentimentCorrelationModel(NetworkAgent):
|
class SentimentCorrelationModel(BaseAgent):
|
||||||
"""
|
"""
|
||||||
Settings:
|
Settings:
|
||||||
outside_effects_prob
|
outside_effects_prob
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
|
|
||||||
|
|
||||||
import nxsim
|
import nxsim
|
||||||
|
import logging
|
||||||
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
|
||||||
|
|
||||||
|
from .. import utils, history
|
||||||
|
|
||||||
agent_types = {}
|
agent_types = {}
|
||||||
|
|
||||||
@@ -27,28 +30,72 @@ 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, environment=None, agent_id=None, state=None,
|
||||||
|
name='network_process', interval=None, **state_params):
|
||||||
|
# Check for REQUIRED arguments
|
||||||
|
assert environment is not None, TypeError('__init__ missing 1 required keyword argument: \'environment\'. '
|
||||||
|
'Cannot be NoneType.')
|
||||||
|
# Initialize agent parameters
|
||||||
|
self.id = agent_id
|
||||||
|
self.name = name
|
||||||
|
self.state_params = state_params
|
||||||
|
|
||||||
|
# Global parameters
|
||||||
|
self.global_topology = environment.G
|
||||||
|
self.environment_params = environment.environment_params
|
||||||
|
|
||||||
|
# Register agent to environment
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
self._neighbors = None
|
self._neighbors = None
|
||||||
super().__init__(*args, **kwargs)
|
self.alive = True
|
||||||
|
real_state = deepcopy(self.defaults)
|
||||||
|
real_state.update(state or {})
|
||||||
|
self._state = real_state
|
||||||
|
self.interval = interval
|
||||||
|
|
||||||
|
if not hasattr(self, 'level'):
|
||||||
|
self.level = logging.DEBUG
|
||||||
|
self.logger = logging.getLogger('{}-Agent-{}'.format(self.env.name,
|
||||||
|
self.id))
|
||||||
|
self.logger.setLevel(self.level)
|
||||||
|
|
||||||
|
# initialize every time an instance of the agent is created
|
||||||
|
self.action = self.env.process(self.run())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state(self, value):
|
||||||
|
for k, v in value.items():
|
||||||
|
self[k] = v
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, tuple):
|
if isinstance(key, tuple):
|
||||||
k, t_step = key
|
key, t_step = key
|
||||||
if k is not None:
|
k = history.Key(key=key, t_step=t_step, agent_id=self.id)
|
||||||
if t_step is not None:
|
return self.env[k]
|
||||||
return self._history[t_step][k]
|
return self.state.get(key, None)
|
||||||
else:
|
|
||||||
return {tt: tv.get(k, None) for tt, tv in self._history.items()}
|
def __delitem__(self, key):
|
||||||
else:
|
self.state[key] = None
|
||||||
return self._history[t_step]
|
|
||||||
return self.state[key]
|
def __contains__(self, key):
|
||||||
|
return key in self.state
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
self.state[key] = value
|
self.state[key] = value
|
||||||
|
k = history.Key(t_step=self.now,
|
||||||
|
agent_id=self.id,
|
||||||
|
key=key)
|
||||||
|
self.env[k] = 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,18 +106,26 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
if self.interval is not None:
|
||||||
|
interval = self.interval
|
||||||
|
elif 'interval' in self:
|
||||||
|
interval = self['interval']
|
||||||
|
else:
|
||||||
|
interval = self.env.interval
|
||||||
|
while self.alive:
|
||||||
res = self.step()
|
res = self.step()
|
||||||
yield res or self.env.timeout(self.env.interval)
|
yield res or self.env.timeout(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.state)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -79,30 +134,69 @@ class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
|||||||
agents = self.global_topology.nodes()
|
agents = self.global_topology.nodes()
|
||||||
count = 0
|
count = 0
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
if state_id and state_id != self.global_topology.node[agent]['agent'].state['id']:
|
if state_id and state_id != self.global_topology.node[agent]['agent']['id']:
|
||||||
continue
|
continue
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
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 len(super().get_agents(state_id, limit_neighbors=True))
|
||||||
|
|
||||||
|
def get_agents(self, state_id=None, limit_neighbors=False, iterator=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
|
||||||
|
|
||||||
|
f = filter(matches_all, agents)
|
||||||
|
if iterator:
|
||||||
|
return f
|
||||||
|
return list(f)
|
||||||
|
|
||||||
|
def log(self, message, *args, level=logging.INFO, **kwargs):
|
||||||
|
message = message + " ".join(str(i) for i in args)
|
||||||
|
message = "\t@{:>5}:\t{}".format(self.now, message)
|
||||||
|
for k, v in kwargs:
|
||||||
|
message += " {k}={v} ".format(k, v)
|
||||||
|
extra = {}
|
||||||
|
extra['now'] = self.now
|
||||||
|
extra['id'] = self.id
|
||||||
|
return self.logger.log(level, message, extra=extra)
|
||||||
|
|
||||||
|
def debug(self, *args, **kwargs):
|
||||||
|
return self.log(*args, level=logging.DEBUG, **kwargs)
|
||||||
|
|
||||||
|
def info(self, *args, **kwargs):
|
||||||
|
return self.log(*args, level=logging.INFO, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def state(func):
|
def state(func):
|
||||||
|
'''
|
||||||
|
A state function should return either a state id, or a tuple (state_id, when)
|
||||||
|
The default value for state_id is the current state id.
|
||||||
|
The default value for when is the interval defined in the nevironment.
|
||||||
|
'''
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def func_wrapper(self):
|
def func_wrapper(self):
|
||||||
when = None
|
|
||||||
next_state = func(self)
|
next_state = func(self)
|
||||||
|
when = None
|
||||||
|
if next_state is None:
|
||||||
|
return when
|
||||||
try:
|
try:
|
||||||
next_state, when = next_state
|
next_state, when = next_state
|
||||||
except TypeError:
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
if next_state:
|
if next_state:
|
||||||
try:
|
self.set_state(next_state)
|
||||||
self.state['id'] = next_state.id
|
|
||||||
except AttributeError:
|
|
||||||
raise NotImplemented('State id %s is not valid.' % next_state)
|
|
||||||
return when
|
return when
|
||||||
|
|
||||||
func_wrapper.id = func.__name__
|
func_wrapper.id = func.__name__
|
||||||
@@ -142,11 +236,13 @@ class FSM(BaseAgent, metaclass=MetaFSM):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(FSM, self).__init__(*args, **kwargs)
|
super(FSM, self).__init__(*args, **kwargs)
|
||||||
if 'id' not in self.state:
|
if 'id' not in self.state:
|
||||||
self.state['id'] = self.default_state.id
|
if not self.default_state:
|
||||||
|
raise ValueError('No default state specified for {}'.format(self.id))
|
||||||
|
self['id'] = self.default_state.id
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
if 'id' in self.state:
|
if 'id' in self.state:
|
||||||
next_state = self.state['id']
|
next_state = self['id']
|
||||||
elif self.default_state:
|
elif self.default_state:
|
||||||
next_state = self.default_state.id
|
next_state = self.default_state.id
|
||||||
else:
|
else:
|
||||||
@@ -155,6 +251,123 @@ 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['id'] = state
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def prob(prob=1):
|
||||||
|
'''
|
||||||
|
A true/False uniform distribution with a given probability.
|
||||||
|
To be used like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
if prob(0.3):
|
||||||
|
do_something()
|
||||||
|
|
||||||
|
'''
|
||||||
|
r = random.random()
|
||||||
|
return r < prob
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_distribution(network_agents=None,
|
||||||
|
agent_type=None):
|
||||||
|
'''
|
||||||
|
Calculate the threshold values (thresholds for a uniform distribution)
|
||||||
|
of an agent distribution given the weights of each agent type.
|
||||||
|
|
||||||
|
The input has this form: ::
|
||||||
|
|
||||||
|
[
|
||||||
|
{'agent_type': 'agent_type_1',
|
||||||
|
'weight': 0.2,
|
||||||
|
'state': {
|
||||||
|
'id': 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{'agent_type': 'agent_type_2',
|
||||||
|
'weight': 0.8,
|
||||||
|
'state': {
|
||||||
|
'id': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
In this example, 20% of the nodes will be marked as type
|
||||||
|
'agent_type_1'.
|
||||||
|
'''
|
||||||
|
if network_agents:
|
||||||
|
network_agents = deepcopy(network_agents)
|
||||||
|
elif agent_type:
|
||||||
|
network_agents = [{'agent_type': agent_type}]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Calculate the thresholds
|
||||||
|
total = sum(x.get('weight', 1) for x in network_agents)
|
||||||
|
acc = 0
|
||||||
|
for v in network_agents:
|
||||||
|
upper = acc + (v.get('weight', 1)/total)
|
||||||
|
v['threshold'] = [acc, upper]
|
||||||
|
acc = upper
|
||||||
|
return network_agents
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_distribution(network_agents):
|
||||||
|
d = _convert_agent_types(network_agents,
|
||||||
|
to_string=True)
|
||||||
|
'''
|
||||||
|
When serializing an agent distribution, remove the thresholds, in order
|
||||||
|
to avoid cluttering the YAML definition file.
|
||||||
|
'''
|
||||||
|
for v in d:
|
||||||
|
if 'threshold' in v:
|
||||||
|
del v['threshold']
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_states(states, topology):
|
||||||
|
'''Validate states to avoid ignoring states during initialization'''
|
||||||
|
states = states or []
|
||||||
|
if isinstance(states, dict):
|
||||||
|
for x in states:
|
||||||
|
assert x in topology.node
|
||||||
|
else:
|
||||||
|
assert len(states) <= len(topology)
|
||||||
|
return states
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_agent_types(ind, to_string=False):
|
||||||
|
'''Convenience method to allow specifying agents by class or class name.'''
|
||||||
|
d = deepcopy(ind)
|
||||||
|
for v in d:
|
||||||
|
agent_type = v['agent_type']
|
||||||
|
if to_string and not isinstance(agent_type, str):
|
||||||
|
v['agent_type'] = str(agent_type.__name__)
|
||||||
|
elif not to_string and isinstance(agent_type, str):
|
||||||
|
v['agent_type'] = agent_types[agent_type]
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_from_distribution(distribution, value=-1):
|
||||||
|
"""Used in the initialization of agents given an agent distribution."""
|
||||||
|
if value < 0:
|
||||||
|
value = random.random()
|
||||||
|
for d in distribution:
|
||||||
|
threshold = d['threshold']
|
||||||
|
if value >= threshold[0] and value < threshold[1]:
|
||||||
|
state = {}
|
||||||
|
if 'state' in d:
|
||||||
|
state = deepcopy(d['state'])
|
||||||
|
return d['agent_type'], state
|
||||||
|
|
||||||
|
raise Exception('Distribution for value {} not found in: {}'.format(value, distribution))
|
||||||
|
|
||||||
|
|
||||||
from .BassModel import *
|
from .BassModel import *
|
||||||
from .BigMarketModel import *
|
from .BigMarketModel import *
|
||||||
|
|||||||
161
soil/analysis.py
@@ -4,20 +4,163 @@ import glob
|
|||||||
import yaml
|
import yaml
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
||||||
|
from . import utils, history
|
||||||
|
|
||||||
def get_data(pattern, process=True, attributes=None):
|
|
||||||
|
def read_data(*args, group=False, **kwargs):
|
||||||
|
iterable = _read_data(*args, **kwargs)
|
||||||
|
if group:
|
||||||
|
return group_trials(iterable)
|
||||||
|
else:
|
||||||
|
return list(iterable)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_data(pattern, *args, from_csv=False, process_args=None, **kwargs):
|
||||||
|
if not process_args:
|
||||||
|
process_args = {}
|
||||||
for folder in glob.glob(pattern):
|
for folder in glob.glob(pattern):
|
||||||
config_file = glob.glob(join(folder, '*.yml'))[0]
|
config_file = glob.glob(join(folder, '*.yml'))[0]
|
||||||
config = yaml.load(open(config_file))
|
config = yaml.load(open(config_file))
|
||||||
for trial_data in sorted(glob.glob(join(folder, '*.environment.csv'))):
|
df = None
|
||||||
df = pd.read_csv(trial_data)
|
if from_csv:
|
||||||
if process:
|
for trial_data in sorted(glob.glob(join(folder,
|
||||||
if attributes is not None:
|
'*.environment.csv'))):
|
||||||
df = df[df['attribute'].isin(attributes)]
|
df = read_csv(trial_data, **kwargs)
|
||||||
df = df.pivot_table(values='attribute', index='tstep', columns=['value'], aggfunc='count').fillna(0)
|
yield config_file, df, config
|
||||||
yield config_file, df, config
|
else:
|
||||||
|
for trial_data in sorted(glob.glob(join(folder, '*.db.sqlite'))):
|
||||||
|
df = read_sql(trial_data, **kwargs)
|
||||||
|
yield config_file, df, config
|
||||||
|
|
||||||
|
|
||||||
|
def read_sql(db, *args, **kwargs):
|
||||||
|
h = history.History(db, backup=False)
|
||||||
|
df = h.read_sql(*args, **kwargs)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def read_csv(filename, keys=None, convert_types=False, **kwargs):
|
||||||
|
'''
|
||||||
|
Read a CSV in canonical form: ::
|
||||||
|
|
||||||
|
<agent_id, t_step, key, value, value_type>
|
||||||
|
|
||||||
|
'''
|
||||||
|
df = pd.read_csv(filename)
|
||||||
|
if convert_types:
|
||||||
|
df = convert_types_slow(df)
|
||||||
|
if keys:
|
||||||
|
df = df[df['key'].isin(keys)]
|
||||||
|
df = process_one(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def convert_row(row):
|
||||||
|
row['value'] = utils.convert(row['value'], row['value_type'])
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def convert_types_slow(df):
|
||||||
|
'''This is a slow operation.'''
|
||||||
|
dtypes = get_types(df)
|
||||||
|
for k, v in dtypes.items():
|
||||||
|
t = df[df['key']==k]
|
||||||
|
t['value'] = t['value'].astype(v)
|
||||||
|
df = df.apply(convert_row, axis=1)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def split_df(df):
|
||||||
|
'''
|
||||||
|
Split a dataframe in two dataframes: one with the history of agents,
|
||||||
|
and one with the environment history
|
||||||
|
'''
|
||||||
|
envmask = (df['agent_id'] == 'env')
|
||||||
|
n_env = envmask.sum()
|
||||||
|
if n_env == len(df):
|
||||||
|
return df, None
|
||||||
|
elif n_env == 0:
|
||||||
|
return None, df
|
||||||
|
agents, env = [x for _, x in df.groupby(envmask)]
|
||||||
|
return env, agents
|
||||||
|
|
||||||
|
|
||||||
|
def process(df, **kwargs):
|
||||||
|
'''
|
||||||
|
Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into
|
||||||
|
two dataframes with a column per key: one with the history of the agents, and one for the
|
||||||
|
history of the environment.
|
||||||
|
'''
|
||||||
|
env, agents = split_df(df)
|
||||||
|
return process_one(env, **kwargs), process_one(agents, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def get_types(df):
|
||||||
|
dtypes = df.groupby(by=['key'])['value_type'].unique()
|
||||||
|
return {k:v[0] for k,v in dtypes.iteritems()}
|
||||||
|
|
||||||
|
|
||||||
|
def process_one(df, *keys, columns=['key', 'agent_id'], values='value',
|
||||||
|
fill=True, index=['t_step',],
|
||||||
|
aggfunc='first', **kwargs):
|
||||||
|
'''
|
||||||
|
Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into
|
||||||
|
a dataframe with a column per key
|
||||||
|
'''
|
||||||
|
if df is None:
|
||||||
|
return df
|
||||||
|
if keys:
|
||||||
|
df = df[df['key'].isin(keys)]
|
||||||
|
|
||||||
|
df = df.pivot_table(values=values, index=index, columns=columns,
|
||||||
|
aggfunc=aggfunc, **kwargs)
|
||||||
|
if fill:
|
||||||
|
df = fillna(df)
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def get_count(df, *keys):
|
||||||
|
if keys:
|
||||||
|
df = df[list(keys)]
|
||||||
|
counts = pd.DataFrame()
|
||||||
|
for key in df.columns.levels[0]:
|
||||||
|
g = df[key].apply(pd.Series.value_counts, axis=1).fillna(0)
|
||||||
|
for value, series in g.iteritems():
|
||||||
|
counts[key, value] = series
|
||||||
|
counts.columns = pd.MultiIndex.from_tuples(counts.columns)
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
def get_value(df, *keys, aggfunc='sum'):
|
||||||
|
if keys:
|
||||||
|
df = df[list(keys)]
|
||||||
|
return df.groupby(axis=1, level=0).agg(aggfunc, axis=1)
|
||||||
|
|
||||||
|
|
||||||
def plot_all(*args, **kwargs):
|
def plot_all(*args, **kwargs):
|
||||||
for config_file, df, config in sorted(get_data(*args, **kwargs)):
|
'''
|
||||||
|
Read all the trial data and plot the result of applying a function on them.
|
||||||
|
'''
|
||||||
|
dfs = do_all(*args, **kwargs)
|
||||||
|
ps = []
|
||||||
|
for line in dfs:
|
||||||
|
f, df, config = line
|
||||||
df.plot(title=config['name'])
|
df.plot(title=config['name'])
|
||||||
|
ps.append(df)
|
||||||
|
return ps
|
||||||
|
|
||||||
|
def do_all(pattern, func, *keys, include_env=False, **kwargs):
|
||||||
|
for config_file, df, config in read_data(pattern, keys=keys):
|
||||||
|
p = func(df, *keys, **kwargs)
|
||||||
|
p.plot(title=config['name'])
|
||||||
|
yield config_file, p, config
|
||||||
|
|
||||||
|
|
||||||
|
def group_trials(trials, aggfunc=['mean', 'min', 'max', 'std']):
|
||||||
|
trials = list(trials)
|
||||||
|
trials = list(map(lambda x: x[1] if isinstance(x, tuple) else x, trials))
|
||||||
|
return pd.concat(trials).groupby(level=0).agg(aggfunc).reorder_levels([2, 0,1] ,axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
def fillna(df):
|
||||||
|
new_df = df.ffill(axis=0)
|
||||||
|
return new_df
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
import os
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
import csv
|
import csv
|
||||||
import weakref
|
import random
|
||||||
from random import random
|
import simpy
|
||||||
|
import tempfile
|
||||||
|
import pandas as pd
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from networkx.readwrite import json_graph
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import nxsim
|
import nxsim
|
||||||
|
|
||||||
|
from . import utils, agents, analysis, history
|
||||||
|
|
||||||
|
|
||||||
class SoilEnvironment(nxsim.NetworkEnvironment):
|
class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||||
|
"""
|
||||||
|
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 parameters and the state of every agent can be accessed
|
||||||
|
both by using the environment as a dictionary or with the environment's
|
||||||
|
:meth:`soil.environment.SoilEnvironment.get` method.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None,
|
def __init__(self, name=None,
|
||||||
network_agents=None,
|
network_agents=None,
|
||||||
@@ -16,20 +32,32 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
states=None,
|
states=None,
|
||||||
default_state=None,
|
default_state=None,
|
||||||
interval=1,
|
interval=1,
|
||||||
|
seed=None,
|
||||||
|
dry_run=False,
|
||||||
|
dir_path=None,
|
||||||
|
topology=None,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
self.name = name or 'UnnamedEnvironment'
|
self.name = name or 'UnnamedEnvironment'
|
||||||
self.states = deepcopy(states) or {}
|
if isinstance(states, list):
|
||||||
|
states = dict(enumerate(states))
|
||||||
|
self.states = deepcopy(states) if states else {}
|
||||||
self.default_state = deepcopy(default_state) or {}
|
self.default_state = deepcopy(default_state) or {}
|
||||||
super().__init__(*args, **kwargs)
|
if not topology:
|
||||||
|
topology = nx.Graph()
|
||||||
|
super().__init__(*args, topology=topology, **kwargs)
|
||||||
self._env_agents = {}
|
self._env_agents = {}
|
||||||
self._history = {}
|
self.dry_run = dry_run
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.logger = None
|
self.dir_path = dir_path or tempfile.mkdtemp('soil-env')
|
||||||
|
self.get_path()
|
||||||
|
self._history = history.History(name=self.name if not dry_run else None,
|
||||||
|
dir_path=self.dir_path)
|
||||||
# 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.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['SEED'] = seed or time.time()
|
||||||
|
random.seed(self['SEED'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def agents(self):
|
def agents(self):
|
||||||
@@ -39,7 +67,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
@property
|
@property
|
||||||
def environment_agents(self):
|
def environment_agents(self):
|
||||||
for ref in self._env_agents.values():
|
for ref in self._env_agents.values():
|
||||||
yield ref()
|
yield ref
|
||||||
|
|
||||||
@environment_agents.setter
|
@environment_agents.setter
|
||||||
def environment_agents(self, environment_agents):
|
def environment_agents(self, environment_agents):
|
||||||
@@ -50,9 +78,8 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
atype = kwargs.pop('agent_type')
|
atype = kwargs.pop('agent_type')
|
||||||
kwargs['agent_id'] = kwargs.get('agent_id', atype.__name__)
|
kwargs['agent_id'] = kwargs.get('agent_id', atype.__name__)
|
||||||
kwargs['state'] = kwargs.get('state', {})
|
kwargs['state'] = kwargs.get('state', {})
|
||||||
a = atype(**kwargs,
|
a = atype(environment=self, **kwargs)
|
||||||
environment=self)
|
self._env_agents[a.id] = a
|
||||||
self._env_agents[a.id] = weakref.ref(a)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_agents(self):
|
def network_agents(self):
|
||||||
@@ -63,60 +90,104 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
|
|
||||||
@network_agents.setter
|
@network_agents.setter
|
||||||
def network_agents(self, network_agents):
|
def network_agents(self, network_agents):
|
||||||
|
if not network_agents:
|
||||||
|
return
|
||||||
for ix in self.G.nodes():
|
for ix in self.G.nodes():
|
||||||
i = ix
|
agent, state = agents._agent_from_distribution(network_agents)
|
||||||
node = self.G.node[i]
|
self.set_agent(ix, agent_type=agent, state=state)
|
||||||
v = random()
|
|
||||||
found = False
|
def set_agent(self, agent_id, agent_type, state=None):
|
||||||
for d in network_agents:
|
node = self.G.nodes[agent_id]
|
||||||
threshold = d['threshold']
|
defstate = deepcopy(self.default_state)
|
||||||
if v >= threshold[0] and v < threshold[1]:
|
defstate.update(self.states.get(agent_id, {}))
|
||||||
agent = d['agent_type']
|
if state:
|
||||||
state = None
|
defstate.update(state)
|
||||||
if 'state' in d:
|
state = defstate
|
||||||
state = deepcopy(d['state'])
|
state.update(node.get('state', {}))
|
||||||
else:
|
a = agent_type(environment=self,
|
||||||
try:
|
agent_id=agent_id,
|
||||||
state = self.states[i]
|
state=state)
|
||||||
except (IndexError, KeyError):
|
node['agent'] = a
|
||||||
state = deepcopy(self.default_state)
|
return a
|
||||||
node['agent'] = agent(environment=self,
|
|
||||||
agent_id=i,
|
def add_node(self, agent_type, state=None):
|
||||||
state=state)
|
agent_id = int(len(self.G.nodes()))
|
||||||
found = True
|
self.G.add_node(agent_id)
|
||||||
break
|
a = self.set_agent(agent_id, agent_type, state)
|
||||||
assert found
|
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()
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
self._save_state()
|
self._history.flush_cache()
|
||||||
|
|
||||||
def _save_state(self):
|
def _save_state(self, now=None):
|
||||||
for agent in self.agents:
|
# for agent in self.agents:
|
||||||
agent.save_state()
|
# agent.save_state()
|
||||||
self._history[self.now] = deepcopy(self.environment_params)
|
utils.logger.debug('Saving state @{}'.format(self.now))
|
||||||
|
self._history.save_records(self.state_to_tuples(now=now))
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
while True:
|
'''
|
||||||
|
:DEPRECATED:
|
||||||
|
Periodically save the state of the environment and the agents.
|
||||||
|
'''
|
||||||
|
self._save_state()
|
||||||
|
while self.peek() != simpy.core.Infinity:
|
||||||
|
delay = max(self.peek() - self.now, self.interval)
|
||||||
|
utils.logger.debug('Step: {}'.format(self.now))
|
||||||
ev = self.event()
|
ev = self.event()
|
||||||
ev._ok = True
|
ev._ok = True
|
||||||
# Schedule the event with minimum priority so
|
# Schedule the event with minimum priority so
|
||||||
# that it executes after all agents are done
|
# that it executes before all agents
|
||||||
self.schedule(ev, -1, self.interval)
|
self.schedule(ev, -999, delay)
|
||||||
yield ev
|
yield ev
|
||||||
self._save_state()
|
self._save_state()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, tuple):
|
||||||
|
self._history.flush_cache()
|
||||||
|
return self._history[key]
|
||||||
|
|
||||||
return self.environment_params[key]
|
return self.environment_params[key]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
if isinstance(key, tuple):
|
||||||
|
k = history.Key(*key)
|
||||||
|
self._history.save_record(*k,
|
||||||
|
value=value)
|
||||||
|
return
|
||||||
self.environment_params[key] = value
|
self.environment_params[key] = value
|
||||||
|
self._history.save_record(agent_id='env',
|
||||||
|
t_step=self.now,
|
||||||
|
key=key,
|
||||||
|
value=value)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.environment_params
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
'''
|
||||||
|
Get the value of an environment attribute in a
|
||||||
|
given point in the simulation (history).
|
||||||
|
If key is an attribute name, this method returns
|
||||||
|
the current value.
|
||||||
|
To get values at other times, use a
|
||||||
|
:meth: `soil.history.Key` tuple.
|
||||||
|
'''
|
||||||
|
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.dir_path
|
||||||
if not os.path.exists(dir_path):
|
if not os.path.exists(dir_path):
|
||||||
os.makedirs(dir_path)
|
try:
|
||||||
|
os.makedirs(dir_path)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
return dir_path
|
return dir_path
|
||||||
|
|
||||||
def get_agent(self, agent_id):
|
def get_agent(self, agent_id):
|
||||||
@@ -131,7 +202,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
|
|
||||||
with open(csv_name, 'w') as f:
|
with open(csv_name, 'w') as f:
|
||||||
cr = csv.writer(f)
|
cr = csv.writer(f)
|
||||||
cr.writerow(('agent_id', 'tstep', 'attribute', 'value'))
|
cr.writerow(('agent_id', 't_step', 'key', 'value', 'value_type'))
|
||||||
for i in self.history_to_tuples():
|
for i in self.history_to_tuples():
|
||||||
cr.writerow(i)
|
cr.writerow(i)
|
||||||
|
|
||||||
@@ -139,28 +210,60 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
G = self.history_to_graph()
|
G = self.history_to_graph()
|
||||||
graph_path = os.path.join(self.get_path(dir_path),
|
graph_path = os.path.join(self.get_path(dir_path),
|
||||||
self.name+".gexf")
|
self.name+".gexf")
|
||||||
|
# Workaround for geometric models
|
||||||
|
# See soil/soil#4
|
||||||
|
for node in G.nodes():
|
||||||
|
if 'pos' in G.node[node]:
|
||||||
|
G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}}
|
||||||
|
del (G.node[node]['pos'])
|
||||||
|
|
||||||
nx.write_gexf(G, graph_path, version="1.2draft")
|
nx.write_gexf(G, graph_path, version="1.2draft")
|
||||||
|
|
||||||
def history_to_tuples(self):
|
def dump(self, dir_path=None, formats=None):
|
||||||
for tstep, state in self._history.items():
|
if not formats:
|
||||||
for attribute, value in state.items():
|
return
|
||||||
yield ('env', tstep, attribute, value)
|
functions = {
|
||||||
|
'csv': self.dump_csv,
|
||||||
|
'gexf': self.dump_gexf
|
||||||
|
}
|
||||||
|
for f in formats:
|
||||||
|
if f in functions:
|
||||||
|
functions[f](dir_path)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown format: {}'.format(f))
|
||||||
|
|
||||||
|
def state_to_tuples(self, now=None):
|
||||||
|
if now is None:
|
||||||
|
now = self.now
|
||||||
|
for k, v in self.environment_params.items():
|
||||||
|
yield history.Record(agent_id='env',
|
||||||
|
t_step=now,
|
||||||
|
key=k,
|
||||||
|
value=v)
|
||||||
for agent in self.agents:
|
for agent in self.agents:
|
||||||
for tstep, state in agent._history.items():
|
for k, v in agent.state.items():
|
||||||
for attribute, value in state.items():
|
yield history.Record(agent_id=agent.id,
|
||||||
yield (agent.id, tstep, attribute, value)
|
t_step=now,
|
||||||
|
key=k,
|
||||||
|
value=v)
|
||||||
|
|
||||||
|
def history_to_tuples(self):
|
||||||
|
return self._history.to_tuples()
|
||||||
|
|
||||||
def history_to_graph(self):
|
def history_to_graph(self):
|
||||||
G = nx.Graph(self.G)
|
G = nx.Graph(self.G)
|
||||||
|
|
||||||
for agent in self.agents:
|
for agent in self.network_agents:
|
||||||
|
|
||||||
attributes = {'agent': str(agent.__class__)}
|
attributes = {'agent': str(agent.__class__)}
|
||||||
lastattributes = {}
|
lastattributes = {}
|
||||||
spells = []
|
spells = []
|
||||||
lastvisible = False
|
lastvisible = False
|
||||||
laststep = None
|
laststep = None
|
||||||
for t_step, state in reversed(agent._history.items()):
|
history = self[agent.id, None, None]
|
||||||
|
if not history:
|
||||||
|
continue
|
||||||
|
for t_step, state in reversed(sorted(list(history.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]
|
||||||
@@ -171,20 +274,41 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
|
|
||||||
lastvisible = nowvisible
|
lastvisible = nowvisible
|
||||||
else:
|
else:
|
||||||
if attribute not in lastattributes or lastattributes[attribute][0] != value:
|
key = 'attr_' + attribute
|
||||||
laststep = lastattributes.get(attribute,
|
if key not in attributes:
|
||||||
(None, None))[1]
|
attributes[key] = list()
|
||||||
value = (state[attribute], t_step, laststep)
|
if key not in lastattributes:
|
||||||
key = 'attr_' + attribute
|
lastattributes[key] = (state[attribute], t_step)
|
||||||
|
elif lastattributes[key][0] != value:
|
||||||
|
last_value, laststep = lastattributes[key]
|
||||||
|
value = (last_value, t_step, laststep)
|
||||||
if key not in attributes:
|
if key not in attributes:
|
||||||
attributes[key] = list()
|
attributes[key] = list()
|
||||||
attributes[key].append(value)
|
attributes[key].append(value)
|
||||||
lastattributes[attribute] = (state[attribute], t_step)
|
lastattributes[key] = (state[attribute], t_step)
|
||||||
|
for k, v in lastattributes.items():
|
||||||
|
attributes[k].append((v[0], 0, v[1]))
|
||||||
if lastvisible:
|
if lastvisible:
|
||||||
spells.append((laststep, None))
|
spells.append((laststep, None))
|
||||||
if spells:
|
if spells:
|
||||||
G.add_node(agent.id, attributes, spells=spells)
|
G.add_node(agent.id, spells=spells, **attributes)
|
||||||
else:
|
else:
|
||||||
G.add_node(agent.id, attributes)
|
G.add_node(agent.id, **attributes)
|
||||||
|
|
||||||
return G
|
return G
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
state = self.__dict__.copy()
|
||||||
|
state['G'] = json_graph.node_link_data(self.G)
|
||||||
|
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
||||||
|
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||||
|
to_string=True)
|
||||||
|
del state['_queue']
|
||||||
|
return state
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self.__dict__ = state
|
||||||
|
self.G = json_graph.node_link_graph(state['G'])
|
||||||
|
self.network_agents = self.calculate_distribution(self._convert_agent_types(self.network_agents))
|
||||||
|
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
||||||
|
return state
|
||||||
|
|||||||
231
soil/history.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import time
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import sqlite3
|
||||||
|
import copy
|
||||||
|
from collections import UserDict, Iterable, namedtuple
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
class History:
|
||||||
|
"""
|
||||||
|
Store and retrieve values from a sqlite database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_path=None, name=None, dir_path=None, backup=True):
|
||||||
|
if db_path is None and name:
|
||||||
|
db_path = os.path.join(dir_path or os.getcwd(),
|
||||||
|
'{}.db.sqlite'.format(name))
|
||||||
|
if db_path is None:
|
||||||
|
db_path = ":memory:"
|
||||||
|
else:
|
||||||
|
if backup and os.path.exists(db_path):
|
||||||
|
newname = db_path + '.backup{}.sqlite'.format(time.time())
|
||||||
|
os.rename(db_path, newname)
|
||||||
|
self._db_path = db_path
|
||||||
|
if isinstance(db_path, str):
|
||||||
|
self._db = sqlite3.connect(db_path)
|
||||||
|
else:
|
||||||
|
self._db = db_path
|
||||||
|
|
||||||
|
with self._db:
|
||||||
|
self._db.execute('''CREATE TABLE IF NOT EXISTS history (agent_id text, t_step int, key text, value text text)''')
|
||||||
|
self._db.execute('''CREATE TABLE IF NOT EXISTS value_types (key text, value_type text)''')
|
||||||
|
self._db.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_history ON history (agent_id, t_step, key);''')
|
||||||
|
self._dtypes = {}
|
||||||
|
self._tups = []
|
||||||
|
|
||||||
|
def conversors(self, key):
|
||||||
|
"""Get the serializer and deserializer for a given key."""
|
||||||
|
if key not in self._dtypes:
|
||||||
|
self.read_types()
|
||||||
|
return self._dtypes[key]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dtypes(self):
|
||||||
|
return {k:v[0] for k, v in self._dtypes.items()}
|
||||||
|
|
||||||
|
def save_tuples(self, tuples):
|
||||||
|
self.save_records(Record(*tup) for tup in tuples)
|
||||||
|
|
||||||
|
def save_records(self, records):
|
||||||
|
with self._db:
|
||||||
|
for rec in records:
|
||||||
|
if not isinstance(rec, Record):
|
||||||
|
rec = Record(*rec)
|
||||||
|
if rec.key not in self._dtypes:
|
||||||
|
name = utils.name(rec.value)
|
||||||
|
serializer = utils.serializer(name)
|
||||||
|
deserializer = utils.deserializer(name)
|
||||||
|
self._dtypes[rec.key] = (name, serializer, deserializer)
|
||||||
|
self._db.execute("replace into value_types (key, value_type) values (?, ?)", (rec.key, name))
|
||||||
|
self._db.execute("replace into history(agent_id, t_step, key, value) values (?, ?, ?, ?)", (rec.agent_id, rec.t_step, rec.key, rec.value))
|
||||||
|
|
||||||
|
def save_record(self, *args, **kwargs):
|
||||||
|
self._tups.append(Record(*args, **kwargs))
|
||||||
|
if len(self._tups) > 100:
|
||||||
|
self.flush_cache()
|
||||||
|
|
||||||
|
def flush_cache(self):
|
||||||
|
'''
|
||||||
|
Use a cache to save state changes to avoid opening a session for every change.
|
||||||
|
The cache will be flushed at the end of the simulation, and when history is accessed.
|
||||||
|
'''
|
||||||
|
self.save_records(self._tups)
|
||||||
|
self._tups = list()
|
||||||
|
|
||||||
|
def to_tuples(self):
|
||||||
|
self.flush_cache()
|
||||||
|
with self._db:
|
||||||
|
res = self._db.execute("select agent_id, t_step, key, value from history ").fetchall()
|
||||||
|
for r in res:
|
||||||
|
agent_id, t_step, key, value = r
|
||||||
|
_, _ , des = self.conversors(key)
|
||||||
|
yield agent_id, t_step, key, des(value)
|
||||||
|
|
||||||
|
def read_types(self):
|
||||||
|
with self._db:
|
||||||
|
res = self._db.execute("select key, value_type from value_types ").fetchall()
|
||||||
|
for k, v in res:
|
||||||
|
serializer = utils.serializer(v)
|
||||||
|
deserializer = utils.deserializer(v)
|
||||||
|
self._dtypes[k] = (v, serializer, deserializer)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
key = Key(*key)
|
||||||
|
agent_ids = [key.agent_id] if key.agent_id is not None else []
|
||||||
|
t_steps = [key.t_step] if key.t_step is not None else []
|
||||||
|
keys = [key.key] if key.key is not None else []
|
||||||
|
|
||||||
|
df = self.read_sql(agent_ids=agent_ids,
|
||||||
|
t_steps=t_steps,
|
||||||
|
keys=keys)
|
||||||
|
r = Records(df, filter=key, dtypes=self._dtypes)
|
||||||
|
return r.value()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def read_sql(self, keys=None, agent_ids=None, t_steps=None, convert_types=False, limit=-1):
|
||||||
|
|
||||||
|
self.read_types()
|
||||||
|
|
||||||
|
def escape_and_join(v):
|
||||||
|
if v is None:
|
||||||
|
return
|
||||||
|
return ",".join(map(lambda x: "\'{}\'".format(x), v))
|
||||||
|
|
||||||
|
filters = [("key in ({})".format(escape_and_join(keys)), keys),
|
||||||
|
("agent_id in ({})".format(escape_and_join(agent_ids)), agent_ids)
|
||||||
|
]
|
||||||
|
filters = list(k[0] for k in filters if k[1])
|
||||||
|
|
||||||
|
last_df = None
|
||||||
|
if t_steps:
|
||||||
|
# Look for the last value before the minimum step in the query
|
||||||
|
min_step = min(t_steps)
|
||||||
|
last_filters = ['t_step < {}'.format(min_step),]
|
||||||
|
last_filters = last_filters + filters
|
||||||
|
condition = ' and '.join(last_filters)
|
||||||
|
|
||||||
|
last_query = '''
|
||||||
|
select h1.*
|
||||||
|
from history h1
|
||||||
|
inner join (
|
||||||
|
select agent_id, key, max(t_step) as t_step
|
||||||
|
from history
|
||||||
|
where {condition}
|
||||||
|
group by agent_id, key
|
||||||
|
) h2
|
||||||
|
on h1.agent_id = h2.agent_id and
|
||||||
|
h1.key = h2.key and
|
||||||
|
h1.t_step = h2.t_step
|
||||||
|
'''.format(condition=condition)
|
||||||
|
last_df = pd.read_sql_query(last_query, self._db)
|
||||||
|
|
||||||
|
filters.append("t_step >= '{}' and t_step <= '{}'".format(min_step, max(t_steps)))
|
||||||
|
|
||||||
|
condition = ''
|
||||||
|
if filters:
|
||||||
|
condition = 'where {} '.format(' and '.join(filters))
|
||||||
|
query = 'select * from history {} limit {}'.format(condition, limit)
|
||||||
|
df = pd.read_sql_query(query, self._db)
|
||||||
|
if last_df is not None:
|
||||||
|
df = pd.concat([df, last_df])
|
||||||
|
|
||||||
|
df_p = df.pivot_table(values='value', index=['t_step'],
|
||||||
|
columns=['key', 'agent_id'],
|
||||||
|
aggfunc='first')
|
||||||
|
|
||||||
|
for k, v in self._dtypes.items():
|
||||||
|
if k in df_p:
|
||||||
|
dtype, _, deserial = v
|
||||||
|
df_p[k] = df_p[k].fillna(method='ffill').fillna(deserial()).astype(dtype)
|
||||||
|
if t_steps:
|
||||||
|
df_p = df_p.reindex(t_steps, method='ffill')
|
||||||
|
return df_p.ffill()
|
||||||
|
|
||||||
|
|
||||||
|
class Records():
|
||||||
|
|
||||||
|
def __init__(self, df, filter=None, dtypes=None):
|
||||||
|
if not filter:
|
||||||
|
filter = Key(agent_id=None,
|
||||||
|
t_step=None,
|
||||||
|
key=None)
|
||||||
|
self._df = df
|
||||||
|
self._filter = filter
|
||||||
|
self.dtypes = dtypes or {}
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def mask(self, tup):
|
||||||
|
res = ()
|
||||||
|
for i, k in zip(tup[:-1], self._filter):
|
||||||
|
if k is None:
|
||||||
|
res = res + (i,)
|
||||||
|
res = res + (tup[-1],)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def filter(self, newKey):
|
||||||
|
f = list(self._filter)
|
||||||
|
for ix, i in enumerate(f):
|
||||||
|
if i is None:
|
||||||
|
f[ix] = newKey
|
||||||
|
self._filter = Key(*f)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resolved(self):
|
||||||
|
return sum(1 for i in self._filter if i is not None) == 3
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for column, series in self._df.iteritems():
|
||||||
|
key, agent_id = column
|
||||||
|
for t_step, value in series.iteritems():
|
||||||
|
r = Record(t_step=t_step,
|
||||||
|
agent_id=agent_id,
|
||||||
|
key=key,
|
||||||
|
value=value)
|
||||||
|
yield self.mask(r)
|
||||||
|
|
||||||
|
def value(self):
|
||||||
|
if self.resolved:
|
||||||
|
f = self._filter
|
||||||
|
try:
|
||||||
|
i = self._df[f.key][str(f.agent_id)]
|
||||||
|
ix = i.index.get_loc(f.t_step, method='ffill')
|
||||||
|
return i.iloc[ix]
|
||||||
|
except KeyError:
|
||||||
|
return self.dtypes[f.key][2]()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __getitem__(self, k):
|
||||||
|
n = copy.copy(self)
|
||||||
|
n.filter(k)
|
||||||
|
return n.value()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._df)
|
||||||
|
|
||||||
|
|
||||||
|
Key = namedtuple('Key', ['agent_id', 't_step', 'key'])
|
||||||
|
Record = namedtuple('Record', 'agent_id t_step key value')
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
import weakref
|
|
||||||
import os
|
import os
|
||||||
import csv
|
|
||||||
import time
|
import time
|
||||||
|
import imp
|
||||||
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
from networkx.readwrite import json_graph
|
from networkx.readwrite import json_graph
|
||||||
|
from multiprocessing import Pool
|
||||||
from copy import deepcopy
|
from functools import partial
|
||||||
from random import random
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from nxsim import NetworkSimulation
|
from nxsim import NetworkSimulation
|
||||||
|
|
||||||
from . import agents, utils, environment, basestring
|
from . import utils, environment, basestring, agents
|
||||||
|
from .utils import logger
|
||||||
|
|
||||||
|
|
||||||
class SoilSimulation(NetworkSimulation):
|
class SoilSimulation(NetworkSimulation):
|
||||||
"""
|
"""
|
||||||
Subclass of nsim.NetworkSimulation with three main differences:
|
Subclass of nsim.NetworkSimulation with three main differences:
|
||||||
1) agent type can be specified by name or by class.
|
1) agent type can be specified by name or by class.
|
||||||
2) instead of just one type, an network_agents can be used.
|
2) instead of just one type, a network agents distribution can be used.
|
||||||
The distribution specifies the weight (or probability) of each
|
The distribution specifies the weight (or probability) of each
|
||||||
agent type in the topology. This is an example distribution: ::
|
agent type in the topology. This is an example distribution: ::
|
||||||
|
|
||||||
@@ -47,9 +46,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=None, dry_run=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 +57,7 @@ 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,78 +66,72 @@ 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 = str(seed) or str(time.time())
|
||||||
|
self.dump = dump
|
||||||
|
self.dry_run = dry_run
|
||||||
self.environment_params = environment_params or {}
|
self.environment_params = environment_params or {}
|
||||||
|
|
||||||
|
if load_module:
|
||||||
|
path = sys.path + [self.dir_path, os.getcwd()]
|
||||||
|
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 = agents._convert_agent_types(environment_agents)
|
||||||
|
|
||||||
distro = self.calculate_distribution(network_agents,
|
distro = agents.calculate_distribution(network_agents,
|
||||||
agent_type)
|
agent_type)
|
||||||
self.network_agents = self._convert_agent_types(distro)
|
self.network_agents = agents._convert_agent_types(distro)
|
||||||
|
|
||||||
self.states = self.validate_states(states,
|
self.states = agents._validate_states(states,
|
||||||
topology)
|
self.topology)
|
||||||
|
|
||||||
def calculate_distribution(self,
|
def run_simulation(self, *args, **kwargs):
|
||||||
network_agents=None,
|
return self.run(*args, **kwargs)
|
||||||
agent_type=None):
|
|
||||||
if network_agents:
|
|
||||||
network_agents = deepcopy(network_agents)
|
|
||||||
elif agent_type:
|
|
||||||
network_agents = [{'agent_type': agent_type}]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Calculate the thresholds
|
def run(self, *args, **kwargs):
|
||||||
total = sum(x.get('weight', 1) for x in network_agents)
|
return list(self.run_simulation_gen(*args, **kwargs))
|
||||||
acc = 0
|
|
||||||
for v in network_agents:
|
|
||||||
upper = acc + (v.get('weight', 1)/total)
|
|
||||||
v['threshold'] = [acc, upper]
|
|
||||||
acc = upper
|
|
||||||
return network_agents
|
|
||||||
|
|
||||||
def serialize_distribution(self):
|
def run_simulation_gen(self, *args, parallel=False, dry_run=False,
|
||||||
d = self._convert_agent_types(self.network_agents,
|
**kwargs):
|
||||||
to_string=True)
|
p = Pool()
|
||||||
for v in d:
|
with utils.timer('simulation {}'.format(self.name)):
|
||||||
if 'threshold' in v:
|
if parallel:
|
||||||
del v['threshold']
|
func = partial(self.run_trial, dry_run=dry_run or self.dry_run,
|
||||||
return d
|
return_env=not parallel, **kwargs)
|
||||||
|
for i in p.imap_unordered(func, range(self.num_trials)):
|
||||||
|
yield i
|
||||||
|
else:
|
||||||
|
for i in range(self.num_trials):
|
||||||
|
yield self.run_trial(i, dry_run=dry_run or self.dry_run, **kwargs)
|
||||||
|
if not (dry_run or self.dry_run):
|
||||||
|
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 _convert_agent_types(self, ind, to_string=False):
|
def get_env(self, trial_id=0, **kwargs):
|
||||||
d = deepcopy(ind)
|
opts = self.environment_params.copy()
|
||||||
for v in d:
|
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||||
agent_type = v['agent_type']
|
opts.update({
|
||||||
if to_string and not isinstance(agent_type, str):
|
'name': env_name,
|
||||||
v['agent_type'] = str(agent_type.__name__)
|
'topology': self.topology.copy(),
|
||||||
elif not to_string and isinstance(agent_type, str):
|
'seed': self.seed+env_name,
|
||||||
v['agent_type'] = agents.agent_types[agent_type]
|
'initial_time': 0,
|
||||||
return d
|
'dry_run': self.dry_run,
|
||||||
|
'interval': self.interval,
|
||||||
|
'network_agents': self.network_agents,
|
||||||
|
'states': self.states,
|
||||||
|
'default_state': self.default_state,
|
||||||
|
'environment_agents': self.environment_agents,
|
||||||
|
'dir_path': self.dir_path,
|
||||||
|
})
|
||||||
|
opts.update(kwargs)
|
||||||
|
env = environment.SoilEnvironment(**opts)
|
||||||
|
return env
|
||||||
|
|
||||||
def validate_states(self, states, topology):
|
def run_trial(self, trial_id=0, until=None, return_env=True, **opts):
|
||||||
states = states or []
|
|
||||||
# Validate states to avoid ignoring states during
|
|
||||||
# initialization
|
|
||||||
if isinstance(states, dict):
|
|
||||||
for x in states:
|
|
||||||
assert x in self.topology.node
|
|
||||||
else:
|
|
||||||
assert len(states) <= len(self.topology)
|
|
||||||
return states
|
|
||||||
|
|
||||||
def run_simulation(self):
|
|
||||||
return self.run()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
return list(self.run_simulation_gen())
|
|
||||||
|
|
||||||
def run_simulation_gen(self):
|
|
||||||
with utils.timer('simulation'):
|
|
||||||
for i in range(self.num_trials):
|
|
||||||
yield self.run_trial(i)
|
|
||||||
|
|
||||||
def run_trial(self, trial_id=0):
|
|
||||||
"""Run a single trial of the simulation
|
"""Run a single trial of the simulation
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -145,24 +139,16 @@ 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))
|
until = until or self.max_time
|
||||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
env = self.get_env(trial_id=trial_id, **opts)
|
||||||
env = environment.SoilEnvironment(name=env_name,
|
|
||||||
topology=self.topology.copy(),
|
|
||||||
initial_time=0,
|
|
||||||
interval=self.interval,
|
|
||||||
network_agents=self.network_agents,
|
|
||||||
states=self.states,
|
|
||||||
default_state=self.default_state,
|
|
||||||
environment_agents=self.environment_agents,
|
|
||||||
**self.environment_params)
|
|
||||||
|
|
||||||
env.sim = weakref.ref(self)
|
|
||||||
# Set up agents on nodes
|
# Set up agents on nodes
|
||||||
print('\tRunning')
|
with utils.timer('Simulation {} trial {}'.format(self.name, trial_id)):
|
||||||
with utils.timer('trial'):
|
env.run(until)
|
||||||
env.run(until=self.max_time)
|
if self.dump and not self.dry_run:
|
||||||
return env
|
with utils.timer('Dumping simulation {} trial {}'.format(self.name, trial_id)):
|
||||||
|
env.dump(formats=self.dump)
|
||||||
|
if return_env:
|
||||||
|
return env
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return self.__getstate__()
|
return self.__getstate__()
|
||||||
@@ -193,20 +179,20 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
state['topology'] = json_graph.node_link_data(self.topology)
|
state['topology'] = json_graph.node_link_data(self.topology)
|
||||||
state['network_agents'] = self.serialize_distribution()
|
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
||||||
state['environment_agents'] = self._convert_agent_types(self.environment_agents,
|
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||||
to_string=True)
|
to_string=True)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.__dict__ = state
|
self.__dict__ = state
|
||||||
self.topology = json_graph.node_link_graph(state['topology'])
|
self.topology = json_graph.node_link_graph(state['topology'])
|
||||||
self.network_agents = self._convert_agent_types(self.network_agents)
|
self.network_agents = agents.calculate_distribution(agents._convert_agent_types(self.network_agents))
|
||||||
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
self.environment_agents = agents._convert_agent_types(self.environment_agents)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
def from_config(config, G=None):
|
def from_config(config):
|
||||||
config = list(utils.load_config(config))
|
config = list(utils.load_config(config))
|
||||||
if len(config) > 1:
|
if len(config) > 1:
|
||||||
raise AttributeError('Provide only one configuration')
|
raise AttributeError('Provide only one configuration')
|
||||||
@@ -215,27 +201,19 @@ def from_config(config, G=None):
|
|||||||
return sim
|
return sim
|
||||||
|
|
||||||
|
|
||||||
def run_from_config(*configs, dump=True, results_dir=None, timestamp=False):
|
def run_from_config(*configs, results_dir='soil_output', dry_run=False, dump=None, timestamp=False, **kwargs):
|
||||||
if not results_dir:
|
|
||||||
results_dir = 'soil_output'
|
|
||||||
for config_def in configs:
|
for config_def in configs:
|
||||||
for config, cpath in utils.load_config(config_def):
|
# logger.info("Found {} config(s)".format(len(ls)))
|
||||||
|
for config, _ 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)
|
|
||||||
if timestamp:
|
if timestamp:
|
||||||
sim_folder = '{}_{}'.format(sim.name,
|
sim_folder = '{}_{}'.format(name,
|
||||||
time.strftime("%Y-%m-%d_%H:%M:%S"))
|
time.strftime("%Y-%m-%d_%H:%M:%S"))
|
||||||
else:
|
else:
|
||||||
sim_folder = sim.name
|
sim_folder = name
|
||||||
dir_path = os.path.join(results_dir,
|
dir_path = os.path.join(results_dir, sim_folder)
|
||||||
sim_folder)
|
sim = SoilSimulation(dir_path=dir_path, dump=dump, **config)
|
||||||
results = sim.run_simulation()
|
logger.info('Dumping results to {} : {}'.format(sim.dir_path, sim.dump))
|
||||||
|
sim.run_simulation(**kwargs)
|
||||||
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,14 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import logging
|
||||||
|
import importlib
|
||||||
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('soil')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def load_network(network_params, dir_path=None):
|
def load_network(network_params, dir_path=None):
|
||||||
|
if network_params is None:
|
||||||
|
return nx.Graph()
|
||||||
path = network_params.get('path', None)
|
path = network_params.get('path', None)
|
||||||
if path:
|
if path:
|
||||||
if dir_path and not os.path.isabs(path):
|
if dir_path and not os.path.isabs(path):
|
||||||
@@ -51,11 +61,45 @@ 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()
|
||||||
|
function('{}Starting {} at {}.'.format(pre, name, start))
|
||||||
yield start
|
yield start
|
||||||
end = time()
|
end = time()
|
||||||
function('{}Finished {} in {} seconds'.format(pre, name, str(end-start)))
|
function('{}Finished {} in {} seconds'.format(pre, name, str(end-start)))
|
||||||
if to_object:
|
if to_object:
|
||||||
to_object.start = start
|
to_object.start = start
|
||||||
to_object.end = end
|
to_object.end = end
|
||||||
|
|
||||||
|
|
||||||
|
def repr(v):
|
||||||
|
func = serializer(v)
|
||||||
|
tname = name(v)
|
||||||
|
return func(v), tname
|
||||||
|
|
||||||
|
|
||||||
|
def name(v):
|
||||||
|
return type(v).__name__
|
||||||
|
|
||||||
|
|
||||||
|
def serializer(type_):
|
||||||
|
if type_ == 'bool':
|
||||||
|
return lambda x: "true" if x else ""
|
||||||
|
return lambda x: x
|
||||||
|
|
||||||
|
|
||||||
|
def deserializer(type_):
|
||||||
|
try:
|
||||||
|
# Check if it's a builtin type
|
||||||
|
module = importlib.import_module('builtins')
|
||||||
|
cls = getattr(module, type_)
|
||||||
|
except AttributeError:
|
||||||
|
# if not, separate module and class
|
||||||
|
module, type_ = type_.rsplit(".", 1)
|
||||||
|
module = importlib.import_module(module)
|
||||||
|
cls = getattr(module, type_)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
def convert(value, type_):
|
||||||
|
return deserializer(type_)(value)
|
||||||
|
|||||||
20
soil/version.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(__file__)
|
||||||
|
DEFAULT_FILE = os.path.join(ROOT, 'VERSION')
|
||||||
|
|
||||||
|
|
||||||
|
def read_version(versionfile=DEFAULT_FILE):
|
||||||
|
try:
|
||||||
|
with open(versionfile) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except IOError: # pragma: no cover
|
||||||
|
logger.error(('Running an unknown version of {}.'
|
||||||
|
'Be careful!.').format(__name__))
|
||||||
|
return '0.0'
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = read_version()
|
||||||
16
tests/test.csv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
agent_id,t_step,key,value,value_type
|
||||||
|
a0,0,hello,w,str
|
||||||
|
a0,1,hello,o,str
|
||||||
|
a0,2,hello,r,str
|
||||||
|
a0,3,hello,l,str
|
||||||
|
a0,4,hello,d,str
|
||||||
|
a0,5,hello,!,str
|
||||||
|
env,1,started,,bool
|
||||||
|
env,2,started,True,bool
|
||||||
|
env,7,started,,bool
|
||||||
|
a0,0,hello,w,str
|
||||||
|
a0,1,hello,o,str
|
||||||
|
a0,2,hello,r,str
|
||||||
|
a0,3,hello,l,str
|
||||||
|
a0,4,hello,d,str
|
||||||
|
a0,5,hello,!,str
|
||||||
|
90
tests/test_analysis.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import yaml
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from os.path import join
|
||||||
|
from soil import simulation, analysis, agents
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
class Ping(agents.FSM):
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'count': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
@agents.default_state
|
||||||
|
@agents.state
|
||||||
|
def even(self):
|
||||||
|
self['count'] += 1
|
||||||
|
return self.odd
|
||||||
|
|
||||||
|
@agents.state
|
||||||
|
def odd(self):
|
||||||
|
self['count'] += 1
|
||||||
|
return self.even
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnalysis(TestCase):
|
||||||
|
|
||||||
|
# Code to generate a simple sqlite history
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
The initial states should be applied to the agent and the
|
||||||
|
agent should be able to update its state."""
|
||||||
|
config = {
|
||||||
|
'name': 'analysis',
|
||||||
|
'dry_run': True,
|
||||||
|
'seed': 'seed',
|
||||||
|
'network_params': {
|
||||||
|
'generator': 'complete_graph',
|
||||||
|
'n': 2
|
||||||
|
},
|
||||||
|
'agent_type': Ping,
|
||||||
|
'states': [{'interval': 1}, {'interval': 2}],
|
||||||
|
'max_time': 30,
|
||||||
|
'num_trials': 1,
|
||||||
|
'environment_params': {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = simulation.from_config(config)
|
||||||
|
self.env = s.run_simulation()[0]
|
||||||
|
|
||||||
|
def test_saved(self):
|
||||||
|
env = self.env
|
||||||
|
assert env.get_agent(0)['count', 0] == 1
|
||||||
|
assert env.get_agent(0)['count', 29] == 30
|
||||||
|
assert env.get_agent(1)['count', 0] == 1
|
||||||
|
assert env.get_agent(1)['count', 29] == 15
|
||||||
|
assert env['env', 29, None]['SEED'] == env['env', 29, 'SEED']
|
||||||
|
|
||||||
|
def test_count(self):
|
||||||
|
env = self.env
|
||||||
|
df = analysis.read_sql(env._history._db)
|
||||||
|
res = analysis.get_count(df, 'SEED', 'id')
|
||||||
|
assert res['SEED']['seedanalysis_trial_0'].iloc[0] == 1
|
||||||
|
assert res['SEED']['seedanalysis_trial_0'].iloc[-1] == 1
|
||||||
|
assert res['id']['odd'].iloc[0] == 2
|
||||||
|
assert res['id']['even'].iloc[0] == 0
|
||||||
|
assert res['id']['odd'].iloc[-1] == 1
|
||||||
|
assert res['id']['even'].iloc[-1] == 1
|
||||||
|
|
||||||
|
def test_value(self):
|
||||||
|
env = self.env
|
||||||
|
df = analysis.read_sql(env._history._db)
|
||||||
|
res_sum = analysis.get_value(df, 'count')
|
||||||
|
|
||||||
|
assert res_sum['count'].iloc[0] == 2
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
res_mean = analysis.get_value(df, 'count', aggfunc=np.mean)
|
||||||
|
assert res_mean['count'].iloc[0] == 1
|
||||||
|
|
||||||
|
res_total = analysis.get_value(df)
|
||||||
|
|
||||||
|
res_total['SEED'].iloc[0] == 'seedanalysis_trial_0'
|
||||||
133
tests/test_history.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
from soil import history
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
DBROOT = os.path.join(ROOT, 'testdb')
|
||||||
|
|
||||||
|
|
||||||
|
class TestHistory(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if not os.path.exists(DBROOT):
|
||||||
|
os.makedirs(DBROOT)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if os.path.exists(DBROOT):
|
||||||
|
shutil.rmtree(DBROOT)
|
||||||
|
|
||||||
|
def test_history(self):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
tuples = (
|
||||||
|
('a_0', 0, 'id', 'h'),
|
||||||
|
('a_0', 1, 'id', 'e'),
|
||||||
|
('a_0', 2, 'id', 'l'),
|
||||||
|
('a_0', 3, 'id', 'l'),
|
||||||
|
('a_0', 4, 'id', 'o'),
|
||||||
|
('a_1', 0, 'id', 'v'),
|
||||||
|
('a_1', 1, 'id', 'a'),
|
||||||
|
('a_1', 2, 'id', 'l'),
|
||||||
|
('a_1', 3, 'id', 'u'),
|
||||||
|
('a_1', 4, 'id', 'e'),
|
||||||
|
('env', 1, 'prob', 1),
|
||||||
|
('env', 3, 'prob', 2),
|
||||||
|
('env', 5, 'prob', 3),
|
||||||
|
('a_2', 7, 'finished', True),
|
||||||
|
)
|
||||||
|
h = history.History()
|
||||||
|
h.save_tuples(tuples)
|
||||||
|
# assert h['env', 0, 'prob'] == 0
|
||||||
|
for i in range(1, 7):
|
||||||
|
assert h['env', i, 'prob'] == ((i-1)//2)+1
|
||||||
|
|
||||||
|
|
||||||
|
for i, k in zip(range(5), 'hello'):
|
||||||
|
assert h['a_0', i, 'id'] == k
|
||||||
|
for record, value in zip(h['a_0', None, 'id'], 'hello'):
|
||||||
|
t_step, val = record
|
||||||
|
assert val == value
|
||||||
|
|
||||||
|
for i, k in zip(range(5), 'value'):
|
||||||
|
assert h['a_1', i, 'id'] == k
|
||||||
|
for i in range(5, 8):
|
||||||
|
assert h['a_1', i, 'id'] == 'e'
|
||||||
|
for i in range(7):
|
||||||
|
assert h['a_2', i, 'finished'] == False
|
||||||
|
assert h['a_2', 7, 'finished']
|
||||||
|
|
||||||
|
def test_history_gen(self):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
tuples = (
|
||||||
|
('a_1', 0, 'id', 'v'),
|
||||||
|
('a_1', 1, 'id', 'a'),
|
||||||
|
('a_1', 2, 'id', 'l'),
|
||||||
|
('a_1', 3, 'id', 'u'),
|
||||||
|
('a_1', 4, 'id', 'e'),
|
||||||
|
('env', 1, 'prob', 1),
|
||||||
|
('env', 2, 'prob', 2),
|
||||||
|
('env', 3, 'prob', 3),
|
||||||
|
('a_2', 7, 'finished', True),
|
||||||
|
)
|
||||||
|
h = history.History()
|
||||||
|
h.save_tuples(tuples)
|
||||||
|
for t_step, key, value in h['env', None, None]:
|
||||||
|
assert t_step == value
|
||||||
|
assert key == 'prob'
|
||||||
|
|
||||||
|
records = list(h[None, 7, None])
|
||||||
|
assert len(records) == 3
|
||||||
|
for i in records:
|
||||||
|
agent_id, key, value = i
|
||||||
|
if agent_id == 'a_1':
|
||||||
|
assert key == 'id'
|
||||||
|
assert value == 'e'
|
||||||
|
elif agent_id == 'a_2':
|
||||||
|
assert key == 'finished'
|
||||||
|
assert value
|
||||||
|
else:
|
||||||
|
assert key == 'prob'
|
||||||
|
assert value == 3
|
||||||
|
|
||||||
|
records = h['a_1', 7, None]
|
||||||
|
assert records['id'] == 'e'
|
||||||
|
|
||||||
|
def test_history_file(self):
|
||||||
|
"""
|
||||||
|
History should be saved to a file
|
||||||
|
"""
|
||||||
|
tuples = (
|
||||||
|
('a_1', 0, 'id', 'v'),
|
||||||
|
('a_1', 1, 'id', 'a'),
|
||||||
|
('a_1', 2, 'id', 'l'),
|
||||||
|
('a_1', 3, 'id', 'u'),
|
||||||
|
('a_1', 4, 'id', 'e'),
|
||||||
|
('env', 1, 'prob', 1),
|
||||||
|
('env', 2, 'prob', 2),
|
||||||
|
('env', 3, 'prob', 3),
|
||||||
|
('a_2', 7, 'finished', True),
|
||||||
|
)
|
||||||
|
db_path = os.path.join(DBROOT, 'test')
|
||||||
|
h = history.History(db_path=db_path)
|
||||||
|
h.save_tuples(tuples)
|
||||||
|
assert os.path.exists(db_path)
|
||||||
|
|
||||||
|
# Recover the data
|
||||||
|
recovered = history.History(db_path=db_path, backup=False)
|
||||||
|
assert recovered['a_1', 0, 'id'] == 'v'
|
||||||
|
assert recovered['a_1', 4, 'id'] == 'e'
|
||||||
|
|
||||||
|
# Using the same name should create a backup copy
|
||||||
|
newhistory = history.History(db_path=db_path, backup=True)
|
||||||
|
backuppaths = glob(db_path + '.backup*.sqlite')
|
||||||
|
assert len(backuppaths) == 1
|
||||||
|
backuppath = backuppaths[0]
|
||||||
|
assert newhistory._db_path == h._db_path
|
||||||
|
assert os.path.exists(backuppath)
|
||||||
|
assert not len(newhistory[None, None, None])
|
||||||
@@ -2,10 +2,11 @@ from unittest import TestCase
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import networkx as nx
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from os.path import join
|
from os.path import join
|
||||||
from soil import simulation, agents, utils
|
from soil import simulation, environment, agents, utils
|
||||||
|
|
||||||
|
|
||||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
@@ -21,6 +22,7 @@ class TestMain(TestCase):
|
|||||||
Raise an exception otherwise.
|
Raise an exception otherwise.
|
||||||
"""
|
"""
|
||||||
config = {
|
config = {
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
}
|
}
|
||||||
@@ -30,6 +32,7 @@ class TestMain(TestCase):
|
|||||||
assert len(G) == 2
|
assert len(G) == 2
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
config = {
|
config = {
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'unknown.extension')
|
'path': join(ROOT, 'unknown.extension')
|
||||||
}
|
}
|
||||||
@@ -43,6 +46,7 @@ class TestMain(TestCase):
|
|||||||
should be used to generate a network
|
should be used to generate a network
|
||||||
"""
|
"""
|
||||||
config = {
|
config = {
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'generator': 'barabasi_albert_graph'
|
'generator': 'barabasi_albert_graph'
|
||||||
}
|
}
|
||||||
@@ -57,43 +61,48 @@ class TestMain(TestCase):
|
|||||||
def test_empty_simulation(self):
|
def test_empty_simulation(self):
|
||||||
"""A simulation with a base behaviour should do nothing"""
|
"""A simulation with a base behaviour should do nothing"""
|
||||||
config = {
|
config = {
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
},
|
},
|
||||||
'agent_type': 'NetworkAgent',
|
'agent_type': 'BaseAgent',
|
||||||
'environment_params': {
|
'environment_params': {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
s.run_simulation()
|
s.run_simulation(dry_run=True)
|
||||||
|
|
||||||
def test_counter_agent(self):
|
def test_counter_agent(self):
|
||||||
"""
|
"""
|
||||||
The initial states should be applied to the agent and the
|
The initial states should be applied to the agent and the
|
||||||
agent should be able to update its state."""
|
agent should be able to update its state."""
|
||||||
config = {
|
config = {
|
||||||
|
'name': 'CounterAgent',
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
},
|
},
|
||||||
'agent_type': 'CounterModel',
|
'agent_type': 'CounterModel',
|
||||||
'states': [{'neighbors': 10}, {'total': 12}],
|
'states': [{'times': 10}, {'times': 20}],
|
||||||
'max_time': 2,
|
'max_time': 2,
|
||||||
'num_trials': 1,
|
'num_trials': 1,
|
||||||
'environment_params': {
|
'environment_params': {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
env = s.run_simulation()[0]
|
env = s.run_simulation(dry_run=True)[0]
|
||||||
assert env.get_agent(0)['neighbors', 0] == 10
|
assert env.get_agent(0)['times', 0] == 11
|
||||||
assert env.get_agent(0)['neighbors', 1] == 1
|
assert env.get_agent(0)['times', 1] == 12
|
||||||
assert env.get_agent(1)['total', 0] == 12
|
assert env.get_agent(1)['times', 0] == 21
|
||||||
assert env.get_agent(1)['neighbors', 1] == 1
|
assert env.get_agent(1)['times', 1] == 22
|
||||||
|
|
||||||
def test_counter_agent_history(self):
|
def test_counter_agent_history(self):
|
||||||
"""
|
"""
|
||||||
The evolution of the state should be recorded in the logging agent
|
The evolution of the state should be recorded in the logging agent
|
||||||
"""
|
"""
|
||||||
config = {
|
config = {
|
||||||
|
'name': 'CounterAgent',
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
},
|
},
|
||||||
@@ -108,22 +117,22 @@ class TestMain(TestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
env = s.run_simulation()[0]
|
env = s.run_simulation(dry_run=True)[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]) == 10
|
||||||
for step, total in agent['total', None].items():
|
for step, total in sorted(agent['total', None]):
|
||||||
if step > 0:
|
assert total == last + 2
|
||||||
assert total == last + 2
|
last = total
|
||||||
last = total
|
|
||||||
|
|
||||||
def test_custom_agent(self):
|
def test_custom_agent(self):
|
||||||
"""Allow for search of neighbors with a certain state_id"""
|
"""Allow for search of neighbors with a certain state_id"""
|
||||||
class CustomAgent(agents.NetworkAgent):
|
class CustomAgent(agents.BaseAgent):
|
||||||
def step(self):
|
def step(self):
|
||||||
self.state['neighbors'] = self.count_agents(state_id=0,
|
self.state['neighbors'] = self.count_agents(state_id=0,
|
||||||
limit_neighbors=True)
|
limit_neighbors=True)
|
||||||
config = {
|
config = {
|
||||||
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
'path': join(ROOT, 'test.gexf')
|
'path': join(ROOT, 'test.gexf')
|
||||||
},
|
},
|
||||||
@@ -138,7 +147,7 @@ class TestMain(TestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
env = s.run_simulation()[0]
|
env = s.run_simulation(dry_run=True)[0]
|
||||||
assert env.get_agent(0).state['neighbors'] == 1
|
assert env.get_agent(0).state['neighbors'] == 1
|
||||||
|
|
||||||
def test_torvalds_example(self):
|
def test_torvalds_example(self):
|
||||||
@@ -147,6 +156,7 @@ class TestMain(TestCase):
|
|||||||
config['network_params']['path'] = join(EXAMPLES,
|
config['network_params']['path'] = join(EXAMPLES,
|
||||||
config['network_params']['path'])
|
config['network_params']['path'])
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
|
s.dry_run = True
|
||||||
env = s.run_simulation()[0]
|
env = s.run_simulation()[0]
|
||||||
for a in env.network_agents:
|
for a in env.network_agents:
|
||||||
skill_level = a.state['skill_level']
|
skill_level = a.state['skill_level']
|
||||||
@@ -171,12 +181,15 @@ class TestMain(TestCase):
|
|||||||
with utils.timer('loading'):
|
with utils.timer('loading'):
|
||||||
config = utils.load_file(join(EXAMPLES, 'complete.yml'))[0]
|
config = utils.load_file(join(EXAMPLES, 'complete.yml'))[0]
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
|
s.dry_run = True
|
||||||
with utils.timer('serializing'):
|
with utils.timer('serializing'):
|
||||||
serial = s.to_yaml()
|
serial = s.to_yaml()
|
||||||
with utils.timer('recovering'):
|
with utils.timer('recovering'):
|
||||||
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']
|
||||||
|
del recovered['dry_run']
|
||||||
assert config == recovered
|
assert config == recovered
|
||||||
|
|
||||||
def test_configuration_changes(self):
|
def test_configuration_changes(self):
|
||||||
@@ -186,16 +199,43 @@ class TestMain(TestCase):
|
|||||||
"""
|
"""
|
||||||
config = utils.load_file('examples/complete.yml')[0]
|
config = utils.load_file('examples/complete.yml')[0]
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
|
s.dry_run = True
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
s.run_simulation()
|
s.run_simulation(dry_run=True)
|
||||||
nconfig = s.to_dict()
|
nconfig = s.to_dict()
|
||||||
del nconfig['topology']
|
del nconfig['topology']
|
||||||
|
del nconfig['dry_run']
|
||||||
|
del nconfig['load_module']
|
||||||
assert config == nconfig
|
assert config == nconfig
|
||||||
|
|
||||||
def test_examples(self):
|
def test_examples(self):
|
||||||
"""
|
"""
|
||||||
Make sure all examples in the examples folder are correct
|
Make sure all examples in the examples folder are correct
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_row_conversion(self):
|
||||||
|
env = environment.SoilEnvironment(dry_run=True)
|
||||||
|
env['test'] = 'test_value'
|
||||||
|
|
||||||
|
res = list(env.history_to_tuples())
|
||||||
|
assert len(res) == len(env.environment_params)
|
||||||
|
|
||||||
|
env._now = 1
|
||||||
|
env['test'] = 'second_value'
|
||||||
|
res = list(env.history_to_tuples())
|
||||||
|
|
||||||
|
assert env['env', 0, 'test' ] == 'test_value'
|
||||||
|
assert env['env', 1, 'test' ] == 'second_value'
|
||||||
|
|
||||||
|
def test_save_geometric(self):
|
||||||
|
"""
|
||||||
|
There is a bug in networkx that prevents it from creating a GEXF file
|
||||||
|
from geometric models. We should work around it.
|
||||||
|
"""
|
||||||
|
G = nx.random_geometric_graph(20,0.1)
|
||||||
|
env = environment.SoilEnvironment(topology=G, dry_run=True)
|
||||||
|
env.dump_gexf('/tmp/dump-gexf')
|
||||||
|
|
||||||
|
|
||||||
def make_example_test(path, config):
|
def make_example_test(path, config):
|
||||||
@@ -203,11 +243,15 @@ def make_example_test(path, config):
|
|||||||
root = os.getcwd()
|
root = os.getcwd()
|
||||||
os.chdir(os.path.dirname(path))
|
os.chdir(os.path.dirname(path))
|
||||||
s = simulation.from_config(config)
|
s = simulation.from_config(config)
|
||||||
envs = s.run_simulation()
|
envs = s.run_simulation(dry_run=True)
|
||||||
|
assert envs
|
||||||
for env in envs:
|
for env in envs:
|
||||||
n = config['network_params'].get('n', None)
|
assert env
|
||||||
if n is not None:
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||