mirror of
https://github.com/gsi-upm/soil
synced 2024-11-22 11:12:29 +00:00
Merge branch 'refactor-imports'
* remove leftover import in example * Update quickstart tutorial * Add gitlab-ci * Added missing gexf for tests * Upgrade to python3.7 and pandas 0.3.4 because pandas has dropped support for python 3.4 -> There are some API changes in pandas, and I've updated the code accordingly. * Set pytest as the default test runner * Update dockerignore * Skip testing long examples (>1000 steps)
This commit is contained in:
commit
01cc8e9128
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
**/soil_output
|
||||||
|
.*
|
21
.gitlab-ci.yml
Normal file
21
.gitlab-ci.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
image: python:3.7
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
image:
|
||||||
|
name: gcr.io/kaniko-project/executor:debug
|
||||||
|
entrypoint: [""]
|
||||||
|
script:
|
||||||
|
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||||
|
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
|
||||||
|
|
||||||
|
test:
|
||||||
|
script:
|
||||||
|
python setup.py test
|
@ -1,4 +1,11 @@
|
|||||||
FROM python:3.4-onbuild
|
FROM python:3.7
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY test-requirements.txt requirements.txt /usr/src/app/
|
||||||
|
RUN pip install --no-cache-dir -r test-requirements.txt -r requirements.txt
|
||||||
|
|
||||||
|
COPY ./ /usr/src/app
|
||||||
|
|
||||||
RUN pip install '.[web]'
|
RUN pip install '.[web]'
|
||||||
|
|
||||||
|
4
Makefile
Normal file
4
Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
test:
|
||||||
|
docker-compose exec dev python -m pytest -s -v
|
||||||
|
|
||||||
|
.PHONY: test
|
@ -2,6 +2,8 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
dev:
|
dev:
|
||||||
build: .
|
build: .
|
||||||
|
environment:
|
||||||
|
PYTHONDONTWRITEBYTECODE: 1
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/app
|
- .:/usr/src/app
|
||||||
tty: true
|
tty: true
|
||||||
|
244
docs/configuration.rst
Normal file
244
docs/configuration.rst
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
Configuring a simulation
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
There are two ways to configure a simulation: programmatically and with a configuration file.
|
||||||
|
In both cases, the parameters used are the same.
|
||||||
|
The advantage of a configuration file is that it is a clean declarative description, and it makes it easier to reproduce.
|
||||||
|
|
||||||
|
Simulation configuration files can be formatted in ``json`` or ``yaml`` and they define all the parameters of a simulation.
|
||||||
|
Here's an example (``example.yml``).
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
name: MyExampleSimulation
|
||||||
|
max_time: 50
|
||||||
|
num_trials: 3
|
||||||
|
interval: 2
|
||||||
|
network_params:
|
||||||
|
generator: barabasi_albert_graph
|
||||||
|
n: 100
|
||||||
|
m: 2
|
||||||
|
network_agents:
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 1
|
||||||
|
state:
|
||||||
|
id: content
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 1
|
||||||
|
state:
|
||||||
|
id: discontent
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 8
|
||||||
|
state:
|
||||||
|
id: neutral
|
||||||
|
environment_params:
|
||||||
|
prob_infect: 0.075
|
||||||
|
|
||||||
|
|
||||||
|
This example configuration will run three trials (``num_trials``) of a simulation containing a randomly generated network (``network_params``).
|
||||||
|
The 100 nodes in the network will be SISaModel agents (``network_agents.agent_type``), which is an agent behavior that is included in Soil.
|
||||||
|
10% of the agents (``weight=1``) will start in the content state, 10% in the discontent state, and the remaining 80% (``weight=8``) in the neutral state.
|
||||||
|
All agents will have access to the environment (``environment_params``), 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:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
soil example.yml
|
||||||
|
|
||||||
|
Once the simulation finishes, its results will be stored in a folder named ``MyExampleSimulation``.
|
||||||
|
Three 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 ``sqlite`` file with the content of the state of every network node and the environment parameters at every step of the simulation.
|
||||||
|
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
soil_output
|
||||||
|
└── MyExampleSimulation
|
||||||
|
├── MyExampleSimulation.dumped.yml
|
||||||
|
├── MyExampleSimulation.simulation.pickle
|
||||||
|
├── MyExampleSimulation_trial_0.db.sqlite
|
||||||
|
├── MyExampleSimulation_trial_1.db.sqlite
|
||||||
|
└── MyExampleSimulation_trial_2.db.sqlite
|
||||||
|
|
||||||
|
|
||||||
|
You may also ask soil to export the states in a ``csv`` file, and the network in gephi format (``gexf``).
|
||||||
|
|
||||||
|
Network
|
||||||
|
=======
|
||||||
|
|
||||||
|
The network topology for the simulation can be loaded from an existing network file or generated with one of the random network generation methods from networkx.
|
||||||
|
|
||||||
|
Loading a network
|
||||||
|
#################
|
||||||
|
|
||||||
|
To load an existing network, specify its path in the configuration:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
network_params:
|
||||||
|
path: /tmp/mynetwork.gexf
|
||||||
|
|
||||||
|
Soil will try to guess what networkx method to use to read the file based on its extension.
|
||||||
|
However, we only test using ``gexf`` files.
|
||||||
|
|
||||||
|
For simple networks, you may also include them in the configuration itself using , using the ``topology`` parameter like so:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
topology:
|
||||||
|
nodes:
|
||||||
|
- id: First
|
||||||
|
- id: Second
|
||||||
|
links:
|
||||||
|
- source: First
|
||||||
|
target: Second
|
||||||
|
|
||||||
|
|
||||||
|
Generating a random network
|
||||||
|
###########################
|
||||||
|
|
||||||
|
To generate a random network using one of networkx's built-in methods, specify the `graph generation algorithm <https://networkx.github.io/documentation/development/reference/generators.html>`_ and other parameters.
|
||||||
|
For example, the following configuration is equivalent to :code:`nx.complete_graph(n=100)`:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
network_params:
|
||||||
|
generator: complete_graph
|
||||||
|
n: 100
|
||||||
|
|
||||||
|
Environment
|
||||||
|
============
|
||||||
|
The environment is the place where the shared state of the simulation is stored.
|
||||||
|
For instance, the probability of disease outbreak.
|
||||||
|
The configuration file may specify the initial value of the environment parameters:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
environment_params:
|
||||||
|
daily_probability_of_earthquake: 0.001
|
||||||
|
number_of_earthquakes: 0
|
||||||
|
|
||||||
|
All agents have access to the environment parameters.
|
||||||
|
|
||||||
|
In some scenarios, it is useful to have a custom environment, to provide additional methods or to control the way agents update environment state.
|
||||||
|
For example, if our agents play the lottery, the environment could provide a method to decide whether the agent wins, instead of leaving it to the agent.
|
||||||
|
|
||||||
|
|
||||||
|
Agents
|
||||||
|
======
|
||||||
|
Agents are a way of modelling behavior.
|
||||||
|
Agents can be characterized with two variables: agent type (``agent_type``) and state.
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 are attached to a node in the topology.
|
||||||
|
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.
|
||||||
|
Hence, every node in the network will be associated to an agent of that type.
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
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 (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.
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
network_agents:
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 1
|
||||||
|
- agent_type: CounterModel
|
||||||
|
weight: 5
|
||||||
|
|
||||||
|
The third option is to specify the type of agent on the node itself, e.g.:
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
topology:
|
||||||
|
nodes:
|
||||||
|
- id: first
|
||||||
|
agent_type: BaseAgent
|
||||||
|
states:
|
||||||
|
first:
|
||||||
|
agent_type: SISaModel
|
||||||
|
|
||||||
|
|
||||||
|
This would also work with a randomly generated network:
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
network:
|
||||||
|
generator: complete
|
||||||
|
n: 5
|
||||||
|
agent_type: BaseAgent
|
||||||
|
states:
|
||||||
|
- agent_type: SISaModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
In addition to agent type, you may add a custom initial state to the distribution.
|
||||||
|
This is very useful to add the same agent type with different states.
|
||||||
|
e.g., to populate the network with SISaModel, roughly 10% of them with a discontent state:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
network_agents:
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 9
|
||||||
|
state:
|
||||||
|
id: neutral
|
||||||
|
- agent_type: SISaModel
|
||||||
|
weight: 1
|
||||||
|
state:
|
||||||
|
id: discontent
|
||||||
|
|
||||||
|
Lastly, the configuration may include initial state for one or more nodes.
|
||||||
|
For instance, to add a state for the two nodes in this configuration:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
agent_type: SISaModel
|
||||||
|
network:
|
||||||
|
generator: complete_graph
|
||||||
|
n: 2
|
||||||
|
states:
|
||||||
|
- id: content
|
||||||
|
- id: discontent
|
||||||
|
|
||||||
|
|
||||||
|
Or to add state only to specific nodes (by ``id``).
|
||||||
|
For example, to apply special skills to Linux Torvalds in a simulation:
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/torvalds.yml
|
||||||
|
:language: yaml
|
||||||
|
|
||||||
|
|
||||||
|
Environment Agents
|
||||||
|
##################
|
||||||
|
In addition to network agents, more agents can be added to the simulation.
|
||||||
|
These agents are programmed in much the same way as network agents, the only difference is that they will not be assigned to network nodes.
|
||||||
|
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
environment_agents:
|
||||||
|
- agent_type: MyAgent
|
||||||
|
state:
|
||||||
|
mood: happy
|
||||||
|
- agent_type: DummyAgent
|
||||||
|
|
||||||
|
|
||||||
|
You may use environment agents to model events that a normal agent cannot control, such as natural disasters or chance.
|
||||||
|
They are also useful to add behavior that has little to do with the network and the interactions within that network.
|
@ -6,7 +6,7 @@
|
|||||||
Welcome to Soil's documentation!
|
Welcome to Soil's documentation!
|
||||||
================================
|
================================
|
||||||
|
|
||||||
Soil is an Agent-based Social Simulator in Python for modelling and simulation of Social Networks.
|
Soil is an Agent-based Social Simulator in Python focused on Social Networks.
|
||||||
|
|
||||||
If you use Soil in your research, do not forget to cite this paper:
|
If you use Soil in your research, do not forget to cite this paper:
|
||||||
|
|
||||||
@ -39,6 +39,7 @@ If you use Soil in your research, do not forget to cite this paper:
|
|||||||
|
|
||||||
installation
|
installation
|
||||||
quickstart
|
quickstart
|
||||||
|
configuration
|
||||||
Tutorial <soil_tutorial>
|
Tutorial <soil_tutorial>
|
||||||
|
|
||||||
..
|
..
|
||||||
|
@ -1,197 +1,94 @@
|
|||||||
Quickstart
|
Quickstart
|
||||||
----------
|
----------
|
||||||
|
|
||||||
This section shows how to run simulations from simulation configuration files.
|
This section shows how to run your first simulation with Soil.
|
||||||
First of all, you need to install the package (See :doc:`installation`)
|
For installation instructions, see :doc:`installation`.
|
||||||
|
|
||||||
Simulation configuration files are ``json`` or ``yaml`` files that define all the parameters of a simulation.
|
There are mainly two parts in a simulation: agent classes and simulation configuration.
|
||||||
Here's an example (``example.yml``).
|
An agent class defines how the agent will behave throughout the simulation.
|
||||||
|
The configuration includes things such as number of agents to use and their type, network topology to use, etc.
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
---
|
|
||||||
name: MyExampleSimulation
|
|
||||||
max_time: 50
|
|
||||||
num_trials: 3
|
|
||||||
interval: 2
|
|
||||||
network_params:
|
|
||||||
network_type: barabasi_albert_graph
|
|
||||||
n: 100
|
|
||||||
m: 2
|
|
||||||
agent_distribution:
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: content
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: discontent
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 8
|
|
||||||
state:
|
|
||||||
id: neutral
|
|
||||||
environment_params:
|
|
||||||
prob_infect: 0.075
|
|
||||||
|
|
||||||
|
|
||||||
This example configuration will run three trials of a simulation containing a randomly generated network.
|
.. image:: soil.png
|
||||||
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.
|
:width: 80%
|
||||||
All agents will have access to the environment, which only contains one variable, ``prob_infected``.
|
:align: center
|
||||||
The state of the agents will be updated every 2 seconds (``interval``).
|
|
||||||
|
|
||||||
Now run the simulation with the command line tool:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
soil example.yml
|
|
||||||
|
|
||||||
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 (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::
|
Soil includes several agent classes in the ``soil.agents`` module, and we will use them in this quickstart.
|
||||||
|
If you are interested in developing your own agents classes, see :doc:`soil_tutorial`.
|
||||||
|
The configuration is the following:
|
||||||
|
|
||||||
soil_output
|
.. literalinclude:: quickstart.yml
|
||||||
├── Sim_prob_0
|
|
||||||
│ ├── Sim_prob_0.dumped.yml
|
|
||||||
│ ├── Sim_prob_0.simulation.pickle
|
|
||||||
│ ├── Sim_prob_0_trial_0.environment.csv
|
|
||||||
│ └── Sim_prob_0_trial_0.gexf
|
|
||||||
|
|
||||||
|
|
||||||
Network
|
|
||||||
=======
|
|
||||||
|
|
||||||
The network topology for the simulation can be loaded from an existing network file or generated with one of the random network generation methods from networkx.
|
|
||||||
|
|
||||||
Loading a network
|
|
||||||
#################
|
|
||||||
|
|
||||||
To load an existing network, specify its path in the configuration:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
---
|
|
||||||
network_params:
|
|
||||||
path: /tmp/mynetwork.gexf
|
|
||||||
|
|
||||||
Soil will try to guess what networkx method to use to read the file based on its extension.
|
|
||||||
However, we only test using ``gexf`` files.
|
|
||||||
|
|
||||||
Generating a random network
|
|
||||||
###########################
|
|
||||||
|
|
||||||
To generate a random network using one of networkx's built-in methods, specify the `graph generation algorithm <https://networkx.github.io/documentation/development/reference/generators.html>`_ and other parameters.
|
|
||||||
For example, the following configuration is equivalent to :code:`nx.complete_graph(100)`:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
network_params:
|
|
||||||
network_type: complete_graph
|
|
||||||
n: 100
|
|
||||||
|
|
||||||
Environment
|
|
||||||
============
|
|
||||||
The environment is the place where the shared state of the simulation is stored.
|
|
||||||
For instance, the probability of disease outbreak.
|
|
||||||
The configuration file may specify the initial value of the environment parameters:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
environment_params:
|
|
||||||
daily_probability_of_earthquake: 0.001
|
|
||||||
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 are a way of modelling behavior.
|
|
||||||
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 ``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.
|
|
||||||
|
|
||||||
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 are attached to a node in the topology.
|
|
||||||
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.
|
|
||||||
Hence, every node in the network will be associated to an agent of that type.
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
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 (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.
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
agent_distribution:
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
- agent_type: CounterModel
|
|
||||||
weight: 5
|
|
||||||
|
|
||||||
In addition to agent type, you may also add a custom initial state to the distribution.
|
|
||||||
This is very useful to add the same agent type with different states.
|
|
||||||
e.g., to populate the network with SISaModel, roughly 10% of them with a discontent state:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
agent_distribution:
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 9
|
|
||||||
state:
|
|
||||||
id: neutral
|
|
||||||
- agent_type: SISaModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
id: discontent
|
|
||||||
|
|
||||||
Lastly, the configuration may include initial state for one or more nodes.
|
|
||||||
For instance, to add a state for the two nodes in this configuration:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
agent_type: SISaModel
|
|
||||||
network:
|
|
||||||
network_type: complete_graph
|
|
||||||
n: 2
|
|
||||||
states:
|
|
||||||
- id: content
|
|
||||||
- id: discontent
|
|
||||||
|
|
||||||
|
|
||||||
Or to add state only to specific nodes (by ``id``).
|
|
||||||
For example, to apply special skills to Linux Torvalds in a simulation:
|
|
||||||
|
|
||||||
.. literalinclude:: ../examples/torvalds.yml
|
|
||||||
:language: yaml
|
:language: yaml
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
Environment Agents
|
You may :download:`download the file <quickstart.yml>` directly.
|
||||||
##################
|
The agent type used, SISa, is a very simple model.
|
||||||
In addition to network agents, more agents can be added to the simulation.
|
It only has three states (neutral, content and discontent),
|
||||||
These agens are programmed in much the same way as network agents, the only difference is that they will not be assigned to network nodes.
|
Its parameters are the probabilities to change from one state to another, either spontaneously or because of contagion from neighboring agents.
|
||||||
|
|
||||||
|
Running the simulation
|
||||||
|
======================
|
||||||
|
|
||||||
|
To see the simulation in action, simply point soil to the configuration, and tell it to store the graph and the history of agent states and environment parameters at every point.
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
environment_agents:
|
❯ soil --graph --csv quickstart.yml [13:35:29]
|
||||||
- agent_type: MyAgent
|
INFO:soil:Using config(s): quickstart
|
||||||
state:
|
INFO:soil:Dumping results to soil_output/quickstart : ['csv', 'gexf']
|
||||||
mood: happy
|
INFO:soil:Starting simulation quickstart at 13:35:30.
|
||||||
- agent_type: DummyAgent
|
INFO:soil:Starting Simulation quickstart trial 0 at 13:35:30.
|
||||||
|
INFO:soil:Finished Simulation quickstart trial 0 at 13:35:49 in 19.43677067756653 seconds
|
||||||
|
INFO:soil:Starting Dumping simulation quickstart trial 0 at 13:35:49.
|
||||||
|
INFO:soil:Finished Dumping simulation quickstart trial 0 at 13:35:51 in 1.7733407020568848 seconds
|
||||||
|
INFO:soil:Dumping results to soil_output/quickstart
|
||||||
|
INFO:soil:Finished simulation quickstart at 13:35:51 in 21.29862952232361 seconds
|
||||||
|
|
||||||
|
|
||||||
Visualizing the results
|
The ``CSV`` file should look like this:
|
||||||
=======================
|
|
||||||
|
|
||||||
The simulation will return a dynamic graph .gexf file which could be visualized with
|
.. code::
|
||||||
|
|
||||||
|
agent_id,t_step,key,value
|
||||||
|
env,0,neutral_discontent_spon_prob,0.05
|
||||||
|
env,0,neutral_discontent_infected_prob,0.1
|
||||||
|
env,0,neutral_content_spon_prob,0.2
|
||||||
|
env,0,neutral_content_infected_prob,0.4
|
||||||
|
env,0,discontent_neutral,0.2
|
||||||
|
env,0,discontent_content,0.05
|
||||||
|
env,0,content_discontent,0.05
|
||||||
|
env,0,variance_d_c,0.05
|
||||||
|
env,0,variance_c_d,0.1
|
||||||
|
|
||||||
|
Results and visualization
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The environment variables are marked as ``agent_id`` env.
|
||||||
|
Th exported values are only stored when they change.
|
||||||
|
To find out how to get every key and value at every point in the simulation, check out the :doc:`soil_tutorial`.
|
||||||
|
|
||||||
|
The dynamic graph is exported as a .gexf file which could be visualized with
|
||||||
`Gephi <https://gephi.org/users/download/>`__.
|
`Gephi <https://gephi.org/users/download/>`__.
|
||||||
|
Now it is your turn to experiment with the simulation.
|
||||||
|
Change some of the parameters, such as the number of agents, the probability of becoming content, or the type of network, and see how the results change.
|
||||||
|
|
||||||
|
|
||||||
|
Soil also includes a web server that allows you to upload your simulations, change parameters, and visualize the results, including a timeline of the network.
|
||||||
|
To make it work, you have to install soil like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install soil[web]
|
||||||
|
```
|
||||||
|
|
||||||
|
Once installed, the soil web UI can be run in two ways:
|
||||||
|
|
||||||
|
```
|
||||||
|
soil-web
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
python -m soil.web
|
||||||
|
```
|
30
docs/quickstart.yml
Normal file
30
docs/quickstart.yml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: quickstart
|
||||||
|
num_trials: 1
|
||||||
|
max_time: 1000
|
||||||
|
network_agents:
|
||||||
|
- agent_type: SISaModel
|
||||||
|
state:
|
||||||
|
id: neutral
|
||||||
|
weight: 1
|
||||||
|
- agent_type: SISaModel
|
||||||
|
state:
|
||||||
|
id: content
|
||||||
|
weight: 2
|
||||||
|
network_params:
|
||||||
|
n: 100
|
||||||
|
k: 5
|
||||||
|
p: 0.2
|
||||||
|
generator: newman_watts_strogatz_graph
|
||||||
|
environment_params:
|
||||||
|
neutral_discontent_spon_prob: 0.05
|
||||||
|
neutral_discontent_infected_prob: 0.1
|
||||||
|
neutral_content_spon_prob: 0.2
|
||||||
|
neutral_content_infected_prob: 0.4
|
||||||
|
discontent_neutral: 0.2
|
||||||
|
discontent_content: 0.05
|
||||||
|
content_discontent: 0.05
|
||||||
|
variance_d_c: 0.05
|
||||||
|
variance_c_d: 0.1
|
||||||
|
content_neutral: 0.1
|
||||||
|
standard_variance: 0.1
|
BIN
docs/soil.png
Normal file
BIN
docs/soil.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
@ -214,7 +214,7 @@ nodes in that network. Notice how node 0 is the only one with a TV.
|
|||||||
MAX_TIME = 100
|
MAX_TIME = 100
|
||||||
EVENT_TIME = 10
|
EVENT_TIME = 10
|
||||||
|
|
||||||
sim = soil.simulation.SoilSimulation(topology=G,
|
sim = soil.Simulation(topology=G,
|
||||||
num_trials=1,
|
num_trials=1,
|
||||||
max_time=MAX_TIME,
|
max_time=MAX_TIME,
|
||||||
environment_agents=[{'agent_type': NewsEnvironmentAgent,
|
environment_agents=[{'agent_type': NewsEnvironmentAgent,
|
||||||
|
@ -2,14 +2,22 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"start_time": "2017-11-02T09:48:41.843Z"
|
"start_time": "2017-11-02T09:48:41.843Z"
|
||||||
},
|
},
|
||||||
"scrolled": false
|
"scrolled": false
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Populating the interactive namespace from numpy and matplotlib\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"import soil\n",
|
"import soil\n",
|
||||||
"import networkx as nx\n",
|
"import networkx as nx\n",
|
||||||
@ -39,26 +47,216 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"total 288K\r\n",
|
||||||
|
"drwxr-xr-x 7 j users 4.0K May 23 12:48 .\r\n",
|
||||||
|
"drwxr-xr-x 15 j users 20K May 7 18:59 ..\r\n",
|
||||||
|
"-rw-r--r-- 1 j users 451 Oct 17 2017 complete.yml\r\n",
|
||||||
|
"drwxr-xr-x 2 j users 4.0K Feb 18 11:22 .ipynb_checkpoints\r\n",
|
||||||
|
"drwxr-xr-x 2 j users 4.0K Oct 17 2017 long_running\r\n",
|
||||||
|
"-rw-r--r-- 1 j users 1.2K May 23 12:49 .nbgrader.log\r\n",
|
||||||
|
"drwxr-xr-x 4 j users 4.0K May 4 11:23 newsspread\r\n",
|
||||||
|
"-rw-r--r-- 1 j users 225K May 4 11:23 NewsSpread.ipynb\r\n",
|
||||||
|
"drwxr-xr-x 4 j users 4.0K May 4 11:21 rabbits\r\n",
|
||||||
|
"-rw-r--r-- 1 j users 42 Jul 3 2017 torvalds.edgelist\r\n",
|
||||||
|
"-rw-r--r-- 1 j users 245 Oct 13 2017 torvalds.yml\r\n",
|
||||||
|
"drwxr-xr-x 4 j users 4.0K May 4 11:23 tutorial\r\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!ls "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"start_time": "2017-11-02T09:48:43.440Z"
|
"start_time": "2017-11-02T09:48:43.440Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"---\r\n",
|
||||||
|
"default_state: {}\r\n",
|
||||||
|
"load_module: newsspread\r\n",
|
||||||
|
"environment_agents: []\r\n",
|
||||||
|
"environment_params:\r\n",
|
||||||
|
" prob_neighbor_spread: 0.0\r\n",
|
||||||
|
" prob_tv_spread: 0.01\r\n",
|
||||||
|
"interval: 1\r\n",
|
||||||
|
"max_time: 30\r\n",
|
||||||
|
"name: Sim_all_dumb\r\n",
|
||||||
|
"network_agents:\r\n",
|
||||||
|
"- agent_type: DumbViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: false\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: DumbViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"num_trials: 50\r\n",
|
||||||
|
"---\r\n",
|
||||||
|
"default_state: {}\r\n",
|
||||||
|
"load_module: newsspread\r\n",
|
||||||
|
"environment_agents: []\r\n",
|
||||||
|
"environment_params:\r\n",
|
||||||
|
" prob_neighbor_spread: 0.0\r\n",
|
||||||
|
" prob_tv_spread: 0.01\r\n",
|
||||||
|
"interval: 1\r\n",
|
||||||
|
"max_time: 30\r\n",
|
||||||
|
"name: Sim_half_herd\r\n",
|
||||||
|
"network_agents:\r\n",
|
||||||
|
"- agent_type: DumbViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: false\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: DumbViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: HerdViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: false\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: HerdViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"num_trials: 50\r\n",
|
||||||
|
"---\r\n",
|
||||||
|
"default_state: {}\r\n",
|
||||||
|
"load_module: newsspread\r\n",
|
||||||
|
"environment_agents: []\r\n",
|
||||||
|
"environment_params:\r\n",
|
||||||
|
" prob_neighbor_spread: 0.0\r\n",
|
||||||
|
" prob_tv_spread: 0.01\r\n",
|
||||||
|
"interval: 1\r\n",
|
||||||
|
"max_time: 30\r\n",
|
||||||
|
"name: Sim_all_herd\r\n",
|
||||||
|
"network_agents:\r\n",
|
||||||
|
"- agent_type: HerdViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" id: neutral\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: HerdViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" id: neutral\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"num_trials: 50\r\n",
|
||||||
|
"---\r\n",
|
||||||
|
"default_state: {}\r\n",
|
||||||
|
"load_module: newsspread\r\n",
|
||||||
|
"environment_agents: []\r\n",
|
||||||
|
"environment_params:\r\n",
|
||||||
|
" prob_neighbor_spread: 0.0\r\n",
|
||||||
|
" prob_tv_spread: 0.01\r\n",
|
||||||
|
" prob_neighbor_cure: 0.1\r\n",
|
||||||
|
"interval: 1\r\n",
|
||||||
|
"max_time: 30\r\n",
|
||||||
|
"name: Sim_wise_herd\r\n",
|
||||||
|
"network_agents:\r\n",
|
||||||
|
"- agent_type: HerdViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" id: neutral\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: WiseViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"num_trials: 50\r\n",
|
||||||
|
"---\r\n",
|
||||||
|
"default_state: {}\r\n",
|
||||||
|
"load_module: newsspread\r\n",
|
||||||
|
"environment_agents: []\r\n",
|
||||||
|
"environment_params:\r\n",
|
||||||
|
" prob_neighbor_spread: 0.0\r\n",
|
||||||
|
" prob_tv_spread: 0.01\r\n",
|
||||||
|
" prob_neighbor_cure: 0.1\r\n",
|
||||||
|
"interval: 1\r\n",
|
||||||
|
"max_time: 30\r\n",
|
||||||
|
"name: Sim_all_wise\r\n",
|
||||||
|
"network_agents:\r\n",
|
||||||
|
"- agent_type: WiseViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" id: neutral\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"- agent_type: WiseViewer\r\n",
|
||||||
|
" state:\r\n",
|
||||||
|
" has_tv: true\r\n",
|
||||||
|
" weight: 1\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"network_params:\r\n",
|
||||||
|
" generator: barabasi_albert_graph\r\n",
|
||||||
|
" n: 500\r\n",
|
||||||
|
" m: 5\r\n",
|
||||||
|
"num_trials: 50\r\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"!cat NewsSpread.yml"
|
"!cat newsspread/NewsSpread.yml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 10,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"start_time": "2017-11-02T09:48:43.879Z"
|
"start_time": "2017-11-02T09:48:43.879Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [
|
||||||
|
{
|
||||||
|
"ename": "ValueError",
|
||||||
|
"evalue": "No objects to concatenate",
|
||||||
|
"output_type": "error",
|
||||||
|
"traceback": [
|
||||||
|
"\u001b[0;31m----------------------------------------------------------------------\u001b[0m",
|
||||||
|
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
|
||||||
|
"\u001b[0;32m<ipython-input-10-bae848826594>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mevodumb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0manalysis\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'soil_output/Sim_all_dumb/'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroup\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mprocess\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0manalysis\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_count\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkeys\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'id'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||||
|
"\u001b[0;32m~/git/lab.gsi/soil/soil/soil/analysis.py\u001b[0m in \u001b[0;36mread_data\u001b[0;34m(group, *args, **kwargs)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0miterable\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_read_data\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgroup\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mgroup_trials\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miterable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||||
|
"\u001b[0;32m~/git/lab.gsi/soil/soil/soil/analysis.py\u001b[0m in \u001b[0;36mgroup_trials\u001b[0;34m(trials, aggfunc)\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0mtrials\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrials\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 160\u001b[0m \u001b[0mtrials\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrials\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 161\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconcat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrials\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroupby\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlevel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0magg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maggfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreorder_levels\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m,\u001b[0m\u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 162\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 163\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||||
|
"\u001b[0;32m~/.local/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36mconcat\u001b[0;34m(objs, axis, join, join_axes, ignore_index, keys, levels, names, verify_integrity, copy)\u001b[0m\n\u001b[1;32m 210\u001b[0m \u001b[0mkeys\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlevels\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mlevels\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnames\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mnames\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 211\u001b[0m \u001b[0mverify_integrity\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mverify_integrity\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 212\u001b[0;31m copy=copy)\n\u001b[0m\u001b[1;32m 213\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 214\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||||
|
"\u001b[0;32m~/.local/lib/python3.6/site-packages/pandas/core/reshape/concat.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, objs, axis, join, join_axes, keys, levels, names, ignore_index, verify_integrity, copy)\u001b[0m\n\u001b[1;32m 243\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 244\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobjs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 245\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'No objects to concatenate'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 246\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 247\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mkeys\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||||
|
"\u001b[0;31mValueError\u001b[0m: No objects to concatenate"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"evodumb = analysis.read_data('soil_output/Sim_all_dumb/', group=True, process=analysis.get_count, keys=['id']);"
|
"evodumb = analysis.read_data('soil_output/Sim_all_dumb/', group=True, process=analysis.get_count, keys=['id']);"
|
||||||
]
|
]
|
||||||
@ -302,7 +500,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.6.2"
|
"version": "3.6.5"
|
||||||
},
|
},
|
||||||
"toc": {
|
"toc": {
|
||||||
"colors": {
|
"colors": {
|
||||||
|
80808
examples/Untitled.ipynb
Normal file
80808
examples/Untitled.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
|||||||
name: simple
|
name: simple
|
||||||
dir_path: "/tmp/"
|
dir_path: "/tmp/"
|
||||||
num_trials: 3
|
num_trials: 3
|
||||||
|
dry_run: True
|
||||||
max_time: 100
|
max_time: 100
|
||||||
interval: 1
|
interval: 1
|
||||||
seed: "CompleteSeed!"
|
seed: "CompleteSeed!"
|
||||||
@ -17,6 +18,7 @@ network_agents:
|
|||||||
- agent_type: AggregatedCounter
|
- agent_type: AggregatedCounter
|
||||||
weight: 0.2
|
weight: 0.2
|
||||||
environment_agents: []
|
environment_agents: []
|
||||||
|
environment_class: Environment
|
||||||
environment_params:
|
environment_params:
|
||||||
am_i_complete: true
|
am_i_complete: true
|
||||||
default_state:
|
default_state:
|
||||||
|
10
examples/pubcrawl/README.md
Normal file
10
examples/pubcrawl/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Simulation of pubs and drinking pals that go from pub to pub.
|
||||||
|
|
||||||
|
Th custom environment includes a list of pubs and methods to allow agents to discover and enter pubs.
|
||||||
|
There are two types of agents:
|
||||||
|
|
||||||
|
* Patron. A patron will do three things, in this order:
|
||||||
|
* Look for other patrons to drink with
|
||||||
|
* Look for a pub where the agent and other agents in the same group can get in.
|
||||||
|
* While in the pub, patrons only drink, until they get drunk and taken home.
|
||||||
|
* Police. There is only one police agent that will take any drunk patrons home (kick them out of the pub).
|
174
examples/pubcrawl/pubcrawl.py
Normal file
174
examples/pubcrawl/pubcrawl.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
from soil.agents import FSM, state, default_state
|
||||||
|
from soil import Environment
|
||||||
|
from random import random, shuffle
|
||||||
|
from itertools import islice
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class CityPubs(Environment):
|
||||||
|
'''Environment with Pubs'''
|
||||||
|
level = logging.INFO
|
||||||
|
|
||||||
|
def __init__(self, *args, number_of_pubs=3, pub_capacity=10, **kwargs):
|
||||||
|
super(CityPubs, self).__init__(*args, **kwargs)
|
||||||
|
pubs = {}
|
||||||
|
for i in range(number_of_pubs):
|
||||||
|
newpub = {
|
||||||
|
'name': 'The awesome pub #{}'.format(i),
|
||||||
|
'open': True,
|
||||||
|
'capacity': pub_capacity,
|
||||||
|
'occupancy': 0,
|
||||||
|
}
|
||||||
|
pubs[newpub['name']] = newpub
|
||||||
|
self['pubs'] = pubs
|
||||||
|
|
||||||
|
def enter(self, pub_id, *nodes):
|
||||||
|
'''Agents will try to enter. The pub checks if it is possible'''
|
||||||
|
try:
|
||||||
|
pub = self['pubs'][pub_id]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError('Pub {} is not available'.format(pub_id))
|
||||||
|
if not pub['open'] or (pub['capacity'] < (len(nodes) + pub['occupancy'])):
|
||||||
|
return False
|
||||||
|
pub['occupancy'] += len(nodes)
|
||||||
|
for node in nodes:
|
||||||
|
node['pub'] = pub_id
|
||||||
|
return True
|
||||||
|
|
||||||
|
def available_pubs(self):
|
||||||
|
for pub in self['pubs'].values():
|
||||||
|
if pub['open'] and (pub['occupancy'] < pub['capacity']):
|
||||||
|
yield pub['name']
|
||||||
|
|
||||||
|
def exit(self, pub_id, *node_ids):
|
||||||
|
'''Agents will notify the pub they want to leave'''
|
||||||
|
try:
|
||||||
|
pub = self['pubs'][pub_id]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError('Pub {} is not available'.format(pub_id))
|
||||||
|
for node_id in node_ids:
|
||||||
|
node = self.get_agent(node_id)
|
||||||
|
if pub_id == node['pub']:
|
||||||
|
del node['pub']
|
||||||
|
pub['occupancy'] -= 1
|
||||||
|
|
||||||
|
|
||||||
|
class Patron(FSM):
|
||||||
|
'''Agent that looks for friends to drink with. It will do three things:
|
||||||
|
1) Look for other patrons to drink with
|
||||||
|
2) Look for a bar where the agent and other agents in the same group can get in.
|
||||||
|
3) While in the bar, patrons only drink, until they get drunk and taken home.
|
||||||
|
'''
|
||||||
|
level = logging.INFO
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'pub': None,
|
||||||
|
'drunk': False,
|
||||||
|
'pints': 0,
|
||||||
|
'max_pints': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def looking_for_friends(self):
|
||||||
|
'''Look for friends to drink with'''
|
||||||
|
self.info('I am looking for friends')
|
||||||
|
available_friends = list(self.get_agents(drunk=False,
|
||||||
|
pub=None,
|
||||||
|
state_id=self.looking_for_friends.id))
|
||||||
|
if not available_friends:
|
||||||
|
self.info('Life sucks and I\'m alone!')
|
||||||
|
return self.at_home
|
||||||
|
befriended = self.try_friends(available_friends)
|
||||||
|
if befriended:
|
||||||
|
return self.looking_for_pub
|
||||||
|
|
||||||
|
@state
|
||||||
|
def looking_for_pub(self):
|
||||||
|
'''Look for a pub that accepts me and my friends'''
|
||||||
|
if self['pub'] != None:
|
||||||
|
return self.sober_in_pub
|
||||||
|
self.debug('I am looking for a pub')
|
||||||
|
group = list(self.get_neighboring_agents())
|
||||||
|
for pub in self.env.available_pubs():
|
||||||
|
self.debug('We\'re trying to get into {}: total: {}'.format(pub, len(group)))
|
||||||
|
if self.env.enter(pub, self, *group):
|
||||||
|
self.info('We\'re all {} getting in {}!'.format(len(group), pub))
|
||||||
|
return self.sober_in_pub
|
||||||
|
|
||||||
|
@state
|
||||||
|
def sober_in_pub(self):
|
||||||
|
'''Drink up.'''
|
||||||
|
self.drink()
|
||||||
|
if self['pints'] > self['max_pints']:
|
||||||
|
return self.drunk_in_pub
|
||||||
|
|
||||||
|
@state
|
||||||
|
def drunk_in_pub(self):
|
||||||
|
'''I'm out. Take me home!'''
|
||||||
|
self.info('I\'m so drunk. Take me home!')
|
||||||
|
self['drunk'] = True
|
||||||
|
pass # out drunk
|
||||||
|
|
||||||
|
@state
|
||||||
|
def at_home(self):
|
||||||
|
'''The end'''
|
||||||
|
self.debug('Life sucks. I\'m home!')
|
||||||
|
|
||||||
|
def drink(self):
|
||||||
|
self['pints'] += 1
|
||||||
|
self.debug('Cheers to that')
|
||||||
|
|
||||||
|
def kick_out(self):
|
||||||
|
self.set_state(self.at_home)
|
||||||
|
|
||||||
|
def befriend(self, other_agent, force=False):
|
||||||
|
'''
|
||||||
|
Try to become friends with another agent. The chances of
|
||||||
|
success depend on both agents' openness.
|
||||||
|
'''
|
||||||
|
if force or self['openness'] > random():
|
||||||
|
self.env.add_edge(self, other_agent)
|
||||||
|
self.info('Made some friend {}'.format(other_agent))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def try_friends(self, others):
|
||||||
|
''' Look for random agents around me and try to befriend them'''
|
||||||
|
befriended = False
|
||||||
|
k = int(10*self['openness'])
|
||||||
|
shuffle(others)
|
||||||
|
for friend in islice(others, k): # random.choice >= 3.7
|
||||||
|
if friend == self:
|
||||||
|
continue
|
||||||
|
if friend.befriend(self):
|
||||||
|
self.befriend(friend, force=True)
|
||||||
|
self.debug('Hooray! new friend: {}'.format(friend.id))
|
||||||
|
befriended = True
|
||||||
|
else:
|
||||||
|
self.debug('{} does not want to be friends'.format(friend.id))
|
||||||
|
return befriended
|
||||||
|
|
||||||
|
|
||||||
|
class Police(FSM):
|
||||||
|
'''Simple agent to take drunk people out of pubs.'''
|
||||||
|
level = logging.INFO
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def patrol(self):
|
||||||
|
drunksters = list(self.get_agents(drunk=True,
|
||||||
|
state_id=Patron.drunk_in_pub.id))
|
||||||
|
for drunk in drunksters:
|
||||||
|
self.info('Kicking out the trash: {}'.format(drunk.id))
|
||||||
|
drunk.kick_out()
|
||||||
|
else:
|
||||||
|
self.info('No trash to take out. Too bad.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from soil import simulation
|
||||||
|
simulation.run_from_config('pubcrawl.yml',
|
||||||
|
dry_run=True,
|
||||||
|
dump=None,
|
||||||
|
parallel=False)
|
26
examples/pubcrawl/pubcrawl.yml
Normal file
26
examples/pubcrawl/pubcrawl.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: pubcrawl
|
||||||
|
num_trials: 3
|
||||||
|
max_time: 10
|
||||||
|
dump: false
|
||||||
|
network_params:
|
||||||
|
# Generate 100 empty nodes. They will be assigned a network agent
|
||||||
|
generator: empty_graph
|
||||||
|
n: 30
|
||||||
|
network_agents:
|
||||||
|
- agent_type: pubcrawl.Patron
|
||||||
|
description: Extroverted patron
|
||||||
|
state:
|
||||||
|
openness: 1.0
|
||||||
|
weight: 9
|
||||||
|
- agent_type: pubcrawl.Patron
|
||||||
|
description: Introverted patron
|
||||||
|
state:
|
||||||
|
openness: 0.1
|
||||||
|
weight: 1
|
||||||
|
environment_agents:
|
||||||
|
- agent_type: pubcrawl.Police
|
||||||
|
environment_class: pubcrawl.CityPubs
|
||||||
|
environment_params:
|
||||||
|
altercations: 0
|
||||||
|
number_of_pubs: 3
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
load_module: rabbit_agents
|
load_module: rabbit_agents
|
||||||
name: rabbits_example
|
name: rabbits_example
|
||||||
max_time: 1200
|
max_time: 500
|
||||||
interval: 1
|
interval: 1
|
||||||
seed: MySeed
|
seed: MySeed
|
||||||
agent_type: RabbitModel
|
agent_type: RabbitModel
|
||||||
|
@ -12327,7 +12327,7 @@ Notice how node 0 is the only one with a TV.</p>
|
|||||||
<span class="n">MAX_TIME</span> <span class="o">=</span> <span class="mi">100</span>
|
<span class="n">MAX_TIME</span> <span class="o">=</span> <span class="mi">100</span>
|
||||||
<span class="n">EVENT_TIME</span> <span class="o">=</span> <span class="mi">10</span>
|
<span class="n">EVENT_TIME</span> <span class="o">=</span> <span class="mi">10</span>
|
||||||
|
|
||||||
<span class="n">sim</span> <span class="o">=</span> <span class="n">soil</span><span class="o">.</span><span class="n">simulation</span><span class="o">.</span><span class="n">SoilSimulation</span><span class="p">(</span><span class="n">topology</span><span class="o">=</span><span class="n">G</span><span class="p">,</span>
|
<span class="n">sim</span> <span class="o">=</span> <span class="n">soil</span><span class="o">.</span><span class="n">Simulation</span><span class="p">(</span><span class="n">topology</span><span class="o">=</span><span class="n">G</span><span class="p">,</span>
|
||||||
<span class="n">num_trials</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
|
<span class="n">num_trials</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
|
||||||
<span class="n">max_time</span><span class="o">=</span><span class="n">MAX_TIME</span><span class="p">,</span>
|
<span class="n">max_time</span><span class="o">=</span><span class="n">MAX_TIME</span><span class="p">,</span>
|
||||||
<span class="n">environment_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">'agent_type'</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
|
<span class="n">environment_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">'agent_type'</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
|
||||||
|
@ -426,7 +426,7 @@
|
|||||||
"MAX_TIME = 100\n",
|
"MAX_TIME = 100\n",
|
||||||
"EVENT_TIME = 10\n",
|
"EVENT_TIME = 10\n",
|
||||||
"\n",
|
"\n",
|
||||||
"sim = soil.simulation.SoilSimulation(topology=G,\n",
|
"sim = soil.Simulation(topology=G,\n",
|
||||||
" num_trials=1,\n",
|
" num_trials=1,\n",
|
||||||
" max_time=MAX_TIME,\n",
|
" max_time=MAX_TIME,\n",
|
||||||
" environment_agents=[{'agent_type': NewsEnvironmentAgent,\n",
|
" environment_agents=[{'agent_type': NewsEnvironmentAgent,\n",
|
||||||
|
4
setup.cfg
Normal file
4
setup.cfg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[aliases]
|
||||||
|
test=pytest
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = --verbose
|
@ -1 +1 @@
|
|||||||
0.12.0
|
0.13.0
|
@ -14,12 +14,11 @@ except NameError:
|
|||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
|
||||||
from . import agents
|
from . import agents
|
||||||
from . import simulation
|
from .simulation import *
|
||||||
from . import environment
|
from .environment import Environment
|
||||||
from . import utils
|
from . import utils
|
||||||
from . import analysis
|
from . import analysis
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import argparse
|
import argparse
|
||||||
from . import simulation
|
from . import simulation
|
||||||
@ -46,11 +45,12 @@ def main():
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.module:
|
if os.getcwd() not in sys.path:
|
||||||
sys.path.append(os.getcwd())
|
sys.path.append(os.getcwd())
|
||||||
|
if args.module:
|
||||||
importlib.import_module(args.module)
|
importlib.import_module(args.module)
|
||||||
|
|
||||||
logging.info('Loading config file: {}'.format(args.file, args.output))
|
logging.info('Loading config file: {}'.format(args.file))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dump = []
|
dump = []
|
||||||
@ -64,7 +64,7 @@ def main():
|
|||||||
dump=dump,
|
dump=dump,
|
||||||
parallel=(not args.synchronous and not args.pdb),
|
parallel=(not args.synchronous and not args.pdb),
|
||||||
results_dir=args.output)
|
results_dir=args.output)
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
if args.pdb:
|
if args.pdb:
|
||||||
pdb.post_mortem()
|
pdb.post_mortem()
|
||||||
else:
|
else:
|
||||||
|
@ -10,7 +10,7 @@ class SISaModel(FSM):
|
|||||||
|
|
||||||
neutral_discontent_infected_prob
|
neutral_discontent_infected_prob
|
||||||
|
|
||||||
neutral_content_spong_prob
|
neutral_content_spon_prob
|
||||||
|
|
||||||
neutral_content_infected_prob
|
neutral_content_infected_prob
|
||||||
|
|
||||||
@ -29,27 +29,27 @@ class SISaModel(FSM):
|
|||||||
standard_variance
|
standard_variance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
def __init__(self, environment, agent_id=0, state=()):
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||||
|
|
||||||
self.neutral_discontent_spon_prob = np.random.normal(environment.environment_params['neutral_discontent_spon_prob'],
|
self.neutral_discontent_spon_prob = np.random.normal(self.env['neutral_discontent_spon_prob'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
self.neutral_discontent_infected_prob = np.random.normal(environment.environment_params['neutral_discontent_infected_prob'],
|
self.neutral_discontent_infected_prob = np.random.normal(self.env['neutral_discontent_infected_prob'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
self.neutral_content_spon_prob = np.random.normal(environment.environment_params['neutral_content_spon_prob'],
|
self.neutral_content_spon_prob = np.random.normal(self.env['neutral_content_spon_prob'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
self.neutral_content_infected_prob = np.random.normal(environment.environment_params['neutral_content_infected_prob'],
|
self.neutral_content_infected_prob = np.random.normal(self.env['neutral_content_infected_prob'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
|
|
||||||
self.discontent_neutral = np.random.normal(environment.environment_params['discontent_neutral'],
|
self.discontent_neutral = np.random.normal(self.env['discontent_neutral'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
self.discontent_content = np.random.normal(environment.environment_params['discontent_content'],
|
self.discontent_content = np.random.normal(self.env['discontent_content'],
|
||||||
environment.environment_params['variance_d_c'])
|
self.env['variance_d_c'])
|
||||||
|
|
||||||
self.content_discontent = np.random.normal(environment.environment_params['content_discontent'],
|
self.content_discontent = np.random.normal(self.env['content_discontent'],
|
||||||
environment.environment_params['variance_c_d'])
|
self.env['variance_c_d'])
|
||||||
self.content_neutral = np.random.normal(environment.environment_params['content_neutral'],
|
self.content_neutral = np.random.normal(self.env['content_neutral'],
|
||||||
environment.environment_params['standard_variance'])
|
self.env['standard_variance'])
|
||||||
|
|
||||||
@state
|
@state
|
||||||
def neutral(self):
|
def neutral(self):
|
||||||
|
@ -16,7 +16,7 @@ class SentimentCorrelationModel(BaseAgent):
|
|||||||
disgust_prob
|
disgust_prob
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, environment=None, agent_id=0, state=()):
|
def __init__(self, environment, agent_id=0, state=()):
|
||||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||||
self.outside_effects_prob = environment.environment_params['outside_effects_prob']
|
self.outside_effects_prob = environment.environment_params['outside_effects_prob']
|
||||||
self.anger_prob = environment.environment_params['anger_prob']
|
self.anger_prob = environment.environment_params['anger_prob']
|
||||||
|
@ -16,23 +16,15 @@ from functools import wraps
|
|||||||
|
|
||||||
from .. import utils, history
|
from .. import utils, history
|
||||||
|
|
||||||
agent_types = {}
|
|
||||||
|
|
||||||
|
class BaseAgent(nxsim.BaseAgent):
|
||||||
class MetaAgent(type):
|
|
||||||
def __init__(cls, name, bases, nmspc):
|
|
||||||
super(MetaAgent, cls).__init__(name, bases, nmspc)
|
|
||||||
agent_types[name] = cls
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defaults = {}
|
defaults = {}
|
||||||
|
|
||||||
def __init__(self, environment=None, agent_id=None, state=None,
|
def __init__(self, environment, agent_id=None, state=None,
|
||||||
name='network_process', interval=None, **state_params):
|
name='network_process', interval=None, **state_params):
|
||||||
# Check for REQUIRED arguments
|
# Check for REQUIRED arguments
|
||||||
assert environment is not None, TypeError('__init__ missing 1 required keyword argument: \'environment\'. '
|
assert environment is not None, TypeError('__init__ missing 1 required keyword argument: \'environment\'. '
|
||||||
@ -152,14 +144,18 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
|||||||
def count_neighboring_agents(self, state_id=None):
|
def count_neighboring_agents(self, state_id=None):
|
||||||
return len(super().get_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):
|
def get_agents(self, state_id=None, agent_type=None, limit_neighbors=False, iterator=False, **kwargs):
|
||||||
|
agents = self.env.agents
|
||||||
if limit_neighbors:
|
if limit_neighbors:
|
||||||
agents = super().get_agents(state_id, 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):
|
def matches_all(agent):
|
||||||
|
if state_id is not None:
|
||||||
|
if agent.state.get('id', None) != state_id:
|
||||||
|
return False
|
||||||
|
if agent_type is not None:
|
||||||
|
if type(agent) != agent_type:
|
||||||
|
return False
|
||||||
state = agent.state
|
state = agent.state
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
if state.get(k, None) != v:
|
if state.get(k, None) != v:
|
||||||
@ -219,7 +215,7 @@ def default_state(func):
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
class MetaFSM(MetaAgent):
|
class MetaFSM(type):
|
||||||
def __init__(cls, name, bases, nmspc):
|
def __init__(cls, name, bases, nmspc):
|
||||||
super(MetaFSM, cls).__init__(name, bases, nmspc)
|
super(MetaFSM, cls).__init__(name, bases, nmspc)
|
||||||
states = {}
|
states = {}
|
||||||
@ -328,16 +324,39 @@ def calculate_distribution(network_agents=None,
|
|||||||
return network_agents
|
return network_agents
|
||||||
|
|
||||||
|
|
||||||
def _serialize_distribution(network_agents):
|
def serialize_type(agent_type, known_modules=[], **kwargs):
|
||||||
d = _convert_agent_types(network_agents,
|
if isinstance(agent_type, str):
|
||||||
to_string=True)
|
return agent_type
|
||||||
|
known_modules += ['soil.agents']
|
||||||
|
return utils.serialize(agent_type, known_modules=known_modules, **kwargs)[1] # Get the name of the class
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_distribution(network_agents, known_modules=[]):
|
||||||
'''
|
'''
|
||||||
When serializing an agent distribution, remove the thresholds, in order
|
When serializing an agent distribution, remove the thresholds, in order
|
||||||
to avoid cluttering the YAML definition file.
|
to avoid cluttering the YAML definition file.
|
||||||
'''
|
'''
|
||||||
|
d = deepcopy(network_agents)
|
||||||
for v in d:
|
for v in d:
|
||||||
if 'threshold' in v:
|
if 'threshold' in v:
|
||||||
del v['threshold']
|
del v['threshold']
|
||||||
|
v['agent_type'] = serialize_type(v['agent_type'],
|
||||||
|
known_modules=known_modules)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_type(agent_type, known_modules=[]):
|
||||||
|
if not isinstance(agent_type, str):
|
||||||
|
return agent_type
|
||||||
|
known = known_modules + ['soil.agents', 'soil.agents.custom' ]
|
||||||
|
agent_type = utils.deserializer(agent_type, known_modules=known)
|
||||||
|
return agent_type
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_distribution(ind, **kwargs):
|
||||||
|
d = deepcopy(ind)
|
||||||
|
for v in d:
|
||||||
|
v['agent_type'] = deserialize_type(v['agent_type'], **kwargs)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
@ -352,16 +371,11 @@ def _validate_states(states, topology):
|
|||||||
return states
|
return states
|
||||||
|
|
||||||
|
|
||||||
def _convert_agent_types(ind, to_string=False):
|
def _convert_agent_types(ind, to_string=False, **kwargs):
|
||||||
'''Convenience method to allow specifying agents by class or class name.'''
|
'''Convenience method to allow specifying agents by class or class name.'''
|
||||||
d = deepcopy(ind)
|
if to_string:
|
||||||
for v in d:
|
return serialize_distribution(ind, **kwargs)
|
||||||
agent_type = v['agent_type']
|
return deserialize_distribution(ind, **kwargs)
|
||||||
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):
|
def _agent_from_distribution(distribution, value=-1):
|
||||||
|
@ -56,7 +56,7 @@ def read_csv(filename, keys=None, convert_types=False, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def convert_row(row):
|
def convert_row(row):
|
||||||
row['value'] = utils.convert(row['value'], row['value_type'])
|
row['value'] = utils.deserialize(row['value_type'], row['value'])
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ def get_count(df, *keys):
|
|||||||
df = df[list(keys)]
|
df = df[list(keys)]
|
||||||
counts = pd.DataFrame()
|
counts = pd.DataFrame()
|
||||||
for key in df.columns.levels[0]:
|
for key in df.columns.levels[0]:
|
||||||
g = df[key].apply(pd.Series.value_counts, axis=1).fillna(0)
|
g = df[[key]].apply(pd.Series.value_counts, axis=1).fillna(0)
|
||||||
for value, series in g.iteritems():
|
for value, series in g.iteritems():
|
||||||
counts[key, value] = series
|
counts[key, value] = series
|
||||||
counts.columns = pd.MultiIndex.from_tuples(counts.columns)
|
counts.columns = pd.MultiIndex.from_tuples(counts.columns)
|
||||||
|
@ -15,7 +15,7 @@ import nxsim
|
|||||||
from . import utils, agents, analysis, history
|
from . import utils, agents, analysis, history
|
||||||
|
|
||||||
|
|
||||||
class SoilEnvironment(nxsim.NetworkEnvironment):
|
class Environment(nxsim.NetworkEnvironment):
|
||||||
"""
|
"""
|
||||||
The environment is key in a simulation. It contains the network topology,
|
The environment is key in a simulation. It contains the network topology,
|
||||||
a reference to network and environment agents, as well as the environment
|
a reference to network and environment agents, as well as the environment
|
||||||
@ -23,7 +23,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
|
|
||||||
The environment parameters and the state of every agent can be accessed
|
The environment parameters and the state of every agent can be accessed
|
||||||
both by using the environment as a dictionary or with the environment's
|
both by using the environment as a dictionary or with the environment's
|
||||||
:meth:`soil.environment.SoilEnvironment.get` method.
|
:meth:`soil.environment.Environment.get` method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None,
|
def __init__(self, name=None,
|
||||||
@ -49,7 +49,8 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.dir_path = dir_path or tempfile.mkdtemp('soil-env')
|
self.dir_path = dir_path or tempfile.mkdtemp('soil-env')
|
||||||
self.get_path()
|
if not dry_run:
|
||||||
|
self.get_path()
|
||||||
self._history = history.History(name=self.name if not dry_run else None,
|
self._history = history.History(name=self.name if not dry_run else None,
|
||||||
dir_path=self.dir_path)
|
dir_path=self.dir_path)
|
||||||
# Add environment agents first, so their events get
|
# Add environment agents first, so their events get
|
||||||
@ -93,17 +94,35 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
if not network_agents:
|
if not network_agents:
|
||||||
return
|
return
|
||||||
for ix in self.G.nodes():
|
for ix in self.G.nodes():
|
||||||
agent, state = agents._agent_from_distribution(network_agents)
|
self.init_agent(ix, agent_distribution=network_agents)
|
||||||
self.set_agent(ix, agent_type=agent, state=state)
|
|
||||||
|
def init_agent(self, agent_id, agent_distribution):
|
||||||
|
node = self.G.nodes[agent_id]
|
||||||
|
init = False
|
||||||
|
state = dict(node)
|
||||||
|
|
||||||
|
agent_type = None
|
||||||
|
if 'agent_type' in self.states.get(agent_id, {}):
|
||||||
|
agent_type = self.states[agent_id]
|
||||||
|
elif 'agent_type' in node:
|
||||||
|
agent_type = node['agent_type']
|
||||||
|
elif 'agent_type' in self.default_state:
|
||||||
|
agent_type = self.default_state['agent_type']
|
||||||
|
|
||||||
|
if agent_type:
|
||||||
|
agent_type = agents.deserialize_type(agent_type)
|
||||||
|
else:
|
||||||
|
agent_type, state = agents._agent_from_distribution(agent_distribution)
|
||||||
|
return self.set_agent(agent_id, agent_type, state)
|
||||||
|
|
||||||
def set_agent(self, agent_id, agent_type, state=None):
|
def set_agent(self, agent_id, agent_type, state=None):
|
||||||
node = self.G.nodes[agent_id]
|
node = self.G.nodes[agent_id]
|
||||||
defstate = deepcopy(self.default_state)
|
defstate = deepcopy(self.default_state) or {}
|
||||||
defstate.update(self.states.get(agent_id, {}))
|
defstate.update(self.states.get(agent_id, {}))
|
||||||
|
defstate.update(node.get('state', {}))
|
||||||
if state:
|
if state:
|
||||||
defstate.update(state)
|
defstate.update(state)
|
||||||
state = defstate
|
state = defstate
|
||||||
state.update(node.get('state', {}))
|
|
||||||
a = agent_type(environment=self,
|
a = agent_type(environment=self,
|
||||||
agent_id=agent_id,
|
agent_id=agent_id,
|
||||||
state=state)
|
state=state)
|
||||||
@ -118,6 +137,10 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
return a
|
return a
|
||||||
|
|
||||||
def add_edge(self, agent1, agent2, attrs=None):
|
def add_edge(self, agent1, agent2, attrs=None):
|
||||||
|
if hasattr(agent1, 'id'):
|
||||||
|
agent1 = agent1.id
|
||||||
|
if hasattr(agent2, 'id'):
|
||||||
|
agent2 = agent2.id
|
||||||
return self.G.add_edge(agent1, agent2)
|
return self.G.add_edge(agent1, agent2)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
@ -202,7 +225,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', 't_step', 'key', 'value', 'value_type'))
|
cr.writerow(('agent_id', 't_step', 'key', 'value'))
|
||||||
for i in self.history_to_tuples():
|
for i in self.history_to_tuples():
|
||||||
cr.writerow(i)
|
cr.writerow(i)
|
||||||
|
|
||||||
@ -302,7 +325,6 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
||||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||||
to_string=True)
|
to_string=True)
|
||||||
del state['_queue']
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
@ -311,3 +333,6 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
|||||||
self.network_agents = self.calculate_distribution(self._convert_agent_types(self.network_agents))
|
self.network_agents = self.calculate_distribution(self._convert_agent_types(self.network_agents))
|
||||||
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
SoilEnvironment = Environment
|
||||||
|
103
soil/history.py
103
soil/history.py
@ -3,7 +3,7 @@ import os
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import copy
|
import copy
|
||||||
from collections import UserDict, Iterable, namedtuple
|
from collections import UserDict, namedtuple
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
@ -17,12 +17,12 @@ class History:
|
|||||||
if db_path is None and name:
|
if db_path is None and name:
|
||||||
db_path = os.path.join(dir_path or os.getcwd(),
|
db_path = os.path.join(dir_path or os.getcwd(),
|
||||||
'{}.db.sqlite'.format(name))
|
'{}.db.sqlite'.format(name))
|
||||||
if db_path is None:
|
if db_path:
|
||||||
db_path = ":memory:"
|
|
||||||
else:
|
|
||||||
if backup and os.path.exists(db_path):
|
if backup and os.path.exists(db_path):
|
||||||
newname = db_path + '.backup{}.sqlite'.format(time.time())
|
newname = db_path + '.backup{}.sqlite'.format(time.time())
|
||||||
os.rename(db_path, newname)
|
os.rename(db_path, newname)
|
||||||
|
else:
|
||||||
|
db_path = ":memory:"
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
|
|
||||||
self.db = db_path
|
self.db = db_path
|
||||||
@ -34,12 +34,6 @@ class History:
|
|||||||
self._dtypes = {}
|
self._dtypes = {}
|
||||||
self._tups = []
|
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
|
@property
|
||||||
def db(self):
|
def db(self):
|
||||||
try:
|
try:
|
||||||
@ -58,55 +52,88 @@ class History:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def dtypes(self):
|
def dtypes(self):
|
||||||
|
self.read_types()
|
||||||
return {k:v[0] for k, v in self._dtypes.items()}
|
return {k:v[0] for k, v in self._dtypes.items()}
|
||||||
|
|
||||||
def save_tuples(self, tuples):
|
def save_tuples(self, tuples):
|
||||||
|
'''
|
||||||
|
Save a series of tuples, converting them to records if necessary
|
||||||
|
'''
|
||||||
self.save_records(Record(*tup) for tup in tuples)
|
self.save_records(Record(*tup) for tup in tuples)
|
||||||
|
|
||||||
def save_records(self, records):
|
def save_records(self, records):
|
||||||
with self.db:
|
'''
|
||||||
for rec in records:
|
Save a collection of records
|
||||||
if not isinstance(rec, Record):
|
'''
|
||||||
rec = Record(*rec)
|
for record in records:
|
||||||
if rec.key not in self._dtypes:
|
if not isinstance(record, Record):
|
||||||
name = utils.name(rec.value)
|
record = Record(*record)
|
||||||
serializer = utils.serializer(name)
|
self.save_record(*record)
|
||||||
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):
|
def save_record(self, agent_id, t_step, key, value):
|
||||||
self._tups.append(Record(*args, **kwargs))
|
'''
|
||||||
|
Save a collection of records to the database.
|
||||||
|
Database writes are cached.
|
||||||
|
'''
|
||||||
|
value = self.convert(key, value)
|
||||||
|
self._tups.append(Record(agent_id=agent_id,
|
||||||
|
t_step=t_step,
|
||||||
|
key=key,
|
||||||
|
value=value))
|
||||||
if len(self._tups) > 100:
|
if len(self._tups) > 100:
|
||||||
self.flush_cache()
|
self.flush_cache()
|
||||||
|
|
||||||
|
def convert(self, key, value):
|
||||||
|
"""Get the serialized value for a given key."""
|
||||||
|
if key not in self._dtypes:
|
||||||
|
self.read_types()
|
||||||
|
if key not in self._dtypes:
|
||||||
|
name = utils.name(value)
|
||||||
|
serializer = utils.serializer(name)
|
||||||
|
deserializer = utils.deserializer(name)
|
||||||
|
self._dtypes[key] = (name, serializer, deserializer)
|
||||||
|
with self.db:
|
||||||
|
self.db.execute("replace into value_types (key, value_type) values (?, ?)", (key, name))
|
||||||
|
return self._dtypes[key][1](value)
|
||||||
|
|
||||||
|
def recover(self, key, value):
|
||||||
|
"""Get the deserialized value for a given key, and the serialized version."""
|
||||||
|
if key not in self._dtypes:
|
||||||
|
self.read_types()
|
||||||
|
if key not in self._dtypes:
|
||||||
|
raise ValueError("Unknown datatype for {} and {}".format(key, value))
|
||||||
|
return self._dtypes[key][2](value)
|
||||||
|
|
||||||
|
|
||||||
def flush_cache(self):
|
def flush_cache(self):
|
||||||
'''
|
'''
|
||||||
Use a cache to save state changes to avoid opening a session for every change.
|
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.
|
The cache will be flushed at the end of the simulation, and when history is accessed.
|
||||||
'''
|
'''
|
||||||
self.save_records(self._tups)
|
with self.db:
|
||||||
|
for rec in self._tups:
|
||||||
|
self.db.execute("replace into history(agent_id, t_step, key, value) values (?, ?, ?, ?)", (rec.agent_id, rec.t_step, rec.key, rec.value))
|
||||||
self._tups = list()
|
self._tups = list()
|
||||||
|
|
||||||
def to_tuples(self):
|
def to_tuples(self):
|
||||||
self.flush_cache()
|
self.flush_cache()
|
||||||
with self.db:
|
with self.db:
|
||||||
res = self.db.execute("select agent_id, t_step, key, value from history ").fetchall()
|
res = self.db.execute("select agent_id, t_step, key, value from history ").fetchall()
|
||||||
for r in res:
|
for r in res:
|
||||||
agent_id, t_step, key, value = r
|
agent_id, t_step, key, value = r
|
||||||
_, _ , des = self.conversors(key)
|
value = self.recover(key, value)
|
||||||
yield agent_id, t_step, key, des(value)
|
yield agent_id, t_step, key, value
|
||||||
|
|
||||||
def read_types(self):
|
def read_types(self):
|
||||||
with self.db:
|
with self.db:
|
||||||
res = self.db.execute("select key, value_type from value_types ").fetchall()
|
res = self.db.execute("select key, value_type from value_types ").fetchall()
|
||||||
for k, v in res:
|
for k, v in res:
|
||||||
serializer = utils.serializer(v)
|
serializer = utils.serializer(v)
|
||||||
deserializer = utils.deserializer(v)
|
deserializer = utils.deserializer(v)
|
||||||
self._dtypes[k] = (v, serializer, deserializer)
|
self._dtypes[k] = (v, serializer, deserializer)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
self.flush_cache()
|
||||||
key = Key(*key)
|
key = Key(*key)
|
||||||
agent_ids = [key.agent_id] if key.agent_id is not None else []
|
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 []
|
t_steps = [key.t_step] if key.t_step is not None else []
|
||||||
@ -176,7 +203,7 @@ class History:
|
|||||||
for k, v in self._dtypes.items():
|
for k, v in self._dtypes.items():
|
||||||
if k in df_p:
|
if k in df_p:
|
||||||
dtype, _, deserial = v
|
dtype, _, deserial = v
|
||||||
df_p[k] = df_p[k].fillna(method='ffill').fillna(deserial()).astype(dtype)
|
df_p[k] = df_p[k].fillna(method='ffill').astype(dtype)
|
||||||
if t_steps:
|
if t_steps:
|
||||||
df_p = df_p.reindex(t_steps, method='ffill')
|
df_p = df_p.reindex(t_steps, method='ffill')
|
||||||
return df_p.ffill()
|
return df_p.ffill()
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import imp
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
import traceback
|
||||||
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 multiprocessing import Pool
|
||||||
@ -12,11 +13,12 @@ import pickle
|
|||||||
|
|
||||||
from nxsim import NetworkSimulation
|
from nxsim import NetworkSimulation
|
||||||
|
|
||||||
from . import utils, environment, basestring, agents
|
from . import utils, basestring, agents
|
||||||
|
from .environment import Environment
|
||||||
from .utils import logger
|
from .utils import logger
|
||||||
|
|
||||||
|
|
||||||
class SoilSimulation(NetworkSimulation):
|
class Simulation(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.
|
||||||
@ -43,13 +45,48 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
'agent_type_1'.
|
'agent_type_1'.
|
||||||
3) if no initial state is given, each node's state will be set
|
3) if no initial state is given, each node's state will be set
|
||||||
to `{'id': 0}`.
|
to `{'id': 0}`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
---------
|
||||||
|
name : str, optional
|
||||||
|
name of the Simulation
|
||||||
|
topology : networkx.Graph instance, optional
|
||||||
|
network_params : dict
|
||||||
|
parameters used to create a topology with networkx, if no topology is given
|
||||||
|
network_agents : dict
|
||||||
|
definition of agents to populate the topology with
|
||||||
|
agent_type : NetworkAgent subclass, optional
|
||||||
|
Default type of NetworkAgent to use for nodes not specified in network_agents
|
||||||
|
states : list, optional
|
||||||
|
List of initial states corresponding to the nodes in the topology. Basic form is a list of integers
|
||||||
|
whose value indicates the state
|
||||||
|
dir_path : str, optional
|
||||||
|
Directory path where to save pickled objects
|
||||||
|
seed : str, optional
|
||||||
|
Seed to use for the random generator
|
||||||
|
num_trials : int, optional
|
||||||
|
Number of independent simulation runs
|
||||||
|
max_time : int, optional
|
||||||
|
Time how long the simulation should run
|
||||||
|
environment_params : dict, optional
|
||||||
|
Dictionary of globally-shared environmental parameters
|
||||||
|
environment_agents: dict, optional
|
||||||
|
Similar to network_agents. Distribution of Agents that control the environment
|
||||||
|
environment_class: soil.environment.Environment subclass, optional
|
||||||
|
Class for the environment. It defailts to soil.environment.Environment
|
||||||
|
load_module : str, module name, deprecated
|
||||||
|
If specified, soil will load the content of this module under 'soil.agents.custom'
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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, dump=None, dry_run=False,
|
default_state=None, interval=1, dump=None, dry_run=False,
|
||||||
dir_path=None, num_trials=1, max_time=100,
|
dir_path=None, num_trials=1, max_time=100,
|
||||||
agent_module=None, load_module=None, seed=None,
|
load_module=None, seed=None,
|
||||||
environment_agents=None, environment_params=None, **kwargs):
|
environment_agents=None, environment_params=None,
|
||||||
|
environment_class=None, **kwargs):
|
||||||
|
|
||||||
if topology is None:
|
if topology is None:
|
||||||
topology = utils.load_network(network_params,
|
topology = utils.load_network(network_params,
|
||||||
@ -69,19 +106,21 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
self.seed = str(seed) or str(time.time())
|
self.seed = str(seed) or str(time.time())
|
||||||
self.dump = dump
|
self.dump = dump
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
self.environment_params = environment_params or {}
|
|
||||||
|
|
||||||
if load_module:
|
sys.path += [self.dir_path, os.getcwd()]
|
||||||
path = sys.path + [self.dir_path, os.getcwd()]
|
|
||||||
f, fp, desc = imp.find_module(load_module, path)
|
self.environment_params = environment_params or {}
|
||||||
imp.load_module('soil.agents.custom', f, fp, desc)
|
self.environment_class = utils.deserialize(environment_class,
|
||||||
|
known_modules=['soil.environment', ]) or Environment
|
||||||
|
|
||||||
environment_agents = environment_agents or []
|
environment_agents = environment_agents or []
|
||||||
self.environment_agents = agents._convert_agent_types(environment_agents)
|
self.environment_agents = agents._convert_agent_types(environment_agents,
|
||||||
|
known_modules=[self.load_module])
|
||||||
|
|
||||||
distro = agents.calculate_distribution(network_agents,
|
distro = agents.calculate_distribution(network_agents,
|
||||||
agent_type)
|
agent_type)
|
||||||
self.network_agents = agents._convert_agent_types(distro)
|
self.network_agents = agents._convert_agent_types(distro,
|
||||||
|
known_modules=[self.load_module])
|
||||||
|
|
||||||
self.states = agents._validate_states(states,
|
self.states = agents._validate_states(states,
|
||||||
self.topology)
|
self.topology)
|
||||||
@ -97,13 +136,17 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
p = Pool()
|
p = Pool()
|
||||||
with utils.timer('simulation {}'.format(self.name)):
|
with utils.timer('simulation {}'.format(self.name)):
|
||||||
if parallel:
|
if parallel:
|
||||||
func = partial(self.run_trial, dry_run=dry_run or self.dry_run,
|
func = partial(self.run_trial_exceptions, dry_run=dry_run or self.dry_run,
|
||||||
return_env=not parallel, **kwargs)
|
return_env=True,
|
||||||
|
**kwargs)
|
||||||
for i in p.imap_unordered(func, range(self.num_trials)):
|
for i in p.imap_unordered(func, range(self.num_trials)):
|
||||||
|
if isinstance(i, Exception):
|
||||||
|
logger.error('Trial failed:\n\t{}'.format(i.message))
|
||||||
|
continue
|
||||||
yield i
|
yield i
|
||||||
else:
|
else:
|
||||||
for i in range(self.num_trials):
|
for i in range(self.num_trials):
|
||||||
yield self.run_trial(i, dry_run=dry_run or self.dry_run, **kwargs)
|
yield self.run_trial(i, dry_run = dry_run or self.dry_run, **kwargs)
|
||||||
if not (dry_run or self.dry_run):
|
if not (dry_run or self.dry_run):
|
||||||
logger.info('Dumping results to {}'.format(self.dir_path))
|
logger.info('Dumping results to {}'.format(self.dir_path))
|
||||||
self.dump_pickle(self.dir_path)
|
self.dump_pickle(self.dir_path)
|
||||||
@ -111,9 +154,9 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
else:
|
else:
|
||||||
logger.info('NOT dumping results')
|
logger.info('NOT dumping results')
|
||||||
|
|
||||||
def get_env(self, trial_id=0, **kwargs):
|
def get_env(self, trial_id = 0, **kwargs):
|
||||||
opts = self.environment_params.copy()
|
opts=self.environment_params.copy()
|
||||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
env_name='{}_trial_{}'.format(self.name, trial_id)
|
||||||
opts.update({
|
opts.update({
|
||||||
'name': env_name,
|
'name': env_name,
|
||||||
'topology': self.topology.copy(),
|
'topology': self.topology.copy(),
|
||||||
@ -128,10 +171,10 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
'dir_path': self.dir_path,
|
'dir_path': self.dir_path,
|
||||||
})
|
})
|
||||||
opts.update(kwargs)
|
opts.update(kwargs)
|
||||||
env = environment.SoilEnvironment(**opts)
|
env=self.environment_class(**opts)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def run_trial(self, trial_id=0, until=None, return_env=True, **opts):
|
def run_trial(self, trial_id = 0, until = None, return_env = True, **opts):
|
||||||
"""Run a single trial of the simulation
|
"""Run a single trial of the simulation
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -139,16 +182,27 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
trial_id : int
|
trial_id : int
|
||||||
"""
|
"""
|
||||||
# Set-up trial environment and graph
|
# Set-up trial environment and graph
|
||||||
until = until or self.max_time
|
until=until or self.max_time
|
||||||
env = self.get_env(trial_id=trial_id, **opts)
|
env=self.get_env(trial_id = trial_id, **opts)
|
||||||
# Set up agents on nodes
|
# Set up agents on nodes
|
||||||
with utils.timer('Simulation {} trial {}'.format(self.name, trial_id)):
|
with utils.timer('Simulation {} trial {}'.format(self.name, trial_id)):
|
||||||
env.run(until)
|
env.run(until)
|
||||||
if self.dump and not self.dry_run:
|
if self.dump and not self.dry_run:
|
||||||
with utils.timer('Dumping simulation {} trial {}'.format(self.name, trial_id)):
|
with utils.timer('Dumping simulation {} trial {}'.format(self.name, trial_id)):
|
||||||
env.dump(formats=self.dump)
|
env.dump(formats = self.dump)
|
||||||
if return_env:
|
if return_env:
|
||||||
return env
|
return env
|
||||||
|
def run_trial_exceptions(self, *args, **kwargs):
|
||||||
|
'''
|
||||||
|
A wrapper for run_trial that catches exceptions and returns them.
|
||||||
|
It is meant for async simulations
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
return self.run_trial(*args, **kwargs)
|
||||||
|
except Exception as ex:
|
||||||
|
c = ex.__cause__
|
||||||
|
c.message = ''.join(traceback.format_tb(c.__traceback__)[3:])
|
||||||
|
return c
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return self.__getstate__()
|
return self.__getstate__()
|
||||||
@ -156,39 +210,53 @@ class SoilSimulation(NetworkSimulation):
|
|||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
return yaml.dump(self.to_dict())
|
return yaml.dump(self.to_dict())
|
||||||
|
|
||||||
def dump_yaml(self, dir_path=None, file_name=None):
|
def dump_yaml(self, dir_path = None, file_name = None):
|
||||||
dir_path = dir_path or self.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)
|
os.makedirs(dir_path)
|
||||||
if not file_name:
|
if not file_name:
|
||||||
file_name = os.path.join(dir_path,
|
file_name=os.path.join(dir_path,
|
||||||
'{}.dumped.yml'.format(self.name))
|
'{}.dumped.yml'.format(self.name))
|
||||||
with open(file_name, 'w') as f:
|
with open(file_name, 'w') as f:
|
||||||
f.write(self.to_yaml())
|
f.write(self.to_yaml())
|
||||||
|
|
||||||
def dump_pickle(self, dir_path=None, pickle_name=None):
|
def dump_pickle(self, dir_path = None, pickle_name = None):
|
||||||
dir_path = dir_path or self.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)
|
os.makedirs(dir_path)
|
||||||
if not pickle_name:
|
if not pickle_name:
|
||||||
pickle_name = os.path.join(dir_path,
|
pickle_name=os.path.join(dir_path,
|
||||||
'{}.simulation.pickle'.format(self.name))
|
'{}.simulation.pickle'.format(self.name))
|
||||||
with open(pickle_name, 'wb') as f:
|
with open(pickle_name, 'wb') as f:
|
||||||
pickle.dump(self, f)
|
pickle.dump(self, f)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
state = self.__dict__.copy()
|
state={}
|
||||||
state['topology'] = json_graph.node_link_data(self.topology)
|
for k, v in self.__dict__.items():
|
||||||
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
if k[0] != '_':
|
||||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
state[k]=v
|
||||||
to_string=True)
|
state['topology']=json_graph.node_link_data(self.topology)
|
||||||
|
state['network_agents']=agents.serialize_distribution(self.network_agents,
|
||||||
|
known_modules = [])
|
||||||
|
state['environment_agents']=agents.serialize_distribution(self.environment_agents,
|
||||||
|
known_modules = [])
|
||||||
|
state['environment_class']=utils.serialize(self.environment_class,
|
||||||
|
known_modules=['soil.environment'])[1] # func, name
|
||||||
|
if state['load_module'] is None:
|
||||||
|
del state['load_module']
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.__dict__ = state
|
self.__dict__ = state
|
||||||
|
self.load_module = getattr(self, 'load_module', None)
|
||||||
|
if self.dir_path not in sys.path:
|
||||||
|
sys.path += [self.dir_path, os.getcwd()]
|
||||||
self.topology = json_graph.node_link_graph(state['topology'])
|
self.topology = json_graph.node_link_graph(state['topology'])
|
||||||
self.network_agents = agents.calculate_distribution(agents._convert_agent_types(self.network_agents))
|
self.network_agents = agents.calculate_distribution(agents._convert_agent_types(self.network_agents))
|
||||||
self.environment_agents = agents._convert_agent_types(self.environment_agents)
|
self.environment_agents = agents._convert_agent_types(self.environment_agents,
|
||||||
|
known_modules=[self.load_module])
|
||||||
|
self.environment_class = utils.deserialize(self.environment_class,
|
||||||
|
known_modules=[self.load_module, 'soil.environment', ]) # func, name
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
||||||
@ -197,11 +265,11 @@ def from_config(config):
|
|||||||
if len(config) > 1:
|
if len(config) > 1:
|
||||||
raise AttributeError('Provide only one configuration')
|
raise AttributeError('Provide only one configuration')
|
||||||
config = config[0][0]
|
config = config[0][0]
|
||||||
sim = SoilSimulation(**config)
|
sim = Simulation(**config)
|
||||||
return sim
|
return sim
|
||||||
|
|
||||||
|
|
||||||
def run_from_config(*configs, results_dir='soil_output', dry_run=False, dump=None, timestamp=False, **kwargs):
|
def run_from_config(*configs, results_dir='soil_output', dump=None, timestamp=False, **kwargs):
|
||||||
for config_def in configs:
|
for config_def in configs:
|
||||||
# logger.info("Found {} config(s)".format(len(ls)))
|
# logger.info("Found {} config(s)".format(len(ls)))
|
||||||
for config, _ in utils.load_config(config_def):
|
for config, _ in utils.load_config(config_def):
|
||||||
@ -214,6 +282,8 @@ def run_from_config(*configs, results_dir='soil_output', dry_run=False, dump=Non
|
|||||||
else:
|
else:
|
||||||
sim_folder = name
|
sim_folder = name
|
||||||
dir_path = os.path.join(results_dir, sim_folder)
|
dir_path = os.path.join(results_dir, sim_folder)
|
||||||
sim = SoilSimulation(dir_path=dir_path, dump=dump, **config)
|
if dump is not None:
|
||||||
|
config['dump'] = dump
|
||||||
|
sim = Simulation(dir_path=dir_path, **config)
|
||||||
logger.info('Dumping results to {} : {}'.format(sim.dir_path, sim.dump))
|
logger.info('Dumping results to {} : {}'.format(sim.dir_path, sim.dump))
|
||||||
sim.run_simulation(**kwargs)
|
sim.run_simulation(**kwargs)
|
||||||
|
101
soil/utils.py
101
soil/utils.py
@ -1,8 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
|
import ast
|
||||||
import yaml
|
import yaml
|
||||||
import logging
|
import logging
|
||||||
import importlib
|
import importlib
|
||||||
from time import time
|
import time
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from random import random
|
from random import random
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -62,44 +63,92 @@ def load_config(config):
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def timer(name='task', pre="", function=logger.info, to_object=None):
|
def timer(name='task', pre="", function=logger.info, to_object=None):
|
||||||
start = time()
|
start = time.time()
|
||||||
function('{}Starting {} at {}.'.format(pre, name, start))
|
function('{}Starting {} at {}.'.format(pre, name,
|
||||||
|
time.strftime("%X", time.gmtime(start))))
|
||||||
yield start
|
yield start
|
||||||
end = time()
|
end = time.time()
|
||||||
function('{}Finished {} in {} seconds'.format(pre, name, str(end-start)))
|
function('{}Finished {} at {} in {} seconds'.format(pre, name,
|
||||||
|
time.strftime("%X", time.gmtime(end)),
|
||||||
|
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):
|
builtins = importlib.import_module('builtins')
|
||||||
func = serializer(v)
|
|
||||||
tname = name(v)
|
|
||||||
return func(v), tname
|
|
||||||
|
|
||||||
|
def name(value, known_modules=[]):
|
||||||
def name(v):
|
'''Return a name that can be imported, to serialize/deserialize an object'''
|
||||||
return type(v).__name__
|
if value is None:
|
||||||
|
return 'None'
|
||||||
|
if not isinstance(value, type): # Get the class name first
|
||||||
|
value = type(value)
|
||||||
|
tname = value.__name__
|
||||||
|
if hasattr(builtins, tname):
|
||||||
|
return tname
|
||||||
|
modname = value.__module__
|
||||||
|
if modname == '__main__':
|
||||||
|
return tname
|
||||||
|
if known_modules and modname in known_modules:
|
||||||
|
return tname
|
||||||
|
for kmod in known_modules:
|
||||||
|
if not kmod:
|
||||||
|
continue
|
||||||
|
module = importlib.import_module(kmod)
|
||||||
|
if hasattr(module, tname):
|
||||||
|
return tname
|
||||||
|
return '{}.{}'.format(modname, tname)
|
||||||
|
|
||||||
|
|
||||||
def serializer(type_):
|
def serializer(type_):
|
||||||
if type_ == 'bool':
|
if type_ != 'str' and hasattr(builtins, type_):
|
||||||
return lambda x: "true" if x else ""
|
return repr
|
||||||
return lambda x: x
|
return lambda x: x
|
||||||
|
|
||||||
|
|
||||||
def deserializer(type_):
|
def serialize(v, known_modules=[]):
|
||||||
try:
|
'''Get a text representation of an object.'''
|
||||||
# Check if it's a builtin type
|
tname = name(v, known_modules=known_modules)
|
||||||
module = importlib.import_module('builtins')
|
func = serializer(tname)
|
||||||
cls = getattr(module, type_)
|
return func(v), tname
|
||||||
except AttributeError:
|
|
||||||
# if not, separate module and class
|
def deserializer(type_, known_modules=[]):
|
||||||
|
if type_ == 'str':
|
||||||
|
return lambda x='': x
|
||||||
|
if type_ == 'None':
|
||||||
|
return lambda x=None: None
|
||||||
|
if hasattr(builtins, type_): # Check if it's a builtin type
|
||||||
|
cls = getattr(builtins, type_)
|
||||||
|
return lambda x=None: ast.literal_eval(x) if x is not None else cls()
|
||||||
|
# Otherwise, see if we can find the module and the class
|
||||||
|
modules = known_modules or []
|
||||||
|
options = []
|
||||||
|
|
||||||
|
for mod in modules:
|
||||||
|
if mod:
|
||||||
|
options.append((mod, type_))
|
||||||
|
|
||||||
|
if '.' in type_: # Fully qualified module
|
||||||
module, type_ = type_.rsplit(".", 1)
|
module, type_ = type_.rsplit(".", 1)
|
||||||
module = importlib.import_module(module)
|
options.append ((module, type_))
|
||||||
cls = getattr(module, type_)
|
|
||||||
return cls
|
errors = []
|
||||||
|
for modname, tname in options:
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(modname)
|
||||||
|
cls = getattr(module, tname)
|
||||||
|
return getattr(cls, 'deserialize', cls)
|
||||||
|
except (ImportError, AttributeError) as ex:
|
||||||
|
errors.append((modname, tname, ex))
|
||||||
|
raise Exception('Could not find type {}. Tried: {}'.format(type_, errors))
|
||||||
|
|
||||||
|
|
||||||
def convert(value, type_):
|
def deserialize(type_, value=None, **kwargs):
|
||||||
return deserializer(type_)(value)
|
'''Get an object from a text representation'''
|
||||||
|
if not isinstance(type_, str):
|
||||||
|
return type_
|
||||||
|
des = deserializer(type_, **kwargs)
|
||||||
|
if value is None:
|
||||||
|
return des
|
||||||
|
return des(value)
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
pytest
|
12
tests/test.gexf
Normal file
12
tests/test.gexf
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<gexf version="1.2" xmlns="http://www.gexf.net/1.2draft" xmlns:viz="http://www.gexf.net/1.2draft/viz" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<graph defaultedgetype="undirected" mode="static">
|
||||||
|
<nodes>
|
||||||
|
<node id="0" label="0" />
|
||||||
|
<node id="1" label="1" />
|
||||||
|
</nodes>
|
||||||
|
<edges>
|
||||||
|
<edge id="0" source="0" target="1" />
|
||||||
|
</edges>
|
||||||
|
</graph>
|
||||||
|
</gexf>
|
48
tests/test_examples.py
Normal file
48
tests/test_examples.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
import os
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
from soil import utils, simulation
|
||||||
|
|
||||||
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
EXAMPLES = join(ROOT, '..', 'examples')
|
||||||
|
|
||||||
|
|
||||||
|
class TestExamples(TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def make_example_test(path, config):
|
||||||
|
def wrapped(self):
|
||||||
|
root = os.getcwd()
|
||||||
|
os.chdir(os.path.dirname(path))
|
||||||
|
s = simulation.from_config(config)
|
||||||
|
iterations = s.max_time * s.num_trials
|
||||||
|
if iterations > 1000:
|
||||||
|
self.skipTest('This example would probably take too long')
|
||||||
|
envs = s.run_simulation(dry_run=True)
|
||||||
|
assert envs
|
||||||
|
for env in envs:
|
||||||
|
assert env
|
||||||
|
try:
|
||||||
|
n = config['network_params']['n']
|
||||||
|
assert len(list(env.network_agents)) == n
|
||||||
|
assert env.now > 2 # It has run
|
||||||
|
assert env.now <= config['max_time'] # But not further than allowed
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
os.chdir(root)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def add_example_tests():
|
||||||
|
for config, path in utils.load_config(join(EXAMPLES, '**', '*.yml')):
|
||||||
|
p = make_example_test(path=path, config=config)
|
||||||
|
fname = os.path.basename(path)
|
||||||
|
p.__name__ = 'test_example_file_%s' % fname
|
||||||
|
p.__doc__ = '%s should be a valid configuration' % fname
|
||||||
|
setattr(TestExamples, p.__name__, p)
|
||||||
|
del p
|
||||||
|
|
||||||
|
|
||||||
|
add_example_tests()
|
@ -116,6 +116,7 @@ class TestHistory(TestCase):
|
|||||||
db_path = os.path.join(DBROOT, 'test')
|
db_path = os.path.join(DBROOT, 'test')
|
||||||
h = history.History(db_path=db_path)
|
h = history.History(db_path=db_path)
|
||||||
h.save_tuples(tuples)
|
h.save_tuples(tuples)
|
||||||
|
h.flush_cache()
|
||||||
assert os.path.exists(db_path)
|
assert os.path.exists(db_path)
|
||||||
|
|
||||||
# Recover the data
|
# Recover the data
|
||||||
@ -128,6 +129,28 @@ class TestHistory(TestCase):
|
|||||||
backuppaths = glob(db_path + '.backup*.sqlite')
|
backuppaths = glob(db_path + '.backup*.sqlite')
|
||||||
assert len(backuppaths) == 1
|
assert len(backuppaths) == 1
|
||||||
backuppath = backuppaths[0]
|
backuppath = backuppaths[0]
|
||||||
assert newhistory._db_path == h._db_path
|
assert newhistory.db_path == h.db_path
|
||||||
assert os.path.exists(backuppath)
|
assert os.path.exists(backuppath)
|
||||||
assert not len(newhistory[None, None, None])
|
assert not len(newhistory[None, None, None])
|
||||||
|
|
||||||
|
def test_history_tuples(self):
|
||||||
|
"""
|
||||||
|
The data recovered should be equal to the one recorded.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
recovered = list(h.to_tuples())
|
||||||
|
assert recovered
|
||||||
|
for i in recovered:
|
||||||
|
assert i in tuples
|
||||||
|
@ -6,14 +6,18 @@ 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, environment, agents, utils
|
from soil import simulation, Environment, agents, utils, history
|
||||||
|
|
||||||
|
|
||||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
EXAMPLES = join(ROOT, '..', 'examples')
|
EXAMPLES = join(ROOT, '..', 'examples')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomAgent(agents.BaseAgent):
|
||||||
|
def step(self):
|
||||||
|
self.state['neighbors'] = self.count_agents(state_id=0,
|
||||||
|
limit_neighbors=True)
|
||||||
|
|
||||||
class TestMain(TestCase):
|
class TestMain(TestCase):
|
||||||
|
|
||||||
def test_load_graph(self):
|
def test_load_graph(self):
|
||||||
@ -127,10 +131,6 @@ class TestMain(TestCase):
|
|||||||
|
|
||||||
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.BaseAgent):
|
|
||||||
def step(self):
|
|
||||||
self.state['neighbors'] = self.count_agents(state_id=0,
|
|
||||||
limit_neighbors=True)
|
|
||||||
config = {
|
config = {
|
||||||
'dry_run': True,
|
'dry_run': True,
|
||||||
'network_params': {
|
'network_params': {
|
||||||
@ -188,8 +188,6 @@ class TestMain(TestCase):
|
|||||||
recovered = yaml.load(serial)
|
recovered = yaml.load(serial)
|
||||||
with utils.timer('deleting'):
|
with utils.timer('deleting'):
|
||||||
del recovered['topology']
|
del recovered['topology']
|
||||||
del recovered['load_module']
|
|
||||||
del recovered['dry_run']
|
|
||||||
assert config == recovered
|
assert config == recovered
|
||||||
|
|
||||||
def test_configuration_changes(self):
|
def test_configuration_changes(self):
|
||||||
@ -197,25 +195,17 @@ class TestMain(TestCase):
|
|||||||
The configuration should not change after running
|
The configuration should not change after running
|
||||||
the simulation.
|
the simulation.
|
||||||
"""
|
"""
|
||||||
config = utils.load_file('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
|
s.dry_run = True
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
s.run_simulation(dry_run=True)
|
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):
|
|
||||||
"""
|
|
||||||
Make sure all examples in the examples folder are correct
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_row_conversion(self):
|
def test_row_conversion(self):
|
||||||
env = environment.SoilEnvironment(dry_run=True)
|
env = Environment(dry_run=True)
|
||||||
env['test'] = 'test_value'
|
env['test'] = 'test_value'
|
||||||
|
|
||||||
res = list(env.history_to_tuples())
|
res = list(env.history_to_tuples())
|
||||||
@ -234,7 +224,7 @@ class TestMain(TestCase):
|
|||||||
from geometric models. We should work around it.
|
from geometric models. We should work around it.
|
||||||
"""
|
"""
|
||||||
G = nx.random_geometric_graph(20, 0.1)
|
G = nx.random_geometric_graph(20, 0.1)
|
||||||
env = environment.SoilEnvironment(topology=G, dry_run=True)
|
env = Environment(topology=G, dry_run=True)
|
||||||
env.dump_gexf('/tmp/dump-gexf')
|
env.dump_gexf('/tmp/dump-gexf')
|
||||||
|
|
||||||
def test_save_graph(self):
|
def test_save_graph(self):
|
||||||
@ -245,7 +235,7 @@ class TestMain(TestCase):
|
|||||||
'''
|
'''
|
||||||
G = nx.cycle_graph(5)
|
G = nx.cycle_graph(5)
|
||||||
distribution = agents.calculate_distribution(None, agents.BaseAgent)
|
distribution = agents.calculate_distribution(None, agents.BaseAgent)
|
||||||
env = environment.SoilEnvironment(topology=G, network_agents=distribution, dry_run=True)
|
env = Environment(topology=G, network_agents=distribution, dry_run=True)
|
||||||
env[0, 0, 'testvalue'] = 'start'
|
env[0, 0, 'testvalue'] = 'start'
|
||||||
env[0, 10, 'testvalue'] = 'finish'
|
env[0, 10, 'testvalue'] = 'finish'
|
||||||
nG = env.history_to_graph()
|
nG = env.history_to_graph()
|
||||||
@ -253,33 +243,65 @@ class TestMain(TestCase):
|
|||||||
assert ('start', 0, 10) in values
|
assert ('start', 0, 10) in values
|
||||||
assert ('finish', 10, None) in values
|
assert ('finish', 10, None) in values
|
||||||
|
|
||||||
|
def test_serialize_class(self):
|
||||||
|
ser, name = utils.serialize(agents.BaseAgent)
|
||||||
|
assert name == 'soil.agents.BaseAgent'
|
||||||
|
assert ser == agents.BaseAgent
|
||||||
|
|
||||||
def make_example_test(path, config):
|
class CustomAgent(agents.BaseAgent):
|
||||||
def wrapped(self):
|
pass
|
||||||
root = os.getcwd()
|
|
||||||
os.chdir(os.path.dirname(path))
|
|
||||||
s = simulation.from_config(config)
|
|
||||||
envs = s.run_simulation(dry_run=True)
|
|
||||||
assert envs
|
|
||||||
for env in envs:
|
|
||||||
assert env
|
|
||||||
try:
|
|
||||||
n = config['network_params']['n']
|
|
||||||
assert len(env.get_agents()) == n
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
os.chdir(root)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
ser, name = utils.serialize(CustomAgent)
|
||||||
|
assert name == 'test_main.CustomAgent'
|
||||||
|
assert ser == CustomAgent
|
||||||
|
|
||||||
def add_example_tests():
|
def test_serialize_builtin_types(self):
|
||||||
for config, path in utils.load_config(join(EXAMPLES, '*.yml')):
|
|
||||||
p = make_example_test(path=path, config=config)
|
|
||||||
fname = os.path.basename(path)
|
|
||||||
p.__name__ = 'test_example_file_%s' % fname
|
|
||||||
p.__doc__ = '%s should be a valid configuration' % fname
|
|
||||||
setattr(TestMain, p.__name__, p)
|
|
||||||
del p
|
|
||||||
|
|
||||||
|
for i in [1, None, True, False, {}, [], list(), dict()]:
|
||||||
|
ser, name = utils.serialize(i)
|
||||||
|
assert type(ser) == str
|
||||||
|
des = utils.deserialize(name, ser)
|
||||||
|
assert i == des
|
||||||
|
|
||||||
add_example_tests()
|
def test_serialize_agent_type(self):
|
||||||
|
'''A class from soil.agents should be serialized without the module part'''
|
||||||
|
ser = agents.serialize_type(CustomAgent)
|
||||||
|
assert ser == 'test_main.CustomAgent'
|
||||||
|
ser = agents.serialize_type(agents.BaseAgent)
|
||||||
|
assert ser == 'BaseAgent'
|
||||||
|
|
||||||
|
def test_deserialize_agent_distribution(self):
|
||||||
|
agent_distro = [
|
||||||
|
{
|
||||||
|
'agent_type': 'CounterModel',
|
||||||
|
'weight': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'agent_type': 'test_main.CustomAgent',
|
||||||
|
'weight': 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
converted = agents.deserialize_distribution(agent_distro)
|
||||||
|
assert converted[0]['agent_type'] == agents.CounterModel
|
||||||
|
assert converted[1]['agent_type'] == CustomAgent
|
||||||
|
|
||||||
|
def test_serialize_agent_distribution(self):
|
||||||
|
agent_distro = [
|
||||||
|
{
|
||||||
|
'agent_type': agents.CounterModel,
|
||||||
|
'weight': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'agent_type': CustomAgent,
|
||||||
|
'weight': 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
converted = agents.serialize_distribution(agent_distro)
|
||||||
|
assert converted[0]['agent_type'] == 'CounterModel'
|
||||||
|
assert converted[1]['agent_type'] == 'test_main.CustomAgent'
|
||||||
|
|
||||||
|
def test_history(self):
|
||||||
|
'''Test storing in and retrieving from history (sqlite)'''
|
||||||
|
h = history.History()
|
||||||
|
h.save_record(agent_id=0, t_step=0, key="test", value="hello")
|
||||||
|
assert h[0, 0, "test"] == "hello"
|
||||||
|
Loading…
Reference in New Issue
Block a user