1
0
mirror of https://github.com/gsi-upm/soil synced 2025-10-27 13:48:17 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
J. Fernando Sánchez
bf481f0f88 v0.20.8 fix bugs 2023-03-23 14:49:09 +01:00
136 changed files with 8110 additions and 89845 deletions

View File

@@ -1,5 +1,7 @@
**/soil_output
.*
**/.*
**/__pycache__
__pycache__
*.pyc
**/backup

3
.gitignore vendored
View File

@@ -8,4 +8,5 @@ soil_output
docs/_build*
build/*
dist/*
prof
prof
backup

View File

@@ -3,21 +3,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3 UNRELEASED]
### Added
* Simple debugging capabilities in `soil.debugging`, with a custom `pdb.Debugger` subclass that exposes commands to list agents and their status and set breakpoints on states (for FSM agents). Try it with `soil --debug <simulation file>`
* Ability to run
* Ability to
* The `soil.exporters` module to export the results of datacollectors (model.datacollector) into files at the end of trials/simulations
* A modular set of classes for environments/models. Now the ability to configure the agents through an agent definition and a topology through a network configuration is split into two classes (`soil.agents.BaseEnvironment` for agents, `soil.agents.NetworkEnvironment` to add topology).
* FSM agents can now have generators as states. They work similar to normal states, with one caveat. Only `time` values can be yielded, not a state. This is because the state will not change, it will be resumed after the yield, at the appropriate time. The return value *can* be a state, or a `(state, time)` tuple, just like in normal states.
### Changed
* Configuration schema is very different now. Check `soil.config` for more information. We are also using Pydantic for (de)serialization.
* There may be more than one topology/network in the simulation
* Ability
### Removed
* Any `tsih` and `History` integration in the main classes. To record the state of environments/agents, just use a datacollector. In some cases this may be slower or consume more memory than the previous system. However, few cases actually used the full potential of the history, and it came at the cost of unnecessary complexity and worse performance for the majority of cases.
## [UNRELEASED]
## [0.20.8]
### Changed
* Tsih bumped to version 0.1.8
### Fixed
* Mentions to `id` in docs. It should be `state_id` now.
* Fixed bug: environment agents were not being added to the simulation
## [0.20.7]
### Changed

View File

@@ -5,42 +5,6 @@ Learn how to run your own simulations with our [documentation](http://soilsim.re
Follow our [tutorial](examples/tutorial/soil_tutorial.ipynb) to develop your own agent models.
# Changes in version 0.3
Version 0.3 came packed with many changes to provide much better integration with MESA.
For a long time, we tried to keep soil backwards-compatible, but it turned out to be a big endeavour and the resulting code was less readable.
This translates to harder maintenance and a worse experience for newcomers.
In the end, we decided to make some breaking changes.
If you have an older Soil simulation, you have two options:
* Update the necessary configuration files and code. You may use the examples in the `examples` folder for reference, as well as the documentation.
* Keep using a previous `soil` version.
## Mesa compatibility
Soil is in the process of becoming fully compatible with MESA.
The idea is to provide a set of modular classes and functions that extend the functionality of mesa, whilst staying compatible.
In the end, it should be possible to add regular mesa agents to a soil simulation, or use a soil agent within a mesa simulation/model.
This is a non-exhaustive list of tasks to achieve compatibility:
- [ ] Integrate `soil.Simulation` with mesa's runners:
- [ ] `soil.Simulation` could mimic/become a `mesa.batchrunner`
- [ ] Integrate `soil.Environment` with `mesa.Model`:
- [x] `Soil.Environment` inherits from `mesa.Model`
- [x] `Soil.Environment` includes a Mesa-like Scheduler (see the `soil.time` module.
- [ ] Allow for `mesa.Model` to be used in a simulation.
- [ ] Integrate `soil.Agent` with `mesa.Agent`:
- [x] Rename agent.id to unique_id?
- [x] mesa agents can be used in soil simulations (see `examples/mesa`)
- [ ] Provide examples
- [ ] Using mesa modules in a soil simulation
- [ ] Using soil modules in a mesa simulation
- [ ] Document the new APIs and usage
## Citation
@@ -67,6 +31,24 @@ If you use Soil in your research, don't forget to cite this paper:
```
## Mesa compatibility
Soil is in the process of becoming fully compatible with MESA.
As of this writing,
This is a non-exhaustive list of tasks to achieve compatibility:
* Environments.agents and mesa.Agent.agents are not the same. env is a property, and it only takes into account network and environment agents. Might rename environment_agents to other_agents or sth like that
- [ ] Integrate `soil.Simulation` with mesa's runners:
- [ ] `soil.Simulation` could mimic/become a `mesa.batchrunner`
- [ ] Integrate `soil.Environment` with `mesa.Model`:
- [x] `Soil.Environment` inherits from `mesa.Model`
- [x] `Soil.Environment` includes a Mesa-like Scheduler (see the `soil.time` module.
- [ ] Integrate `soil.Agent` with `mesa.Agent`:
- [x] Rename agent.id to unique_id?
- [x] mesa agents can be used in soil simulations (see `examples/mesa`)
- [ ] Document the new APIs and usage
@Copyright GSI - Universidad Politécnica de Madrid 2017-2021
[![SOIL](logo_gsi.png)](https://www.gsi.upm.es)

View File

@@ -13,7 +13,7 @@ Here's an example (``example.yml``).
This example configuration will run three trials (``num_trials``) of a simulation containing a randomly generated network (``network_params``).
The 100 nodes in the network will be SISaModel agents (``network_agents.agent_class``), which is an agent behavior that is included in Soil.
The 100 nodes in the network will be SISaModel agents (``network_agents.agent_type``), which is an agent behavior that is included in Soil.
10% of the agents (``weight=1``) will start in the content state, 10% in the discontent state, and the remaining 80% (``weight=8``) in the neutral state.
All agents will have access to the environment (``environment_params``), which only contains one variable, ``prob_infected``.
The state of the agents will be updated every 2 seconds (``interval``).
@@ -88,18 +88,9 @@ 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.
That means both global parameters, such as the probability of disease outbreak.
But it also means other data, such as a map, or a network topology that connects multiple agents.
As a result, it is also typical to add custom functions in an environment that help agents interact with each other and with the state of the simulation.
Last but not least, an environment controls when and how its agents will be executed.
By default, soil environments incorporate a ``soil.time.TimedActivation`` model for agent execution (more on this on the following section).
Soil environments are very similar, and often interchangeable with, mesa models (``mesa.Model``).
A configuration may specify the initial value of the environment parameters:
For instance, the probability of disease outbreak.
The configuration file may specify the initial value of the environment parameters:
.. code:: yaml
@@ -107,33 +98,23 @@ A configuration may specify the initial value of the environment parameters:
daily_probability_of_earthquake: 0.001
number_of_earthquakes: 0
All agents have access to the environment (and its parameters).
All agents have access to the environment parameters.
In some scenarios, it is useful to have a custom environment, to provide additional methods or to control the way agents update environment state.
For example, if our agents play the lottery, the environment could provide a method to decide whether the agent wins, instead of leaving it to the agent.
Agents
======
Agents are a way of modelling behavior.
Agents can be characterized with two variables: agent type (``agent_class``) and state.
The agent type is a ``soil.Agent`` class, which contains the code that encapsulates the behavior of the agent.
The state is a set of variables, which may change during the simulation, and that the code may use to control the behavior.
All agents provide a ``step`` method either explicitly or implicitly (by inheriting it from a superclass), which controls how the agent will behave in each step of the simulation.
When and how agent steps are executed in a simulation depends entirely on the ``environment``.
Most environments will internally use a scheduler (``mesa.time.BaseScheduler``), which controls the activation of agents.
In soil, we generally used the ``soil.time.TimedActivation`` scheduler, which allows agents to specify when their next activation will happen, defaulting to a
When an agent's step is executed (generally, every ``interval`` seconds), the agent has access to its state and the environment.
Agents can be characterized with two variables: agent type (``agent_type``) and state.
Only one agent is executed at a time (generally, every ``interval`` seconds), and it has access to its state and the environment parameters.
Through the environment, it can access the network topology and the state of other agents.
There are two types of agents according to how they are added to the simulation: network agents and environment agent.
There are three three types of agents according to how they are added to the simulation: network agents and environment agent.
Network Agents
##############
Network agents are attached to a node in the topology.
The configuration file allows you to specify how agents will be mapped to topology nodes.
@@ -142,19 +123,17 @@ Hence, every node in the network will be associated to an agent of that type.
.. code:: yaml
agent_class: SISaModel
agent_type: SISaModel
It is also possible to add more than one type of agent to the simulation.
To control the ratio of each type (using the ``weight`` property).
It is also possible to add more than one type of agent to the simulation, and to control the ratio of each type (using the ``weight`` property).
For instance, with following configuration, it is five times more likely for a node to be assigned a CounterModel type than a SISaModel type.
.. code:: yaml
network_agents:
- agent_class: SISaModel
- agent_type: SISaModel
weight: 1
- agent_class: CounterModel
- agent_type: CounterModel
weight: 5
The third option is to specify the type of agent on the node itself, e.g.:
@@ -165,10 +144,10 @@ The third option is to specify the type of agent on the node itself, e.g.:
topology:
nodes:
- id: first
agent_class: BaseAgent
agent_type: BaseAgent
states:
first:
agent_class: SISaModel
agent_type: SISaModel
This would also work with a randomly generated network:
@@ -179,9 +158,9 @@ This would also work with a randomly generated network:
network:
generator: complete
n: 5
agent_class: BaseAgent
agent_type: BaseAgent
states:
- agent_class: SISaModel
- agent_type: SISaModel
@@ -192,11 +171,11 @@ e.g., to populate the network with SISaModel, roughly 10% of them with a discont
.. code:: yaml
network_agents:
- agent_class: SISaModel
- agent_type: SISaModel
weight: 9
state:
id: neutral
- agent_class: SISaModel
- agent_type: SISaModel
weight: 1
state:
id: discontent
@@ -206,7 +185,7 @@ For instance, to add a state for the two nodes in this configuration:
.. code:: yaml
agent_class: SISaModel
agent_type: SISaModel
network:
generator: complete_graph
n: 2
@@ -231,10 +210,10 @@ These agents are programmed in much the same way as network agents, the only dif
.. code::
environment_agents:
- agent_class: MyAgent
- agent_type: MyAgent
state:
mood: happy
- agent_class: DummyAgent
- agent_type: DummyAgent
You may use environment agents to model events that a normal agent cannot control, such as natural disasters or chance.

View File

@@ -8,15 +8,15 @@ network_params:
n: 100
m: 2
network_agents:
- agent_class: SISaModel
- agent_type: SISaModel
weight: 1
state:
id: content
- agent_class: SISaModel
- agent_type: SISaModel
weight: 1
state:
id: discontent
- agent_class: SISaModel
- agent_type: SISaModel
weight: 8
state:
id: neutral

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/output_58_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/output_58_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/output_58_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/output_58_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/output_58_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/output_60_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/output_60_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
docs/output_60_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/output_60_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/output_60_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/output_62_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/output_62_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/output_62_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/output_62_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/output_62_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

BIN
docs/output_68_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

BIN
docs/output_70_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/output_77_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/output_81_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/output_82_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/output_83_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -3,11 +3,11 @@ name: quickstart
num_trials: 1
max_time: 1000
network_agents:
- agent_class: SISaModel
- agent_type: SISaModel
state:
id: neutral
weight: 1
- agent_class: SISaModel
- agent_type: SISaModel
state:
id: content
weight: 2

View File

@@ -1 +1 @@
ipython>=7.31.1
ipython==7.31.1

View File

@@ -1,12 +0,0 @@
### MESA
Starting with version 0.3, Soil has been redesigned to complement Mesa, while remaining compatible with it.
That means that every component in Soil (i.e., Models, Environments, etc.) can be mixed with existing mesa components.
In fact, there are examples that show how that integration may be used, in the `examples/mesa` folder in the repository.
Here are some reasons to use Soil instead of plain mesa:
- Less boilerplate for common scenarios (by some definitions of common)
- Functions to automatically populate a topology with an agent distribution (i.e., different ratios of agent class and state)
- The `soil.Simulation` class allows you to run multiple instances of the same experiment (i.e., multiple trials with the same parameters but a different randomness seed)
- Reporting functions that aggregate multiple

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,27 @@
---
version: '2'
name: simple
group: tests
dir_path: "/tmp/"
num_trials: 3
max_steps: 100
max_time: 100
interval: 1
seed: "CompleteSeed!"
model_class: Environment
model_params:
am_i_complete: true
topology:
params:
generator: complete_graph
n: 12
environment:
agents:
agent_class: CounterModel
topology: true
network_params:
generator: complete_graph
n: 10
network_agents:
- agent_type: CounterModel
weight: 1
state:
times: 1
# In this group we are not specifying any topology
fixed:
- name: 'Environment Agent 1'
agent_class: BaseAgent
group: environment
topology: false
hidden: true
state:
times: 10
- agent_class: CounterModel
id: 0
group: fixed_counters
state:
times: 1
total: 0
- agent_class: CounterModel
group: fixed_counters
id: 1
distribution:
- agent_class: CounterModel
weight: 1
group: distro_counters
state:
times: 3
- agent_class: AggregatedCounter
weight: 0.2
override:
- filter:
agent_class: AggregatedCounter
n: 2
state:
times: 5
state_id: 0
- agent_type: AggregatedCounter
weight: 0.2
environment_agents: []
environment_class: Environment
environment_params:
am_i_complete: true
default_state:
incidents: 0
states:
- name: 'The first node'
- name: 'The second node'

View File

@@ -2,7 +2,7 @@
name: custom-generator
description: Using a custom generator for the network
num_trials: 3
max_steps: 100
max_time: 100
interval: 1
network_params:
generator: mymodule.mygenerator
@@ -10,7 +10,7 @@ network_params:
n: 10
n_edges: 5
network_agents:
- agent_class: CounterModel
- agent_type: CounterModel
weight: 1
state:
state_id: 0

View File

@@ -1,6 +1,6 @@
from networkx import Graph
import random
import networkx as nx
from random import choice
def mygenerator(n=5, n_edges=5):
'''
@@ -14,9 +14,9 @@ def mygenerator(n=5, n_edges=5):
for i in range(n_edges):
nodes = list(G.nodes)
n_in = random.choice(nodes)
n_in = choice(nodes)
nodes.remove(n_in) # Avoid loops
n_out = random.choice(nodes)
n_out = choice(nodes)
G.add_edge(n_in, n_out)
return G
@@ -24,4 +24,4 @@ def mygenerator(n=5, n_edges=5):

View File

@@ -15,7 +15,6 @@ class Fibonacci(FSM):
prev, self['prev'] = self['prev'], max([self.now, self['prev']])
return None, self.env.timeout(prev)
class Odds(FSM):
'''Agent that only executes in odd t_steps'''
@default_state
@@ -24,11 +23,12 @@ class Odds(FSM):
self.log('Stopping at {}'.format(self.now))
return None, self.env.timeout(1+self.now%2)
if __name__ == '__main__':
import logging
logging.basicConfig(level=logging.INFO)
from soil import Simulation
s = Simulation(network_agents=[{'ids': [0], 'agent_class': Fibonacci},
{'ids': [1], 'agent_class': Odds}],
s = Simulation(network_agents=[{'ids': [0], 'agent_type': Fibonacci},
{'ids': [1], 'agent_type': Odds}],
network_params={"generator": "complete_graph", "n": 2},
max_time=100,
)

View File

@@ -3,17 +3,19 @@ name: mesa_sim
group: tests
dir_path: "/tmp"
num_trials: 3
max_steps: 100
max_time: 100
interval: 1
seed: '1'
model_class: social_wealth.MoneyEnv
model_params:
network_params:
generator: social_wealth.graph_generator
agents:
topology: true
distribution:
- agent_class: social_wealth.SocialMoneyAgent
weight: 1
n: 5
network_agents:
- agent_type: social_wealth.SocialMoneyAgent
weight: 1
environment_class: social_wealth.MoneyEnv
environment_params:
num_mesa_agents: 5
mesa_agent_type: social_wealth.MoneyAgent
N: 10
width: 50
height: 50

View File

@@ -2,7 +2,6 @@ from mesa.visualization.ModularVisualization import ModularServer
from soil.visualization import UserSettableParameter
from mesa.visualization.modules import ChartModule, NetworkModule, CanvasGrid
from social_wealth import MoneyEnv, graph_generator, SocialMoneyAgent
import networkx as nx
class MyNetwork(NetworkModule):
@@ -14,16 +13,15 @@ def network_portrayal(env):
# The model ensures there is 0 or 1 agent per node
portrayal = dict()
wealths = {node_id: data['agent'].wealth for (node_id, data) in env.G.nodes(data=True)}
portrayal["nodes"] = [
{
"id": node_id,
"size": 2*(wealth+1),
"color": "#CC0000" if wealth == 0 else "#007959",
# "color": "#CC0000",
"label": f"{node_id}: {wealth}",
} for (node_id, wealth) in wealths.items()
"id": agent_id,
"size": env.get_agent(agent_id).wealth,
# "color": "#CC0000" if not agents or agents[0].wealth == 0 else "#007959",
"color": "#CC0000",
"label": f"{agent_id}: {env.get_agent(agent_id).wealth}",
}
for (agent_id) in env.G.nodes
]
portrayal["edges"] = [
@@ -31,6 +29,7 @@ def network_portrayal(env):
for edge_id, (source, target) in enumerate(env.G.edges)
]
return portrayal
@@ -56,7 +55,7 @@ def gridPortrayal(agent):
}
grid = MyNetwork(network_portrayal, 500, 500)
grid = MyNetwork(network_portrayal, 500, 500, library="sigma")
chart = ChartModule(
[{"Label": "Gini", "Color": "Black"}], data_collector_name="datacollector"
)
@@ -71,6 +70,7 @@ model_params = {
1,
description="Choose how many agents to include in the model",
),
"network_agents": [{"agent_type": SocialMoneyAgent}],
"height": UserSettableParameter(
"slider",
"height",
@@ -89,15 +89,12 @@ model_params = {
1,
description="Grid width",
),
"agent_class": UserSettableParameter('choice', 'Agent class', value='MoneyAgent',
choices=['MoneyAgent', 'SocialMoneyAgent']),
"generator": graph_generator,
"network_params": {
'generator': graph_generator
},
}
canvas_element = CanvasGrid(gridPortrayal,
model_params["width"].value,
model_params["height"].value, 500, 500)
canvas_element = CanvasGrid(gridPortrayal, model_params["width"].value, model_params["height"].value, 500, 500)
server = ModularServer(

View File

@@ -10,7 +10,7 @@ from mesa.batchrunner import BatchRunner
import networkx as nx
from soil import NetworkAgent, Environment, serialization
from soil import NetworkAgent, Environment
def compute_gini(model):
agent_wealths = [agent.wealth for agent in model.agents]
@@ -19,16 +19,15 @@ def compute_gini(model):
B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x))
return (1 + (1/N) - 2*B)
class MoneyAgent(MesaAgent):
"""
A MESA agent with fixed initial wealth.
It will only share wealth with neighbors based on grid proximity
"""
def __init__(self, unique_id, model, wealth=1):
def __init__(self, unique_id, model):
super().__init__(unique_id=unique_id, model=model)
self.wealth = wealth
self.wealth = 1
def move(self):
possible_steps = self.model.grid.get_neighborhood(
@@ -46,7 +45,7 @@ class MoneyAgent(MesaAgent):
self.wealth -= 1
def step(self):
print("Crying wolf", self.pos)
self.info("Crying wolf", self.pos)
self.move()
if self.wealth > 0:
self.give_money()
@@ -59,8 +58,8 @@ class SocialMoneyAgent(NetworkAgent, MoneyAgent):
cellmates = set(self.model.grid.get_cell_list_contents([self.pos]))
friends = set(self.get_neighboring_agents())
self.info("Trying to give money")
self.info("Cellmates: ", cellmates)
self.info("Friends: ", friends)
self.debug("Cellmates: ", cellmates)
self.debug("Friends: ", friends)
nearby_friends = list(cellmates & friends)
@@ -69,29 +68,15 @@ class SocialMoneyAgent(NetworkAgent, MoneyAgent):
other.wealth += 1
self.wealth -= 1
def graph_generator(n=5):
G = nx.Graph()
for ix in range(n):
G.add_edge(0, ix)
return G
class MoneyEnv(Environment):
"""A model with some number of agents."""
def __init__(self, width, height, N, generator=graph_generator,
agent_class=SocialMoneyAgent,
topology=None, **kwargs):
def __init__(self, N, width, height, *args, network_params, **kwargs):
generator = serialization.deserialize(generator)
agent_class = serialization.deserialize(agent_class, globs=globals())
topology = generator(n=N)
super().__init__(topology=topology,
N=N,
**kwargs)
network_params['n'] = N
super().__init__(*args, network_params=network_params, **kwargs)
self.grid = MultiGrid(width, height, False)
self.populate_network(agent_class=agent_class)
# Create agents
for agent in self.agents:
x = self.random.randrange(self.grid.width)
@@ -103,11 +88,19 @@ class MoneyEnv(Environment):
agent_reporters={"Wealth": "wealth"})
def graph_generator(n=5):
G = nx.Graph()
for ix in range(n):
G.add_edge(0, ix)
return G
if __name__ == '__main__':
fixed_params = {"generator": nx.complete_graph,
G = graph_generator()
fixed_params = {"topology": G,
"width": 10,
"network_agents": [{"agent_class": SocialMoneyAgent,
"network_agents": [{"agent_type": SocialMoneyAgent,
'weight': 1}],
"height": 10}
@@ -124,3 +117,4 @@ if __name__ == '__main__':
run_data = batch_run.get_model_vars_dataframe()
run_data.head()
print(run_data.Gini)

View File

@@ -2,13 +2,12 @@
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2017-11-08T16:22:30.732107Z",
"start_time": "2017-11-08T17:22:30.059855+01:00"
},
"collapsed": true
}
},
"outputs": [],
"source": [
@@ -28,24 +27,16 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2017-11-08T16:22:35.580593Z",
"start_time": "2017-11-08T17:22:35.542745+01:00"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"outputs": [],
"source": [
"%pylab inline\n",
"%matplotlib inline\n",
"\n",
"from soil import *"
]
@@ -66,7 +57,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2017-11-08T16:22:37.242327Z",
@@ -86,14 +77,14 @@
" prob_neighbor_spread: 0.0\r\n",
" prob_tv_spread: 0.01\r\n",
"interval: 1\r\n",
"max_time: 30\r\n",
"max_time: 300\r\n",
"name: Sim_all_dumb\r\n",
"network_agents:\r\n",
"- agent_class: DumbViewer\r\n",
"- agent_type: DumbViewer\r\n",
" state:\r\n",
" has_tv: false\r\n",
" weight: 1\r\n",
"- agent_class: DumbViewer\r\n",
"- agent_type: DumbViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" weight: 1\r\n",
@@ -110,22 +101,22 @@
" prob_neighbor_spread: 0.0\r\n",
" prob_tv_spread: 0.01\r\n",
"interval: 1\r\n",
"max_time: 30\r\n",
"max_time: 300\r\n",
"name: Sim_half_herd\r\n",
"network_agents:\r\n",
"- agent_class: DumbViewer\r\n",
"- agent_type: DumbViewer\r\n",
" state:\r\n",
" has_tv: false\r\n",
" weight: 1\r\n",
"- agent_class: DumbViewer\r\n",
"- agent_type: DumbViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" weight: 1\r\n",
"- agent_class: HerdViewer\r\n",
"- agent_type: HerdViewer\r\n",
" state:\r\n",
" has_tv: false\r\n",
" weight: 1\r\n",
"- agent_class: HerdViewer\r\n",
"- agent_type: HerdViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" weight: 1\r\n",
@@ -142,18 +133,18 @@
" prob_neighbor_spread: 0.0\r\n",
" prob_tv_spread: 0.01\r\n",
"interval: 1\r\n",
"max_time: 30\r\n",
"max_time: 300\r\n",
"name: Sim_all_herd\r\n",
"network_agents:\r\n",
"- agent_class: HerdViewer\r\n",
"- agent_type: HerdViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" id: neutral\r\n",
" state_id: neutral\r\n",
" weight: 1\r\n",
"- agent_class: HerdViewer\r\n",
"- agent_type: HerdViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" id: neutral\r\n",
" state_id: neutral\r\n",
" weight: 1\r\n",
"network_params:\r\n",
" generator: barabasi_albert_graph\r\n",
@@ -169,15 +160,15 @@
" prob_tv_spread: 0.01\r\n",
" prob_neighbor_cure: 0.1\r\n",
"interval: 1\r\n",
"max_time: 30\r\n",
"max_time: 300\r\n",
"name: Sim_wise_herd\r\n",
"network_agents:\r\n",
"- agent_class: HerdViewer\r\n",
"- agent_type: HerdViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" id: neutral\r\n",
" state_id: neutral\r\n",
" weight: 1\r\n",
"- agent_class: WiseViewer\r\n",
"- agent_type: WiseViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" weight: 1\r\n",
@@ -195,15 +186,15 @@
" prob_tv_spread: 0.01\r\n",
" prob_neighbor_cure: 0.1\r\n",
"interval: 1\r\n",
"max_time: 30\r\n",
"max_time: 300\r\n",
"name: Sim_all_wise\r\n",
"network_agents:\r\n",
"- agent_class: WiseViewer\r\n",
"- agent_type: WiseViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" id: neutral\r\n",
" state_id: neutral\r\n",
" weight: 1\r\n",
"- agent_class: WiseViewer\r\n",
"- agent_type: WiseViewer\r\n",
" state:\r\n",
" has_tv: true\r\n",
" weight: 1\r\n",
@@ -225,7 +216,7 @@
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2017-11-08T18:07:46.781745Z",
@@ -233,7 +224,24 @@
},
"scrolled": true
},
"outputs": [],
"outputs": [
{
"ename": "ValueError",
"evalue": "No objects to concatenate",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m evodumb \u001b[38;5;241m=\u001b[39m \u001b[43manalysis\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_data\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43msoil_output/Sim_all_dumb/\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprocess\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43manalysis\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_count\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgroup\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkeys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mid\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m;\n",
"File \u001b[0;32m/mnt/data/home/j/git/lab.gsi/soil/soil/soil/analysis.py:14\u001b[0m, in \u001b[0;36mread_data\u001b[0;34m(group, *args, **kwargs)\u001b[0m\n\u001b[1;32m 12\u001b[0m iterable \u001b[38;5;241m=\u001b[39m _read_data(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m group:\n\u001b[0;32m---> 14\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mgroup_trials\u001b[49m\u001b[43m(\u001b[49m\u001b[43miterable\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mlist\u001b[39m(iterable)\n",
"File \u001b[0;32m/mnt/data/home/j/git/lab.gsi/soil/soil/soil/analysis.py:201\u001b[0m, in \u001b[0;36mgroup_trials\u001b[0;34m(trials, aggfunc)\u001b[0m\n\u001b[1;32m 199\u001b[0m trials \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(trials)\n\u001b[1;32m 200\u001b[0m trials \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mmap\u001b[39m(\u001b[38;5;28;01mlambda\u001b[39;00m x: x[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mtuple\u001b[39m) \u001b[38;5;28;01melse\u001b[39;00m x, trials))\n\u001b[0;32m--> 201\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mpd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconcat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrials\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241m.\u001b[39mgroupby(level\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0\u001b[39m)\u001b[38;5;241m.\u001b[39magg(aggfunc)\u001b[38;5;241m.\u001b[39mreorder_levels([\u001b[38;5;241m2\u001b[39m, \u001b[38;5;241m0\u001b[39m,\u001b[38;5;241m1\u001b[39m] ,axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n",
"File \u001b[0;32m/mnt/data/home/j/git/lab.gsi/soil/soil/.env-v0.20/lib/python3.8/site-packages/pandas/util/_decorators.py:331\u001b[0m, in \u001b[0;36mdeprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(args) \u001b[38;5;241m>\u001b[39m num_allow_args:\n\u001b[1;32m 326\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 327\u001b[0m msg\u001b[38;5;241m.\u001b[39mformat(arguments\u001b[38;5;241m=\u001b[39m_format_argument_list(allow_args)),\n\u001b[1;32m 328\u001b[0m \u001b[38;5;167;01mFutureWarning\u001b[39;00m,\n\u001b[1;32m 329\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mfind_stack_level(),\n\u001b[1;32m 330\u001b[0m )\n\u001b[0;32m--> 331\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/mnt/data/home/j/git/lab.gsi/soil/soil/.env-v0.20/lib/python3.8/site-packages/pandas/core/reshape/concat.py:368\u001b[0m, in \u001b[0;36mconcat\u001b[0;34m(objs, axis, join, ignore_index, keys, levels, names, verify_integrity, sort, copy)\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[38;5;129m@deprecate_nonkeyword_arguments\u001b[39m(version\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, allowed_args\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mobjs\u001b[39m\u001b[38;5;124m\"\u001b[39m])\n\u001b[1;32m 147\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mconcat\u001b[39m(\n\u001b[1;32m 148\u001b[0m objs: Iterable[NDFrame] \u001b[38;5;241m|\u001b[39m Mapping[HashableT, NDFrame],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 157\u001b[0m copy: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[1;32m 158\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m DataFrame \u001b[38;5;241m|\u001b[39m Series:\n\u001b[1;32m 159\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 160\u001b[0m \u001b[38;5;124;03m Concatenate pandas objects along a particular axis.\u001b[39;00m\n\u001b[1;32m 161\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 366\u001b[0m \u001b[38;5;124;03m 1 3 4\u001b[39;00m\n\u001b[1;32m 367\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 368\u001b[0m op \u001b[38;5;241m=\u001b[39m \u001b[43m_Concatenator\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 369\u001b[0m \u001b[43m \u001b[49m\u001b[43mobjs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 370\u001b[0m \u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 371\u001b[0m \u001b[43m \u001b[49m\u001b[43mignore_index\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mignore_index\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 372\u001b[0m \u001b[43m \u001b[49m\u001b[43mjoin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mjoin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 373\u001b[0m \u001b[43m \u001b[49m\u001b[43mkeys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mkeys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 374\u001b[0m \u001b[43m \u001b[49m\u001b[43mlevels\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlevels\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 375\u001b[0m \u001b[43m \u001b[49m\u001b[43mnames\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnames\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 376\u001b[0m \u001b[43m \u001b[49m\u001b[43mverify_integrity\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mverify_integrity\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 377\u001b[0m \u001b[43m \u001b[49m\u001b[43mcopy\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcopy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 378\u001b[0m \u001b[43m \u001b[49m\u001b[43msort\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msort\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 379\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 381\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m op\u001b[38;5;241m.\u001b[39mget_result()\n",
"File \u001b[0;32m/mnt/data/home/j/git/lab.gsi/soil/soil/.env-v0.20/lib/python3.8/site-packages/pandas/core/reshape/concat.py:425\u001b[0m, in \u001b[0;36m_Concatenator.__init__\u001b[0;34m(self, objs, axis, join, keys, levels, names, ignore_index, verify_integrity, copy, sort)\u001b[0m\n\u001b[1;32m 422\u001b[0m objs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(objs)\n\u001b[1;32m 424\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(objs) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m--> 425\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo objects to concatenate\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 427\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m keys \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 428\u001b[0m objs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(com\u001b[38;5;241m.\u001b[39mnot_none(\u001b[38;5;241m*\u001b[39mobjs))\n",
"\u001b[0;31mValueError\u001b[0m: No objects to concatenate"
]
}
],
"source": [
"evodumb = analysis.read_data('soil_output/Sim_all_dumb/', process=analysis.get_count, group=True, keys=['id']);"
]
@@ -721,9 +729,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "venv-soil",
"language": "python",
"name": "python3"
"name": "venv-soil"
},
"language_info": {
"codemirror_mode": {
@@ -735,7 +743,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
"version": "3.8.10"
},
"toc": {
"colors": {

View File

@@ -1,18 +1,19 @@
---
default_state: {}
load_module: newsspread
environment_agents: []
environment_params:
prob_neighbor_spread: 0.0
prob_tv_spread: 0.01
interval: 1
max_steps: 300
max_time: 300
name: Sim_all_dumb
network_agents:
- agent_class: newsspread.DumbViewer
- agent_type: DumbViewer
state:
has_tv: false
weight: 1
- agent_class: newsspread.DumbViewer
- agent_type: DumbViewer
state:
has_tv: true
weight: 1
@@ -23,27 +24,28 @@ network_params:
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_steps: 300
max_time: 300
name: Sim_half_herd
network_agents:
- agent_class: newsspread.DumbViewer
- agent_type: DumbViewer
state:
has_tv: false
weight: 1
- agent_class: newsspread.DumbViewer
- agent_type: DumbViewer
state:
has_tv: true
weight: 1
- agent_class: newsspread.HerdViewer
- agent_type: HerdViewer
state:
has_tv: false
weight: 1
- agent_class: newsspread.HerdViewer
- agent_type: HerdViewer
state:
has_tv: true
weight: 1
@@ -54,20 +56,21 @@ network_params:
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_steps: 300
max_time: 300
name: Sim_all_herd
network_agents:
- agent_class: newsspread.HerdViewer
- agent_type: HerdViewer
state:
has_tv: true
state_id: neutral
weight: 1
- agent_class: newsspread.HerdViewer
- agent_type: HerdViewer
state:
has_tv: true
state_id: neutral
@@ -79,21 +82,22 @@ network_params:
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_steps: 300
max_time: 300
name: Sim_wise_herd
network_agents:
- agent_class: newsspread.HerdViewer
- agent_type: HerdViewer
state:
has_tv: true
state_id: neutral
weight: 1
- agent_class: newsspread.WiseViewer
- agent_type: WiseViewer
state:
has_tv: true
weight: 1
@@ -104,21 +108,22 @@ network_params:
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_steps: 300
max_time: 300
name: Sim_all_wise
network_agents:
- agent_class: newsspread.WiseViewer
- agent_type: WiseViewer
state:
has_tv: true
state_id: neutral
weight: 1
- agent_class: newsspread.WiseViewer
- agent_type: WiseViewer
state:
has_tv: true
weight: 1

View File

@@ -1,8 +1,8 @@
from soil.agents import FSM, NetworkAgent, state, default_state, prob
from soil.agents import FSM, state, default_state, prob
import logging
class DumbViewer(FSM, NetworkAgent):
class DumbViewer(FSM):
'''
A viewer that gets infected via TV (if it has one) and tries to infect
its neighbors once it's infected.
@@ -16,13 +16,13 @@ class DumbViewer(FSM, NetworkAgent):
@state
def neutral(self):
if self['has_tv']:
if self.prob(self.model['prob_tv_spread']):
if prob(self.env['prob_tv_spread']):
return self.infected
@state
def infected(self):
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
if self.prob(self.model['prob_neighbor_spread']):
if prob(self.env['prob_neighbor_spread']):
neighbor.infect()
def infect(self):
@@ -44,9 +44,9 @@ class HerdViewer(DumbViewer):
'''Notice again that this is NOT a state. See DumbViewer.infect for reference'''
infected = self.count_neighboring_agents(state_id=self.infected.id)
total = self.count_neighboring_agents()
prob_infect = self.model['prob_neighbor_spread'] * infected/total
prob_infect = self.env['prob_neighbor_spread'] * infected/total
self.debug('prob_infect', prob_infect)
if self.prob(prob_infect):
if prob(prob_infect):
self.set_state(self.infected)
@@ -63,9 +63,9 @@ class WiseViewer(HerdViewer):
@state
def cured(self):
prob_cure = self.model['prob_neighbor_cure']
prob_cure = self.env['prob_neighbor_cure']
for neighbor in self.get_neighboring_agents(state_id=self.infected.id):
if self.prob(prob_cure):
if prob(prob_cure):
try:
neighbor.cure()
except AttributeError:
@@ -80,7 +80,7 @@ class WiseViewer(HerdViewer):
1.0)
infected = max(self.count_neighboring_agents(self.infected.id),
1.0)
prob_cure = self.model['prob_neighbor_cure'] * (cured/infected)
if self.prob(prob_cure):
prob_cure = self.env['prob_neighbor_cure'] * (cured/infected)
if prob(prob_cure):
return self.cured
return self.set_state(super().infected)

View File

@@ -27,7 +27,7 @@ s = Simulation(name='Programmatic',
network_params={'generator': mygenerator},
num_trials=1,
max_time=100,
agent_class=MyAgent,
agent_type=MyAgent,
dry_run=True)

View File

@@ -1,5 +1,6 @@
from soil.agents import FSM, NetworkAgent, state, default_state
from soil.agents import FSM, state, default_state
from soil import Environment
from random import random, shuffle
from itertools import islice
import logging
@@ -52,7 +53,7 @@ class CityPubs(Environment):
pub['occupancy'] -= 1
class Patron(FSM, NetworkAgent):
class Patron(FSM):
'''Agent that looks for friends to drink with. It will do three things:
1) Look for other patrons to drink with
2) Look for a bar where the agent and other agents in the same group can get in.
@@ -60,10 +61,12 @@ class Patron(FSM, NetworkAgent):
'''
level = logging.DEBUG
pub = None
drunk = False
pints = 0
max_pints = 3
defaults = {
'pub': None,
'drunk': False,
'pints': 0,
'max_pints': 3,
}
@default_state
@state
@@ -87,9 +90,9 @@ class Patron(FSM, NetworkAgent):
return self.sober_in_pub
self.debug('I am looking for a pub')
group = list(self.get_neighboring_agents())
for pub in self.model.available_pubs():
for pub in self.env.available_pubs():
self.debug('We\'re trying to get into {}: total: {}'.format(pub, len(group)))
if self.model.enter(pub, self, *group):
if self.env.enter(pub, self, *group):
self.info('We\'re all {} getting in {}!'.format(len(group), pub))
return self.sober_in_pub
@@ -125,8 +128,8 @@ class Patron(FSM, NetworkAgent):
Try to become friends with another agent. The chances of
success depend on both agents' openness.
'''
if force or self['openness'] > self.random.random():
self.add_edge(self, other_agent)
if force or self['openness'] > random():
self.env.add_edge(self, other_agent)
self.info('Made some friend {}'.format(other_agent))
return True
return False
@@ -135,7 +138,7 @@ class Patron(FSM, NetworkAgent):
''' Look for random agents around me and try to befriend them'''
befriended = False
k = int(10*self['openness'])
self.random.shuffle(others)
shuffle(others)
for friend in islice(others, k): # random.choice >= 3.7
if friend == self:
continue

View File

@@ -1,25 +1,25 @@
---
name: pubcrawl
num_trials: 3
max_steps: 10
max_time: 10
dump: false
network_params:
# Generate 100 empty nodes. They will be assigned a network agent
generator: empty_graph
n: 30
network_agents:
- agent_class: pubcrawl.Patron
- agent_type: pubcrawl.Patron
description: Extroverted patron
state:
openness: 1.0
weight: 9
- agent_class: pubcrawl.Patron
- agent_type: pubcrawl.Patron
description: Introverted patron
state:
openness: 0.1
weight: 1
environment_agents:
- agent_class: pubcrawl.Police
- agent_type: pubcrawl.Police
environment_class: pubcrawl.CityPubs
environment_params:
altercations: 0

View File

@@ -1,4 +0,0 @@
There are two similar implementations of this simulation.
- `basic`. Using simple primites
- `improved`. Using more advanced features such as the `time` module to avoid unnecessary computations (i.e., skip steps), and generator functions.

View File

@@ -1,150 +0,0 @@
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment
from soil.time import Delta
from enum import Enum
from collections import Counter
import logging
import math
class RabbitEnv(Environment):
@property
def num_rabbits(self):
return self.count_agents(agent_class=Rabbit)
@property
def num_males(self):
return self.count_agents(agent_class=Male)
@property
def num_females(self):
return self.count_agents(agent_class=Female)
class Rabbit(FSM, NetworkAgent):
sexual_maturity = 30
life_expectancy = 300
@default_state
@state
def newborn(self):
self.info('I am a newborn.')
self.age = 0
self.offspring = 0
return self.youngling
@state
def youngling(self):
self.age += 1
if self.age >= self.sexual_maturity:
self.info(f'I am fertile! My age is {self.age}')
return self.fertile
@state
def fertile(self):
raise Exception("Each subclass should define its fertile state")
@state
def dead(self):
self.die()
class Male(Rabbit):
max_females = 5
mating_prob = 0.001
@state
def fertile(self):
self.age += 1
if self.age > self.life_expectancy:
return self.dead
# Males try to mate
for f in self.model.agents(agent_class=Female,
state_id=Female.fertile.id,
limit=self.max_females):
self.debug('FOUND A FEMALE: ', repr(f), self.mating_prob)
if self.prob(self['mating_prob']):
f.impregnate(self)
break # Take a break
class Female(Rabbit):
gestation = 30
@state
def fertile(self):
# Just wait for a Male
self.age += 1
if self.age > self.life_expectancy:
return self.dead
def impregnate(self, male):
self.info(f'{repr(male)} impregnating female {repr(self)}')
self.mate = male
self.pregnancy = -1
self.set_state(self.pregnant, when=self.now)
self.number_of_babies = int(8+4*self.random.random())
@state
def pregnant(self):
self.debug('I am pregnant')
self.age += 1
self.pregnancy += 1
if self.prob(self.age / self.life_expectancy):
return self.die()
if self.pregnancy >= self.gestation:
self.info('Having {} babies'.format(self.number_of_babies))
for i in range(self.number_of_babies):
state = {}
agent_class = self.random.choice([Male, Female])
child = self.model.add_node(agent_class=agent_class,
**state)
child.add_edge(self)
try:
child.add_edge(self.mate)
self.model.agents[self.mate].offspring += 1
except ValueError:
self.debug('The father has passed away')
self.offspring += 1
self.mate = None
return self.fertile
@state
def dead(self):
super().dead()
if 'pregnancy' in self and self['pregnancy'] > -1:
self.info('A mother has died carrying a baby!!')
class RandomAccident(BaseAgent):
level = logging.INFO
def step(self):
rabbits_alive = self.model.G.number_of_nodes()
if not rabbits_alive:
return self.die()
prob_death = self.model.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.iter_agents(agent_class=Rabbit):
if i.state_id == i.dead.id:
continue
if self.prob(prob_death):
self.info('I killed a rabbit: {}'.format(i.id))
rabbits_alive -= 1
i.set_state(i.dead)
self.debug('Rabbits alive: {}'.format(rabbits_alive))
if __name__ == '__main__':
from soil import easy
sim = easy('rabbits.yml')
sim.run()

View File

@@ -1,42 +0,0 @@
---
version: '2'
name: rabbits_basic
num_trials: 1
seed: MySeed
description: null
group: null
interval: 1.0
max_time: 100
model_class: rabbit_agents.RabbitEnv
model_params:
agents:
topology: true
distribution:
- agent_class: rabbit_agents.Male
weight: 1
- agent_class: rabbit_agents.Female
weight: 1
fixed:
- agent_class: rabbit_agents.RandomAccident
topology: false
hidden: true
state:
group: environment
state:
group: network
mating_prob: 0.1
prob_death: 0.001
topology:
fixed:
directed: true
links: []
nodes:
- id: 1
- id: 0
model_reporters:
num_males: 'num_males'
num_females: 'num_females'
num_rabbits: |
py:lambda env: env.num_males + env.num_females
extra:
visualization_params: {}

View File

@@ -1,130 +0,0 @@
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
from soil.time import Delta, When, NEVER
from enum import Enum
import logging
import math
class RabbitModel(FSM, NetworkAgent):
mating_prob = 0.005
offspring = 0
birth = None
sexual_maturity = 3
life_expectancy = 30
@default_state
@state
def newborn(self):
self.birth = self.now
self.info(f'I am a newborn.')
self.model['rabbits_alive'] = self.model.get('rabbits_alive', 0) + 1
# Here we can skip the `youngling` state by using a coroutine/generator.
while self.age < self.sexual_maturity:
interval = self.sexual_maturity - self.age
yield Delta(interval)
self.info(f'I am fertile! My age is {self.age}')
return self.fertile
@property
def age(self):
return self.now - self.birth
@state
def fertile(self):
raise Exception("Each subclass should define its fertile state")
def step(self):
super().step()
if self.prob(self.age / self.life_expectancy):
return self.die()
class Male(RabbitModel):
max_females = 5
@state
def fertile(self):
# Males try to mate
for f in self.model.agents(agent_class=Female,
state_id=Female.fertile.id,
limit=self.max_females):
self.debug('Found a female:', repr(f))
if self.prob(self['mating_prob']):
f.impregnate(self)
break # Take a break, don't try to impregnate the rest
class Female(RabbitModel):
due_date = None
age_of_pregnancy = None
gestation = 10
mate = None
@state
def fertile(self):
return self.fertile, NEVER
@state
def pregnant(self):
self.info('I am pregnant')
if self.age > self.life_expectancy:
return self.dead
self.due_date = self.now + self.gestation
number_of_babies = int(8+4*self.random.random())
while self.now < self.due_date:
yield When(self.due_date)
self.info('Having {} babies'.format(number_of_babies))
for i in range(number_of_babies):
agent_class = self.random.choice([Male, Female])
child = self.model.add_node(agent_class=agent_class,
topology=self.topology)
self.model.add_edge(self, child)
self.model.add_edge(self.mate, child)
self.offspring += 1
self.model.agents[self.mate].offspring += 1
self.mate = None
self.due_date = None
return self.fertile
@state
def dead(self):
super().dead()
if self.due_date is not None:
self.info('A mother has died carrying a baby!!')
def impregnate(self, male):
self.info(f'{repr(male)} impregnating female {repr(self)}')
self.mate = male
self.set_state(self.pregnant, when=self.now)
class RandomAccident(BaseAgent):
level = logging.INFO
def step(self):
rabbits_total = self.model.topology.number_of_nodes()
if 'rabbits_alive' not in self.model:
self.model['rabbits_alive'] = 0
rabbits_alive = self.model.get('rabbits_alive', rabbits_total)
prob_death = self.model.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.model.network_agents:
if i.state.id == i.dead.id:
continue
if self.prob(prob_death):
self.info('I killed a rabbit: {}'.format(i.id))
rabbits_alive = self.model['rabbits_alive'] = rabbits_alive -1
i.set_state(i.dead)
self.debug('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
if self.model.count_agents(state_id=RabbitModel.dead.id) == self.model.topology.number_of_nodes():
self.die()

View File

@@ -1,38 +0,0 @@
---
version: '2'
name: rabbits_improved
num_trials: 1
seed: MySeed
description: null
group: null
interval: 1.0
max_time: 100
model_class: soil.environment.Environment
model_params:
agents:
topology: true
agent_class: rabbit_agents.RabbitModel
distribution:
- agent_class: rabbit_agents.Male
weight: 1
- agent_class: rabbit_agents.Female
weight: 1
fixed:
- agent_class: rabbit_agents.RandomAccident
topology: false
hidden: true
state:
group: environment
state:
group: network
mating_prob: 0.1
prob_death: 0.001
topology:
fixed:
directed: true
links: []
nodes:
- id: 1
- id: 0
extra:
visualization_params: {}

View File

@@ -0,0 +1,135 @@
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
from enum import Enum
from random import random, choice
import logging
import math
class Genders(Enum):
male = 'male'
female = 'female'
class RabbitModel(FSM):
defaults = {
'age': 0,
'gender': Genders.male.value,
'mating_prob': 0.001,
'offspring': 0,
}
sexual_maturity = 3 #4*30
life_expectancy = 365 * 3
gestation = 33
pregnancy = -1
max_females = 5
@default_state
@state
def newborn(self):
self.debug(f'I am a newborn at age {self["age"]}')
self['age'] += 1
if self['age'] >= self.sexual_maturity:
self.debug('I am fertile!')
return self.fertile
@state
def fertile(self):
raise Exception("Each subclass should define its fertile state")
@state
def dead(self):
self.info('Agent {} is dying'.format(self.id))
self.die()
class Male(RabbitModel):
@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
for f in self.get_agents(state_id=Female.fertile.id,
agent_type=Female,
limit_neighbors=False,
limit=self.max_females):
r = random()
if r < self['mating_prob']:
self.impregnate(f)
break # Take a break
def impregnate(self, whom):
whom['pregnancy'] = 0
whom['mate'] = self.id
whom.set_state(whom.pregnant)
self.debug('{} impregnating: {}. {}'.format(self.id, whom.id, whom.state))
class Female(RabbitModel):
@state
def fertile(self):
# Just wait for a Male
pass
@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.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):
super().dead()
if 'pregnancy' in self and self['pregnancy'] > -1:
self.info('A mother has died carrying a baby!!')
class RandomAccident(NetworkAgent):
level = logging.DEBUG
def step(self):
rabbits_total = self.topology.number_of_nodes()
if 'rabbits_alive' not in self.env:
self.env['rabbits_alive'] = 0
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.topology.number_of_nodes():
self.die()

View File

@@ -0,0 +1,21 @@
---
load_module: rabbit_agents
name: rabbits_example
max_time: 1000
interval: 1
seed: MySeed
agent_type: rabbit_agents.RabbitModel
environment_agents:
- agent_type: rabbit_agents.RandomAccident
environment_params:
prob_death: 0.001
default_state:
mating_prob: 0.1
topology:
nodes:
- id: 1
agent_type: rabbit_agents.Male
- id: 0
agent_type: rabbit_agents.Female
directed: true
links: []

View File

@@ -4,6 +4,8 @@ Example of a fully programmatic simulation, without definition files.
'''
from soil import Simulation, agents
from soil.time import Delta
from random import expovariate
import logging
@@ -18,7 +20,7 @@ class MyAgent(agents.FSM):
@agents.state
def ping(self):
self.info('Ping')
return self.pong, Delta(self.random.expovariate(1/16))
return self.pong, Delta(expovariate(1/16))
@agents.state
def pong(self):
@@ -27,16 +29,17 @@ class MyAgent(agents.FSM):
self.info(str(self.pong_counts))
if self.pong_counts < 1:
return self.die()
return None, Delta(self.random.expovariate(1/16))
return None, Delta(expovariate(1/16))
s = Simulation(name='Programmatic',
network_agents=[{'agent_class': MyAgent, 'id': 0}],
network_agents=[{'agent_type': MyAgent, 'id': 0}],
topology={'nodes': [{'id': 0}], 'links': []},
num_trials=1,
max_time=100,
agent_class=MyAgent,
agent_type=MyAgent,
dry_run=True)
logging.basicConfig(level=logging.INFO)
envs = s.run()

View File

@@ -6,20 +6,20 @@ template:
group: simple
num_trials: 1
interval: 1
max_steps: 2
max_time: 2
seed: "CompleteSeed!"
dump: false
model_params:
network_params:
generator: complete_graph
n: 10
network_agents:
- agent_class: CounterModel
weight: "{{ x1 }}"
state:
state_id: 0
- agent_class: AggregatedCounter
weight: "{{ 1 - x1 }}"
network_params:
generator: complete_graph
n: 10
network_agents:
- agent_type: CounterModel
weight: "{{ x1 }}"
state:
state_id: 0
- agent_type: AggregatedCounter
weight: "{{ 1 - x1 }}"
environment_params:
name: "{{ x3 }}"
skip_test: true
vars:

View File

@@ -1,3 +1,4 @@
import random
import networkx as nx
from soil.agents import Geo, NetworkAgent, FSM, state, default_state
from soil import Environment
@@ -25,26 +26,26 @@ class TerroristSpreadModel(FSM, Geo):
self.prob_interaction = model.environment_params['prob_interaction']
if self['id'] == self.civilian.id: # Civilian
self.mean_belief = self.random.uniform(0.00, 0.5)
self.mean_belief = random.uniform(0.00, 0.5)
elif self['id'] == self.terrorist.id: # Terrorist
self.mean_belief = self.random.uniform(0.8, 1.00)
self.mean_belief = random.uniform(0.8, 1.00)
elif self['id'] == self.leader.id: # Leader
self.mean_belief = 1.00
else:
raise Exception('Invalid state id: {}'.format(self['id']))
if 'min_vulnerability' in model.environment_params:
self.vulnerability = self.random.uniform( model.environment_params['min_vulnerability'], model.environment_params['max_vulnerability'] )
self.vulnerability = random.uniform( model.environment_params['min_vulnerability'], model.environment_params['max_vulnerability'] )
else :
self.vulnerability = self.random.uniform( 0, model.environment_params['max_vulnerability'] )
self.vulnerability = random.uniform( 0, model.environment_params['max_vulnerability'] )
@state
def civilian(self):
neighbours = list(self.get_neighboring_agents(agent_class=TerroristSpreadModel))
neighbours = list(self.get_neighboring_agents(agent_type=TerroristSpreadModel))
if len(neighbours) > 0:
# Only interact with some of the neighbors
interactions = list(n for n in neighbours if self.random.random() <= self.prob_interaction)
interactions = list(n for n in neighbours if random.random() <= self.prob_interaction)
influence = sum( self.degree(i) for i in interactions )
mean_belief = sum( i.mean_belief * self.degree(i) / influence for i in interactions )
mean_belief = mean_belief * self.information_spread_intensity + self.mean_belief * ( 1 - self.information_spread_intensity )
@@ -63,7 +64,7 @@ class TerroristSpreadModel(FSM, Geo):
@state
def terrorist(self):
neighbours = self.get_agents(state_id=[self.terrorist.id, self.leader.id],
agent_class=TerroristSpreadModel,
agent_type=TerroristSpreadModel,
limit_neighbors=True)
if len(neighbours) > 0:
influence = sum( self.degree(n) for n in neighbours )
@@ -81,26 +82,6 @@ class TerroristSpreadModel(FSM, Geo):
return
return self.leader
def ego_search(self, steps=1, center=False, node=None, **kwargs):
'''Get a list of nodes in the ego network of *node* of radius *steps*'''
node = as_node(node if node is not None else self)
G = self.subgraph(**kwargs)
return nx.ego_graph(G, node, center=center, radius=steps).nodes()
def degree(self, node, force=False):
node = as_node(node)
if force or (not hasattr(self.model, '_degree')) or getattr(self.model, '_last_step', 0) < self.now:
self.model._degree = nx.degree_centrality(self.G)
self.model._last_step = self.now
return self.model._degree[node]
def betweenness(self, node, force=False):
node = as_node(node)
if force or (not hasattr(self.model, '_betweenness')) or getattr(self.model, '_last_step', 0) < self.now:
self.model._betweenness = nx.betweenness_centrality(self.G)
self.model._last_step = self.now
return self.model._betweenness[node]
class TrainingAreaModel(FSM, Geo):
"""
@@ -122,7 +103,7 @@ class TrainingAreaModel(FSM, Geo):
@default_state
@state
def terrorist(self):
for neighbour in self.get_neighboring_agents(agent_class=TerroristSpreadModel):
for neighbour in self.get_neighboring_agents(agent_type=TerroristSpreadModel):
if neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.training_influence )
@@ -148,7 +129,7 @@ class HavenModel(FSM, Geo):
self.max_vulnerability = model.environment_params['max_vulnerability']
def get_occupants(self, **kwargs):
return self.get_neighboring_agents(agent_class=TerroristSpreadModel, **kwargs)
return self.get_neighboring_agents(agent_type=TerroristSpreadModel, **kwargs)
@state
def civilian(self):
@@ -201,27 +182,27 @@ class TerroristNetworkModel(TerroristSpreadModel):
def update_relationships(self):
if self.count_neighboring_agents(state_id=self.civilian.id) == 0:
close_ups = set(self.geo_search(radius=self.vision_range, agent_class=TerroristNetworkModel))
step_neighbours = set(self.ego_search(self.sphere_influence, agent_class=TerroristNetworkModel, center=False))
neighbours = set(agent.id for agent in self.get_neighboring_agents(agent_class=TerroristNetworkModel))
close_ups = set(self.geo_search(radius=self.vision_range, agent_type=TerroristNetworkModel))
step_neighbours = set(self.ego_search(self.sphere_influence, agent_type=TerroristNetworkModel, center=False))
neighbours = set(agent.id for agent in self.get_neighboring_agents(agent_type=TerroristNetworkModel))
search = (close_ups | step_neighbours) - neighbours
for agent in self.get_agents(search):
social_distance = 1 / self.shortest_path_length(agent.id)
spatial_proximity = ( 1 - self.get_distance(agent.id) )
prob_new_interaction = self.weight_social_distance * social_distance + self.weight_link_distance * spatial_proximity
if agent['id'] == agent.civilian.id and self.random.random() < prob_new_interaction:
if agent['id'] == agent.civilian.id and random.random() < prob_new_interaction:
self.add_edge(agent)
break
def get_distance(self, target):
source_x, source_y = nx.get_node_attributes(self.G, 'pos')[self.id]
target_x, target_y = nx.get_node_attributes(self.G, 'pos')[target]
source_x, source_y = nx.get_node_attributes(self.topology, 'pos')[self.id]
target_x, target_y = nx.get_node_attributes(self.topology, '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, target):
try:
return nx.shortest_path_length(self.G, self.id, target)
return nx.shortest_path_length(self.topology, self.id, target)
except nx.NetworkXNoPath:
return float('inf')

View File

@@ -1,31 +1,32 @@
name: TerroristNetworkModel_sim
max_steps: 150
load_module: TerroristNetworkModel
max_time: 150
num_trials: 1
model_params:
network_params:
generator: random_geometric_graph
radius: 0.2
# generator: geographical_threshold_graph
# theta: 20
n: 100
network_agents:
- agent_class: TerroristNetworkModel.TerroristNetworkModel
weight: 0.8
state:
id: civilian # Civilians
- agent_class: TerroristNetworkModel.TerroristNetworkModel
weight: 0.1
state:
id: leader # Leaders
- agent_class: TerroristNetworkModel.TrainingAreaModel
weight: 0.05
state:
id: terrorist # Terrorism
- agent_class: TerroristNetworkModel.HavenModel
weight: 0.05
state:
id: civilian # Civilian
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

View File

@@ -1,15 +1,14 @@
---
name: torvalds_example
max_steps: 10
max_time: 10
interval: 2
model_params:
agent_class: CounterModel
default_state:
skill_level: 'beginner'
network_params:
path: 'torvalds.edgelist'
states:
Torvalds:
skill_level: 'God'
balkian:
skill_level: 'developer'
agent_type: CounterModel
default_state:
skill_level: 'beginner'
network_params:
path: 'torvalds.edgelist'
states:
Torvalds:
skill_level: 'God'
balkian:
skill_level: 'developer'

View File

@@ -12330,11 +12330,11 @@ Notice how node 0 is the only one with a TV.</p>
<span class="n">sim</span> <span class="o">=</span> <span class="n">soil</span><span class="o">.</span><span class="n">Simulation</span><span class="p">(</span><span class="n">topology</span><span class="o">=</span><span class="n">G</span><span class="p">,</span>
<span class="n">num_trials</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="n">max_time</span><span class="o">=</span><span class="n">MAX_TIME</span><span class="p">,</span>
<span class="n">environment_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">&#39;agent_class&#39;</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
<span class="n">environment_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">&#39;agent_type&#39;</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
<span class="s1">&#39;state&#39;</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">&#39;event_time&#39;</span><span class="p">:</span> <span class="n">EVENT_TIME</span>
<span class="p">}}],</span>
<span class="n">network_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">&#39;agent_class&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="n">network_agents</span><span class="o">=</span><span class="p">[{</span><span class="s1">&#39;agent_type&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="s1">&#39;weight&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">}],</span>
<span class="n">states</span><span class="o">=</span><span class="p">{</span><span class="mi">0</span><span class="p">:</span> <span class="p">{</span><span class="s1">&#39;has_tv&#39;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}},</span>
<span class="n">default_state</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;has_tv&#39;</span><span class="p">:</span> <span class="kc">False</span><span class="p">},</span>
@@ -12468,14 +12468,14 @@ For this demo, we will use a python dictionary:</p>
<span class="p">},</span>
<span class="s1">&#39;network_agents&#39;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">&#39;agent_class&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="s1">&#39;agent_type&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="s1">&#39;weight&#39;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">&#39;state&#39;</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">&#39;has_tv&#39;</span><span class="p">:</span> <span class="kc">False</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">&#39;agent_class&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="s1">&#39;agent_type&#39;</span><span class="p">:</span> <span class="n">NewsSpread</span><span class="p">,</span>
<span class="s1">&#39;weight&#39;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="s1">&#39;state&#39;</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">&#39;has_tv&#39;</span><span class="p">:</span> <span class="kc">True</span>
@@ -12483,7 +12483,7 @@ For this demo, we will use a python dictionary:</p>
<span class="p">}</span>
<span class="p">],</span>
<span class="s1">&#39;environment_agents&#39;</span><span class="p">:[</span>
<span class="p">{</span><span class="s1">&#39;agent_class&#39;</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
<span class="p">{</span><span class="s1">&#39;agent_type&#39;</span><span class="p">:</span> <span class="n">NewsEnvironmentAgent</span><span class="p">,</span>
<span class="s1">&#39;state&#39;</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">&#39;event_time&#39;</span><span class="p">:</span> <span class="mi">10</span>
<span class="p">}</span>

File diff suppressed because one or more lines are too long

View File

@@ -2,9 +2,8 @@ networkx>=2.5
numpy
matplotlib
pyyaml>=5.1
pandas>=1
pandas>=0.23
SALib>=1.3
Jinja2
Mesa>=1.1
pydantic>=1.9
sqlalchemy>=1.4
Mesa>=0.8
tsih>=0.1.9

View File

@@ -49,10 +49,9 @@ setup(
extras_require=extras_require,
tests_require=test_reqs,
setup_requires=['pytest-runner', ],
pytest_plugins = ['pytest_profiling'],
include_package_data=True,
entry_points={
'console_scripts':
['soil = soil.__main__:main',
['soil = soil.__init__:main',
'soil-web = soil.web.__init__:main']
})

View File

@@ -1 +1 @@
0.20.7
0.20.8

View File

@@ -1,10 +1,8 @@
from __future__ import annotations
import importlib
import sys
import os
import pdb
import logging
import traceback
from .version import __version__
@@ -18,205 +16,84 @@ from . import agents
from .simulation import *
from .environment import Environment
from . import serialization
from . import analysis
from .utils import logger
from .time import *
def main(
cfg="simulation.yml",
exporters=None,
parallel=None,
output="soil_output",
*,
do_run=False,
debug=False,
**kwargs,
):
def main():
import argparse
from . import simulation
logger.info("Running SOIL version: {}".format(__version__))
logger.info('Running SOIL version: {}'.format(__version__))
parser = argparse.ArgumentParser(description="Run a SOIL simulation")
parser.add_argument(
"file",
type=str,
nargs="?",
default=cfg,
help="Configuration file for the simulation (e.g., YAML or JSON)",
)
parser.add_argument(
"--version", action="store_true", help="Show version info and exit"
)
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 to disk, show in terminal instead.",
)
parser.add_argument(
"--pdb", action="store_true", help="Use a pdb console in case of exception."
)
parser.add_argument(
"--debug",
action="store_true",
help="Run a customized version of a pdb console to debug a simulation.",
)
parser.add_argument(
"--graph",
"-g",
action="store_true",
help="Dump each trial's network topology as a GEXF graph. Defaults to false.",
)
parser.add_argument(
"--csv",
action="store_true",
help="Dump all data collected in CSV format. Defaults to false.",
)
parser.add_argument("--level", type=str, help="Logging level")
parser.add_argument(
"--output",
"-o",
type=str,
default=output or "soil_output",
help="folder to write results to. It defaults to the current directory.",
)
if parallel is None:
parser.add_argument(
"--synchronous",
action="store_true",
help="Run trials serially and synchronously instead of in parallel. Defaults to false.",
)
parser.add_argument(
"-e",
"--exporter",
action="append",
default=[],
help="Export environment and/or simulations using this exporter",
)
parser.add_argument(
"--only-convert",
"--convert",
action="store_true",
help="Do not run the simulation, only convert the configuration file(s) and output them.",
)
parser.add_argument(
"--set",
metavar="KEY=VALUE",
action="append",
help="Set a number of parameters that will be passed to the simulation."
"(do not put spaces before or after the = sign). "
"If a value contains spaces, you should define "
"it with double quotes: "
'foo="this is a sentence". Note that '
"values are always treated as strings.",
)
parser = argparse.ArgumentParser(description='Run a SOIL simulation')
parser.add_argument('file', type=str,
nargs="?",
default='simulation.yml',
help='Configuration file for the simulation (e.g., YAML or JSON)')
parser.add_argument('--version', action='store_true',
help='Show version info and exit')
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('--level', type=str,
help='Logging level')
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.')
parser.add_argument('-e', '--exporter', action='append',
help='Export environment and/or simulations using this exporter')
args = parser.parse_args()
logger.setLevel(getattr(logging, (args.level or "INFO").upper()))
logging.basicConfig(level=getattr(logging, (args.level or 'INFO').upper()))
if args.version:
return
if parallel is None:
parallel = not args.synchronous
exporters = exporters or [
"default",
]
for exp in args.exporter:
if exp not in exporters:
exporters.append(exp)
if args.csv:
exporters.append("csv")
if args.graph:
exporters.append("gexf")
if os.getcwd() not in sys.path:
sys.path.append(os.getcwd())
if args.module:
importlib.import_module(args.module)
if output is None:
output = args.output
logger.info("Loading config file: {}".format(args.file))
logger.info('Loading config file: {}'.format(args.file))
debug = debug or args.debug
if args.pdb or debug:
if args.pdb:
args.synchronous = True
res = []
try:
exporters = list(args.exporter or ['default', ])
if args.csv:
exporters.append('csv')
if args.graph:
exporters.append('gexf')
exp_params = {}
if args.dry_run:
exp_params['copy_to'] = sys.stdout
if not os.path.exists(args.file):
logger.error("Please, input a valid file")
logger.error('Please, input a valid file')
return
for sim in simulation.iter_from_config(
args.file,
dry_run=args.dry_run,
exporters=exporters,
parallel=parallel,
outdir=output,
exporter_params=exp_params,
**kwargs,
):
if args.set:
for s in args.set:
k, v = s.split("=", 1)[:2]
v = eval(v)
tail, *head = k.rsplit(".", 1)[::-1]
target = sim
if head:
for part in head[0].split("."):
try:
target = getattr(target, part)
except AttributeError:
target = target[part]
try:
setattr(target, tail, v)
except AttributeError:
target[tail] = v
if args.only_convert:
print(sim.to_yaml())
continue
if do_run:
res.append(sim.run())
else:
print("not running")
res.append(sim)
except Exception as ex:
simulation.run_from_config(args.file,
dry_run=args.dry_run,
exporters=exporters,
parallel=(not args.synchronous),
outdir=args.output,
exporter_params=exp_params)
except Exception:
if args.pdb:
from .debugging import post_mortem
print(traceback.format_exc())
post_mortem()
pdb.post_mortem()
else:
raise
if debug:
from .debugging import set_trace
os.environ["SOIL_DEBUG"] = "true"
set_trace()
return res
def easy(cfg, debug=False, **kwargs):
return main(cfg, **kwargs)[0]
if __name__ == "__main__":
main(do_run=True)
if __name__ == '__main__':
main()

View File

@@ -1,9 +1,4 @@
from . import main as init_main
from . import main
def main():
init_main(do_run=True)
if __name__ == "__main__":
init_main(do_run=True)
if __name__ == '__main__':
main()

View File

@@ -1,3 +1,4 @@
import random
from . import FSM, state, default_state
@@ -7,7 +8,6 @@ class BassModel(FSM):
innovation_prob
imitation_prob
"""
sentimentCorrelation = 0
def step(self):
@@ -16,13 +16,13 @@ class BassModel(FSM):
@default_state
@state
def innovation(self):
if self.prob(self.innovation_prob):
if random.random() < self.innovation_prob:
self.sentimentCorrelation = 1
return self.aware
else:
aware_neighbors = self.get_neighboring_agents(state_id=self.aware.id)
num_neighbors_aware = len(aware_neighbors)
if self.prob((self["imitation_prob"] * num_neighbors_aware)):
if random.random() < (self['imitation_prob']*num_neighbors_aware):
self.sentimentCorrelation = 1
return self.aware

View File

@@ -1,3 +1,4 @@
import random
from . import FSM, state, default_state
@@ -6,54 +7,42 @@ class BigMarketModel(FSM):
Settings:
Names:
enterprises [Array]
tweet_probability_enterprises [Array]
Users:
tweet_probability_users
tweet_relevant_probability
tweet_probability_about [Array]
sentiment_about [Array]
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.enterprises = self.env.environment_params["enterprises"]
self.enterprises = self.env.environment_params['enterprises']
self.type = ""
if self.id < len(self.enterprises): # Enterprises
self.set_state(self.enterprise.id)
self.type = "Enterprise"
self.tweet_probability = environment.environment_params[
"tweet_probability_enterprises"
][self.id]
self.tweet_probability = environment.environment_params['tweet_probability_enterprises'][self.id]
else: # normal users
self.type = "User"
self.set_state(self.user.id)
self.tweet_probability = environment.environment_params[
"tweet_probability_users"
]
self.tweet_relevant_probability = environment.environment_params[
"tweet_relevant_probability"
]
self.tweet_probability_about = environment.environment_params[
"tweet_probability_about"
] # List
self.sentiment_about = environment.environment_params[
"sentiment_about"
] # List
self.tweet_probability = environment.environment_params['tweet_probability_users']
self.tweet_relevant_probability = environment.environment_params['tweet_relevant_probability']
self.tweet_probability_about = environment.environment_params['tweet_probability_about'] # List
self.sentiment_about = environment.environment_params['sentiment_about'] # List
@state
def enterprise(self):
if self.random.random() < self.tweet_probability: # Tweets
aware_neighbors = self.get_neighboring_agents(
state_id=self.number_of_enterprises
) # Nodes neighbour users
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 self.random.uniform(0, 10) < 5:
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
@@ -61,49 +50,39 @@ class BigMarketModel(FSM):
# Establecemos limites
if x.sentiment_about[self.id] > 1:
x.sentiment_about[self.id] = 1
if 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]
x.attrs['sentiment_enterprise_%s'% self.enterprises[self.id]] = x.sentiment_about[self.id]
@state
def user(self):
if self.random.random() < self.tweet_probability: # Tweets
if (
self.random.random() < self.tweet_relevant_probability
): # Tweets something relevant
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(len(self.enterprises)):
random_num = self.random.random()
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)
self.userTweets("negative",i)
elif self.sentiment_about[i] == 0:
# NEUTRO
pass
else:
# POSITIVO
self.userTweets("positive", i)
for i in range(
len(self.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]
self.userTweets("positive",i)
for i in range(len(self.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]
def userTweets(self, sentiment, enterprise):
aware_neighbors = self.get_neighboring_agents(
state_id=self.number_of_enterprises
) # Nodes neighbours users
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
x.sentiment_about[enterprise] +=0.003
elif sentiment == "negative":
x.sentiment_about[enterprise] -= 0.003
x.sentiment_about[enterprise] -=0.003
else:
pass
@@ -113,6 +92,4 @@ class BigMarketModel(FSM):
if x.sentiment_about[enterprise] < -1:
x.sentiment_about[enterprise] = -1
x.attrs[
"sentiment_enterprise_%s" % self.enterprises[enterprise]
] = x.sentiment_about[enterprise]
x.attrs['sentiment_enterprise_%s'% self.enterprises[enterprise]] = x.sentiment_about[enterprise]

View File

@@ -7,17 +7,13 @@ class CounterModel(NetworkAgent):
in each step and adds it to its state.
"""
times = 0
neighbors = 0
total = 0
def step(self):
# Outside effects
total = len(list(self.model.schedule._agents))
total = len(list(self.get_agents()))
neighbors = len(list(self.get_neighboring_agents()))
self["times"] = self.get("times", 0) + 1
self["neighbors"] = neighbors
self["total"] = total
self['times'] = self.get('times', 0) + 1
self['neighbors'] = neighbors
self['total'] = total
class AggregatedCounter(NetworkAgent):
@@ -26,15 +22,17 @@ class AggregatedCounter(NetworkAgent):
in each step and adds it to its state.
"""
times = 0
neighbors = 0
total = 0
defaults = {
'times': 0,
'neighbors': 0,
'total': 0
}
def step(self):
# Outside effects
self["times"] += 1
self['times'] += 1
neighbors = len(list(self.get_neighboring_agents()))
self["neighbors"] += neighbors
total = len(list(self.model.schedule.agents))
self["total"] += total
self.debug("Running for step: {}. Total: {}".format(self.now, total))
self['neighbors'] += neighbors
total = len(list(self.get_agents()))
self['total'] += total
self.debug('Running for step: {}. Total: {}'.format(self.now, total))

Some files were not shown because too many files have changed in this diff Show More