Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9165979b49 | ||
|
078f8ace9e | ||
|
8fec544772 | ||
|
5420501d36 | ||
|
5d89827ccf | ||
|
fc48ed7e09 | ||
|
73c90887e8 | ||
|
497c8a55db | ||
|
7d1c800490 | ||
|
a4b32afa2f | ||
|
a7c51742f6 | ||
|
78364d89d5 | ||
|
af76f54a28 | ||
|
dbc182c6d0 | ||
|
eafecc9e5e | ||
|
e8988015e2 | ||
|
ccc8e43416 | ||
|
347d295b09 |
5
Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM python:3.4-onbuild
|
||||
|
||||
RUN pip install '.[web]'
|
||||
|
||||
ENTRYPOINT ["python", "-m", "soil"]
|
@@ -3,7 +3,7 @@
|
||||
Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks.
|
||||
Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io).
|
||||
|
||||
Follow our [tutorial](notebooks/soil_tutorial.ipynb) to develop your own agent models.
|
||||
Follow our [tutorial](examples/tutorial/soil_tutorial.ipynb) to develop your own agent models.
|
||||
|
||||
If you use Soil in your research, don't forget to cite this paper:
|
||||
|
||||
|
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '3'
|
||||
services:
|
||||
dev:
|
||||
build: .
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
tty: true
|
||||
entrypoint: /bin/bash
|
||||
ports:
|
||||
- '8001:8001'
|
@@ -34,13 +34,14 @@ If you use Soil in your research, do not forget to cite this paper:
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 0
|
||||
:caption: Learn more about soil:
|
||||
|
||||
installation
|
||||
quickstart
|
||||
Tutorial - Spreading news
|
||||
Tutorial <soil_tutorial>
|
||||
|
||||
..
|
||||
|
||||
|
||||
.. Indices and tables
|
||||
|
@@ -1,7 +1,7 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
The easiest way to install Soil is through pip:
|
||||
The easiest way to install Soil is through pip, with Python >= 3.4:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
BIN
docs/output_54_0.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_54_1.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_0.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_1.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_55_2.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_3.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_4.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_55_5.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_55_6.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_7.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_8.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_55_9.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_56_0.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_1.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_2.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_3.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_4.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_5.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_6.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_7.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_8.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_56_9.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/output_61_0.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/output_63_1.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_66_1.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/output_67_1.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
docs/output_72_0.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
docs/output_72_1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
docs/output_74_1.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/output_75_1.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/output_76_1.png
Normal file
After Width: | Height: | Size: 19 KiB |
@@ -13,7 +13,7 @@ Here's an example (``example.yml``).
|
||||
name: MyExampleSimulation
|
||||
max_time: 50
|
||||
num_trials: 3
|
||||
timeout: 2
|
||||
interval: 2
|
||||
network_params:
|
||||
network_type: barabasi_albert_graph
|
||||
n: 100
|
||||
@@ -34,6 +34,12 @@ Here's an example (``example.yml``).
|
||||
environment_params:
|
||||
prob_infect: 0.075
|
||||
|
||||
|
||||
This example configuration will run three trials of a simulation containing a randomly generated network.
|
||||
The 100 nodes in the network will be SISaModel agents, 10% of them will start in the content state, 10% in the discontent state, and the remaining 80% in the neutral state.
|
||||
All agents will have access to the environment, which only contains one variable, ``prob_infected``.
|
||||
The state of the agents will be updated every 2 seconds (``interval``).
|
||||
|
||||
Now run the simulation with the command line tool:
|
||||
|
||||
.. code:: bash
|
||||
@@ -41,7 +47,7 @@ Now run the simulation with the command line tool:
|
||||
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 (to re-launch it), for every trial, a csv file with the content of the state of every network node and the environment parameters at every step of the simulation as well as the network in gephi format (``gexf``).
|
||||
Four types of objects are saved by default: a pickle of the simulation; a ``YAML`` representation of the simulation (which can be used to re-launch it); and for every trial, a csv file with the content of the state of every network node and the environment parameters at every step of the simulation, as well as the network in gephi format (``gexf``).
|
||||
|
||||
|
||||
.. code::
|
||||
@@ -54,12 +60,6 @@ Four types of objects are saved by default: a pickle of the simulation, a ``YAML
|
||||
│ └── Sim_prob_0_trial_0.gexf
|
||||
|
||||
|
||||
This example configuration will run three trials of a simulation containing a randomly generated network.
|
||||
The 100 nodes in the network will be SISaModel agents, 10% of them will start in the content state, 10% in the discontent state, and the remaining 80% in the neutral state.
|
||||
All agents will have access to the environment, which only contains one variable, ``prob_infected``.
|
||||
The state of the agents will be updated every 2 seconds (``timeout``).
|
||||
|
||||
|
||||
Network
|
||||
=======
|
||||
|
||||
@@ -94,7 +94,7 @@ For example, the following configuration is equivalent to :code:`nx.complete_gra
|
||||
Environment
|
||||
============
|
||||
The environment is the place where the shared state of the simulation is stored.
|
||||
For instance, the probability of certain events.
|
||||
For instance, the probability of disease outbreak.
|
||||
The configuration file may specify the initial value of the environment parameters:
|
||||
|
||||
.. code:: yaml
|
||||
@@ -103,14 +103,17 @@ The configuration file may specify the initial value of the environment paramete
|
||||
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 ``timeout`` seconds), and it has access to its state and the environment parameters.
|
||||
Only one agent is executed at a time (generally, every ``interval`` seconds), and it has access to its state and the environment parameters.
|
||||
Through the environment, it can access the network topology and the state of other agents.
|
||||
|
||||
There are three three types of agents according to how they are added to the simulation: network agents, environment agent, and other agents.
|
||||
There are three three types of agents according to how they are added to the simulation: network agents and environment agent.
|
||||
|
||||
Network Agents
|
||||
##############
|
||||
@@ -118,13 +121,13 @@ Network agents are attached to a node in the topology.
|
||||
The configuration file allows you to specify how agents will be mapped to topology nodes.
|
||||
|
||||
The simplest way is to specify a single type of agent.
|
||||
Hence, every node in the network will have an associated agent of that type.
|
||||
Hence, every node in the network will be associated to an agent of that type.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
agent_type: SISaModel
|
||||
|
||||
It is also possible to add more than one type of agent to the simulation, and to control the ratio of each type (``weight``).
|
||||
It is also possible to add more than one type of agent to the simulation, and to control the ratio of each type (using the ``weight`` property).
|
||||
For instance, with following configuration, it is five times more likely for a node to be assigned a CounterModel type than a SISaModel type.
|
||||
|
||||
.. code:: yaml
|
||||
|
2612
docs/soil_tutorial.rst
Normal file
334
examples/NewsSpread.ipynb
Normal file
@@ -4,6 +4,8 @@ dir_path: "/tmp/"
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
interval: 1
|
||||
seed: "CompleteSeed!"
|
||||
dump: false
|
||||
network_params:
|
||||
generator: complete_graph
|
||||
n: 10
|
||||
@@ -21,4 +23,4 @@ default_state:
|
||||
incidents: 0
|
||||
states:
|
||||
- name: 'The first node'
|
||||
- name: 'The second node'
|
||||
- name: 'The second node'
|
||||
|
@@ -1,17 +0,0 @@
|
||||
default_state: {}
|
||||
environment_agents: []
|
||||
environment_params: {prob_neighbor_spread: 0.0, prob_tv_spread: 0.01}
|
||||
interval: 1
|
||||
max_time: 20
|
||||
name: Sim_prob_0
|
||||
network_agents:
|
||||
- agent_type: NewsSpread
|
||||
state: {has_tv: false}
|
||||
weight: 1
|
||||
- agent_type: NewsSpread
|
||||
state: {has_tv: true}
|
||||
weight: 2
|
||||
network_params: {generator: erdos_renyi_graph, n: 500, p: 0.1}
|
||||
num_trials: 1
|
||||
states:
|
||||
- {has_tv: true}
|
@@ -1,20 +0,0 @@
|
||||
import soil
|
||||
import random
|
||||
|
||||
class NewsSpread(soil.agents.FSM):
|
||||
@soil.agents.default_state
|
||||
@soil.agents.state
|
||||
def neutral(self):
|
||||
r = random.random()
|
||||
if self['has_tv'] and r < self.env['prob_tv_spread']:
|
||||
return self.infected
|
||||
return
|
||||
|
||||
@soil.agents.state
|
||||
def infected(self):
|
||||
prob_infect = self.env['prob_neighbor_spread']
|
||||
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
|
||||
r = random.random()
|
||||
if r < prob_infect:
|
||||
neighbor.state['id'] = self.infected.id
|
||||
return
|
767
examples/newsspread/NewsSpread.ipynb
Normal file
138
examples/newsspread/NewsSpread.yml
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
default_state: {}
|
||||
load_module: newsspread
|
||||
environment_agents: []
|
||||
environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 30
|
||||
name: Sim_all_dumb
|
||||
network_agents:
|
||||
- agent_type: DumbViewer
|
||||
state:
|
||||
has_tv: false
|
||||
weight: 1
|
||||
- agent_type: DumbViewer
|
||||
state:
|
||||
has_tv: true
|
||||
weight: 1
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
num_trials: 50
|
||||
---
|
||||
default_state: {}
|
||||
load_module: newsspread
|
||||
environment_agents: []
|
||||
environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 30
|
||||
name: Sim_half_herd
|
||||
network_agents:
|
||||
- agent_type: DumbViewer
|
||||
state:
|
||||
has_tv: false
|
||||
weight: 1
|
||||
- agent_type: DumbViewer
|
||||
state:
|
||||
has_tv: true
|
||||
weight: 1
|
||||
- agent_type: HerdViewer
|
||||
state:
|
||||
has_tv: false
|
||||
weight: 1
|
||||
- agent_type: HerdViewer
|
||||
state:
|
||||
has_tv: true
|
||||
weight: 1
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
num_trials: 50
|
||||
---
|
||||
default_state: {}
|
||||
load_module: newsspread
|
||||
environment_agents: []
|
||||
environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 30
|
||||
name: Sim_all_herd
|
||||
network_agents:
|
||||
- agent_type: HerdViewer
|
||||
state:
|
||||
has_tv: true
|
||||
id: neutral
|
||||
weight: 1
|
||||
- agent_type: HerdViewer
|
||||
state:
|
||||
has_tv: true
|
||||
id: neutral
|
||||
weight: 1
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
num_trials: 50
|
||||
---
|
||||
default_state: {}
|
||||
load_module: newsspread
|
||||
environment_agents: []
|
||||
environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
prob_neighbor_cure: 0.1
|
||||
interval: 1
|
||||
max_time: 30
|
||||
name: Sim_wise_herd
|
||||
network_agents:
|
||||
- agent_type: HerdViewer
|
||||
state:
|
||||
has_tv: true
|
||||
id: neutral
|
||||
weight: 1
|
||||
- agent_type: WiseViewer
|
||||
state:
|
||||
has_tv: true
|
||||
weight: 1
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
num_trials: 50
|
||||
---
|
||||
default_state: {}
|
||||
load_module: newsspread
|
||||
environment_agents: []
|
||||
environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
prob_neighbor_cure: 0.1
|
||||
interval: 1
|
||||
max_time: 30
|
||||
name: Sim_all_wise
|
||||
network_agents:
|
||||
- agent_type: WiseViewer
|
||||
state:
|
||||
has_tv: true
|
||||
id: neutral
|
||||
weight: 1
|
||||
- agent_type: WiseViewer
|
||||
state:
|
||||
has_tv: true
|
||||
weight: 1
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 500
|
||||
m: 5
|
||||
num_trials: 50
|
81
examples/newsspread/newsspread.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from soil.agents import FSM, state, default_state, prob
|
||||
import logging
|
||||
|
||||
|
||||
class DumbViewer(FSM):
|
||||
'''
|
||||
A viewer that gets infected via TV (if it has one) and tries to infect
|
||||
its neighbors once it's infected.
|
||||
'''
|
||||
defaults = {
|
||||
'prob_neighbor_spread': 0.5,
|
||||
'prob_tv_spread': 0.1,
|
||||
}
|
||||
|
||||
@default_state
|
||||
@state
|
||||
def neutral(self):
|
||||
if self['has_tv']:
|
||||
if prob(self.env['prob_tv_spread']):
|
||||
self.set_state(self.infected)
|
||||
|
||||
@state
|
||||
def infected(self):
|
||||
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
|
||||
if prob(self.env['prob_neighbor_spread']):
|
||||
neighbor.infect()
|
||||
|
||||
def infect(self):
|
||||
self.set_state(self.infected)
|
||||
|
||||
|
||||
class HerdViewer(DumbViewer):
|
||||
'''
|
||||
A viewer whose probability of infection depends on the state of its neighbors.
|
||||
'''
|
||||
|
||||
level = logging.DEBUG
|
||||
|
||||
def infect(self):
|
||||
infected = self.count_neighboring_agents(state_id=self.infected.id)
|
||||
total = self.count_neighboring_agents()
|
||||
prob_infect = self.env['prob_neighbor_spread'] * infected/total
|
||||
self.debug('prob_infect', prob_infect)
|
||||
if prob(prob_infect):
|
||||
self.set_state(self.infected.id)
|
||||
|
||||
|
||||
class WiseViewer(HerdViewer):
|
||||
'''
|
||||
A viewer that can change its mind.
|
||||
'''
|
||||
|
||||
defaults = {
|
||||
'prob_neighbor_spread': 0.5,
|
||||
'prob_neighbor_cure': 0.25,
|
||||
'prob_tv_spread': 0.1,
|
||||
}
|
||||
|
||||
@state
|
||||
def cured(self):
|
||||
prob_cure = self.env['prob_neighbor_cure']
|
||||
for neighbor in self.get_neighboring_agents(state_id=self.infected.id):
|
||||
if prob(prob_cure):
|
||||
try:
|
||||
neighbor.cure()
|
||||
except AttributeError:
|
||||
self.debug('Viewer {} cannot be cured'.format(neighbor.id))
|
||||
|
||||
def cure(self):
|
||||
self.set_state(self.cured.id)
|
||||
|
||||
@state
|
||||
def infected(self):
|
||||
cured = max(self.count_neighboring_agents(self.cured.id),
|
||||
1.0)
|
||||
infected = max(self.count_neighboring_agents(self.infected.id),
|
||||
1.0)
|
||||
prob_cure = self.env['prob_neighbor_cure'] * (cured/infected)
|
||||
if prob(prob_cure):
|
||||
return self.cure()
|
||||
return self.set_state(super().infected)
|
120
examples/rabbits/rabbit_agents.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from soil.agents import FSM, state, default_state, BaseAgent
|
||||
from enum import Enum
|
||||
from random import random, choice
|
||||
from itertools import islice
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
||||
class Genders(Enum):
|
||||
male = 'male'
|
||||
female = 'female'
|
||||
|
||||
|
||||
class RabbitModel(FSM):
|
||||
|
||||
level = logging.INFO
|
||||
|
||||
defaults = {
|
||||
'age': 0,
|
||||
'gender': Genders.male.value,
|
||||
'mating_prob': 0.001,
|
||||
'offspring': 0,
|
||||
}
|
||||
|
||||
sexual_maturity = 4*30
|
||||
life_expectancy = 365 * 3
|
||||
gestation = 33
|
||||
pregnancy = -1
|
||||
max_females = 5
|
||||
|
||||
@default_state
|
||||
@state
|
||||
def newborn(self):
|
||||
self['age'] += 1
|
||||
|
||||
if self['age'] >= self.sexual_maturity:
|
||||
return self.fertile
|
||||
|
||||
@state
|
||||
def fertile(self):
|
||||
self['age'] += 1
|
||||
if self['age'] > self.life_expectancy:
|
||||
return self.dead
|
||||
|
||||
if self['gender'] == Genders.female.value:
|
||||
return
|
||||
|
||||
# Males try to mate
|
||||
females = self.get_agents(state_id=self.fertile.id, gender=Genders.female.value, limit_neighbors=False)
|
||||
for f in islice(females, self.max_females):
|
||||
r = random()
|
||||
if r < self['mating_prob']:
|
||||
self.impregnate(f)
|
||||
break # Take a break
|
||||
|
||||
def impregnate(self, whom):
|
||||
if self['gender'] == Genders.female.value:
|
||||
raise NotImplementedError('Females cannot impregnate')
|
||||
whom['pregnancy'] = 0
|
||||
whom['mate'] = self.id
|
||||
whom.set_state(whom.pregnant)
|
||||
self.debug('{} impregnating: {}. {}'.format(self.id, whom.id, whom.state))
|
||||
|
||||
@state
|
||||
def pregnant(self):
|
||||
self['age'] += 1
|
||||
if self['age'] > self.life_expectancy:
|
||||
return self.dead
|
||||
|
||||
self['pregnancy'] += 1
|
||||
self.debug('Pregnancy: {}'.format(self['pregnancy']))
|
||||
if self['pregnancy'] >= self.gestation:
|
||||
number_of_babies = int(8+4*random())
|
||||
self.info('Having {} babies'.format(number_of_babies))
|
||||
for i in range(number_of_babies):
|
||||
state = {}
|
||||
state['gender'] = choice(list(Genders)).value
|
||||
child = self.env.add_node(self.__class__, state)
|
||||
self.env.add_edge(self.id, child.id)
|
||||
self.env.add_edge(self['mate'], child.id)
|
||||
# self.add_edge()
|
||||
self.debug('A BABY IS COMING TO LIFE')
|
||||
self.env['rabbits_alive'] = self.env.get('rabbits_alive', self.global_topology.number_of_nodes())+1
|
||||
self.debug('Rabbits alive: {}'.format(self.env['rabbits_alive']))
|
||||
self['offspring'] += 1
|
||||
self.env.get_agent(self['mate'])['offspring'] += 1
|
||||
del self['mate']
|
||||
self['pregnancy'] = -1
|
||||
return self.fertile
|
||||
|
||||
@state
|
||||
def dead(self):
|
||||
self.info('Agent {} is dying'.format(self.id))
|
||||
if 'pregnancy' in self and self['pregnancy'] > -1:
|
||||
self.info('A mother has died carrying a baby!!')
|
||||
self.die()
|
||||
return
|
||||
|
||||
|
||||
class RandomAccident(BaseAgent):
|
||||
|
||||
level = logging.DEBUG
|
||||
|
||||
def step(self):
|
||||
rabbits_total = self.global_topology.number_of_nodes()
|
||||
rabbits_alive = self.env.get('rabbits_alive', rabbits_total)
|
||||
prob_death = self.env.get('prob_death', 1e-100)*math.floor(math.log10(max(1, rabbits_alive)))
|
||||
self.debug('Killing some rabbits with prob={}!'.format(prob_death))
|
||||
for i in self.env.network_agents:
|
||||
if i.state['id'] == i.dead.id:
|
||||
continue
|
||||
r = random()
|
||||
if r < prob_death:
|
||||
self.debug('I killed a rabbit: {}'.format(i.id))
|
||||
rabbits_alive = self.env['rabbits_alive'] = rabbits_alive -1
|
||||
self.log('Rabbits alive: {}'.format(self.env['rabbits_alive']))
|
||||
i.set_state(i.dead)
|
||||
self.log('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
|
||||
if self.count_agents(state_id=RabbitModel.dead.id) == self.global_topology.number_of_nodes():
|
||||
self.die()
|
23
examples/rabbits/rabbits.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
load_module: rabbit_agents
|
||||
name: rabbits_example
|
||||
max_time: 1200
|
||||
interval: 1
|
||||
seed: MySeed
|
||||
agent_type: RabbitModel
|
||||
environment_agents:
|
||||
- agent_type: RandomAccident
|
||||
environment_params:
|
||||
prob_death: 0.001
|
||||
default_state:
|
||||
mating_prob: 0.01
|
||||
topology:
|
||||
nodes:
|
||||
- id: 1
|
||||
state:
|
||||
gender: female
|
||||
- id: 0
|
||||
state:
|
||||
gender: male
|
||||
directed: true
|
||||
links: []
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: torvalds_example
|
||||
max_time: 1
|
||||
max_time: 10
|
||||
interval: 2
|
||||
agent_type: CounterModel
|
||||
default_state:
|
||||
@@ -11,4 +11,4 @@ states:
|
||||
Torvalds:
|
||||
skill_level: 'God'
|
||||
balkian:
|
||||
skill_level: 'developer'
|
||||
skill_level: 'developer'
|
||||
|
23569
examples/tutorial/soil_tutorial.html
Normal file
1350
examples/tutorial/soil_tutorial.ipynb
Normal file
596
models_org.py
@@ -1,596 +0,0 @@
|
||||
from nxsim import BaseNetworkAgent
|
||||
import numpy as np
|
||||
import random
|
||||
import settings
|
||||
|
||||
settings.init()
|
||||
|
||||
##############################
|
||||
# Variables initialization #
|
||||
##############################
|
||||
def init():
|
||||
global networkStatus
|
||||
networkStatus = {} # Dict that will contain the status of every agent in the network
|
||||
|
||||
sentimentCorrelationNodeArray=[]
|
||||
for x in range(0, settings.number_of_nodes):
|
||||
sentimentCorrelationNodeArray.append({'id':x})
|
||||
# Initialize agent states. Let's assume everyone is normal.
|
||||
init_states = [{'id': 0, } for _ in range(settings.number_of_nodes)] # add keys as as necessary, but "id" must always refer to that state category
|
||||
|
||||
|
||||
####################
|
||||
# Available models #
|
||||
####################
|
||||
|
||||
class BaseBehaviour(BaseNetworkAgent):
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self._attrs = {}
|
||||
|
||||
@property
|
||||
def attrs(self):
|
||||
now = self.env.now
|
||||
if now not in self._attrs:
|
||||
self._attrs[now] = {}
|
||||
return self._attrs[now]
|
||||
|
||||
@attrs.setter
|
||||
def attrs(self, value):
|
||||
self._attrs[self.env.now] = value
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.step(self.env.now)
|
||||
yield self.env.timeout(settings.timeout)
|
||||
|
||||
def step(self, now):
|
||||
networkStatus['agent_%s'% self.id] = self.to_json()
|
||||
|
||||
def to_json(self):
|
||||
final = {}
|
||||
for stamp, attrs in self._attrs.items():
|
||||
for a in attrs:
|
||||
if a not in final:
|
||||
final[a] = {}
|
||||
final[a][stamp] = attrs[a]
|
||||
return final
|
||||
|
||||
class ControlModelM2(BaseBehaviour):
|
||||
#Init infected
|
||||
init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}
|
||||
init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}
|
||||
|
||||
# Init beacons
|
||||
init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}
|
||||
init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
|
||||
self.prob_neutral_making_denier = np.random.normal(settings.prob_neutral_making_denier, settings.standard_variance)
|
||||
|
||||
self.prob_infect = np.random.normal(settings.prob_infect, settings.standard_variance)
|
||||
|
||||
self.prob_cured_healing_infected = np.random.normal(settings.prob_cured_healing_infected, settings.standard_variance)
|
||||
self.prob_cured_vaccinate_neutral = np.random.normal(settings.prob_cured_vaccinate_neutral, settings.standard_variance)
|
||||
|
||||
self.prob_vaccinated_healing_infected = np.random.normal(settings.prob_vaccinated_healing_infected, settings.standard_variance)
|
||||
self.prob_vaccinated_vaccinate_neutral = np.random.normal(settings.prob_vaccinated_vaccinate_neutral, settings.standard_variance)
|
||||
self.prob_generate_anti_rumor = np.random.normal(settings.prob_generate_anti_rumor, settings.standard_variance)
|
||||
|
||||
def step(self, now):
|
||||
|
||||
if self.state['id'] == 0: #Neutral
|
||||
self.neutral_behaviour()
|
||||
elif self.state['id'] == 1: #Infected
|
||||
self.infected_behaviour()
|
||||
elif self.state['id'] == 2: #Cured
|
||||
self.cured_behaviour()
|
||||
elif self.state['id'] == 3: #Vaccinated
|
||||
self.vaccinated_behaviour()
|
||||
elif self.state['id'] == 4: #Beacon-off
|
||||
self.beacon_off_behaviour()
|
||||
elif self.state['id'] == 5: #Beacon-on
|
||||
self.beacon_on_behaviour()
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
super().step(now)
|
||||
|
||||
|
||||
def neutral_behaviour(self):
|
||||
|
||||
# Infected
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
if len(infected_neighbors)>0:
|
||||
if random.random() < self.prob_neutral_making_denier:
|
||||
self.state['id'] = 3 # Vaccinated making denier
|
||||
|
||||
def infected_behaviour(self):
|
||||
|
||||
# Neutral
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_infect:
|
||||
neighbor.state['id'] = 1 # Infected
|
||||
|
||||
def cured_behaviour(self):
|
||||
|
||||
# Vaccinate
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
|
||||
# Cure
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors:
|
||||
if random.random() < self.prob_cured_healing_infected:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
def vaccinated_behaviour(self):
|
||||
|
||||
# Cure
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors:
|
||||
if random.random() < self.prob_cured_healing_infected:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
# Vaccinate
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
|
||||
# Generate anti-rumor
|
||||
infected_neighbors_2 = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors_2:
|
||||
if random.random() < self.prob_generate_anti_rumor:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
def beacon_off_behaviour(self):
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
if len(infected_neighbors) > 0:
|
||||
self.state['id'] == 5 #Beacon on
|
||||
|
||||
def beacon_on_behaviour(self):
|
||||
|
||||
# Cure (M2 feature added)
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors:
|
||||
if random.random() < self.prob_generate_anti_rumor:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
neutral_neighbors_infected = neighbor.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors_infected:
|
||||
if random.random() < self.prob_generate_anti_rumor:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
infected_neighbors_infected = neighbor.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors_infected:
|
||||
if random.random() < self.prob_generate_anti_rumor:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
# Vaccinate
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
|
||||
|
||||
class SpreadModelM2(BaseBehaviour):
|
||||
init_states[random.randint(0,settings.number_of_nodes)] = {'id':1}
|
||||
init_states[random.randint(0,settings.number_of_nodes)] = {'id':1}
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
|
||||
self.prob_neutral_making_denier = np.random.normal(settings.prob_neutral_making_denier, settings.standard_variance)
|
||||
|
||||
self.prob_infect = np.random.normal(settings.prob_infect, settings.standard_variance)
|
||||
|
||||
self.prob_cured_healing_infected = np.random.normal(settings.prob_cured_healing_infected, settings.standard_variance)
|
||||
self.prob_cured_vaccinate_neutral = np.random.normal(settings.prob_cured_vaccinate_neutral, settings.standard_variance)
|
||||
|
||||
self.prob_vaccinated_healing_infected = np.random.normal(settings.prob_vaccinated_healing_infected, settings.standard_variance)
|
||||
self.prob_vaccinated_vaccinate_neutral = np.random.normal(settings.prob_vaccinated_vaccinate_neutral, settings.standard_variance)
|
||||
self.prob_generate_anti_rumor = np.random.normal(settings.prob_generate_anti_rumor, settings.standard_variance)
|
||||
|
||||
def step(self, now):
|
||||
|
||||
if self.state['id'] == 0: #Neutral
|
||||
self.neutral_behaviour()
|
||||
elif self.state['id'] == 1: #Infected
|
||||
self.infected_behaviour()
|
||||
elif self.state['id'] == 2: #Cured
|
||||
self.cured_behaviour()
|
||||
elif self.state['id'] == 3: #Vaccinated
|
||||
self.vaccinated_behaviour()
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
super().step(now)
|
||||
|
||||
|
||||
def neutral_behaviour(self):
|
||||
|
||||
# Infected
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
if len(infected_neighbors)>0:
|
||||
if random.random() < self.prob_neutral_making_denier:
|
||||
self.state['id'] = 3 # Vaccinated making denier
|
||||
|
||||
def infected_behaviour(self):
|
||||
|
||||
# Neutral
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_infect:
|
||||
neighbor.state['id'] = 1 # Infected
|
||||
|
||||
def cured_behaviour(self):
|
||||
|
||||
# Vaccinate
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
|
||||
# Cure
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors:
|
||||
if random.random() < self.prob_cured_healing_infected:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
def vaccinated_behaviour(self):
|
||||
|
||||
# Cure
|
||||
infected_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors:
|
||||
if random.random() < self.prob_cured_healing_infected:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
# Vaccinate
|
||||
neutral_neighbors = self.get_neighboring_agents(state_id=0)
|
||||
for neighbor in neutral_neighbors:
|
||||
if random.random() < self.prob_cured_vaccinate_neutral:
|
||||
neighbor.state['id'] = 3 # Vaccinated
|
||||
|
||||
# Generate anti-rumor
|
||||
infected_neighbors_2 = self.get_neighboring_agents(state_id=1)
|
||||
for neighbor in infected_neighbors_2:
|
||||
if random.random() < self.prob_generate_anti_rumor:
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
class SISaModel(BaseBehaviour):
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
|
||||
self.neutral_discontent_spon_prob = np.random.normal(settings.neutral_discontent_spon_prob, settings.standard_variance)
|
||||
self.neutral_discontent_infected_prob = np.random.normal(settings.neutral_discontent_infected_prob,settings.standard_variance)
|
||||
self.neutral_content_spon_prob = np.random.normal(settings.neutral_content_spon_prob,settings.standard_variance)
|
||||
self.neutral_content_infected_prob = np.random.normal(settings.neutral_content_infected_prob,settings.standard_variance)
|
||||
|
||||
self.discontent_neutral = np.random.normal(settings.discontent_neutral,settings.standard_variance)
|
||||
self.discontent_content = np.random.normal(settings.discontent_content,settings.variance_d_c)
|
||||
|
||||
self.content_discontent = np.random.normal(settings.content_discontent,settings.variance_c_d)
|
||||
self.content_neutral = np.random.normal(settings.content_neutral,settings.standard_variance)
|
||||
|
||||
def step(self, now):
|
||||
|
||||
if self.state['id'] == 0:
|
||||
self.neutral_behaviour()
|
||||
if self.state['id'] == 1:
|
||||
self.discontent_behaviour()
|
||||
if self.state['id'] == 2:
|
||||
self.content_behaviour()
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
super().step(now)
|
||||
|
||||
|
||||
def neutral_behaviour(self):
|
||||
|
||||
#Spontaneus effects
|
||||
if random.random() < self.neutral_discontent_spon_prob:
|
||||
self.state['id'] = 1
|
||||
if random.random() < self.neutral_content_spon_prob:
|
||||
self.state['id'] = 2
|
||||
|
||||
#Infected
|
||||
discontent_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
if random.random() < len(discontent_neighbors)*self.neutral_discontent_infected_prob:
|
||||
self.state['id'] = 1
|
||||
content_neighbors = self.get_neighboring_agents(state_id=2)
|
||||
if random.random() < len(content_neighbors)*self.neutral_content_infected_prob:
|
||||
self.state['id'] = 2
|
||||
|
||||
def discontent_behaviour(self):
|
||||
|
||||
#Healing
|
||||
if random.random() < self.discontent_neutral:
|
||||
self.state['id'] = 0
|
||||
|
||||
#Superinfected
|
||||
content_neighbors = self.get_neighboring_agents(state_id=2)
|
||||
if random.random() < len(content_neighbors)*self.discontent_content:
|
||||
self.state['id'] = 2
|
||||
|
||||
def content_behaviour(self):
|
||||
|
||||
#Healing
|
||||
if random.random() < self.content_neutral:
|
||||
self.state['id'] = 0
|
||||
|
||||
#Superinfected
|
||||
discontent_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
if random.random() < len(discontent_neighbors)*self.content_discontent:
|
||||
self.state['id'] = 1
|
||||
|
||||
|
||||
class BigMarketModel(BaseBehaviour):
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.enterprises = settings.enterprises
|
||||
self.type = ""
|
||||
self.number_of_enterprises = len(settings.enterprises)
|
||||
|
||||
if self.id < self.number_of_enterprises: #Enterprises
|
||||
self.state['id']=self.id
|
||||
self.type="Enterprise"
|
||||
self.tweet_probability = settings.tweet_probability_enterprises[self.id]
|
||||
else: #normal users
|
||||
self.state['id']=self.number_of_enterprises
|
||||
self.type="User"
|
||||
self.tweet_probability = settings.tweet_probability_users
|
||||
self.tweet_relevant_probability = settings.tweet_relevant_probability
|
||||
self.tweet_probability_about = settings.tweet_probability_about #List
|
||||
self.sentiment_about = settings.sentiment_about #List
|
||||
|
||||
def step(self, now):
|
||||
|
||||
if(self.id < self.number_of_enterprises): # Ennterprise
|
||||
self.enterpriseBehaviour()
|
||||
else: # Usuario
|
||||
self.userBehaviour()
|
||||
for i in range(self.number_of_enterprises): # So that it never is set to 0 if there are not changes (logs)
|
||||
self.attrs['sentiment_enterprise_%s'% self.enterprises[i]] = self.sentiment_about[i]
|
||||
|
||||
super().step(now)
|
||||
|
||||
def enterpriseBehaviour(self):
|
||||
|
||||
if random.random()< self.tweet_probability: #Tweets
|
||||
aware_neighbors = self.get_neighboring_agents(state_id=self.number_of_enterprises) #Nodes neighbour users
|
||||
for x in aware_neighbors:
|
||||
if random.uniform(0,10) < 5:
|
||||
x.sentiment_about[self.id] += 0.1 #Increments for enterprise
|
||||
else:
|
||||
x.sentiment_about[self.id] -= 0.1 #Decrements for enterprise
|
||||
|
||||
# Establecemos limites
|
||||
if x.sentiment_about[self.id] > 1:
|
||||
x.sentiment_about[self.id] = 1
|
||||
if x.sentiment_about[self.id]< -1:
|
||||
x.sentiment_about[self.id] = -1
|
||||
|
||||
x.attrs['sentiment_enterprise_%s'% self.enterprises[self.id]] = x.sentiment_about[self.id]
|
||||
|
||||
|
||||
def userBehaviour(self):
|
||||
|
||||
if random.random() < self.tweet_probability: #Tweets
|
||||
if random.random() < self.tweet_relevant_probability: #Tweets something relevant
|
||||
#Tweet probability per enterprise
|
||||
for i in range(self.number_of_enterprises):
|
||||
random_num = random.random()
|
||||
if random_num < self.tweet_probability_about[i]:
|
||||
#The condition is fulfilled, sentiments are evaluated towards that enterprise
|
||||
if self.sentiment_about[i] < 0:
|
||||
#NEGATIVO
|
||||
self.userTweets("negative",i)
|
||||
elif self.sentiment_about[i] == 0:
|
||||
#NEUTRO
|
||||
pass
|
||||
else:
|
||||
#POSITIVO
|
||||
self.userTweets("positive",i)
|
||||
|
||||
def userTweets(self,sentiment,enterprise):
|
||||
aware_neighbors = self.get_neighboring_agents(state_id=self.number_of_enterprises) #Nodes neighbours users
|
||||
for x in aware_neighbors:
|
||||
if sentiment == "positive":
|
||||
x.sentiment_about[enterprise] +=0.003
|
||||
elif sentiment == "negative":
|
||||
x.sentiment_about[enterprise] -=0.003
|
||||
else:
|
||||
pass
|
||||
|
||||
# Establecemos limites
|
||||
if x.sentiment_about[enterprise] > 1:
|
||||
x.sentiment_about[enterprise] = 1
|
||||
if x.sentiment_about[enterprise] < -1:
|
||||
x.sentiment_about[enterprise] = -1
|
||||
|
||||
x.attrs['sentiment_enterprise_%s'% self.enterprises[enterprise]] = x.sentiment_about[enterprise]
|
||||
|
||||
class SentimentCorrelationModel(BaseBehaviour):
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.outside_effects_prob = settings.outside_effects_prob
|
||||
self.anger_prob = settings.anger_prob
|
||||
self.joy_prob = settings.joy_prob
|
||||
self.sadness_prob = settings.sadness_prob
|
||||
self.disgust_prob = settings.disgust_prob
|
||||
self.time_awareness=[]
|
||||
for i in range(4): #In this model we have 4 sentiments
|
||||
self.time_awareness.append(0) #0-> Anger, 1-> joy, 2->sadness, 3 -> disgust
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
||||
|
||||
|
||||
def step(self, now):
|
||||
self.behaviour()
|
||||
super().step(now)
|
||||
|
||||
def behaviour(self):
|
||||
|
||||
angry_neighbors_1_time_step=[]
|
||||
joyful_neighbors_1_time_step=[]
|
||||
sad_neighbors_1_time_step=[]
|
||||
disgusted_neighbors_1_time_step=[]
|
||||
|
||||
|
||||
angry_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for x in angry_neighbors:
|
||||
if x.time_awareness[0] > (self.env.now-500):
|
||||
angry_neighbors_1_time_step.append(x)
|
||||
num_neighbors_angry = len(angry_neighbors_1_time_step)
|
||||
|
||||
|
||||
joyful_neighbors = self.get_neighboring_agents(state_id=2)
|
||||
for x in joyful_neighbors:
|
||||
if x.time_awareness[1] > (self.env.now-500):
|
||||
joyful_neighbors_1_time_step.append(x)
|
||||
num_neighbors_joyful = len(joyful_neighbors_1_time_step)
|
||||
|
||||
|
||||
sad_neighbors = self.get_neighboring_agents(state_id=3)
|
||||
for x in sad_neighbors:
|
||||
if x.time_awareness[2] > (self.env.now-500):
|
||||
sad_neighbors_1_time_step.append(x)
|
||||
num_neighbors_sad = len(sad_neighbors_1_time_step)
|
||||
|
||||
|
||||
disgusted_neighbors = self.get_neighboring_agents(state_id=4)
|
||||
for x in disgusted_neighbors:
|
||||
if x.time_awareness[3] > (self.env.now-500):
|
||||
disgusted_neighbors_1_time_step.append(x)
|
||||
num_neighbors_disgusted = len(disgusted_neighbors_1_time_step)
|
||||
|
||||
|
||||
anger_prob= settings.anger_prob+(len(angry_neighbors_1_time_step)*settings.anger_prob)
|
||||
joy_prob= settings.joy_prob+(len(joyful_neighbors_1_time_step)*settings.joy_prob)
|
||||
sadness_prob = settings.sadness_prob+(len(sad_neighbors_1_time_step)*settings.sadness_prob)
|
||||
disgust_prob = settings.disgust_prob+(len(disgusted_neighbors_1_time_step)*settings.disgust_prob)
|
||||
outside_effects_prob= settings.outside_effects_prob
|
||||
|
||||
|
||||
num = random.random()
|
||||
|
||||
|
||||
if(num<outside_effects_prob):
|
||||
self.state['id'] = random.randint(1,4)
|
||||
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=self.state['id'] #It is stored when it has been infected for the dynamic network
|
||||
self.time_awareness[self.state['id']-1] = self.env.now
|
||||
self.attrs['sentiment'] = self.state['id']
|
||||
|
||||
|
||||
|
||||
if(num<anger_prob):
|
||||
|
||||
self.state['id'] = 1
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
||||
self.time_awareness[self.state['id']-1] = self.env.now
|
||||
elif (num<joy_prob+anger_prob and num>anger_prob):
|
||||
|
||||
self.state['id'] = 2
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=2
|
||||
self.time_awareness[self.state['id']-1] = self.env.now
|
||||
elif (num<sadness_prob+anger_prob+joy_prob and num>joy_prob+anger_prob):
|
||||
|
||||
|
||||
self.state['id'] = 3
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=3
|
||||
self.time_awareness[self.state['id']-1] = self.env.now
|
||||
elif (num<disgust_prob+sadness_prob+anger_prob+joy_prob and num>sadness_prob+anger_prob+joy_prob):
|
||||
|
||||
|
||||
self.state['id'] = 4
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=4
|
||||
self.time_awareness[self.state['id']-1] = self.env.now
|
||||
|
||||
self.attrs['sentiment'] = self.state['id']
|
||||
|
||||
|
||||
class BassModel(BaseBehaviour):
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.innovation_prob = settings.innovation_prob
|
||||
self.imitation_prob = settings.imitation_prob
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
||||
|
||||
def step(self, now):
|
||||
self.behaviour()
|
||||
super().step(now)
|
||||
|
||||
def behaviour(self):
|
||||
#Outside effects
|
||||
if random.random() < settings.innovation_prob:
|
||||
if self.state['id'] == 0:
|
||||
self.state['id'] = 1
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
||||
else:
|
||||
pass
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
return
|
||||
|
||||
#Imitation effects
|
||||
if self.state['id'] == 0:
|
||||
aware_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
num_neighbors_aware = len(aware_neighbors)
|
||||
if random.random() < (settings.imitation_prob*num_neighbors_aware):
|
||||
self.state['id'] = 1
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
||||
|
||||
else:
|
||||
pass
|
||||
self.attrs['status'] = self.state['id']
|
||||
|
||||
|
||||
class IndependentCascadeModel(BaseBehaviour):
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.innovation_prob = settings.innovation_prob
|
||||
self.imitation_prob = settings.imitation_prob
|
||||
self.time_awareness = 0
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=0
|
||||
|
||||
def step(self,now):
|
||||
self.behaviour()
|
||||
super().step(now)
|
||||
|
||||
def behaviour(self):
|
||||
aware_neighbors_1_time_step=[]
|
||||
#Outside effects
|
||||
if random.random() < settings.innovation_prob:
|
||||
if self.state['id'] == 0:
|
||||
self.state['id'] = 1
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
||||
self.time_awareness = self.env.now #To know when they have been infected
|
||||
else:
|
||||
pass
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
return
|
||||
|
||||
#Imitation effects
|
||||
if self.state['id'] == 0:
|
||||
aware_neighbors = self.get_neighboring_agents(state_id=1)
|
||||
for x in aware_neighbors:
|
||||
if x.time_awareness == (self.env.now-1):
|
||||
aware_neighbors_1_time_step.append(x)
|
||||
num_neighbors_aware = len(aware_neighbors_1_time_step)
|
||||
if random.random() < (settings.imitation_prob*num_neighbors_aware):
|
||||
self.state['id'] = 1
|
||||
sentimentCorrelationNodeArray[self.id][self.env.now]=1
|
||||
else:
|
||||
pass
|
||||
|
||||
self.attrs['status'] = self.state['id']
|
||||
return
|
@@ -1,6 +1,7 @@
|
||||
nxsim
|
||||
simpy
|
||||
networkx
|
||||
networkx>=2.0
|
||||
numpy
|
||||
matplotlib
|
||||
pyyaml
|
||||
pandas
|
||||
|
47
setup.py
@@ -1,20 +1,21 @@
|
||||
import pip
|
||||
import os
|
||||
from setuptools import setup
|
||||
# parse_requirements() returns generator of pip.req.InstallRequirement objects
|
||||
from pip.req import parse_requirements
|
||||
from soil import __version__
|
||||
|
||||
try:
|
||||
install_reqs = parse_requirements(
|
||||
"requirements.txt", session=pip.download.PipSession())
|
||||
test_reqs = parse_requirements(
|
||||
"test-requirements.txt", session=pip.download.PipSession())
|
||||
except AttributeError:
|
||||
install_reqs = parse_requirements("requirements.txt")
|
||||
test_reqs = parse_requirements("test-requirements.txt")
|
||||
|
||||
install_reqs = [str(ir.req) for ir in install_reqs]
|
||||
test_reqs = [str(ir.req) for ir in test_reqs]
|
||||
with open(os.path.join('soil', 'VERSION')) as f:
|
||||
__version__ = f.readlines()[0].strip()
|
||||
assert __version__
|
||||
|
||||
|
||||
def parse_requirements(filename):
|
||||
""" load requirements from a pip requirements file """
|
||||
with open(filename, 'r') as f:
|
||||
lineiter = list(line.strip() for line in f)
|
||||
return [line for line in lineiter if line and not line.startswith("#")]
|
||||
|
||||
|
||||
install_reqs = parse_requirements("requirements.txt")
|
||||
test_reqs = parse_requirements("test-requirements.txt")
|
||||
|
||||
|
||||
setup(
|
||||
@@ -28,12 +29,26 @@ setup(
|
||||
download_url='https://github.com/gsi-upm/soil/archive/{}.tar.gz'.format(
|
||||
__version__),
|
||||
keywords=['agent', 'social', 'simulator'],
|
||||
classifiers=[],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python :: 3'],
|
||||
install_requires=install_reqs,
|
||||
extras_require={
|
||||
'web': ['tornado']
|
||||
|
||||
},
|
||||
tests_require=test_reqs,
|
||||
setup_requires=['pytest-runner', ],
|
||||
include_package_data=True,
|
||||
entry_points={
|
||||
'console_scripts':
|
||||
['soil = soil.__init__:main']
|
||||
['soil = soil.__init__:main',
|
||||
'soil-web = soil.web.__init__:main']
|
||||
})
|
||||
|
@@ -1,63 +0,0 @@
|
||||
---
|
||||
name: ControlModelM2_sim
|
||||
max_time: 50
|
||||
num_trials: 1
|
||||
timeout: 2
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 100
|
||||
m: 2
|
||||
agent_distribution:
|
||||
- agent_type: ControlModelM2
|
||||
weight: 0.1
|
||||
state:
|
||||
id: 1
|
||||
- agent_type: ControlModelM2
|
||||
weight: 0.9
|
||||
state:
|
||||
id: 0
|
||||
environment_params:
|
||||
prob_neutral_making_denier: 0.035
|
||||
prob_infect: 0.075
|
||||
prob_cured_healing_infected: 0.035
|
||||
prob_cured_vaccinate_neutral: 0.035
|
||||
prob_vaccinated_healing_infected: 0.035
|
||||
prob_vaccinated_vaccinate_neutral: 0.035
|
||||
prob_generate_anti_rumor: 0.035
|
||||
standard_variance: 0.055
|
||||
---
|
||||
name: SISA_sm
|
||||
max_time: 50
|
||||
num_trials: 2
|
||||
timeout: 2
|
||||
network_params:
|
||||
generator: erdos_renyi_graph
|
||||
n: 10000
|
||||
p: 0.05
|
||||
#other_agents:
|
||||
# - agent_type: DrawingAgent
|
||||
agent_distribution:
|
||||
- agent_type: SISaModel
|
||||
weight: 1
|
||||
state:
|
||||
id: content
|
||||
- agent_type: SISaModel
|
||||
weight: 1
|
||||
state:
|
||||
id: neutral
|
||||
- agent_type: SISaModel
|
||||
weight: 1
|
||||
state:
|
||||
id: discontent
|
||||
environment_params:
|
||||
neutral_discontent_spon_prob: 0.04
|
||||
neutral_discontent_infected_prob: 0.04
|
||||
neutral_content_spon_prob: 0.18
|
||||
neutral_content_infected_prob: 0.02
|
||||
discontent_neutral: 0.13
|
||||
discontent_content: 0.07
|
||||
variance_d_c: 0.02
|
||||
content_discontent: 0.009
|
||||
variance_c_d: 0.003
|
||||
content_neutral: 0.088
|
||||
standard_variance: 0.055
|
1
soil/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.12.0
|
@@ -1,19 +1,23 @@
|
||||
import importlib
|
||||
import sys
|
||||
import os
|
||||
import pdb
|
||||
import logging
|
||||
|
||||
__version__ = "0.9.2"
|
||||
from .version import __version__
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str
|
||||
|
||||
logging.basicConfig()
|
||||
|
||||
from . import agents
|
||||
from . import simulation
|
||||
from . import environment
|
||||
from . import utils
|
||||
from . import settings
|
||||
from . import analysis
|
||||
|
||||
|
||||
def main():
|
||||
@@ -27,6 +31,18 @@ def main():
|
||||
help='python module containing the simulation configuration.')
|
||||
parser.add_argument('--module', '-m', type=str,
|
||||
help='file containing the code of any custom agents.')
|
||||
parser.add_argument('--dry-run', '--dry', action='store_true',
|
||||
help='Do not store the results of the simulation.')
|
||||
parser.add_argument('--pdb', action='store_true',
|
||||
help='Use a pdb console in case of exception.')
|
||||
parser.add_argument('--graph', '-g', action='store_true',
|
||||
help='Dump GEXF graph. Defaults to false.')
|
||||
parser.add_argument('--csv', action='store_true',
|
||||
help='Dump history in CSV format. Defaults to false.')
|
||||
parser.add_argument('--output', '-o', type=str, default="soil_output",
|
||||
help='folder to write results to. It defaults to the current directory.')
|
||||
parser.add_argument('--synchronous', action='store_true',
|
||||
help='Run trials serially and synchronously instead of in parallel. Defaults to false.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -34,8 +50,25 @@ def main():
|
||||
sys.path.append(os.getcwd())
|
||||
importlib.import_module(args.module)
|
||||
|
||||
print('Loading config file: {}'.format(args.file))
|
||||
simulation.run_from_config(args.file)
|
||||
logging.info('Loading config file: {}'.format(args.file, args.output))
|
||||
|
||||
try:
|
||||
dump = []
|
||||
if not args.dry_run:
|
||||
if args.csv:
|
||||
dump.append('csv')
|
||||
if args.graph:
|
||||
dump.append('gexf')
|
||||
simulation.run_from_config(args.file,
|
||||
dry_run=args.dry_run,
|
||||
dump=dump,
|
||||
parallel=(not args.synchronous and not args.pdb),
|
||||
results_dir=args.output)
|
||||
except Exception as ex:
|
||||
if args.pdb:
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
4
soil/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -1,123 +0,0 @@
|
||||
import nxsim
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
import json
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
class BaseAgent(nxsim.BaseAgent):
|
||||
"""
|
||||
A special simpy BaseAgent that keeps track of its state history.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._history = OrderedDict()
|
||||
self._neighbors = None
|
||||
super().__init__(*args, **kwargs)
|
||||
self._history[None] = deepcopy(self.state)
|
||||
|
||||
@property
|
||||
def now(self):
|
||||
try:
|
||||
return self.env.now
|
||||
except AttributeError:
|
||||
# No environment
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
res = self.step()
|
||||
self._history[self.env.now] = deepcopy(self.state)
|
||||
yield res or self.env.timeout(self.env.interval)
|
||||
|
||||
def step(self):
|
||||
pass
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self._history)
|
||||
|
||||
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
||||
|
||||
def count_agents(self, state_id=None, limit_neighbors=False):
|
||||
if limit_neighbors:
|
||||
agents = self.global_topology.neighbors(self.id)
|
||||
else:
|
||||
agents = self.global_topology.nodes()
|
||||
count = 0
|
||||
for agent in agents:
|
||||
if state_id and state_id != self.global_topology.node[agent]['agent'].state['id']:
|
||||
continue
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def count_neighboring_agents(self, state_id=None):
|
||||
return self.count_agents(state_id, limit_neighbors=True)
|
||||
|
||||
|
||||
def state(func):
|
||||
|
||||
@wraps(func)
|
||||
def func_wrapper(self):
|
||||
when = None
|
||||
next_state = func(self)
|
||||
try:
|
||||
next_state, when = next_state
|
||||
except TypeError:
|
||||
pass
|
||||
if next_state:
|
||||
try:
|
||||
self.state['id'] = next_state.id
|
||||
except AttributeError:
|
||||
raise NotImplemented('State id %s is not valid.' % next_state)
|
||||
return when
|
||||
|
||||
func_wrapper.id = func.__name__
|
||||
func_wrapper.is_default = False
|
||||
return func_wrapper
|
||||
|
||||
|
||||
def default_state(func):
|
||||
func.is_default = True
|
||||
return func
|
||||
|
||||
|
||||
class MetaFSM(type):
|
||||
def __init__(cls, name, bases, nmspc):
|
||||
super(MetaFSM, cls).__init__(name, bases, nmspc)
|
||||
states = {}
|
||||
# Re-use states from inherited classes
|
||||
default_state = None
|
||||
for i in bases:
|
||||
if isinstance(i, MetaFSM):
|
||||
for state_id, state in i.states.items():
|
||||
if state.is_default:
|
||||
default_state = state
|
||||
states[state_id] = state
|
||||
|
||||
# Add new states
|
||||
for name, func in nmspc.items():
|
||||
if hasattr(func, 'id'):
|
||||
if func.is_default:
|
||||
default_state = func
|
||||
states[func.id] = func
|
||||
cls.default_state = default_state
|
||||
cls.states = states
|
||||
|
||||
|
||||
class FSM(BaseAgent, metaclass=MetaFSM):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FSM, self).__init__(*args, **kwargs)
|
||||
if 'id' not in self.state:
|
||||
self.state['id'] = self.default_state.id
|
||||
|
||||
def step(self):
|
||||
if 'id' in self.state:
|
||||
next_state = self.state['id']
|
||||
elif self.default_state:
|
||||
next_state = self.default_state.id
|
||||
else:
|
||||
raise Exception('{} has no valid state id or default state'.format(self))
|
||||
if next_state not in self.states:
|
||||
raise Exception('{} is not a valid id for {}'.format(next_state, self))
|
||||
self.states[next_state](self)
|
@@ -1,8 +1,8 @@
|
||||
import random
|
||||
from . import NetworkAgent
|
||||
from . import BaseAgent
|
||||
|
||||
|
||||
class BassModel(NetworkAgent):
|
||||
class BassModel(BaseAgent):
|
||||
"""
|
||||
Settings:
|
||||
innovation_prob
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import random
|
||||
from . import NetworkAgent
|
||||
from . import BaseAgent
|
||||
|
||||
|
||||
class BigMarketModel(NetworkAgent):
|
||||
class BigMarketModel(BaseAgent):
|
||||
"""
|
||||
Settings:
|
||||
Names:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from . import NetworkAgent
|
||||
from . import BaseAgent
|
||||
|
||||
|
||||
class CounterModel(NetworkAgent):
|
||||
class CounterModel(BaseAgent):
|
||||
"""
|
||||
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||
in each step and adds it to its state.
|
||||
@@ -9,14 +9,14 @@ class CounterModel(NetworkAgent):
|
||||
|
||||
def step(self):
|
||||
# Outside effects
|
||||
total = len(self.get_all_agents())
|
||||
neighbors = len(self.get_neighboring_agents())
|
||||
self.state['times'] = self.state.get('times', 0) + 1
|
||||
self.state['neighbors'] = neighbors
|
||||
self.state['total'] = total
|
||||
total = len(list(self.get_all_agents()))
|
||||
neighbors = len(list(self.get_neighboring_agents()))
|
||||
self['times'] = self.get('times', 0) + 1
|
||||
self['neighbors'] = neighbors
|
||||
self['total'] = total
|
||||
|
||||
|
||||
class AggregatedCounter(NetworkAgent):
|
||||
class AggregatedCounter(BaseAgent):
|
||||
"""
|
||||
Dummy behaviour. It counts the number of nodes in the network and neighbors
|
||||
in each step and adds it to its state.
|
||||
@@ -24,8 +24,9 @@ class AggregatedCounter(NetworkAgent):
|
||||
|
||||
def step(self):
|
||||
# Outside effects
|
||||
total = len(self.get_all_agents())
|
||||
neighbors = len(self.get_neighboring_agents())
|
||||
self.state['times'] = self.state.get('times', 0) + 1
|
||||
self.state['neighbors'] = self.state.get('neighbors', 0) + neighbors
|
||||
self.state['total'] = self.state.get('total', 0) + total
|
||||
total = len(list(self.get_all_agents()))
|
||||
neighbors = len(list(self.get_neighboring_agents()))
|
||||
self['times'] = self.get('times', 0) + 1
|
||||
self['neighbors'] = self.get('neighbors', 0) + neighbors
|
||||
self['total'] = total = self.get('total', 0) + total
|
||||
self.debug('Running for step: {}. Total: {}'.format(self.now, total))
|
||||
|
@@ -15,4 +15,4 @@ class DrawingAgent(BaseAgent):
|
||||
# Outside effects
|
||||
f = plt.figure()
|
||||
nx.draw(self.env.G, node_size=10, width=0.2, pos=nx.spring_layout(self.env.G, scale=100), ax=f.add_subplot(111))
|
||||
f.savefig(os.path.join(self.env.sim().dir_path, "graph-"+str(self.env.now)+".png"))
|
||||
f.savefig(os.path.join(self.env.get_path(), "graph-"+str(self.env.now)+".png"))
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import random
|
||||
import numpy as np
|
||||
from . import NetworkAgent
|
||||
from . import BaseAgent
|
||||
|
||||
|
||||
class SpreadModelM2(NetworkAgent):
|
||||
class SpreadModelM2(BaseAgent):
|
||||
"""
|
||||
Settings:
|
||||
prob_neutral_making_denier
|
||||
@@ -104,7 +104,7 @@ class SpreadModelM2(NetworkAgent):
|
||||
neighbor.state['id'] = 2 # Cured
|
||||
|
||||
|
||||
class ControlModelM2(NetworkAgent):
|
||||
class ControlModelM2(BaseAgent):
|
||||
"""
|
||||
Settings:
|
||||
prob_neutral_making_denier
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import random
|
||||
from . import NetworkAgent
|
||||
from . import BaseAgent
|
||||
|
||||
|
||||
class SentimentCorrelationModel(NetworkAgent):
|
||||
class SentimentCorrelationModel(BaseAgent):
|
||||
"""
|
||||
Settings:
|
||||
outside_effects_prob
|
||||
|
@@ -6,12 +6,15 @@
|
||||
|
||||
|
||||
import nxsim
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
import json
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from .. import utils, history
|
||||
|
||||
agent_types = {}
|
||||
|
||||
@@ -27,28 +30,82 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
||||
A special simpy BaseAgent that keeps track of its state history.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._history = OrderedDict()
|
||||
defaults = {}
|
||||
|
||||
def __init__(self, environment=None, agent_id=None, state=None,
|
||||
name='network_process', interval=None, **state_params):
|
||||
# Check for REQUIRED arguments
|
||||
assert environment is not None, TypeError('__init__ missing 1 required keyword argument: \'environment\'. '
|
||||
'Cannot be NoneType.')
|
||||
# Initialize agent parameters
|
||||
self.id = agent_id
|
||||
self.name = name
|
||||
self.state_params = state_params
|
||||
|
||||
# Global parameters
|
||||
self.global_topology = environment.G
|
||||
self.environment_params = environment.environment_params
|
||||
|
||||
# Register agent to environment
|
||||
self.env = environment
|
||||
|
||||
self._neighbors = None
|
||||
super().__init__(*args, **kwargs)
|
||||
self.alive = True
|
||||
real_state = deepcopy(self.defaults)
|
||||
real_state.update(state or {})
|
||||
self.state = real_state
|
||||
self.interval = interval
|
||||
|
||||
if not hasattr(self, 'level'):
|
||||
self.level = logging.DEBUG
|
||||
self.logger = logging.getLogger('{}-Agent-{}'.format(self.env.name,
|
||||
self.id))
|
||||
self.logger.setLevel(self.level)
|
||||
|
||||
# initialize every time an instance of the agent is created
|
||||
self.action = self.env.process(self.run())
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
'''
|
||||
Return the agent itself, which behaves as a dictionary.
|
||||
Changes made to `agent.state` will be reflected in the history.
|
||||
|
||||
This method shouldn't be used, but is kept here for backwards compatibility.
|
||||
'''
|
||||
return self
|
||||
|
||||
@state.setter
|
||||
def state(self, value):
|
||||
self._state = {}
|
||||
for k, v in value.items():
|
||||
self[k] = v
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, tuple):
|
||||
k, t_step = key
|
||||
if k is not None:
|
||||
if t_step is not None:
|
||||
return self._history[t_step][k]
|
||||
else:
|
||||
return {tt: tv.get(k, None) for tt, tv in self._history.items()}
|
||||
else:
|
||||
return self._history[t_step]
|
||||
return self.state[key]
|
||||
key, t_step = key
|
||||
k = history.Key(key=key, t_step=t_step, agent_id=self.id)
|
||||
return self.env[k]
|
||||
return self._state.get(key, None)
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._state[key] = None
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._state
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.state[key] = value
|
||||
self._state[key] = value
|
||||
k = history.Key(t_step=self.now,
|
||||
agent_id=self.id,
|
||||
key=key)
|
||||
self.env[k] = value
|
||||
|
||||
def save_state(self):
|
||||
self._history[self.now] = deepcopy(self.state)
|
||||
def items(self):
|
||||
return self._state.items()
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self[key] if key in self else default
|
||||
|
||||
@property
|
||||
def now(self):
|
||||
@@ -59,18 +116,26 @@ class BaseAgent(nxsim.BaseAgent, metaclass=MetaAgent):
|
||||
return None
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.interval is not None:
|
||||
interval = self.interval
|
||||
elif 'interval' in self:
|
||||
interval = self['interval']
|
||||
else:
|
||||
interval = self.env.interval
|
||||
while self.alive:
|
||||
res = self.step()
|
||||
yield res or self.env.timeout(self.env.interval)
|
||||
yield res or self.env.timeout(interval)
|
||||
|
||||
def die(self, remove=False):
|
||||
self.alive = False
|
||||
if remove:
|
||||
super().die()
|
||||
|
||||
def step(self):
|
||||
pass
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self._history)
|
||||
|
||||
|
||||
class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
||||
return json.dumps(self.state)
|
||||
|
||||
def count_agents(self, state_id=None, limit_neighbors=False):
|
||||
if limit_neighbors:
|
||||
@@ -79,30 +144,69 @@ class NetworkAgent(BaseAgent, nxsim.BaseNetworkAgent):
|
||||
agents = self.global_topology.nodes()
|
||||
count = 0
|
||||
for agent in agents:
|
||||
if state_id and state_id != self.global_topology.node[agent]['agent'].state['id']:
|
||||
if state_id and state_id != self.global_topology.node[agent]['agent']['id']:
|
||||
continue
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def count_neighboring_agents(self, state_id=None):
|
||||
return self.count_agents(state_id, limit_neighbors=True)
|
||||
return len(super().get_agents(state_id, limit_neighbors=True))
|
||||
|
||||
def get_agents(self, state_id=None, limit_neighbors=False, iterator=False, **kwargs):
|
||||
if limit_neighbors:
|
||||
agents = super().get_agents(state_id, limit_neighbors)
|
||||
else:
|
||||
agents = filter(lambda x: state_id is None or x.state.get('id', None) == state_id,
|
||||
self.env.agents)
|
||||
|
||||
def matches_all(agent):
|
||||
state = agent.state
|
||||
for k, v in kwargs.items():
|
||||
if state.get(k, None) != v:
|
||||
return False
|
||||
return True
|
||||
|
||||
f = filter(matches_all, agents)
|
||||
if iterator:
|
||||
return f
|
||||
return list(f)
|
||||
|
||||
def log(self, message, *args, level=logging.INFO, **kwargs):
|
||||
message = message + " ".join(str(i) for i in args)
|
||||
message = "\t@{:>5}:\t{}".format(self.now, message)
|
||||
for k, v in kwargs:
|
||||
message += " {k}={v} ".format(k, v)
|
||||
extra = {}
|
||||
extra['now'] = self.now
|
||||
extra['id'] = self.id
|
||||
return self.logger.log(level, message, extra=extra)
|
||||
|
||||
def debug(self, *args, **kwargs):
|
||||
return self.log(*args, level=logging.DEBUG, **kwargs)
|
||||
|
||||
def info(self, *args, **kwargs):
|
||||
return self.log(*args, level=logging.INFO, **kwargs)
|
||||
|
||||
|
||||
def state(func):
|
||||
'''
|
||||
A state function should return either a state id, or a tuple (state_id, when)
|
||||
The default value for state_id is the current state id.
|
||||
The default value for when is the interval defined in the nevironment.
|
||||
'''
|
||||
|
||||
@wraps(func)
|
||||
def func_wrapper(self):
|
||||
when = None
|
||||
next_state = func(self)
|
||||
when = None
|
||||
if next_state is None:
|
||||
return when
|
||||
try:
|
||||
next_state, when = next_state
|
||||
except TypeError:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if next_state:
|
||||
try:
|
||||
self.state['id'] = next_state.id
|
||||
except AttributeError:
|
||||
raise NotImplemented('State id %s is not valid.' % next_state)
|
||||
self.set_state(next_state)
|
||||
return when
|
||||
|
||||
func_wrapper.id = func.__name__
|
||||
@@ -142,11 +246,13 @@ class FSM(BaseAgent, metaclass=MetaFSM):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FSM, self).__init__(*args, **kwargs)
|
||||
if 'id' not in self.state:
|
||||
self.state['id'] = self.default_state.id
|
||||
if not self.default_state:
|
||||
raise ValueError('No default state specified for {}'.format(self.id))
|
||||
self['id'] = self.default_state.id
|
||||
|
||||
def step(self):
|
||||
if 'id' in self.state:
|
||||
next_state = self.state['id']
|
||||
next_state = self['id']
|
||||
elif self.default_state:
|
||||
next_state = self.default_state.id
|
||||
else:
|
||||
@@ -155,6 +261,123 @@ class FSM(BaseAgent, metaclass=MetaFSM):
|
||||
raise Exception('{} is not a valid id for {}'.format(next_state, self))
|
||||
self.states[next_state](self)
|
||||
|
||||
def set_state(self, state):
|
||||
if hasattr(state, 'id'):
|
||||
state = state.id
|
||||
if state not in self.states:
|
||||
raise ValueError('{} is not a valid state'.format(state))
|
||||
self['id'] = state
|
||||
return state
|
||||
|
||||
|
||||
def prob(prob=1):
|
||||
'''
|
||||
A true/False uniform distribution with a given probability.
|
||||
To be used like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if prob(0.3):
|
||||
do_something()
|
||||
|
||||
'''
|
||||
r = random.random()
|
||||
return r < prob
|
||||
|
||||
|
||||
def calculate_distribution(network_agents=None,
|
||||
agent_type=None):
|
||||
'''
|
||||
Calculate the threshold values (thresholds for a uniform distribution)
|
||||
of an agent distribution given the weights of each agent type.
|
||||
|
||||
The input has this form: ::
|
||||
|
||||
[
|
||||
{'agent_type': 'agent_type_1',
|
||||
'weight': 0.2,
|
||||
'state': {
|
||||
'id': 0
|
||||
}
|
||||
},
|
||||
{'agent_type': 'agent_type_2',
|
||||
'weight': 0.8,
|
||||
'state': {
|
||||
'id': 1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
In this example, 20% of the nodes will be marked as type
|
||||
'agent_type_1'.
|
||||
'''
|
||||
if network_agents:
|
||||
network_agents = deepcopy(network_agents)
|
||||
elif agent_type:
|
||||
network_agents = [{'agent_type': agent_type}]
|
||||
else:
|
||||
return []
|
||||
|
||||
# Calculate the thresholds
|
||||
total = sum(x.get('weight', 1) for x in network_agents)
|
||||
acc = 0
|
||||
for v in network_agents:
|
||||
upper = acc + (v.get('weight', 1)/total)
|
||||
v['threshold'] = [acc, upper]
|
||||
acc = upper
|
||||
return network_agents
|
||||
|
||||
|
||||
def _serialize_distribution(network_agents):
|
||||
d = _convert_agent_types(network_agents,
|
||||
to_string=True)
|
||||
'''
|
||||
When serializing an agent distribution, remove the thresholds, in order
|
||||
to avoid cluttering the YAML definition file.
|
||||
'''
|
||||
for v in d:
|
||||
if 'threshold' in v:
|
||||
del v['threshold']
|
||||
return d
|
||||
|
||||
|
||||
def _validate_states(states, topology):
|
||||
'''Validate states to avoid ignoring states during initialization'''
|
||||
states = states or []
|
||||
if isinstance(states, dict):
|
||||
for x in states:
|
||||
assert x in topology.node
|
||||
else:
|
||||
assert len(states) <= len(topology)
|
||||
return states
|
||||
|
||||
|
||||
def _convert_agent_types(ind, to_string=False):
|
||||
'''Convenience method to allow specifying agents by class or class name.'''
|
||||
d = deepcopy(ind)
|
||||
for v in d:
|
||||
agent_type = v['agent_type']
|
||||
if to_string and not isinstance(agent_type, str):
|
||||
v['agent_type'] = str(agent_type.__name__)
|
||||
elif not to_string and isinstance(agent_type, str):
|
||||
v['agent_type'] = agent_types[agent_type]
|
||||
return d
|
||||
|
||||
|
||||
def _agent_from_distribution(distribution, value=-1):
|
||||
"""Used in the initialization of agents given an agent distribution."""
|
||||
if value < 0:
|
||||
value = random.random()
|
||||
for d in distribution:
|
||||
threshold = d['threshold']
|
||||
if value >= threshold[0] and value < threshold[1]:
|
||||
state = {}
|
||||
if 'state' in d:
|
||||
state = deepcopy(d['state'])
|
||||
return d['agent_type'], state
|
||||
|
||||
raise Exception('Distribution for value {} not found in: {}'.format(value, distribution))
|
||||
|
||||
|
||||
from .BassModel import *
|
||||
from .BigMarketModel import *
|
||||
|
161
soil/analysis.py
@@ -4,20 +4,163 @@ import glob
|
||||
import yaml
|
||||
from os.path import join
|
||||
|
||||
from . import utils, history
|
||||
|
||||
def get_data(pattern, process=True, attributes=None):
|
||||
|
||||
def read_data(*args, group=False, **kwargs):
|
||||
iterable = _read_data(*args, **kwargs)
|
||||
if group:
|
||||
return group_trials(iterable)
|
||||
else:
|
||||
return list(iterable)
|
||||
|
||||
|
||||
def _read_data(pattern, *args, from_csv=False, process_args=None, **kwargs):
|
||||
if not process_args:
|
||||
process_args = {}
|
||||
for folder in glob.glob(pattern):
|
||||
config_file = glob.glob(join(folder, '*.yml'))[0]
|
||||
config = yaml.load(open(config_file))
|
||||
for trial_data in sorted(glob.glob(join(folder, '*.environment.csv'))):
|
||||
df = pd.read_csv(trial_data)
|
||||
if process:
|
||||
if attributes is not None:
|
||||
df = df[df['attribute'].isin(attributes)]
|
||||
df = df.pivot_table(values='attribute', index='tstep', columns=['value'], aggfunc='count').fillna(0)
|
||||
yield config_file, df, config
|
||||
df = None
|
||||
if from_csv:
|
||||
for trial_data in sorted(glob.glob(join(folder,
|
||||
'*.environment.csv'))):
|
||||
df = read_csv(trial_data, **kwargs)
|
||||
yield config_file, df, config
|
||||
else:
|
||||
for trial_data in sorted(glob.glob(join(folder, '*.db.sqlite'))):
|
||||
df = read_sql(trial_data, **kwargs)
|
||||
yield config_file, df, config
|
||||
|
||||
|
||||
def read_sql(db, *args, **kwargs):
|
||||
h = history.History(db, backup=False)
|
||||
df = h.read_sql(*args, **kwargs)
|
||||
return df
|
||||
|
||||
|
||||
def read_csv(filename, keys=None, convert_types=False, **kwargs):
|
||||
'''
|
||||
Read a CSV in canonical form: ::
|
||||
|
||||
<agent_id, t_step, key, value, value_type>
|
||||
|
||||
'''
|
||||
df = pd.read_csv(filename)
|
||||
if convert_types:
|
||||
df = convert_types_slow(df)
|
||||
if keys:
|
||||
df = df[df['key'].isin(keys)]
|
||||
df = process_one(df)
|
||||
return df
|
||||
|
||||
|
||||
def convert_row(row):
|
||||
row['value'] = utils.convert(row['value'], row['value_type'])
|
||||
return row
|
||||
|
||||
|
||||
def convert_types_slow(df):
|
||||
'''This is a slow operation.'''
|
||||
dtypes = get_types(df)
|
||||
for k, v in dtypes.items():
|
||||
t = df[df['key']==k]
|
||||
t['value'] = t['value'].astype(v)
|
||||
df = df.apply(convert_row, axis=1)
|
||||
return df
|
||||
|
||||
def split_df(df):
|
||||
'''
|
||||
Split a dataframe in two dataframes: one with the history of agents,
|
||||
and one with the environment history
|
||||
'''
|
||||
envmask = (df['agent_id'] == 'env')
|
||||
n_env = envmask.sum()
|
||||
if n_env == len(df):
|
||||
return df, None
|
||||
elif n_env == 0:
|
||||
return None, df
|
||||
agents, env = [x for _, x in df.groupby(envmask)]
|
||||
return env, agents
|
||||
|
||||
|
||||
def process(df, **kwargs):
|
||||
'''
|
||||
Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into
|
||||
two dataframes with a column per key: one with the history of the agents, and one for the
|
||||
history of the environment.
|
||||
'''
|
||||
env, agents = split_df(df)
|
||||
return process_one(env, **kwargs), process_one(agents, **kwargs)
|
||||
|
||||
|
||||
def get_types(df):
|
||||
dtypes = df.groupby(by=['key'])['value_type'].unique()
|
||||
return {k:v[0] for k,v in dtypes.iteritems()}
|
||||
|
||||
|
||||
def process_one(df, *keys, columns=['key', 'agent_id'], values='value',
|
||||
fill=True, index=['t_step',],
|
||||
aggfunc='first', **kwargs):
|
||||
'''
|
||||
Process a dataframe in canonical form ``(t_step, agent_id, key, value, value_type)`` into
|
||||
a dataframe with a column per key
|
||||
'''
|
||||
if df is None:
|
||||
return df
|
||||
if keys:
|
||||
df = df[df['key'].isin(keys)]
|
||||
|
||||
df = df.pivot_table(values=values, index=index, columns=columns,
|
||||
aggfunc=aggfunc, **kwargs)
|
||||
if fill:
|
||||
df = fillna(df)
|
||||
return df
|
||||
|
||||
|
||||
def get_count(df, *keys):
|
||||
if keys:
|
||||
df = df[list(keys)]
|
||||
counts = pd.DataFrame()
|
||||
for key in df.columns.levels[0]:
|
||||
g = df[key].apply(pd.Series.value_counts, axis=1).fillna(0)
|
||||
for value, series in g.iteritems():
|
||||
counts[key, value] = series
|
||||
counts.columns = pd.MultiIndex.from_tuples(counts.columns)
|
||||
return counts
|
||||
|
||||
|
||||
def get_value(df, *keys, aggfunc='sum'):
|
||||
if keys:
|
||||
df = df[list(keys)]
|
||||
return df.groupby(axis=1, level=0).agg(aggfunc, axis=1)
|
||||
|
||||
|
||||
def plot_all(*args, **kwargs):
|
||||
for config_file, df, config in sorted(get_data(*args, **kwargs)):
|
||||
'''
|
||||
Read all the trial data and plot the result of applying a function on them.
|
||||
'''
|
||||
dfs = do_all(*args, **kwargs)
|
||||
ps = []
|
||||
for line in dfs:
|
||||
f, df, config = line
|
||||
df.plot(title=config['name'])
|
||||
ps.append(df)
|
||||
return ps
|
||||
|
||||
def do_all(pattern, func, *keys, include_env=False, **kwargs):
|
||||
for config_file, df, config in read_data(pattern, keys=keys):
|
||||
p = func(df, *keys, **kwargs)
|
||||
p.plot(title=config['name'])
|
||||
yield config_file, p, config
|
||||
|
||||
|
||||
def group_trials(trials, aggfunc=['mean', 'min', 'max', 'std']):
|
||||
trials = list(trials)
|
||||
trials = list(map(lambda x: x[1] if isinstance(x, tuple) else x, trials))
|
||||
return pd.concat(trials).groupby(level=0).agg(aggfunc).reorder_levels([2, 0,1] ,axis=1)
|
||||
|
||||
|
||||
def fillna(df):
|
||||
new_df = df.ffill(axis=0)
|
||||
return new_df
|
||||
|
@@ -1,14 +1,30 @@
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
import csv
|
||||
import weakref
|
||||
from random import random
|
||||
import random
|
||||
import simpy
|
||||
import tempfile
|
||||
import pandas as pd
|
||||
from copy import deepcopy
|
||||
from networkx.readwrite import json_graph
|
||||
|
||||
import networkx as nx
|
||||
import nxsim
|
||||
|
||||
from . import utils, agents, analysis, history
|
||||
|
||||
|
||||
class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
"""
|
||||
The environment is key in a simulation. It contains the network topology,
|
||||
a reference to network and environment agents, as well as the environment
|
||||
params, which are used as shared state between agents.
|
||||
|
||||
The environment parameters and the state of every agent can be accessed
|
||||
both by using the environment as a dictionary or with the environment's
|
||||
:meth:`soil.environment.SoilEnvironment.get` method.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None,
|
||||
network_agents=None,
|
||||
@@ -16,20 +32,32 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
states=None,
|
||||
default_state=None,
|
||||
interval=1,
|
||||
seed=None,
|
||||
dry_run=False,
|
||||
dir_path=None,
|
||||
topology=None,
|
||||
*args, **kwargs):
|
||||
self.name = name or 'UnnamedEnvironment'
|
||||
self.states = deepcopy(states) or {}
|
||||
if isinstance(states, list):
|
||||
states = dict(enumerate(states))
|
||||
self.states = deepcopy(states) if states else {}
|
||||
self.default_state = deepcopy(default_state) or {}
|
||||
super().__init__(*args, **kwargs)
|
||||
if not topology:
|
||||
topology = nx.Graph()
|
||||
super().__init__(*args, topology=topology, **kwargs)
|
||||
self._env_agents = {}
|
||||
self._history = {}
|
||||
self.dry_run = dry_run
|
||||
self.interval = interval
|
||||
self.logger = None
|
||||
self.dir_path = dir_path or tempfile.mkdtemp('soil-env')
|
||||
self.get_path()
|
||||
self._history = history.History(name=self.name if not dry_run else None,
|
||||
dir_path=self.dir_path)
|
||||
# Add environment agents first, so their events get
|
||||
# executed before network agents
|
||||
self.environment_agents = environment_agents or []
|
||||
self.network_agents = network_agents or []
|
||||
self.process(self.save_state())
|
||||
self['SEED'] = seed or time.time()
|
||||
random.seed(self['SEED'])
|
||||
|
||||
@property
|
||||
def agents(self):
|
||||
@@ -39,7 +67,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
@property
|
||||
def environment_agents(self):
|
||||
for ref in self._env_agents.values():
|
||||
yield ref()
|
||||
yield ref
|
||||
|
||||
@environment_agents.setter
|
||||
def environment_agents(self, environment_agents):
|
||||
@@ -50,9 +78,8 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
atype = kwargs.pop('agent_type')
|
||||
kwargs['agent_id'] = kwargs.get('agent_id', atype.__name__)
|
||||
kwargs['state'] = kwargs.get('state', {})
|
||||
a = atype(**kwargs,
|
||||
environment=self)
|
||||
self._env_agents[a.id] = weakref.ref(a)
|
||||
a = atype(environment=self, **kwargs)
|
||||
self._env_agents[a.id] = a
|
||||
|
||||
@property
|
||||
def network_agents(self):
|
||||
@@ -63,60 +90,104 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
|
||||
@network_agents.setter
|
||||
def network_agents(self, network_agents):
|
||||
if not network_agents:
|
||||
return
|
||||
for ix in self.G.nodes():
|
||||
i = ix
|
||||
node = self.G.node[i]
|
||||
v = random()
|
||||
found = False
|
||||
for d in network_agents:
|
||||
threshold = d['threshold']
|
||||
if v >= threshold[0] and v < threshold[1]:
|
||||
agent = d['agent_type']
|
||||
state = None
|
||||
if 'state' in d:
|
||||
state = deepcopy(d['state'])
|
||||
else:
|
||||
try:
|
||||
state = self.states[i]
|
||||
except (IndexError, KeyError):
|
||||
state = deepcopy(self.default_state)
|
||||
node['agent'] = agent(environment=self,
|
||||
agent_id=i,
|
||||
state=state)
|
||||
found = True
|
||||
break
|
||||
assert found
|
||||
agent, state = agents._agent_from_distribution(network_agents)
|
||||
self.set_agent(ix, agent_type=agent, state=state)
|
||||
|
||||
def set_agent(self, agent_id, agent_type, state=None):
|
||||
node = self.G.nodes[agent_id]
|
||||
defstate = deepcopy(self.default_state)
|
||||
defstate.update(self.states.get(agent_id, {}))
|
||||
if state:
|
||||
defstate.update(state)
|
||||
state = defstate
|
||||
state.update(node.get('state', {}))
|
||||
a = agent_type(environment=self,
|
||||
agent_id=agent_id,
|
||||
state=state)
|
||||
node['agent'] = a
|
||||
return a
|
||||
|
||||
def add_node(self, agent_type, state=None):
|
||||
agent_id = int(len(self.G.nodes()))
|
||||
self.G.add_node(agent_id)
|
||||
a = self.set_agent(agent_id, agent_type, state)
|
||||
a['visible'] = True
|
||||
return a
|
||||
|
||||
def add_edge(self, agent1, agent2, attrs=None):
|
||||
return self.G.add_edge(agent1, agent2)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
self._save_state()
|
||||
super().run(*args, **kwargs)
|
||||
self._save_state()
|
||||
self._history.flush_cache()
|
||||
|
||||
def _save_state(self):
|
||||
for agent in self.agents:
|
||||
agent.save_state()
|
||||
self._history[self.now] = deepcopy(self.environment_params)
|
||||
def _save_state(self, now=None):
|
||||
# for agent in self.agents:
|
||||
# agent.save_state()
|
||||
utils.logger.debug('Saving state @{}'.format(self.now))
|
||||
self._history.save_records(self.state_to_tuples(now=now))
|
||||
|
||||
def save_state(self):
|
||||
while True:
|
||||
'''
|
||||
:DEPRECATED:
|
||||
Periodically save the state of the environment and the agents.
|
||||
'''
|
||||
self._save_state()
|
||||
while self.peek() != simpy.core.Infinity:
|
||||
delay = max(self.peek() - self.now, self.interval)
|
||||
utils.logger.debug('Step: {}'.format(self.now))
|
||||
ev = self.event()
|
||||
ev._ok = True
|
||||
# Schedule the event with minimum priority so
|
||||
# that it executes after all agents are done
|
||||
self.schedule(ev, -1, self.interval)
|
||||
# that it executes before all agents
|
||||
self.schedule(ev, -999, delay)
|
||||
yield ev
|
||||
self._save_state()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, tuple):
|
||||
self._history.flush_cache()
|
||||
return self._history[key]
|
||||
|
||||
return self.environment_params[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(key, tuple):
|
||||
k = history.Key(*key)
|
||||
self._history.save_record(*k,
|
||||
value=value)
|
||||
return
|
||||
self.environment_params[key] = value
|
||||
self._history.save_record(agent_id='env',
|
||||
t_step=self.now,
|
||||
key=key,
|
||||
value=value)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.environment_params
|
||||
|
||||
def get(self, key, default=None):
|
||||
'''
|
||||
Get the value of an environment attribute in a
|
||||
given point in the simulation (history).
|
||||
If key is an attribute name, this method returns
|
||||
the current value.
|
||||
To get values at other times, use a
|
||||
:meth: `soil.history.Key` tuple.
|
||||
'''
|
||||
return self[key] if key in self else default
|
||||
|
||||
def get_path(self, dir_path=None):
|
||||
dir_path = dir_path or self.sim().dir_path
|
||||
dir_path = dir_path or self.dir_path
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
try:
|
||||
os.makedirs(dir_path)
|
||||
except FileExistsError:
|
||||
pass
|
||||
return dir_path
|
||||
|
||||
def get_agent(self, agent_id):
|
||||
@@ -131,7 +202,7 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
|
||||
with open(csv_name, 'w') as f:
|
||||
cr = csv.writer(f)
|
||||
cr.writerow(('agent_id', 'tstep', 'attribute', 'value'))
|
||||
cr.writerow(('agent_id', 't_step', 'key', 'value', 'value_type'))
|
||||
for i in self.history_to_tuples():
|
||||
cr.writerow(i)
|
||||
|
||||
@@ -139,52 +210,104 @@ class SoilEnvironment(nxsim.NetworkEnvironment):
|
||||
G = self.history_to_graph()
|
||||
graph_path = os.path.join(self.get_path(dir_path),
|
||||
self.name+".gexf")
|
||||
# Workaround for geometric models
|
||||
# See soil/soil#4
|
||||
for node in G.nodes():
|
||||
if 'pos' in G.node[node]:
|
||||
G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}}
|
||||
del (G.node[node]['pos'])
|
||||
|
||||
nx.write_gexf(G, graph_path, version="1.2draft")
|
||||
|
||||
def history_to_tuples(self):
|
||||
for tstep, state in self._history.items():
|
||||
for attribute, value in state.items():
|
||||
yield ('env', tstep, attribute, value)
|
||||
def dump(self, dir_path=None, formats=None):
|
||||
if not formats:
|
||||
return
|
||||
functions = {
|
||||
'csv': self.dump_csv,
|
||||
'gexf': self.dump_gexf
|
||||
}
|
||||
for f in formats:
|
||||
if f in functions:
|
||||
functions[f](dir_path)
|
||||
else:
|
||||
raise ValueError('Unknown format: {}'.format(f))
|
||||
|
||||
def state_to_tuples(self, now=None):
|
||||
if now is None:
|
||||
now = self.now
|
||||
for k, v in self.environment_params.items():
|
||||
yield history.Record(agent_id='env',
|
||||
t_step=now,
|
||||
key=k,
|
||||
value=v)
|
||||
for agent in self.agents:
|
||||
for tstep, state in agent._history.items():
|
||||
for attribute, value in state.items():
|
||||
yield (agent.id, tstep, attribute, value)
|
||||
for k, v in agent.state.items():
|
||||
yield history.Record(agent_id=agent.id,
|
||||
t_step=now,
|
||||
key=k,
|
||||
value=v)
|
||||
|
||||
def history_to_tuples(self):
|
||||
return self._history.to_tuples()
|
||||
|
||||
def history_to_graph(self):
|
||||
G = nx.Graph(self.G)
|
||||
|
||||
for agent in self.agents:
|
||||
for agent in self.network_agents:
|
||||
|
||||
attributes = {'agent': str(agent.__class__)}
|
||||
lastattributes = {}
|
||||
spells = []
|
||||
lastvisible = False
|
||||
laststep = None
|
||||
for t_step, state in reversed(agent._history.items()):
|
||||
for attribute, value in state.items():
|
||||
if attribute == 'visible':
|
||||
nowvisible = state[attribute]
|
||||
if nowvisible and not lastvisible:
|
||||
laststep = t_step
|
||||
if not nowvisible and lastvisible:
|
||||
spells.append((laststep, t_step))
|
||||
history = self[agent.id, None, None]
|
||||
if not history:
|
||||
continue
|
||||
for t_step, attribute, value in sorted(list(history)):
|
||||
if attribute == 'visible':
|
||||
nowvisible = value
|
||||
if nowvisible and not lastvisible:
|
||||
laststep = t_step
|
||||
if not nowvisible and lastvisible:
|
||||
spells.append((laststep, t_step))
|
||||
|
||||
lastvisible = nowvisible
|
||||
else:
|
||||
if attribute not in lastattributes or lastattributes[attribute][0] != value:
|
||||
laststep = lastattributes.get(attribute,
|
||||
(None, None))[1]
|
||||
value = (state[attribute], t_step, laststep)
|
||||
key = 'attr_' + attribute
|
||||
if key not in attributes:
|
||||
attributes[key] = list()
|
||||
attributes[key].append(value)
|
||||
lastattributes[attribute] = (state[attribute], t_step)
|
||||
lastvisible = nowvisible
|
||||
continue
|
||||
key = 'attr_' + attribute
|
||||
if key not in attributes:
|
||||
attributes[key] = list()
|
||||
if key not in lastattributes:
|
||||
lastattributes[key] = (value, t_step)
|
||||
elif lastattributes[key][0] != value:
|
||||
last_value, laststep = lastattributes[key]
|
||||
commit_value = (last_value, laststep, t_step)
|
||||
if key not in attributes:
|
||||
attributes[key] = list()
|
||||
attributes[key].append(commit_value)
|
||||
lastattributes[key] = (value, t_step)
|
||||
for k, v in lastattributes.items():
|
||||
attributes[k].append((v[0], v[1], None))
|
||||
if lastvisible:
|
||||
spells.append((laststep, None))
|
||||
if spells:
|
||||
G.add_node(agent.id, attributes, spells=spells)
|
||||
G.add_node(agent.id, spells=spells, **attributes)
|
||||
else:
|
||||
G.add_node(agent.id, attributes)
|
||||
G.add_node(agent.id, **attributes)
|
||||
|
||||
return G
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['G'] = json_graph.node_link_data(self.G)
|
||||
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||
to_string=True)
|
||||
del state['_queue']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self.G = json_graph.node_link_graph(state['G'])
|
||||
self.network_agents = self.calculate_distribution(self._convert_agent_types(self.network_agents))
|
||||
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
||||
return state
|
||||
|
254
soil/history.py
Normal file
@@ -0,0 +1,254 @@
|
||||
import time
|
||||
import os
|
||||
import pandas as pd
|
||||
import sqlite3
|
||||
import copy
|
||||
from collections import UserDict, Iterable, namedtuple
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
class History:
|
||||
"""
|
||||
Store and retrieve values from a sqlite database.
|
||||
"""
|
||||
|
||||
def __init__(self, db_path=None, name=None, dir_path=None, backup=True):
|
||||
if db_path is None and name:
|
||||
db_path = os.path.join(dir_path or os.getcwd(),
|
||||
'{}.db.sqlite'.format(name))
|
||||
if db_path is None:
|
||||
db_path = ":memory:"
|
||||
else:
|
||||
if backup and os.path.exists(db_path):
|
||||
newname = db_path + '.backup{}.sqlite'.format(time.time())
|
||||
os.rename(db_path, newname)
|
||||
self.db_path = db_path
|
||||
|
||||
self.db = db_path
|
||||
|
||||
with self.db:
|
||||
self.db.execute('''CREATE TABLE IF NOT EXISTS history (agent_id text, t_step int, key text, value text text)''')
|
||||
self.db.execute('''CREATE TABLE IF NOT EXISTS value_types (key text, value_type text)''')
|
||||
self.db.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_history ON history (agent_id, t_step, key);''')
|
||||
self._dtypes = {}
|
||||
self._tups = []
|
||||
|
||||
def conversors(self, key):
|
||||
"""Get the serializer and deserializer for a given key."""
|
||||
if key not in self._dtypes:
|
||||
self.read_types()
|
||||
return self._dtypes[key]
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
try:
|
||||
self._db.cursor()
|
||||
except sqlite3.ProgrammingError:
|
||||
self.db = None # Reset the database
|
||||
return self._db
|
||||
|
||||
@db.setter
|
||||
def db(self, db_path=None):
|
||||
db_path = db_path or self.db_path
|
||||
if isinstance(db_path, str):
|
||||
self._db = sqlite3.connect(db_path)
|
||||
else:
|
||||
self._db = db_path
|
||||
|
||||
@property
|
||||
def dtypes(self):
|
||||
return {k:v[0] for k, v in self._dtypes.items()}
|
||||
|
||||
def save_tuples(self, tuples):
|
||||
self.save_records(Record(*tup) for tup in tuples)
|
||||
|
||||
def save_records(self, records):
|
||||
with self.db:
|
||||
for rec in records:
|
||||
if not isinstance(rec, Record):
|
||||
rec = Record(*rec)
|
||||
if rec.key not in self._dtypes:
|
||||
name = utils.name(rec.value)
|
||||
serializer = utils.serializer(name)
|
||||
deserializer = utils.deserializer(name)
|
||||
self._dtypes[rec.key] = (name, serializer, deserializer)
|
||||
self.db.execute("replace into value_types (key, value_type) values (?, ?)", (rec.key, name))
|
||||
self.db.execute("replace into history(agent_id, t_step, key, value) values (?, ?, ?, ?)", (rec.agent_id, rec.t_step, rec.key, rec.value))
|
||||
|
||||
def save_record(self, *args, **kwargs):
|
||||
self._tups.append(Record(*args, **kwargs))
|
||||
if len(self._tups) > 100:
|
||||
self.flush_cache()
|
||||
|
||||
def flush_cache(self):
|
||||
'''
|
||||
Use a cache to save state changes to avoid opening a session for every change.
|
||||
The cache will be flushed at the end of the simulation, and when history is accessed.
|
||||
'''
|
||||
self.save_records(self._tups)
|
||||
self._tups = list()
|
||||
|
||||
def to_tuples(self):
|
||||
self.flush_cache()
|
||||
with self.db:
|
||||
res = self.db.execute("select agent_id, t_step, key, value from history ").fetchall()
|
||||
for r in res:
|
||||
agent_id, t_step, key, value = r
|
||||
_, _ , des = self.conversors(key)
|
||||
yield agent_id, t_step, key, des(value)
|
||||
|
||||
def read_types(self):
|
||||
with self.db:
|
||||
res = self.db.execute("select key, value_type from value_types ").fetchall()
|
||||
for k, v in res:
|
||||
serializer = utils.serializer(v)
|
||||
deserializer = utils.deserializer(v)
|
||||
self._dtypes[k] = (v, serializer, deserializer)
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = Key(*key)
|
||||
agent_ids = [key.agent_id] if key.agent_id is not None else []
|
||||
t_steps = [key.t_step] if key.t_step is not None else []
|
||||
keys = [key.key] if key.key is not None else []
|
||||
|
||||
df = self.read_sql(agent_ids=agent_ids,
|
||||
t_steps=t_steps,
|
||||
keys=keys)
|
||||
r = Records(df, filter=key, dtypes=self._dtypes)
|
||||
if r.resolved:
|
||||
return r.value()
|
||||
return r
|
||||
|
||||
|
||||
|
||||
def read_sql(self, keys=None, agent_ids=None, t_steps=None, convert_types=False, limit=-1):
|
||||
|
||||
self.read_types()
|
||||
|
||||
def escape_and_join(v):
|
||||
if v is None:
|
||||
return
|
||||
return ",".join(map(lambda x: "\'{}\'".format(x), v))
|
||||
|
||||
filters = [("key in ({})".format(escape_and_join(keys)), keys),
|
||||
("agent_id in ({})".format(escape_and_join(agent_ids)), agent_ids)
|
||||
]
|
||||
filters = list(k[0] for k in filters if k[1])
|
||||
|
||||
last_df = None
|
||||
if t_steps:
|
||||
# Look for the last value before the minimum step in the query
|
||||
min_step = min(t_steps)
|
||||
last_filters = ['t_step < {}'.format(min_step),]
|
||||
last_filters = last_filters + filters
|
||||
condition = ' and '.join(last_filters)
|
||||
|
||||
last_query = '''
|
||||
select h1.*
|
||||
from history h1
|
||||
inner join (
|
||||
select agent_id, key, max(t_step) as t_step
|
||||
from history
|
||||
where {condition}
|
||||
group by agent_id, key
|
||||
) h2
|
||||
on h1.agent_id = h2.agent_id and
|
||||
h1.key = h2.key and
|
||||
h1.t_step = h2.t_step
|
||||
'''.format(condition=condition)
|
||||
last_df = pd.read_sql_query(last_query, self.db)
|
||||
|
||||
filters.append("t_step >= '{}' and t_step <= '{}'".format(min_step, max(t_steps)))
|
||||
|
||||
condition = ''
|
||||
if filters:
|
||||
condition = 'where {} '.format(' and '.join(filters))
|
||||
query = 'select * from history {} limit {}'.format(condition, limit)
|
||||
df = pd.read_sql_query(query, self.db)
|
||||
if last_df is not None:
|
||||
df = pd.concat([df, last_df])
|
||||
|
||||
df_p = df.pivot_table(values='value', index=['t_step'],
|
||||
columns=['key', 'agent_id'],
|
||||
aggfunc='first')
|
||||
|
||||
for k, v in self._dtypes.items():
|
||||
if k in df_p:
|
||||
dtype, _, deserial = v
|
||||
df_p[k] = df_p[k].fillna(method='ffill').fillna(deserial()).astype(dtype)
|
||||
if t_steps:
|
||||
df_p = df_p.reindex(t_steps, method='ffill')
|
||||
return df_p.ffill()
|
||||
|
||||
|
||||
class Records():
|
||||
|
||||
def __init__(self, df, filter=None, dtypes=None):
|
||||
if not filter:
|
||||
filter = Key(agent_id=None,
|
||||
t_step=None,
|
||||
key=None)
|
||||
self._df = df
|
||||
self._filter = filter
|
||||
self.dtypes = dtypes or {}
|
||||
super().__init__()
|
||||
|
||||
def mask(self, tup):
|
||||
res = ()
|
||||
for i, k in zip(tup[:-1], self._filter):
|
||||
if k is None:
|
||||
res = res + (i,)
|
||||
res = res + (tup[-1],)
|
||||
return res
|
||||
|
||||
def filter(self, newKey):
|
||||
f = list(self._filter)
|
||||
for ix, i in enumerate(f):
|
||||
if i is None:
|
||||
f[ix] = newKey
|
||||
self._filter = Key(*f)
|
||||
|
||||
@property
|
||||
def resolved(self):
|
||||
return sum(1 for i in self._filter if i is not None) == 3
|
||||
|
||||
def __iter__(self):
|
||||
for column, series in self._df.iteritems():
|
||||
key, agent_id = column
|
||||
for t_step, value in series.iteritems():
|
||||
r = Record(t_step=t_step,
|
||||
agent_id=agent_id,
|
||||
key=key,
|
||||
value=value)
|
||||
yield self.mask(r)
|
||||
|
||||
def value(self):
|
||||
if self.resolved:
|
||||
f = self._filter
|
||||
try:
|
||||
i = self._df[f.key][str(f.agent_id)]
|
||||
ix = i.index.get_loc(f.t_step, method='ffill')
|
||||
return i.iloc[ix]
|
||||
except KeyError:
|
||||
return self.dtypes[f.key][2]()
|
||||
return list(self)
|
||||
|
||||
def __getitem__(self, k):
|
||||
n = copy.copy(self)
|
||||
n.filter(k)
|
||||
if n.resolved:
|
||||
return n.value()
|
||||
return n
|
||||
|
||||
def __len__(self):
|
||||
return len(self._df)
|
||||
|
||||
def __str__(self):
|
||||
if self.resolved:
|
||||
return str(self.value())
|
||||
return '<Records for [{}]>'.format(self._filter)
|
||||
|
||||
|
||||
Key = namedtuple('Key', ['agent_id', 't_step', 'key'])
|
||||
Record = namedtuple('Record', 'agent_id t_step key value')
|
@@ -1,27 +1,26 @@
|
||||
import weakref
|
||||
import os
|
||||
import csv
|
||||
import time
|
||||
import imp
|
||||
import sys
|
||||
import yaml
|
||||
import networkx as nx
|
||||
from networkx.readwrite import json_graph
|
||||
|
||||
from copy import deepcopy
|
||||
from random import random
|
||||
from matplotlib import pyplot as plt
|
||||
from multiprocessing import Pool
|
||||
from functools import partial
|
||||
|
||||
import pickle
|
||||
|
||||
from nxsim import NetworkSimulation
|
||||
|
||||
from . import agents, utils, environment, basestring
|
||||
from . import utils, environment, basestring, agents
|
||||
from .utils import logger
|
||||
|
||||
|
||||
class SoilSimulation(NetworkSimulation):
|
||||
"""
|
||||
Subclass of nsim.NetworkSimulation with three main differences:
|
||||
1) agent type can be specified by name or by class.
|
||||
2) instead of just one type, an network_agents can be used.
|
||||
2) instead of just one type, a network agents distribution can be used.
|
||||
The distribution specifies the weight (or probability) of each
|
||||
agent type in the topology. This is an example distribution: ::
|
||||
|
||||
@@ -47,10 +46,10 @@ class SoilSimulation(NetworkSimulation):
|
||||
"""
|
||||
def __init__(self, name=None, topology=None, network_params=None,
|
||||
network_agents=None, agent_type=None, states=None,
|
||||
default_state=None, interval=1,
|
||||
dir_path=None, num_trials=3, max_time=100,
|
||||
agent_module=None,
|
||||
environment_agents=None, environment_params=None):
|
||||
default_state=None, interval=1, dump=None, dry_run=False,
|
||||
dir_path=None, num_trials=1, max_time=100,
|
||||
agent_module=None, load_module=None, seed=None,
|
||||
environment_agents=None, environment_params=None, **kwargs):
|
||||
|
||||
if topology is None:
|
||||
topology = utils.load_network(network_params,
|
||||
@@ -58,6 +57,7 @@ class SoilSimulation(NetworkSimulation):
|
||||
elif isinstance(topology, basestring) or isinstance(topology, dict):
|
||||
topology = json_graph.node_link_graph(topology)
|
||||
|
||||
self.load_module = load_module
|
||||
self.topology = nx.Graph(topology)
|
||||
self.network_params = network_params
|
||||
self.name = name or 'UnnamedSimulation'
|
||||
@@ -66,78 +66,72 @@ class SoilSimulation(NetworkSimulation):
|
||||
self.default_state = default_state or {}
|
||||
self.dir_path = dir_path or os.getcwd()
|
||||
self.interval = interval
|
||||
self.seed = str(seed) or str(time.time())
|
||||
self.dump = dump
|
||||
self.dry_run = dry_run
|
||||
self.environment_params = environment_params or {}
|
||||
|
||||
if load_module:
|
||||
path = sys.path + [self.dir_path, os.getcwd()]
|
||||
f, fp, desc = imp.find_module(load_module, path)
|
||||
imp.load_module('soil.agents.custom', f, fp, desc)
|
||||
|
||||
environment_agents = environment_agents or []
|
||||
self.environment_agents = self._convert_agent_types(environment_agents)
|
||||
self.environment_agents = agents._convert_agent_types(environment_agents)
|
||||
|
||||
distro = self.calculate_distribution(network_agents,
|
||||
agent_type)
|
||||
self.network_agents = self._convert_agent_types(distro)
|
||||
distro = agents.calculate_distribution(network_agents,
|
||||
agent_type)
|
||||
self.network_agents = agents._convert_agent_types(distro)
|
||||
|
||||
self.states = self.validate_states(states,
|
||||
topology)
|
||||
self.states = agents._validate_states(states,
|
||||
self.topology)
|
||||
|
||||
def calculate_distribution(self,
|
||||
network_agents=None,
|
||||
agent_type=None):
|
||||
if network_agents:
|
||||
network_agents = deepcopy(network_agents)
|
||||
elif agent_type:
|
||||
network_agents = [{'agent_type': agent_type}]
|
||||
else:
|
||||
return []
|
||||
def run_simulation(self, *args, **kwargs):
|
||||
return self.run(*args, **kwargs)
|
||||
|
||||
# Calculate the thresholds
|
||||
total = sum(x.get('weight', 1) for x in network_agents)
|
||||
acc = 0
|
||||
for v in network_agents:
|
||||
upper = acc + (v.get('weight', 1)/total)
|
||||
v['threshold'] = [acc, upper]
|
||||
acc = upper
|
||||
return network_agents
|
||||
def run(self, *args, **kwargs):
|
||||
return list(self.run_simulation_gen(*args, **kwargs))
|
||||
|
||||
def serialize_distribution(self):
|
||||
d = self._convert_agent_types(self.network_agents,
|
||||
to_string=True)
|
||||
for v in d:
|
||||
if 'threshold' in v:
|
||||
del v['threshold']
|
||||
return d
|
||||
def run_simulation_gen(self, *args, parallel=False, dry_run=False,
|
||||
**kwargs):
|
||||
p = Pool()
|
||||
with utils.timer('simulation {}'.format(self.name)):
|
||||
if parallel:
|
||||
func = partial(self.run_trial, dry_run=dry_run or self.dry_run,
|
||||
return_env=not parallel, **kwargs)
|
||||
for i in p.imap_unordered(func, range(self.num_trials)):
|
||||
yield i
|
||||
else:
|
||||
for i in range(self.num_trials):
|
||||
yield self.run_trial(i, dry_run=dry_run or self.dry_run, **kwargs)
|
||||
if not (dry_run or self.dry_run):
|
||||
logger.info('Dumping results to {}'.format(self.dir_path))
|
||||
self.dump_pickle(self.dir_path)
|
||||
self.dump_yaml(self.dir_path)
|
||||
else:
|
||||
logger.info('NOT dumping results')
|
||||
|
||||
def _convert_agent_types(self, ind, to_string=False):
|
||||
d = deepcopy(ind)
|
||||
for v in d:
|
||||
agent_type = v['agent_type']
|
||||
if to_string and not isinstance(agent_type, str):
|
||||
v['agent_type'] = str(agent_type.__name__)
|
||||
elif not to_string and isinstance(agent_type, str):
|
||||
v['agent_type'] = agents.agent_types[agent_type]
|
||||
return d
|
||||
def get_env(self, trial_id=0, **kwargs):
|
||||
opts = self.environment_params.copy()
|
||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||
opts.update({
|
||||
'name': env_name,
|
||||
'topology': self.topology.copy(),
|
||||
'seed': self.seed+env_name,
|
||||
'initial_time': 0,
|
||||
'dry_run': self.dry_run,
|
||||
'interval': self.interval,
|
||||
'network_agents': self.network_agents,
|
||||
'states': self.states,
|
||||
'default_state': self.default_state,
|
||||
'environment_agents': self.environment_agents,
|
||||
'dir_path': self.dir_path,
|
||||
})
|
||||
opts.update(kwargs)
|
||||
env = environment.SoilEnvironment(**opts)
|
||||
return env
|
||||
|
||||
def validate_states(self, states, topology):
|
||||
states = states or []
|
||||
# Validate states to avoid ignoring states during
|
||||
# initialization
|
||||
if isinstance(states, dict):
|
||||
for x in states:
|
||||
assert x in self.topology.node
|
||||
else:
|
||||
assert len(states) <= len(self.topology)
|
||||
return states
|
||||
|
||||
def run_simulation(self):
|
||||
return self.run()
|
||||
|
||||
def run(self):
|
||||
return list(self.run_simulation_gen())
|
||||
|
||||
def run_simulation_gen(self):
|
||||
with utils.timer('simulation'):
|
||||
for i in range(self.num_trials):
|
||||
yield self.run_trial(i)
|
||||
|
||||
def run_trial(self, trial_id=0):
|
||||
def run_trial(self, trial_id=0, until=None, return_env=True, **opts):
|
||||
"""Run a single trial of the simulation
|
||||
|
||||
Parameters
|
||||
@@ -145,24 +139,16 @@ class SoilSimulation(NetworkSimulation):
|
||||
trial_id : int
|
||||
"""
|
||||
# Set-up trial environment and graph
|
||||
print('Trial: {}'.format(trial_id))
|
||||
env_name = '{}_trial_{}'.format(self.name, trial_id)
|
||||
env = environment.SoilEnvironment(name=env_name,
|
||||
topology=self.topology.copy(),
|
||||
initial_time=0,
|
||||
interval=self.interval,
|
||||
network_agents=self.network_agents,
|
||||
states=self.states,
|
||||
default_state=self.default_state,
|
||||
environment_agents=self.environment_agents,
|
||||
**self.environment_params)
|
||||
|
||||
env.sim = weakref.ref(self)
|
||||
until = until or self.max_time
|
||||
env = self.get_env(trial_id=trial_id, **opts)
|
||||
# Set up agents on nodes
|
||||
print('\tRunning')
|
||||
with utils.timer('trial'):
|
||||
env.run(until=self.max_time)
|
||||
return env
|
||||
with utils.timer('Simulation {} trial {}'.format(self.name, trial_id)):
|
||||
env.run(until)
|
||||
if self.dump and not self.dry_run:
|
||||
with utils.timer('Dumping simulation {} trial {}'.format(self.name, trial_id)):
|
||||
env.dump(formats=self.dump)
|
||||
if return_env:
|
||||
return env
|
||||
|
||||
def to_dict(self):
|
||||
return self.__getstate__()
|
||||
@@ -193,20 +179,20 @@ class SoilSimulation(NetworkSimulation):
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['topology'] = json_graph.node_link_data(self.topology)
|
||||
state['network_agents'] = self.serialize_distribution()
|
||||
state['environment_agents'] = self._convert_agent_types(self.environment_agents,
|
||||
to_string=True)
|
||||
state['network_agents'] = agents._serialize_distribution(self.network_agents)
|
||||
state['environment_agents'] = agents._convert_agent_types(self.environment_agents,
|
||||
to_string=True)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
self.topology = json_graph.node_link_graph(state['topology'])
|
||||
self.network_agents = self._convert_agent_types(self.network_agents)
|
||||
self.environment_agents = self._convert_agent_types(self.environment_agents)
|
||||
self.network_agents = agents.calculate_distribution(agents._convert_agent_types(self.network_agents))
|
||||
self.environment_agents = agents._convert_agent_types(self.environment_agents)
|
||||
return state
|
||||
|
||||
|
||||
def from_config(config, G=None):
|
||||
def from_config(config):
|
||||
config = list(utils.load_config(config))
|
||||
if len(config) > 1:
|
||||
raise AttributeError('Provide only one configuration')
|
||||
@@ -215,27 +201,19 @@ def from_config(config, G=None):
|
||||
return sim
|
||||
|
||||
|
||||
def run_from_config(*configs, dump=True, results_dir=None, timestamp=False):
|
||||
if not results_dir:
|
||||
results_dir = 'soil_output'
|
||||
def run_from_config(*configs, results_dir='soil_output', dry_run=False, dump=None, timestamp=False, **kwargs):
|
||||
for config_def in configs:
|
||||
for config, cpath in utils.load_config(config_def):
|
||||
# logger.info("Found {} config(s)".format(len(ls)))
|
||||
for config, _ in utils.load_config(config_def):
|
||||
name = config.get('name', 'unnamed')
|
||||
print("Using config(s): {name}".format(name=name))
|
||||
logger.info("Using config(s): {name}".format(name=name))
|
||||
|
||||
sim = SoilSimulation(**config)
|
||||
if timestamp:
|
||||
sim_folder = '{}_{}'.format(sim.name,
|
||||
sim_folder = '{}_{}'.format(name,
|
||||
time.strftime("%Y-%m-%d_%H:%M:%S"))
|
||||
else:
|
||||
sim_folder = sim.name
|
||||
dir_path = os.path.join(results_dir,
|
||||
sim_folder)
|
||||
results = sim.run_simulation()
|
||||
|
||||
if dump:
|
||||
sim.dump_pickle(dir_path)
|
||||
sim.dump_yaml(dir_path)
|
||||
for env in results:
|
||||
env.dump_gexf(dir_path)
|
||||
env.dump_csv(dir_path)
|
||||
sim_folder = name
|
||||
dir_path = os.path.join(results_dir, sim_folder)
|
||||
sim = SoilSimulation(dir_path=dir_path, dump=dump, **config)
|
||||
logger.info('Dumping results to {} : {}'.format(sim.dir_path, sim.dump))
|
||||
sim.run_simulation(**kwargs)
|
||||
|
@@ -1,14 +1,24 @@
|
||||
import os
|
||||
import yaml
|
||||
import logging
|
||||
import importlib
|
||||
from time import time
|
||||
from glob import glob
|
||||
from random import random
|
||||
from copy import deepcopy
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
logger = logging.getLogger('soil')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
def load_network(network_params, dir_path=None):
|
||||
if network_params is None:
|
||||
return nx.Graph()
|
||||
path = network_params.get('path', None)
|
||||
if path:
|
||||
if dir_path and not os.path.isabs(path):
|
||||
@@ -51,11 +61,45 @@ def load_config(config):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timer(name='task', pre="", function=print, to_object=None):
|
||||
def timer(name='task', pre="", function=logger.info, to_object=None):
|
||||
start = time()
|
||||
function('{}Starting {} at {}.'.format(pre, name, start))
|
||||
yield start
|
||||
end = time()
|
||||
function('{}Finished {} in {} seconds'.format(pre, name, str(end-start)))
|
||||
if to_object:
|
||||
to_object.start = start
|
||||
to_object.end = end
|
||||
|
||||
|
||||
def repr(v):
|
||||
func = serializer(v)
|
||||
tname = name(v)
|
||||
return func(v), tname
|
||||
|
||||
|
||||
def name(v):
|
||||
return type(v).__name__
|
||||
|
||||
|
||||
def serializer(type_):
|
||||
if type_ == 'bool':
|
||||
return lambda x: "true" if x else ""
|
||||
return lambda x: x
|
||||
|
||||
|
||||
def deserializer(type_):
|
||||
try:
|
||||
# Check if it's a builtin type
|
||||
module = importlib.import_module('builtins')
|
||||
cls = getattr(module, type_)
|
||||
except AttributeError:
|
||||
# if not, separate module and class
|
||||
module, type_ = type_.rsplit(".", 1)
|
||||
module = importlib.import_module(module)
|
||||
cls = getattr(module, type_)
|
||||
return cls
|
||||
|
||||
|
||||
def convert(value, type_):
|
||||
return deserializer(type_)(value)
|
||||
|
20
soil/version.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ROOT = os.path.dirname(__file__)
|
||||
DEFAULT_FILE = os.path.join(ROOT, 'VERSION')
|
||||
|
||||
|
||||
def read_version(versionfile=DEFAULT_FILE):
|
||||
try:
|
||||
with open(versionfile) as f:
|
||||
return f.read().strip()
|
||||
except IOError: # pragma: no cover
|
||||
logger.error(('Running an unknown version of {}.'
|
||||
'Be careful!.').format(__name__))
|
||||
return '0.0'
|
||||
|
||||
|
||||
__version__ = read_version()
|
4
soil/web/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
__pycache__/
|
||||
output/
|
||||
tests/
|
||||
soil_output/
|
59
soil/web/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Graph Visualization with D3.js
|
||||
|
||||
The aim of this software is to provide a useful tool for visualising and analysing the result of different simulations based on graph. Once you run the simulation, you will be able to interact with the simulation in real time.
|
||||
|
||||
For this purpose, a model which tries to simulate the spread of information to comprehend the radicalism spread in a society is included. Whith all this, the main project goals could be divided in five as it is shown in the following.
|
||||
|
||||
* Simulate the spread of information through a network applied to radicalism.
|
||||
* Visualize the results of the simulation.
|
||||
* Interact with the simulation in real time.
|
||||
* Extract data from the results.
|
||||
* Show data in a right way for its research.
|
||||
|
||||
## Deploying the server
|
||||
|
||||
For deploying the application, you will only need to run the following command.
|
||||
|
||||
`python3 run.py [--name NAME] [--dump] [--port PORT] [--verbose]`
|
||||
|
||||
Where the options are detailed in the following table.
|
||||
|
||||
| Option | Description |
|
||||
| --- | --- |
|
||||
| `--name NAME` | The name of the simulation. It will appear on the app. |
|
||||
| `--dump` | For dumping the results in server side. |
|
||||
| `--port PORT` | The port where the server will listen. |
|
||||
| `--verbose` | Verbose mode. |
|
||||
|
||||
> You can dump the results of the simulation in server side. Anyway, you will be able to download them in GEXF or JSON Graph format directly from the browser.
|
||||
|
||||
## Visualization Params
|
||||
|
||||
The configuration of the simulation is based on the simulator configuration. In this case, it follows the [SOIL](https://github.com/gsi-upm/soil) configuration syntax and for visualising the results in a more comfortable way, more params can be added in `visualization_params` dictionary.
|
||||
|
||||
* For setting a background image, the tag needed is `background image`. You can also add a `background_opacity` and `background_filter_color` if the image is so clear than you can difficult view the nodes.
|
||||
* For setting colors to the nodes, you can do it based on their properties values. Using the `color` tag, you will need to indicate the attribute key and value, and then the color you want to apply.
|
||||
* The shapes applied to a group of nodes are always the same. This means than it won't change dynamically, so you will have to indicate the property with the `shape_property` tag and add a dictionary called `shapes` in which for each value, you indicate the shape.
|
||||
All shapes have to had been downloaded before in SVG format and added to the server.
|
||||
|
||||
An example of this configuration applied to the TerroristNetworkModel is presented.
|
||||
|
||||
```yaml
|
||||
visualization_params:
|
||||
# Icons downloaded from https://www.iconfinder.com/
|
||||
shape_property: agent
|
||||
shapes:
|
||||
TrainingAreaModel: target
|
||||
HavenModel: home
|
||||
TerroristNetworkModel: person
|
||||
colors:
|
||||
- attr_id: 0
|
||||
color: '#40de40'
|
||||
- attr_id: 1
|
||||
color: red
|
||||
- attr_id: 2
|
||||
color: '#c16a6a'
|
||||
background_image: 'map_4800x2860.jpg'
|
||||
background_opacity: '0.9'
|
||||
background_filter_color: 'blue'
|
||||
```
|
255
soil/web/TerroristNetworkModel.py
Normal file
@@ -0,0 +1,255 @@
|
||||
import random
|
||||
import networkx as nx
|
||||
from soil.agents import BaseAgent, FSM, state, default_state
|
||||
from scipy.spatial import cKDTree as KDTree
|
||||
|
||||
global betweenness_centrality_global
|
||||
global degree_centrality_global
|
||||
|
||||
betweenness_centrality_global = None
|
||||
degree_centrality_global = None
|
||||
|
||||
class TerroristSpreadModel(FSM):
|
||||
"""
|
||||
Settings:
|
||||
information_spread_intensity
|
||||
|
||||
terrorist_additional_influence
|
||||
|
||||
min_vulnerability (optional else zero)
|
||||
|
||||
max_vulnerability
|
||||
|
||||
prob_interaction
|
||||
"""
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
|
||||
global betweenness_centrality_global
|
||||
global degree_centrality_global
|
||||
|
||||
if betweenness_centrality_global == None:
|
||||
betweenness_centrality_global = nx.betweenness_centrality(self.global_topology)
|
||||
if degree_centrality_global == None:
|
||||
degree_centrality_global = nx.degree_centrality(self.global_topology)
|
||||
|
||||
self.information_spread_intensity = environment.environment_params['information_spread_intensity']
|
||||
self.terrorist_additional_influence = environment.environment_params['terrorist_additional_influence']
|
||||
self.prob_interaction = environment.environment_params['prob_interaction']
|
||||
|
||||
if self['id'] == self.civilian.id: # Civilian
|
||||
self.initial_belief = random.uniform(0.00, 0.5)
|
||||
elif self['id'] == self.terrorist.id: # Terrorist
|
||||
self.initial_belief = random.uniform(0.8, 1.00)
|
||||
elif self['id'] == self.leader.id: # Leader
|
||||
self.initial_belief = 1.00
|
||||
else:
|
||||
raise Exception('Invalid state id: {}'.format(self['id']))
|
||||
|
||||
if 'min_vulnerability' in environment.environment_params:
|
||||
self.vulnerability = random.uniform( environment.environment_params['min_vulnerability'], environment.environment_params['max_vulnerability'] )
|
||||
else :
|
||||
self.vulnerability = random.uniform( 0, environment.environment_params['max_vulnerability'] )
|
||||
|
||||
self.mean_belief = self.initial_belief
|
||||
self.betweenness_centrality = betweenness_centrality_global[self.id]
|
||||
self.degree_centrality = degree_centrality_global[self.id]
|
||||
|
||||
# self.state['radicalism'] = self.mean_belief
|
||||
|
||||
def count_neighboring_agents(self, state_id=None):
|
||||
if isinstance(state_id, list):
|
||||
return len(self.get_neighboring_agents(state_id))
|
||||
else:
|
||||
return len(super().get_agents(state_id, limit_neighbors=True))
|
||||
|
||||
def get_neighboring_agents(self, state_id=None):
|
||||
if isinstance(state_id, list):
|
||||
_list = []
|
||||
for i in state_id:
|
||||
_list += super().get_agents(i, limit_neighbors=True)
|
||||
return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ]
|
||||
else:
|
||||
_list = super().get_agents(state_id, limit_neighbors=True)
|
||||
return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ]
|
||||
|
||||
@state
|
||||
def civilian(self):
|
||||
if self.count_neighboring_agents() > 0:
|
||||
neighbours = []
|
||||
for neighbour in self.get_neighboring_agents():
|
||||
if random.random() < self.prob_interaction:
|
||||
neighbours.append(neighbour)
|
||||
influence = sum( neighbour.degree_centrality for neighbour in neighbours )
|
||||
mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours )
|
||||
self.initial_belief = self.mean_belief
|
||||
mean_belief = mean_belief * self.information_spread_intensity + self.initial_belief * ( 1 - self.information_spread_intensity )
|
||||
self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability )
|
||||
|
||||
if self.mean_belief >= 0.8:
|
||||
return self.terrorist
|
||||
|
||||
@state
|
||||
def leader(self):
|
||||
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
|
||||
if self.count_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]) > 0:
|
||||
for neighbour in self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]):
|
||||
if neighbour.betweenness_centrality > self.betweenness_centrality:
|
||||
return self.terrorist
|
||||
|
||||
@state
|
||||
def terrorist(self):
|
||||
if self.count_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]) > 0:
|
||||
neighbours = self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id])
|
||||
influence = sum( neighbour.degree_centrality for neighbour in neighbours )
|
||||
mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours )
|
||||
self.initial_belief = self.mean_belief
|
||||
self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability )
|
||||
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
|
||||
|
||||
if self.count_neighboring_agents(state_id=self.leader.id) == 0 and self.count_neighboring_agents(state_id=self.terrorist.id) > 0:
|
||||
max_betweenness_centrality = self
|
||||
for neighbour in self.get_neighboring_agents(state_id=self.terrorist.id):
|
||||
if neighbour.betweenness_centrality > max_betweenness_centrality.betweenness_centrality:
|
||||
max_betweenness_centrality = neighbour
|
||||
if max_betweenness_centrality == self:
|
||||
return self.leader
|
||||
|
||||
def add_edge(self, G, source, target):
|
||||
G.add_edge(source.id, target.id, start=self.env._now)
|
||||
|
||||
def link_search(self, G, node, radius):
|
||||
pos = nx.get_node_attributes(G, 'pos')
|
||||
nodes, coords = list(zip(*pos.items()))
|
||||
kdtree = KDTree(coords) # Cannot provide generator.
|
||||
edge_indexes = kdtree.query_pairs(radius, 2)
|
||||
_list = [ edge[int(not edge.index(node))] for edge in edge_indexes if node in edge ]
|
||||
return [ G.nodes()[index]['agent'] for index in _list ]
|
||||
|
||||
def social_search(self, G, node, steps):
|
||||
nodes = list(nx.ego_graph(G, node, radius=steps).nodes())
|
||||
nodes.remove(node)
|
||||
return [ G.nodes()[index]['agent'] for index in nodes ]
|
||||
|
||||
|
||||
class TrainingAreaModel(FSM):
|
||||
"""
|
||||
Settings:
|
||||
training_influence
|
||||
|
||||
min_vulnerability
|
||||
|
||||
Requires TerroristSpreadModel.
|
||||
"""
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.training_influence = environment.environment_params['training_influence']
|
||||
if 'min_vulnerability' in environment.environment_params:
|
||||
self.min_vulnerability = environment.environment_params['min_vulnerability']
|
||||
else: self.min_vulnerability = 0
|
||||
|
||||
@default_state
|
||||
@state
|
||||
def terrorist(self):
|
||||
for neighbour in self.get_neighboring_agents():
|
||||
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability > self.min_vulnerability:
|
||||
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.training_influence )
|
||||
|
||||
|
||||
class HavenModel(FSM):
|
||||
"""
|
||||
Settings:
|
||||
haven_influence
|
||||
|
||||
min_vulnerability
|
||||
|
||||
max_vulnerability
|
||||
|
||||
Requires TerroristSpreadModel.
|
||||
"""
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
self.haven_influence = environment.environment_params['haven_influence']
|
||||
if 'min_vulnerability' in environment.environment_params:
|
||||
self.min_vulnerability = environment.environment_params['min_vulnerability']
|
||||
else: self.min_vulnerability = 0
|
||||
self.max_vulnerability = environment.environment_params['max_vulnerability']
|
||||
|
||||
@state
|
||||
def civilian(self):
|
||||
for neighbour_agent in self.get_neighboring_agents():
|
||||
if isinstance(neighbour_agent, TerroristSpreadModel) and neighbour_agent['id'] == neighbour_agent.civilian.id:
|
||||
for neighbour in self.get_neighboring_agents():
|
||||
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability > self.min_vulnerability:
|
||||
neighbour.vulnerability = neighbour.vulnerability * ( 1 - self.haven_influence )
|
||||
return self.civilian
|
||||
return self.terrorist
|
||||
|
||||
@state
|
||||
def terrorist(self):
|
||||
for neighbour in self.get_neighboring_agents():
|
||||
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability < self.max_vulnerability:
|
||||
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.haven_influence )
|
||||
return self.terrorist
|
||||
|
||||
|
||||
class TerroristNetworkModel(TerroristSpreadModel):
|
||||
"""
|
||||
Settings:
|
||||
sphere_influence
|
||||
|
||||
vision_range
|
||||
|
||||
weight_social_distance
|
||||
|
||||
weight_link_distance
|
||||
"""
|
||||
|
||||
def __init__(self, environment=None, agent_id=0, state=()):
|
||||
super().__init__(environment=environment, agent_id=agent_id, state=state)
|
||||
|
||||
self.vision_range = environment.environment_params['vision_range']
|
||||
self.sphere_influence = environment.environment_params['sphere_influence']
|
||||
self.weight_social_distance = environment.environment_params['weight_social_distance']
|
||||
self.weight_link_distance = environment.environment_params['weight_link_distance']
|
||||
|
||||
@state
|
||||
def terrorist(self):
|
||||
self.update_relationships()
|
||||
return super().terrorist()
|
||||
|
||||
@state
|
||||
def leader(self):
|
||||
self.update_relationships()
|
||||
return super().leader()
|
||||
|
||||
def update_relationships(self):
|
||||
if self.count_neighboring_agents(state_id=self.civilian.id) == 0:
|
||||
close_ups = self.link_search(self.global_topology, self.id, self.vision_range)
|
||||
step_neighbours = self.social_search(self.global_topology, self.id, self.sphere_influence)
|
||||
search = list(set(close_ups).union(step_neighbours))
|
||||
neighbours = self.get_neighboring_agents()
|
||||
search = [item for item in search if not item in neighbours and isinstance(item, TerroristNetworkModel)]
|
||||
for agent in search:
|
||||
social_distance = 1 / self.shortest_path_length(self.global_topology, self.id, agent.id)
|
||||
spatial_proximity = ( 1 - self.get_distance(self.global_topology, self.id, agent.id) )
|
||||
prob_new_interaction = self.weight_social_distance * social_distance + self.weight_link_distance * spatial_proximity
|
||||
if agent['id'] == agent.civilian.id and random.random() < prob_new_interaction:
|
||||
self.add_edge(self.global_topology, self, agent)
|
||||
break
|
||||
|
||||
def get_distance(self, G, source, target):
|
||||
source_x, source_y = nx.get_node_attributes(G, 'pos')[source]
|
||||
target_x, target_y = nx.get_node_attributes(G, 'pos')[target]
|
||||
dx = abs( source_x - target_x )
|
||||
dy = abs( source_y - target_y )
|
||||
return ( dx ** 2 + dy ** 2 ) ** ( 1 / 2 )
|
||||
|
||||
def shortest_path_length(self, G, source, target):
|
||||
try:
|
||||
return nx.shortest_path_length(G, source, target)
|
||||
except nx.NetworkXNoPath:
|
||||
return float('inf')
|
62
soil/web/TerroristNetworkModel.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
name: TerroristNetworkModel_sim
|
||||
load_module: TerroristNetworkModel
|
||||
max_time: 150
|
||||
num_trials: 1
|
||||
network_params:
|
||||
generator: random_geometric_graph
|
||||
radius: 0.2
|
||||
# generator: geographical_threshold_graph
|
||||
# theta: 20
|
||||
n: 100
|
||||
network_agents:
|
||||
- agent_type: TerroristNetworkModel
|
||||
weight: 0.8
|
||||
state:
|
||||
id: civilian # Civilians
|
||||
- agent_type: TerroristNetworkModel
|
||||
weight: 0.1
|
||||
state:
|
||||
id: leader # Leaders
|
||||
- agent_type: TrainingAreaModel
|
||||
weight: 0.05
|
||||
state:
|
||||
id: terrorist # Terrorism
|
||||
- agent_type: HavenModel
|
||||
weight: 0.05
|
||||
state:
|
||||
id: civilian # Civilian
|
||||
|
||||
environment_params:
|
||||
# TerroristSpreadModel
|
||||
information_spread_intensity: 0.7
|
||||
terrorist_additional_influence: 0.035
|
||||
max_vulnerability: 0.7
|
||||
prob_interaction: 0.5
|
||||
|
||||
# TrainingAreaModel and HavenModel
|
||||
training_influence: 0.20
|
||||
haven_influence: 0.20
|
||||
|
||||
# TerroristNetworkModel
|
||||
vision_range: 0.30
|
||||
sphere_influence: 2
|
||||
weight_social_distance: 0.035
|
||||
weight_link_distance: 0.035
|
||||
|
||||
visualization_params:
|
||||
# Icons downloaded from https://www.iconfinder.com/
|
||||
shape_property: agent
|
||||
shapes:
|
||||
TrainingAreaModel: target
|
||||
HavenModel: home
|
||||
TerroristNetworkModel: person
|
||||
colors:
|
||||
- attr_id: civilian
|
||||
color: '#40de40'
|
||||
- attr_id: terrorist
|
||||
color: red
|
||||
- attr_id: leader
|
||||
color: '#c16a6a'
|
||||
background_image: 'map_4800x2860.jpg'
|
||||
background_opacity: '0.9'
|
||||
background_filter_color: 'blue'
|
274
soil/web/__init__.py
Normal file
@@ -0,0 +1,274 @@
|
||||
import io
|
||||
import threading
|
||||
import asyncio
|
||||
import logging
|
||||
import networkx as nx
|
||||
import os
|
||||
import sys
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.escape
|
||||
import tornado.gen
|
||||
import yaml
|
||||
import webbrowser
|
||||
from contextlib import contextmanager
|
||||
from time import sleep
|
||||
from xml.etree.ElementTree import tostring
|
||||
|
||||
from tornado.concurrent import run_on_executor
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from ..simulation import SoilSimulation
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
MAX_WORKERS = 4
|
||||
LOGGING_INTERVAL = 0.5
|
||||
|
||||
# Workaround to let Soil load the required modules
|
||||
sys.path.append(ROOT)
|
||||
|
||||
class PageHandler(tornado.web.RequestHandler):
|
||||
""" Handler for the HTML template which holds the visualization. """
|
||||
|
||||
def get(self):
|
||||
self.render('index.html', port=self.application.port,
|
||||
name=self.application.name)
|
||||
|
||||
|
||||
class SocketHandler(tornado.websocket.WebSocketHandler):
|
||||
""" Handler for websocket. """
|
||||
executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)
|
||||
|
||||
def open(self):
|
||||
if self.application.verbose:
|
||||
logger.info('Socket opened!')
|
||||
|
||||
def check_origin(self, origin):
|
||||
return True
|
||||
|
||||
def on_message(self, message):
|
||||
""" Receiving a message from the websocket, parse, and act accordingly. """
|
||||
|
||||
msg = tornado.escape.json_decode(message)
|
||||
|
||||
if msg['type'] == 'config_file':
|
||||
|
||||
if self.application.verbose:
|
||||
print(msg['data'])
|
||||
|
||||
self.config = list(yaml.load_all(msg['data']))
|
||||
|
||||
if len(self.config) > 1:
|
||||
error = 'Please, provide only one configuration.'
|
||||
if self.application.verbose:
|
||||
logger.error(error)
|
||||
self.write_message({'type': 'error',
|
||||
'error': error})
|
||||
return
|
||||
|
||||
self.config = self.config[0]
|
||||
self.send_log('INFO.' + self.simulation_name,
|
||||
'Using config: {name}'.format(name=self.config['name']))
|
||||
|
||||
if 'visualization_params' in self.config:
|
||||
self.write_message({'type': 'visualization_params',
|
||||
'data': self.config['visualization_params']})
|
||||
self.name = self.config['name']
|
||||
self.run_simulation()
|
||||
|
||||
settings = []
|
||||
for key in self.config['environment_params']:
|
||||
if type(self.config['environment_params'][key]) == float or type(self.config['environment_params'][key]) == int:
|
||||
if self.config['environment_params'][key] <= 1:
|
||||
setting_type = 'number'
|
||||
else:
|
||||
setting_type = 'great_number'
|
||||
elif type(self.config['environment_params'][key]) == bool:
|
||||
setting_type = 'boolean'
|
||||
else:
|
||||
setting_type = 'undefined'
|
||||
|
||||
settings.append({
|
||||
'label': key,
|
||||
'type': setting_type,
|
||||
'value': self.config['environment_params'][key]
|
||||
})
|
||||
|
||||
self.write_message({'type': 'settings',
|
||||
'data': settings})
|
||||
|
||||
elif msg['type'] == 'get_trial':
|
||||
if self.application.verbose:
|
||||
logger.info('Trial {} requested!'.format(msg['data']))
|
||||
self.send_log('INFO.' + __name__, 'Trial {} requested!'.format(msg['data']))
|
||||
self.write_message({'type': 'get_trial',
|
||||
'data': self.get_trial(int(msg['data']))})
|
||||
|
||||
elif msg['type'] == 'run_simulation':
|
||||
if self.application.verbose:
|
||||
logger.info('Running new simulation for {name}'.format(name=self.config['name']))
|
||||
self.send_log('INFO.' + self.simulation_name, 'Running new simulation for {name}'.format(name=self.config['name']))
|
||||
self.config['environment_params'] = msg['data']
|
||||
self.run_simulation()
|
||||
|
||||
elif msg['type'] == 'download_gexf':
|
||||
G = self.trials[ int(msg['data']) ].history_to_graph()
|
||||
for node in G.nodes():
|
||||
if 'pos' in G.node[node]:
|
||||
G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}}
|
||||
del (G.node[node]['pos'])
|
||||
writer = nx.readwrite.gexf.GEXFWriter(version='1.2draft')
|
||||
writer.add_graph(G)
|
||||
self.write_message({'type': 'download_gexf',
|
||||
'filename': self.config['name'] + '_trial_' + str(msg['data']),
|
||||
'data': tostring(writer.xml).decode(writer.encoding) })
|
||||
|
||||
elif msg['type'] == 'download_json':
|
||||
G = self.trials[ int(msg['data']) ].history_to_graph()
|
||||
for node in G.nodes():
|
||||
if 'pos' in G.node[node]:
|
||||
G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}}
|
||||
del (G.node[node]['pos'])
|
||||
self.write_message({'type': 'download_json',
|
||||
'filename': self.config['name'] + '_trial_' + str(msg['data']),
|
||||
'data': nx.node_link_data(G) })
|
||||
|
||||
else:
|
||||
if self.application.verbose:
|
||||
logger.info('Unexpected message!')
|
||||
|
||||
def update_logging(self):
|
||||
try:
|
||||
if (not self.log_capture_string.closed and self.log_capture_string.getvalue()):
|
||||
for i in range(len(self.log_capture_string.getvalue().split('\n')) - 1):
|
||||
self.send_log('INFO.' + self.simulation_name, self.log_capture_string.getvalue().split('\n')[i])
|
||||
self.log_capture_string.truncate(0)
|
||||
self.log_capture_string.seek(0)
|
||||
finally:
|
||||
if self.capture_logging:
|
||||
tornado.ioloop.IOLoop.current().call_later(LOGGING_INTERVAL, self.update_logging)
|
||||
|
||||
|
||||
def on_close(self):
|
||||
if self.application.verbose:
|
||||
logger.info('Socket closed!')
|
||||
|
||||
def send_log(self, logger, logging):
|
||||
self.write_message({'type': 'log',
|
||||
'logger': logger,
|
||||
'logging': logging})
|
||||
|
||||
@property
|
||||
def simulation_name(self):
|
||||
return self.config.get('name', 'NoSimulationRunning')
|
||||
|
||||
@run_on_executor
|
||||
def nonblocking(self, config):
|
||||
simulation = SoilSimulation(**config)
|
||||
return simulation.run()
|
||||
|
||||
@tornado.gen.coroutine
|
||||
def run_simulation(self):
|
||||
# Run simulation and capture logs
|
||||
logger.info('Running simulation!')
|
||||
if 'visualization_params' in self.config:
|
||||
del self.config['visualization_params']
|
||||
with self.logging(self.simulation_name):
|
||||
try:
|
||||
config = dict(**self.config)
|
||||
config['dir_path'] = os.path.join(self.application.dir_path, config['name'])
|
||||
config['dump'] = self.application.dump
|
||||
self.trials = yield self.nonblocking(config)
|
||||
|
||||
self.write_message({'type': 'trials',
|
||||
'data': list(trial.name for trial in self.trials) })
|
||||
except Exception as ex:
|
||||
error = 'Something went wrong:\n\t{}'.format(ex)
|
||||
logging.info(error)
|
||||
self.write_message({'type': 'error',
|
||||
'error': error})
|
||||
self.send_log('ERROR.' + self.simulation_name, error)
|
||||
|
||||
def get_trial(self, trial):
|
||||
logger.info('Available trials: %s ' % len(self.trials))
|
||||
logger.info('Ask for : %s' % trial)
|
||||
trial = self.trials[trial]
|
||||
G = trial.history_to_graph()
|
||||
return nx.node_link_data(G)
|
||||
|
||||
@contextmanager
|
||||
def logging(self, logger):
|
||||
self.capture_logging = True
|
||||
self.logger_application = logging.getLogger(logger)
|
||||
self.log_capture_string = io.StringIO()
|
||||
ch = logging.StreamHandler(self.log_capture_string)
|
||||
self.logger_application.addHandler(ch)
|
||||
self.update_logging()
|
||||
yield self.capture_logging
|
||||
|
||||
sleep(0.2)
|
||||
self.log_capture_string.close()
|
||||
self.logger_application.removeHandler(ch)
|
||||
self.capture_logging = False
|
||||
return self.capture_logging
|
||||
|
||||
|
||||
class ModularServer(tornado.web.Application):
|
||||
""" Main visualization application. """
|
||||
|
||||
port = 8001
|
||||
page_handler = (r'/', PageHandler)
|
||||
socket_handler = (r'/ws', SocketHandler)
|
||||
static_handler = (r'/(.*)', tornado.web.StaticFileHandler,
|
||||
{'path': os.path.join(ROOT, 'static')})
|
||||
local_handler = (r'/local/(.*)', tornado.web.StaticFileHandler,
|
||||
{'path': ''})
|
||||
|
||||
handlers = [page_handler, socket_handler, static_handler, local_handler]
|
||||
settings = {'debug': True,
|
||||
'template_path': ROOT + '/templates'}
|
||||
|
||||
def __init__(self, dump=False, dir_path='output', name='SOIL', verbose=True, *args, **kwargs):
|
||||
|
||||
self.verbose = verbose
|
||||
self.name = name
|
||||
self.dump = dump
|
||||
self.dir_path = dir_path
|
||||
|
||||
# Initializing the application itself:
|
||||
super().__init__(self.handlers, **self.settings)
|
||||
|
||||
def launch(self, port=None):
|
||||
""" Run the app. """
|
||||
|
||||
if port is not None:
|
||||
self.port = port
|
||||
url = 'http://127.0.0.1:{PORT}'.format(PORT=self.port)
|
||||
print('Interface starting at {url}'.format(url=url))
|
||||
self.listen(self.port)
|
||||
# webbrowser.open(url)
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
||||
|
||||
def run(*args, **kwargs):
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
server = ModularServer(*args, **kwargs)
|
||||
server.launch()
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Visualization of a Graph Model')
|
||||
|
||||
parser.add_argument('--name', '-n', nargs=1, default='SOIL', help='name of the simulation')
|
||||
parser.add_argument('--dump', '-d', help='dumping results in folder output', action='store_true')
|
||||
parser.add_argument('--port', '-p', nargs=1, default=8001, help='port for launching the server')
|
||||
parser.add_argument('--verbose', '-v', help='verbose mode', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
run(name=args.name, port=(args.port[0] if isinstance(args.port, list) else args.port), verbose=args.verbose)
|
5
soil/web/__main__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from . import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
25
soil/web/config.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: ControlModelM2_sim
|
||||
max_time: 50
|
||||
num_trials: 2
|
||||
network_params:
|
||||
generator: barabasi_albert_graph
|
||||
n: 100
|
||||
m: 2
|
||||
network_agents:
|
||||
- agent_type: ControlModelM2
|
||||
weight: 0.1
|
||||
state:
|
||||
id: 1
|
||||
- agent_type: ControlModelM2
|
||||
weight: 0.9
|
||||
state:
|
||||
id: 0
|
||||
environment_params:
|
||||
prob_neutral_making_denier: 0.035
|
||||
prob_infect: 0.075
|
||||
prob_cured_healing_infected: 0.035
|
||||
prob_cured_vaccinate_neutral: 0.035
|
||||
prob_vaccinated_healing_infected: 0.035
|
||||
prob_vaccinated_vaccinate_neutral: 0.035
|
||||
prob_generate_anti_rumor: 0.035
|
||||
standard_variance: 0.055
|
23
soil/web/run.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import argparse
|
||||
from server import ModularServer
|
||||
from simulator import Simulator
|
||||
|
||||
|
||||
def run(simulator, name="SOIL", port=8001, verbose=False):
|
||||
server = ModularServer(simulator, name=(name[0] if isinstance(name, list) else name), verbose=verbose)
|
||||
server.port = port
|
||||
server.launch()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description='Visualization of a Graph Model')
|
||||
|
||||
parser.add_argument('--name', '-n', nargs=1, default='SOIL', help='name of the simulation')
|
||||
parser.add_argument('--dump', '-d', help='dumping results in folder output', action='store_true')
|
||||
parser.add_argument('--port', '-p', nargs=1, default=8001, help='port for launching the server')
|
||||
parser.add_argument('--verbose', '-v', help='verbose mode', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
soil = Simulator(dump=args.dump)
|
||||
run(soil, name=args.name, port=(args.port[0] if isinstance(args.port, list) else args.port), verbose=args.verbose)
|
431
soil/web/static/css/main.css
Normal file
@@ -0,0 +1,431 @@
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.carousel {
|
||||
height: calc(100% - 150px);
|
||||
}
|
||||
|
||||
.carousel-inner {
|
||||
height: calc(100% - 50px) !important;
|
||||
}
|
||||
|
||||
.carousel-inner .item,
|
||||
.carousel-inner .item .container-fluid {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, .2)
|
||||
}
|
||||
|
||||
.nav.navbar-right {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
.nav.navbar-right a {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a:hover {
|
||||
background-color: #d4d3d3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wrapper-heading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.soil_logo {
|
||||
padding: 0 !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
background-color: rgb(88, 88, 88);
|
||||
}
|
||||
|
||||
.soil_logo > img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.node {
|
||||
stroke: #fff;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.link {
|
||||
stroke: #999;
|
||||
stroke-opacity: .6;
|
||||
}
|
||||
|
||||
svg#graph, #configuration {
|
||||
background-color: white;
|
||||
margin-top: 15px;
|
||||
border-style: double;
|
||||
border-color: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 5px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#timeline {
|
||||
padding: 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#configuration {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
border-left: none !important;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: inherit;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
button {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.btn-toolbar.controls {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.controls > .btn {
|
||||
margin-left: 10px !important;
|
||||
}
|
||||
|
||||
button.pressed {
|
||||
background-color: rgb(167, 242, 168);
|
||||
-webkit-animation: background 1s cubic-bezier(1,0,0,1) infinite;
|
||||
animation: background 1s cubic-bezier(1,0,0,1) infinite;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes background {
|
||||
50% { background-color: #dddddd; }
|
||||
100% { background-color: rgb(167, 242, 168); }
|
||||
}
|
||||
|
||||
@keyframes background {
|
||||
50% { background-color: #dddddd; }
|
||||
100% { background-color: rgb(167, 242, 168); }
|
||||
}
|
||||
|
||||
#slider3 {
|
||||
background: repeating-linear-gradient( 90deg, white 27px, white 30px, #fff 32px, #aaa 33px );
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 15px !important;
|
||||
margin-bottom: 15px !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#update .config-item {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
|
||||
/** LOADER **/
|
||||
#load {
|
||||
position: absolute;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#load.loader {
|
||||
border: 5px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 5px solid #3498db;
|
||||
border-bottom: 5px solid #3498db;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#load:before {
|
||||
content: 'No file'
|
||||
}
|
||||
|
||||
#load.loader:before {
|
||||
content: '' !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/** ALERT **/
|
||||
.alert-danger {
|
||||
position: absolute;
|
||||
margin-top: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/** FILE BROWSER **/
|
||||
.custom-file {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-file-input {
|
||||
min-width: 14rem;
|
||||
max-width: 100%;
|
||||
height: 35px;
|
||||
margin: 0;
|
||||
filter: alpha(opacity=0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.custom-file-control {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
height: 35px;
|
||||
padding: .5rem 1rem;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
color: #464a4c;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.custom-file-control::before {
|
||||
content: "Browse";
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
z-index: 6;
|
||||
display: block;
|
||||
height: 35px;
|
||||
padding: .5rem 1rem;
|
||||
line-height: 1.5;
|
||||
color: #464a4c;
|
||||
background-color: #eceeef;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-radius: 0 .25rem .25rem 0;
|
||||
}
|
||||
|
||||
.custom-file-control::after {
|
||||
content: attr(data-content);
|
||||
}
|
||||
|
||||
/** TABLES **/
|
||||
#percentTable {
|
||||
height: 150px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#percentTable tr {
|
||||
padding: 5px 2px;
|
||||
}
|
||||
|
||||
#percentTable .no-data-table {
|
||||
font-size: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 15px !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
#info-graph {
|
||||
width: 70% !important;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: -40px;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
/** SLIDER **/
|
||||
.speed-slider,
|
||||
.link-distance-slider {
|
||||
padding: 0 10px !important;
|
||||
margin-top: 5px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.slider .slider-selection {
|
||||
background-image: linear-gradient(to bottom,
|
||||
rgba(36, 110, 162, 0.5) 0%,
|
||||
rgba(3, 169, 224, 0.5) 100%) !important;
|
||||
}
|
||||
|
||||
.slider-disabled .slider-selection {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.slider.slider-disabled .slider-track {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
table#speed,
|
||||
table#link-distance {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table#speed .min,
|
||||
table#speed .max,
|
||||
table#link-distance .min,
|
||||
table#link-distance .max {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
/* Console */
|
||||
|
||||
#update, .console, .soil_logo {
|
||||
padding: 10px 15px;
|
||||
height: 135px;
|
||||
border: 1px solid #585858;
|
||||
}
|
||||
|
||||
#update {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.container-fluid.fixed {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.console {
|
||||
background-color: rgb(88,88,88);
|
||||
font-family: "Ubuntu Mono";
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
line-height: 14px;
|
||||
overflow: auto;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.console::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.console::-webkit-scrollbar-thumb {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
/** FORMS **/
|
||||
.checkbox {
|
||||
margin-left: 10px !important;
|
||||
}
|
||||
|
||||
#wrapper-settings {
|
||||
padding: 15px !important;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#wrapper-settings.none {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#wrapper-settings.none:before {
|
||||
content: 'No configuration provided';
|
||||
}
|
||||
|
||||
#wrapper-settings .btn-group button:focus {
|
||||
background: initial;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
#wrapper-settings .btn-group button {
|
||||
font-size: xx-small;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
.item.settings .container-fluid {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
|
||||
#wrapper-settings::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
#wrapper-settings::-webkit-scrollbar-thumb {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
/** CHARTS **/
|
||||
#charts {
|
||||
height: 100%;
|
||||
padding-left: 0 !important;
|
||||
padding-top: 15px !important;
|
||||
padding-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.chart {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.chart.no-data:before {
|
||||
content: 'No data';
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
padding-bottom: 35px;
|
||||
}
|
||||
|
||||
.chart.no-data {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/** MODAL **/
|
||||
.modal-footer,
|
||||
.modal-header {
|
||||
border: none !important;
|
||||
}
|
72
soil/web/static/css/timeline.css
Normal file
@@ -0,0 +1,72 @@
|
||||
#slider3 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.d3-slider {
|
||||
position: relative;
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1.1em;
|
||||
border: 1px solid #aaaaaa;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.d3-slider-horizontal {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.d3-slider-range {
|
||||
background:#2980b9;
|
||||
left:0px;
|
||||
right:0px;
|
||||
height: 0.8em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.d3-slider-handle {
|
||||
position: absolute;
|
||||
width: .8em;
|
||||
height: 48px;
|
||||
border: 1px solid #d3d3d3;
|
||||
border-radius: 4px;
|
||||
background: #eee;
|
||||
background: linear-gradient(to bottom, #eee 0%, #ddd 100%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.d3-slider-handle:hover {
|
||||
border: 1px solid #999999;
|
||||
}
|
||||
|
||||
.d3-slider-horizontal .d3-slider-handle {
|
||||
top: -.3em;
|
||||
margin-left: -.4em;
|
||||
}
|
||||
|
||||
.d3-slider-axis {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.d3-slider-axis-bottom {
|
||||
top: 38px;
|
||||
}
|
||||
|
||||
.d3-slider-axis-right {
|
||||
left: .8em;
|
||||
}
|
||||
|
||||
.d3-slider-axis path {
|
||||
stroke-width: 0;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.d3-slider-axis line {
|
||||
fill: none;
|
||||
stroke: #aaa;
|
||||
shape-rendering: crispEdges;
|
||||
stroke-dasharray: 2;
|
||||
}
|
||||
|
||||
.d3-slider-axis text {
|
||||
font-size: 11px;
|
||||
}
|
BIN
soil/web/static/img/background/map.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
soil/web/static/img/background/map_4800x2860.jpg
Normal file
After Width: | Height: | Size: 8.0 MiB |
140
soil/web/static/img/logo_gsi.svg
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="360"
|
||||
height="330"
|
||||
id="svg3752"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.4 r9939"
|
||||
sodipodi:docname="logo_gsi_nuevo.svg"
|
||||
inkscape:export-filename="/home/cif/GoogleDrive/docs/gsi/corporativo/logos-fondos/logos/logo_gsi_nuevo_740_671.png"
|
||||
inkscape:export-xdpi="185.6273"
|
||||
inkscape:export-ydpi="185.6273">
|
||||
<defs
|
||||
id="defs3754">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3760" />
|
||||
<inkscape:perspective
|
||||
id="perspective3730"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3757"
|
||||
x="-0.4412556"
|
||||
width="1.8825113"
|
||||
y="-0.4412556"
|
||||
height="1.8825113"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.91928251"
|
||||
id="feGaussianBlur3759" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.5"
|
||||
inkscape:cx="187.12284"
|
||||
inkscape:cy="78.411806"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1855"
|
||||
inkscape:window-height="1056"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata3757">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Capa 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-722.36218)">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g3761"
|
||||
transform="matrix(2.2932314,0,0,2.2932314,-744.72199,6804.6985)">
|
||||
<g
|
||||
transform="translate(-161.76758,3.3349672)"
|
||||
id="g2940">
|
||||
<g
|
||||
id="g2922">
|
||||
<g
|
||||
transform="translate(-79.72168,-3162.9998)"
|
||||
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#00a9e0;fill-opacity:1;stroke:none;font-family:Calibri;-inkscape-font-specification:Calibri Bold"
|
||||
id="flowRoot4140">
|
||||
<path
|
||||
d="m 688.88086,587.87773 c -6e-5,3.71096 -0.70806,6.98244 -2.12402,9.81446 -1.36724,2.83204 -3.32037,5.2002 -5.85938,7.10449 -2.53911,1.9043 -5.54203,3.32031 -9.00879,4.24805 -3.46683,0.97656 -7.22659,1.46484 -11.2793,1.46484 -2.44143,0 -4.78517,-0.19531 -7.03125,-0.58594 -2.19728,-0.34179 -4.17482,-0.78125 -5.93261,-1.31836 -1.75783,-0.58593 -3.22267,-1.17187 -4.39453,-1.75781 -1.17189,-0.58593 -2.02638,-1.12304 -2.56348,-1.61133 -0.53712,-0.5371 -0.95215,-1.34277 -1.24512,-2.41699 -0.29297,-1.12304 -0.43946,-2.75878 -0.43945,-4.90723 -10e-6,-1.416 0.0488,-2.53904 0.14648,-3.36914 0.0977,-0.87889 0.24414,-1.56248 0.43946,-2.05078 0.1953,-0.53709 0.43944,-0.87889 0.73242,-1.02539 0.29296,-0.19529 0.65917,-0.29295 1.09863,-0.29297 0.5371,2e-5 1.31835,0.3174 2.34375,0.95215 1.07421,0.58595 2.39257,1.24513 3.95508,1.97754 1.56248,0.73244 3.36912,1.41603 5.41992,2.05078 2.09959,0.63478 4.46775,0.95216 7.10449,0.95215 1.66013,10e-6 3.12497,-0.17089 4.39454,-0.5127 1.31832,-0.34178 2.44137,-0.83006 3.36914,-1.46484 0.97652,-0.63475 1.70894,-1.44041 2.19726,-2.41699 0.48825,-0.97655 0.73239,-2.09959 0.73242,-3.36914 -3e-5,-1.46482 -0.4639,-2.70994 -1.3916,-3.73535 -0.87894,-1.0742 -2.07523,-2.00193 -3.58887,-2.78321 -1.46487,-0.78122 -3.14944,-1.51364 -5.05371,-2.19726 -1.85549,-0.68357 -3.7842,-1.4404 -5.78613,-2.27051 -1.95314,-0.83005 -3.88185,-1.78219 -5.78613,-2.85645 -1.85548,-1.07418 -3.54005,-2.39254 -5.05371,-3.95507 -1.46486,-1.56246 -2.66114,-3.44235 -3.58887,-5.63965 -0.87891,-2.19722 -1.31837,-4.83394 -1.31836,-7.91016 -10e-6,-3.12494 0.61035,-5.98139 1.83106,-8.56933 1.22069,-2.63666 2.9785,-4.88275 5.27343,-6.73829 2.29491,-1.8554 5.07811,-3.29582 8.34961,-4.32128 3.32029,-1.02532 7.03122,-1.53802 11.13281,-1.53809 2.05075,7e-5 4.02829,0.14656 5.93262,0.43945 1.95309,0.29304 3.7109,0.65925 5.27344,1.09864 1.56245,0.43952 2.88081,0.9278 3.95508,1.46484 1.07417,0.48835 1.831,0.9278 2.27051,1.31836 0.48823,0.34186 0.83002,0.70807 1.02539,1.09863 0.19526,0.34186 0.34174,0.78132 0.43945,1.31836 0.0976,0.48835 0.17085,1.12311 0.21973,1.9043 0.0976,0.73248 0.14643,1.66022 0.14648,2.7832 -5e-5,1.31842 -0.0489,2.39264 -0.14648,3.22266 -0.0489,0.83013 -0.17095,1.48931 -0.36622,1.97754 -0.14653,0.48833 -0.36626,0.83013 -0.65918,1.02539 -0.29301,0.14654 -0.63481,0.21978 -1.02539,0.21972 -0.4395,6e-5 -1.12309,-0.24408 -2.05078,-0.73242 -0.92778,-0.53705 -2.09965,-1.09858 -3.51562,-1.68457 -1.36723,-0.58588 -2.97856,-1.12299 -4.83399,-1.61133 -1.80667,-0.53705 -3.88187,-0.8056 -6.22558,-0.80566 -1.66019,6e-5 -3.10062,0.17096 -4.32129,0.51269 -1.22073,0.34186 -2.22171,0.83014 -3.00293,1.46485 -0.78128,0.63482 -1.36721,1.39166 -1.75781,2.27051 -0.39065,0.83013 -0.58596,1.73345 -0.58594,2.70996 -2e-5,1.51372 0.46384,2.78325 1.3916,3.80859 0.92771,1.02544 2.14841,1.92876 3.66211,2.70996 1.51364,0.7813 3.22262,1.51372 5.12695,2.19727 1.95309,0.68363 3.90622,1.44047 5.85938,2.27051 2.00191,0.78129 3.95503,1.70902 5.85937,2.7832 1.95308,1.07425 3.68648,2.39261 5.2002,3.95508 1.51362,1.56253 2.73432,3.44241 3.66211,5.63964 0.92768,2.14847 1.39154,4.71194 1.3916,7.69043"
|
||||
style="font-size:150px;fill:#00a9e0;font-family:Calibri;-inkscape-font-specification:Calibri Bold"
|
||||
id="path3806" />
|
||||
<path
|
||||
d="m 721.10742,606.33477 c -3e-5,0.48828 -0.14651,0.92773 -0.43945,1.31836 -0.293,0.34179 -0.80569,0.63476 -1.53809,0.8789 -0.68362,0.24414 -1.61135,0.41504 -2.7832,0.5127 -1.1719,0.14648 -2.66115,0.21972 -4.46777,0.21972 -1.80666,0 -3.29592,-0.0732 -4.46778,-0.21972 -1.17189,-0.0977 -2.12403,-0.26856 -2.85644,-0.5127 -0.68361,-0.24414 -1.17189,-0.53711 -1.46485,-0.8789 -0.29297,-0.39063 -0.43946,-0.83008 -0.43945,-1.31836 l 0,-65.18555 c -10e-6,-0.48821 0.14648,-0.90325 0.43945,-1.24512 0.29296,-0.39055 0.78124,-0.70794 1.46485,-0.95215 0.73241,-0.2929 1.68455,-0.51262 2.85644,-0.65918 1.17186,-0.14641 2.66112,-0.21965 4.46778,-0.21972 1.80662,7e-5 3.29587,0.0733 4.46777,0.21972 1.17185,0.14656 2.09958,0.36628 2.7832,0.65918 0.7324,0.24421 1.24509,0.5616 1.53809,0.95215 0.29294,0.34187 0.43942,0.75691 0.43945,1.24512 l 0,65.18555 m 1.3916,-87.45118 c -3e-5,3.71103 -0.75686,6.2745 -2.2705,7.69043 -1.5137,1.4161 -4.32132,2.12411 -8.42286,2.12403 -4.1504,8e-5 -6.95802,-0.68352 -8.42285,-2.05078 -1.41602,-1.36711 -2.12403,-3.83293 -2.12402,-7.39747 -10e-6,-3.71084 0.73241,-6.27431 2.19726,-7.69042 1.51367,-1.46475 4.34569,-2.19717 8.4961,-2.19727 4.10154,1e-4 6.88474,0.70811 8.34961,2.12402 1.46481,1.36729 2.19723,3.8331 2.19726,7.39746"
|
||||
style="font-size:150px;fill:#00a9e0;font-family:Calibri;-inkscape-font-specification:Calibri Bold"
|
||||
id="path3808" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1,0,0,1.1503876,-79.72168,-3243.7762)"
|
||||
style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#00a9e0;fill-opacity:1;stroke:none;font-family:Calibri;-inkscape-font-specification:Calibri Bold"
|
||||
id="flowRoot2914">
|
||||
<path
|
||||
d="m 633.80273,545.54375 c -6e-5,2.34381 -0.26862,4.07721 -0.80566,5.2002 -0.53718,1.1231 -1.19635,1.68462 -1.97754,1.68457 l -7.69043,0 c 1.07416,1.17193 1.831,2.5147 2.27051,4.02832 0.48822,1.46489 0.73236,3.00297 0.73242,4.61425 -6e-5,3.80864 -0.63482,7.20219 -1.9043,10.18067 -1.26958,2.92972 -3.10064,5.41995 -5.49316,7.4707 -2.3438,2.00198 -5.20024,3.54007 -8.56934,4.61426 -3.32035,1.02541 -7.03128,1.53811 -11.13281,1.53808 -2.09964,3e-5 -4.10159,-0.24411 -6.00586,-0.73242 -1.90432,-0.53708 -3.36916,-1.14743 -4.39453,-1.83105 -0.58596,0.63479 -1.12307,1.39162 -1.61133,2.27051 -0.43947,0.87893 -0.6592,1.85549 -0.65918,2.92968 -2e-5,1.41604 0.61033,2.58791 1.83106,3.51563 1.26951,0.87892 3.02732,1.3672 5.27344,1.46484 l 15.89355,0.58594 c 3.71089,0.1465 7.00679,0.68361 9.8877,1.61133 2.92963,0.87892 5.37103,2.14845 7.32421,3.80859 2.00189,1.61134 3.51556,3.56446 4.54102,5.85938 1.07415,2.29492 1.61126,4.90722 1.61133,7.83691 -7e-5,3.22265 -0.70808,6.24999 -2.12403,9.08203 -1.41607,2.88085 -3.5401,5.37108 -6.37207,7.47071 -2.83208,2.09958 -6.39653,3.75974 -10.69336,4.98046 -4.24809,1.22068 -9.22855,1.83103 -14.9414,1.83106 -5.56644,-3e-5 -10.32717,-0.43948 -14.28223,-1.31836 -3.90626,-0.87893 -7.12892,-2.09963 -9.66797,-3.66211 -2.49024,-1.56252 -4.32129,-3.4424 -5.49316,-5.63965 -1.12305,-2.14845 -1.68457,-4.51661 -1.68457,-7.10449 0,-1.61134 0.19531,-3.14942 0.58594,-4.61426 0.43945,-1.46485 1.0498,-2.88086 1.83105,-4.24805 0.83007,-1.31835 1.83105,-2.58788 3.00293,-3.80859 1.17187,-1.2207 2.51464,-2.39257 4.02832,-3.51562 -2.09962,-1.12304 -3.73536,-2.63671 -4.90723,-4.54102 -1.12305,-1.95311 -1.68457,-4.07713 -1.68457,-6.37207 0,-2.88084 0.65918,-5.49314 1.97754,-7.83691 1.31835,-2.39255 3.02734,-4.54099 5.12696,-6.44532 -1.709,-1.70895 -3.07618,-3.75973 -4.10157,-6.15234 -1.02539,-2.39254 -1.53809,-5.37105 -1.53808,-8.93555 -10e-6,-3.80854 0.65917,-7.20209 1.97754,-10.18066 1.36718,-3.02728 3.24706,-5.56634 5.63965,-7.61719 2.39256,-2.09954 5.249,-3.68645 8.56933,-4.76074 3.32029,-1.12298 6.98239,-1.6845 10.98633,-1.68457 2.05075,7e-5 4.00387,0.12214 5.85937,0.36621 1.90426,0.24421 3.66207,0.58601 5.27344,1.02539 l 20.72754,0 c 0.83001,7e-5 1.48919,0.53718 1.97754,1.61133 0.53704,1.07428 0.8056,2.88092 0.80566,5.41992 m -23.65722,15.4541 c -5e-5,-3.51557 -0.97661,-6.24994 -2.92969,-8.20312 -1.95316,-1.95307 -4.71195,-2.92963 -8.27637,-2.92969 -1.80667,6e-5 -3.39358,0.31744 -4.76074,0.95215 -1.36721,0.58599 -2.51467,1.41607 -3.44238,2.49023 -0.87893,1.02545 -1.53811,2.24615 -1.97754,3.66211 -0.43948,1.36724 -0.6592,2.80767 -0.65918,4.32129 -2e-5,3.32036 0.97654,5.95707 2.92969,7.91016 1.95309,1.90433 4.66305,2.85648 8.12988,2.85644 1.85543,4e-5 3.46676,-0.29293 4.83398,-0.8789 1.36715,-0.5859 2.4902,-1.39157 3.36914,-2.417 0.9277,-1.02535 1.61129,-2.19722 2.05079,-3.51562 0.48823,-1.36714 0.73237,-2.78316 0.73242,-4.24805 m 4.32129,52.14844 c -5e-5,-2.19727 -0.87896,-3.88184 -2.63672,-5.05371 -1.75786,-1.17187 -4.17485,-1.80664 -7.25098,-1.9043 l -13.11035,-0.36621 c -1.26956,0.92774 -2.29495,1.83106 -3.07617,2.70996 -0.73245,0.83008 -1.3428,1.63574 -1.83106,2.41699 -0.43947,0.78125 -0.73244,1.53809 -0.8789,2.27051 -0.14651,0.73242 -0.21975,1.48925 -0.21973,2.27051 -2e-5,2.4414 1.22068,4.29686 3.66211,5.56641 2.49021,1.26951 5.98142,1.90428 10.47363,1.90429 2.78317,-10e-6 5.12692,-0.29298 7.03125,-0.8789 1.90426,-0.53713 3.44234,-1.26955 4.61426,-2.19727 1.17183,-0.92774 2.00191,-1.97755 2.49023,-3.14941 0.48824,-1.12306 0.73238,-2.31935 0.73243,-3.58887"
|
||||
style="font-size:150px;fill:#00a9e0;font-family:Calibri;-inkscape-font-specification:Calibri Bold"
|
||||
id="path3811" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(-154.76465,-3152.2336)"
|
||||
style="font-size:42px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#00629b;fill-opacity:0.98431373;stroke:none;font-family:Calibri;-inkscape-font-specification:AlArabiya Bold"
|
||||
id="flowRoot3727">
|
||||
<path
|
||||
d="m 574.2041,625.37123 c -2e-5,1.62697 -0.23928,3.08302 -0.71777,4.36817 -0.47854,1.28516 -1.18264,2.37207 -2.11231,3.26074 -0.9297,0.88867 -2.07814,1.56543 -3.44531,2.03027 -1.3672,0.46485 -2.93947,0.69727 -4.7168,0.69727 -1.66798,0 -3.16505,-0.20508 -4.49121,-0.61523 -1.32618,-0.42383 -2.44727,-1.05957 -3.36328,-1.90723 -0.91602,-0.84765 -1.62012,-1.90039 -2.1123,-3.1582 -0.47852,-1.27148 -0.71778,-2.75488 -0.71778,-4.4502 l 0,-16.13965 c 0,-0.13669 0.041,-0.25974 0.12305,-0.36914 0.082,-0.10935 0.22558,-0.19821 0.43066,-0.2666 0.21875,-0.0683 0.49902,-0.12302 0.84082,-0.16406 0.3418,-0.041 0.7793,-0.0615 1.3125,-0.0615 0.51953,3e-5 0.95019,0.0205 1.292,0.0615 0.34179,0.041 0.61522,0.0957 0.82031,0.16406 0.20507,0.0684 0.34862,0.15725 0.43066,0.2666 0.0957,0.1094 0.14355,0.23245 0.14356,0.36914 l 0,15.66797 c -10e-6,1.05274 0.12987,1.96876 0.38965,2.74805 0.25975,0.76563 0.62889,1.40137 1.10742,1.90722 0.49218,0.50587 1.07323,0.88868 1.74316,1.14844 0.68358,0.2461 1.44237,0.36915 2.27637,0.36914 0.84764,10e-6 1.60643,-0.12988 2.27637,-0.38965 0.6699,-0.25976 1.23728,-0.63573 1.70215,-1.12793 0.46482,-0.50585 0.82029,-1.12108 1.0664,-1.8457 0.25975,-0.73827 0.38963,-1.57226 0.38965,-2.50195 l 0,-15.97559 c -2e-5,-0.13669 0.041,-0.25974 0.12305,-0.36914 0.082,-0.10935 0.22556,-0.19821 0.43066,-0.2666 0.20506,-0.0683 0.4785,-0.12302 0.82031,-0.16406 0.35545,-0.041 0.79295,-0.0615 1.3125,-0.0615 0.51951,3e-5 0.94334,0.0205 1.27149,0.0615 0.34177,0.041 0.61521,0.0957 0.82031,0.16406 0.20505,0.0684 0.34861,0.15725 0.43066,0.2666 0.082,0.1094 0.12303,0.23245 0.12305,0.36914 l 0,15.91406"
|
||||
id="path3814" />
|
||||
<path
|
||||
d="m 598.11621,616.77846 c -2e-5,1.49025 -0.23244,2.80959 -0.69726,3.95801 -0.46487,1.14845 -1.14163,2.11915 -2.03028,2.91211 -0.88869,0.77931 -1.98244,1.37403 -3.28125,1.78418 -1.28517,0.41016 -2.80274,0.61524 -4.55273,0.61523 l -2.21485,0 0,8.46973 c 0,0.13672 -0.0479,0.25976 -0.14355,0.36914 -0.082,0.10937 -0.22559,0.19824 -0.43067,0.2666 -0.20508,0.0684 -0.47852,0.12305 -0.82031,0.16406 -0.3418,0.041 -0.7793,0.0615 -1.3125,0.0615 -0.51953,0 -0.95703,-0.0205 -1.3125,-0.0615 -0.3418,-0.041 -0.61524,-0.0957 -0.82031,-0.16406 -0.20508,-0.0684 -0.34864,-0.15723 -0.43066,-0.2666 -0.082,-0.10938 -0.12305,-0.23242 -0.12305,-0.36914 l 0,-23.8711 c 0,-0.64255 0.16406,-1.12106 0.49219,-1.43554 0.34179,-0.3281 0.78613,-0.49216 1.333,-0.49219 l 6.25489,0 c 0.62889,3e-5 1.22362,0.0274 1.78418,0.082 0.5742,0.041 1.2578,0.14358 2.05078,0.30762 0.79295,0.15042 1.59276,0.43752 2.39941,0.86133 0.8203,0.42385 1.51756,0.96389 2.0918,1.62011 0.5742,0.64261 1.0117,1.40139 1.3125,2.27637 0.30076,0.86135 0.45115,1.83205 0.45117,2.91211 m -5.63965,0.38965 c -10e-6,-0.92967 -0.16408,-1.69529 -0.49219,-2.29688 -0.32813,-0.60154 -0.73145,-1.04587 -1.20996,-1.333 -0.47852,-0.28709 -0.98438,-0.46483 -1.51757,-0.53321 -0.51955,-0.082 -1.05959,-0.12302 -1.62012,-0.12304 l -2.29688,0 0,9.00293 2.41993,0 c 0.86131,1e-5 1.57908,-0.1162 2.15332,-0.34864 0.58787,-0.2324 1.06639,-0.55369 1.43554,-0.96386 0.36913,-0.42382 0.6494,-0.92284 0.84082,-1.49707 0.19139,-0.58788 0.2871,-1.22362 0.28711,-1.90723"
|
||||
id="path3816" />
|
||||
<path
|
||||
d="m 633.2666,634.51772 c -3e-5,0.13672 -0.0411,0.25976 -0.12305,0.36914 -0.0684,0.10937 -0.20511,0.19824 -0.41015,0.2666 -0.19144,0.0684 -0.45121,0.12305 -0.7793,0.16406 -0.32816,0.041 -0.74515,0.0615 -1.25098,0.0615 -0.49221,0 -0.90237,-0.0205 -1.23046,-0.0615 -0.32816,-0.041 -0.58792,-0.0957 -0.7793,-0.16406 -0.19144,-0.0684 -0.32815,-0.15723 -0.41016,-0.2666 -0.0821,-0.10938 -0.12307,-0.23242 -0.12304,-0.36914 l 0,-21.59473 -0.041,0 -7.69043,21.57422 c -0.0547,0.17774 -0.14357,0.32813 -0.2666,0.45117 -0.12307,0.10938 -0.29397,0.19825 -0.5127,0.2666 -0.20509,0.0684 -0.4717,0.10938 -0.7998,0.12305 -0.32814,0.0273 -0.72463,0.041 -1.18945,0.041 -0.46487,0 -0.86135,-0.0205 -1.18946,-0.0615 -0.32814,-0.0273 -0.60158,-0.0752 -0.82031,-0.14355 -0.20509,-0.082 -0.36916,-0.17774 -0.49219,-0.28711 -0.12306,-0.10938 -0.20509,-0.23926 -0.24609,-0.38965 l -7.42383,-21.57422 -0.041,0 0,21.59473 c -1e-5,0.13672 -0.041,0.25976 -0.12305,0.36914 -0.0684,0.10937 -0.20509,0.19824 -0.41016,0.2666 -0.20508,0.0684 -0.47168,0.12305 -0.7998,0.16406 -0.31446,0.041 -0.72462,0.0615 -1.23047,0.0615 -0.49219,0 -0.90235,-0.0205 -1.23047,-0.0615 -0.32813,-0.041 -0.59473,-0.0957 -0.7998,-0.16406 -0.19141,-0.0684 -0.32813,-0.15723 -0.41016,-0.2666 -0.0684,-0.10938 -0.10254,-0.23242 -0.10254,-0.36914 l 0,-23.64551 c 0,-0.69724 0.18457,-1.23044 0.55371,-1.59961 0.36914,-0.36911 0.86133,-0.55368 1.47656,-0.55371 l 3.52735,0 c 0.62889,3e-5 1.16893,0.0547 1.62011,0.16406 0.45117,0.0957 0.84081,0.26663 1.16895,0.5127 0.32811,0.23245 0.60155,0.5469 0.82031,0.94336 0.21874,0.38283 0.41015,0.86135 0.57422,1.43554 l 5.74219,15.81153 0.082,0 5.94727,-15.77051 c 0.17771,-0.57419 0.36911,-1.05955 0.57421,-1.45605 0.21873,-0.39646 0.46482,-0.71775 0.73829,-0.96387 0.28708,-0.24607 0.62204,-0.41697 1.00488,-0.5127 0.38278,-0.10935 0.82712,-0.16403 1.33301,-0.16406 l 3.62988,0 c 0.36911,3e-5 0.68356,0.0479 0.94336,0.14356 0.2734,0.0957 0.49215,0.23928 0.65625,0.43066 0.1777,0.17776 0.30758,0.40335 0.38965,0.67676 0.0957,0.25979 0.14352,0.56057 0.14355,0.90234 l 0,23.64551"
|
||||
id="path3818" />
|
||||
</g>
|
||||
<path
|
||||
transform="matrix(1.6,0,0,1.6,-233,-3460.4252)"
|
||||
style="fill:#ffffff;fill-opacity:0.98431373;filter:url(#filter3757)"
|
||||
d="m 445,510.49219 c 0,1.38071 -1.11929,2.5 -2.5,2.5 -1.38071,0 -2.5,-1.11929 -2.5,-2.5 0,-1.38071 1.11929,-2.5 2.5,-2.5 1.38071,0 2.5,1.11929 2.5,2.5 z"
|
||||
id="path3735" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 18 KiB |
BIN
soil/web/static/img/logo_soil.png
Normal file
After Width: | Height: | Size: 101 KiB |
1
soil/web/static/img/svg/home.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg height="1792" viewBox="0 0 1792 1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M1472 992v480q0 26-19 45t-45 19h-384v-384h-256v384h-384q-26 0-45-19t-19-45v-480q0-1 .5-3t.5-3l575-474 575 474q1 2 1 6zm223-69l-62 74q-8 9-21 11h-3q-13 0-21-7l-692-577-692 577q-12 8-24 7-13-2-21-11l-62-74q-8-10-7-23.5t11-21.5l719-599q32-26 76-26t76 26l244 204v-195q0-14 9-23t23-9h192q14 0 23 9t9 23v408l219 182q10 8 11 21.5t-7 23.5z"/></svg>
|
After Width: | Height: | Size: 462 B |
1
soil/web/static/img/svg/person.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M448,448c0,0,0-26.4-2.2-40.2c-1.8-10.9-16.9-25.3-81.1-48.9c-63.2-23.2-59.3-11.9-59.3-54.6c0-27.7,14.1-11.6,23.1-64.2 c3.5-20.7,6.3-6.9,13.9-40.1c4-17.4-2.7-18.7-1.9-27c0.8-8.3,1.6-15.7,3.1-32.7C345.4,119.3,325.9,64,256,64 c-69.9,0-89.4,55.3-87.5,76.4c1.5,16.9,2.3,24.4,3.1,32.7c0.8,8.3-5.9,9.6-1.9,27c7.6,33.1,10.4,19.3,13.9,40.1 c9,52.6,23.1,36.5,23.1,64.2c0,42.8,3.9,31.5-59.3,54.6c-64.2,23.5-79.4,38-81.1,48.9C64,421.6,64,448,64,448h192H448z"/></svg>
|
After Width: | Height: | Size: 812 B |