mirror of https://github.com/gsi-upm/soil
Compare commits
8 Commits
2869b1e1e6
...
be65592055
Author | SHA1 | Date |
---|---|---|
J. Fernando Sánchez | be65592055 | 1 year ago |
J. Fernando Sánchez | 1d882dcff6 | 1 year ago |
J. Fernando Sánchez | b3e77cbff5 | 1 year ago |
J. Fernando Sánchez | 05748a3250 | 1 year ago |
J. Fernando Sánchez | a3fc6a5efa | 1 year ago |
J. Fernando Sánchez | 4e95709188 | 1 year ago |
J. Fernando Sánchez | feab0ba79e | 1 year ago |
J. Fernando Sánchez | 73282530fd | 1 year ago |
@ -0,0 +1,35 @@
|
|||||||
|
What are the main changes between version 0.3 and 0.2?
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
Version 0.3 is a major rewrite of the Soil system, focused on simplifying the API, aligning it with Mesa, and making it easier to use.
|
||||||
|
Unfortunately, this comes at the cost of backwards compatibility.
|
||||||
|
|
||||||
|
We drew several lessons from the previous version of Soil, and tried to address them in this version.
|
||||||
|
Mainly:
|
||||||
|
|
||||||
|
- The split between simulation configuration and simulation code was overly complicated for most use cases. As a result, most users ended up reusing configuration.
|
||||||
|
- Storing **all** the simulation data in a database is costly and unnecessary for most use cases. For most use cases, only a handful of variables need to be stored. This fits nicely with Mesa's data collection system.
|
||||||
|
- The API was too complex, and it was difficult to understand how to use it.
|
||||||
|
- Most parts of the API were not aligned with Mesa, which made it difficult to use Mesa's features or to integrate Soil modules with Mesa code, especially for newcomers.
|
||||||
|
- Many parts of the API were tightly coupled, which made it difficult to find bugs, test the system and add new features.
|
||||||
|
|
||||||
|
The 0.30 rewrite should provide a middle ground between Soil's opinionated approach and Mesa's flexibility.
|
||||||
|
The new Soil is less configuration-centric.
|
||||||
|
It aims to provide more modular and convenient functions, most of which can be used in vanilla Mesa.
|
||||||
|
|
||||||
|
How are agents assigned to nodes in the network
|
||||||
|
###############################################
|
||||||
|
|
||||||
|
The constructor of the `NetworkAgent` class has two arguments: `node_id` and `topology`.
|
||||||
|
If `topology` is not provided, it will default to `self.model.topology`.
|
||||||
|
This assignment might err if the model does not have a `topology` attribute, but most Soil environments derive from `NetworkEnvironment`, so they include a topology by default.
|
||||||
|
If `node_id` is not provided, a random node will be selected from the topology, until a node with no agent is found.
|
||||||
|
Then, the `node_id` of that node is assigned to the agent.
|
||||||
|
If no node with no agent is found, a new node is automatically added to the topology.
|
||||||
|
|
||||||
|
|
||||||
|
Can Soil environments include more than one network / topology?
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
Yes, but each network has to be included manually.
|
||||||
|
Somewhere between 0.20 and 0.30 we included the ability to include multiple networks, but it was deemed too complex and was removed.
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
version: '2'
|
|
||||||
name: simple
|
|
||||||
group: tests
|
|
||||||
dir_path: "/tmp/"
|
|
||||||
num_trials: 3
|
|
||||||
max_steps: 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
|
|
||||||
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
|
|
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: custom-generator
|
|
||||||
description: Using a custom generator for the network
|
|
||||||
num_trials: 3
|
|
||||||
max_steps: 100
|
|
||||||
interval: 1
|
|
||||||
network_params:
|
|
||||||
generator: mymodule.mygenerator
|
|
||||||
# These are custom parameters
|
|
||||||
n: 10
|
|
||||||
n_edges: 5
|
|
||||||
network_agents:
|
|
||||||
- agent_class: CounterModel
|
|
||||||
weight: 1
|
|
||||||
state:
|
|
||||||
state_id: 0
|
|
@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: mesa_sim
|
|
||||||
group: tests
|
|
||||||
dir_path: "/tmp"
|
|
||||||
num_trials: 3
|
|
||||||
max_steps: 100
|
|
||||||
interval: 1
|
|
||||||
seed: '1'
|
|
||||||
model_class: social_wealth.MoneyEnv
|
|
||||||
model_params:
|
|
||||||
generator: social_wealth.graph_generator
|
|
||||||
agents:
|
|
||||||
topology: true
|
|
||||||
distribution:
|
|
||||||
- agent_class: social_wealth.SocialMoneyAgent
|
|
||||||
weight: 1
|
|
||||||
N: 10
|
|
||||||
width: 50
|
|
||||||
height: 50
|
|
@ -0,0 +1,7 @@
|
|||||||
|
from soil import Simulation
|
||||||
|
from social_wealth import MoneyEnv, graph_generator
|
||||||
|
|
||||||
|
sim = Simulation(name="mesa_sim", dump=False, max_steps=10, interval=2, model=MoneyEnv, model_params=dict(generator=graph_generator, N=10, width=50, height=50))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sim.run()
|
@ -1,133 +0,0 @@
|
|||||||
---
|
|
||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params:
|
|
||||||
prob_neighbor_spread: 0.0
|
|
||||||
prob_tv_spread: 0.01
|
|
||||||
interval: 1
|
|
||||||
max_steps: 300
|
|
||||||
name: Sim_all_dumb
|
|
||||||
network_agents:
|
|
||||||
- agent_class: newsspread.DumbViewer
|
|
||||||
state:
|
|
||||||
has_tv: false
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.DumbViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
weight: 1
|
|
||||||
network_params:
|
|
||||||
generator: barabasi_albert_graph
|
|
||||||
n: 500
|
|
||||||
m: 5
|
|
||||||
num_trials: 50
|
|
||||||
---
|
|
||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params:
|
|
||||||
prob_neighbor_spread: 0.0
|
|
||||||
prob_tv_spread: 0.01
|
|
||||||
interval: 1
|
|
||||||
max_steps: 300
|
|
||||||
name: Sim_half_herd
|
|
||||||
network_agents:
|
|
||||||
- agent_class: newsspread.DumbViewer
|
|
||||||
state:
|
|
||||||
has_tv: false
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.DumbViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.HerdViewer
|
|
||||||
state:
|
|
||||||
has_tv: false
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.HerdViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
weight: 1
|
|
||||||
network_params:
|
|
||||||
generator: barabasi_albert_graph
|
|
||||||
n: 500
|
|
||||||
m: 5
|
|
||||||
num_trials: 50
|
|
||||||
---
|
|
||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params:
|
|
||||||
prob_neighbor_spread: 0.0
|
|
||||||
prob_tv_spread: 0.01
|
|
||||||
interval: 1
|
|
||||||
max_steps: 300
|
|
||||||
name: Sim_all_herd
|
|
||||||
network_agents:
|
|
||||||
- agent_class: newsspread.HerdViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
state_id: neutral
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.HerdViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
state_id: neutral
|
|
||||||
weight: 1
|
|
||||||
network_params:
|
|
||||||
generator: barabasi_albert_graph
|
|
||||||
n: 500
|
|
||||||
m: 5
|
|
||||||
num_trials: 50
|
|
||||||
---
|
|
||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params:
|
|
||||||
prob_neighbor_spread: 0.0
|
|
||||||
prob_tv_spread: 0.01
|
|
||||||
prob_neighbor_cure: 0.1
|
|
||||||
interval: 1
|
|
||||||
max_steps: 300
|
|
||||||
name: Sim_wise_herd
|
|
||||||
network_agents:
|
|
||||||
- agent_class: newsspread.HerdViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
state_id: neutral
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.WiseViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
weight: 1
|
|
||||||
network_params:
|
|
||||||
generator: barabasi_albert_graph
|
|
||||||
n: 500
|
|
||||||
m: 5
|
|
||||||
num_trials: 50
|
|
||||||
---
|
|
||||||
default_state: {}
|
|
||||||
environment_agents: []
|
|
||||||
environment_params:
|
|
||||||
prob_neighbor_spread: 0.0
|
|
||||||
prob_tv_spread: 0.01
|
|
||||||
prob_neighbor_cure: 0.1
|
|
||||||
interval: 1
|
|
||||||
max_steps: 300
|
|
||||||
name: Sim_all_wise
|
|
||||||
network_agents:
|
|
||||||
- agent_class: newsspread.WiseViewer
|
|
||||||
state:
|
|
||||||
has_tv: true
|
|
||||||
state_id: neutral
|
|
||||||
weight: 1
|
|
||||||
- agent_class: newsspread.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
|
|
@ -1,87 +0,0 @@
|
|||||||
from soil.agents import FSM, NetworkAgent, state, default_state, prob
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class DumbViewer(FSM, NetworkAgent):
|
|
||||||
"""
|
|
||||||
A viewer that gets infected via TV (if it has one) and tries to infect
|
|
||||||
its neighbors once it's infected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
prob_neighbor_spread = 0.5
|
|
||||||
prob_tv_spread = 0.1
|
|
||||||
has_been_infected = False
|
|
||||||
|
|
||||||
@default_state
|
|
||||||
@state
|
|
||||||
def neutral(self):
|
|
||||||
if self["has_tv"]:
|
|
||||||
if self.prob(self.model["prob_tv_spread"]):
|
|
||||||
return self.infected
|
|
||||||
if self.has_been_infected:
|
|
||||||
return self.infected
|
|
||||||
|
|
||||||
@state
|
|
||||||
def infected(self):
|
|
||||||
for neighbor in self.get_neighbors(state_id=self.neutral.id):
|
|
||||||
if self.prob(self.model["prob_neighbor_spread"]):
|
|
||||||
neighbor.infect()
|
|
||||||
|
|
||||||
def infect(self):
|
|
||||||
"""
|
|
||||||
This is not a state. It is a function that other agents can use to try to
|
|
||||||
infect this agent. DumbViewer always gets infected, but other agents like
|
|
||||||
HerdViewer might not become infected right away
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.has_been_infected = True
|
|
||||||
|
|
||||||
|
|
||||||
class HerdViewer(DumbViewer):
|
|
||||||
"""
|
|
||||||
A viewer whose probability of infection depends on the state of its neighbors.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def infect(self):
|
|
||||||
"""Notice again that this is NOT a state. See DumbViewer.infect for reference"""
|
|
||||||
infected = self.count_neighbors(state_id=self.infected.id)
|
|
||||||
total = self.count_neighbors()
|
|
||||||
prob_infect = self.model["prob_neighbor_spread"] * infected / total
|
|
||||||
self.debug("prob_infect", prob_infect)
|
|
||||||
if self.prob(prob_infect):
|
|
||||||
self.has_been_infected = True
|
|
||||||
|
|
||||||
|
|
||||||
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.model["prob_neighbor_cure"]
|
|
||||||
for neighbor in self.get_neighbors(state_id=self.infected.id):
|
|
||||||
if self.prob(prob_cure):
|
|
||||||
try:
|
|
||||||
neighbor.cure()
|
|
||||||
except AttributeError:
|
|
||||||
self.debug("Viewer {} cannot be cured".format(neighbor.id))
|
|
||||||
|
|
||||||
def cure(self):
|
|
||||||
self.has_been_cured = True
|
|
||||||
|
|
||||||
@state
|
|
||||||
def infected(self):
|
|
||||||
if self.has_been_cured:
|
|
||||||
return self.cured
|
|
||||||
cured = max(self.count_neighbors(self.cured.id), 1.0)
|
|
||||||
infected = max(self.count_neighbors(self.infected.id), 1.0)
|
|
||||||
prob_cure = self.model["prob_neighbor_cure"] * (cured / infected)
|
|
||||||
if self.prob(prob_cure):
|
|
||||||
return self.cured
|
|
@ -0,0 +1,134 @@
|
|||||||
|
from soil.agents import FSM, NetworkAgent, state, default_state, prob
|
||||||
|
from soil.parameters import *
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from soil.environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
class DumbViewer(FSM, NetworkAgent):
|
||||||
|
"""
|
||||||
|
A viewer that gets infected via TV (if it has one) and tries to infect
|
||||||
|
its neighbors once it's infected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_been_infected: bool = False
|
||||||
|
has_tv: bool = False
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def neutral(self):
|
||||||
|
if self.has_tv:
|
||||||
|
if self.prob(self.get("prob_tv_spread")):
|
||||||
|
return self.infected
|
||||||
|
if self.has_been_infected:
|
||||||
|
return self.infected
|
||||||
|
|
||||||
|
@state
|
||||||
|
def infected(self):
|
||||||
|
for neighbor in self.get_neighbors(state_id=self.neutral.id):
|
||||||
|
if self.prob(self.get("prob_neighbor_spread")):
|
||||||
|
neighbor.infect()
|
||||||
|
|
||||||
|
def infect(self):
|
||||||
|
"""
|
||||||
|
This is not a state. It is a function that other agents can use to try to
|
||||||
|
infect this agent. DumbViewer always gets infected, but other agents like
|
||||||
|
HerdViewer might not become infected right away
|
||||||
|
"""
|
||||||
|
self.has_been_infected = True
|
||||||
|
|
||||||
|
|
||||||
|
class HerdViewer(DumbViewer):
|
||||||
|
"""
|
||||||
|
A viewer whose probability of infection depends on the state of its neighbors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def infect(self):
|
||||||
|
"""Notice again that this is NOT a state. See DumbViewer.infect for reference"""
|
||||||
|
infected = self.count_neighbors(state_id=self.infected.id)
|
||||||
|
total = self.count_neighbors()
|
||||||
|
prob_infect = self.get("prob_neighbor_spread") * infected / total
|
||||||
|
self.debug("prob_infect", prob_infect)
|
||||||
|
if self.prob(prob_infect):
|
||||||
|
self.has_been_infected = True
|
||||||
|
|
||||||
|
|
||||||
|
class WiseViewer(HerdViewer):
|
||||||
|
"""
|
||||||
|
A viewer that can change its mind.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@state
|
||||||
|
def cured(self):
|
||||||
|
prob_cure = self.get("prob_neighbor_cure")
|
||||||
|
for neighbor in self.get_neighbors(state_id=self.infected.id):
|
||||||
|
if self.prob(prob_cure):
|
||||||
|
try:
|
||||||
|
neighbor.cure()
|
||||||
|
except AttributeError:
|
||||||
|
self.debug("Viewer {} cannot be cured".format(neighbor.id))
|
||||||
|
|
||||||
|
def cure(self):
|
||||||
|
self.has_been_cured = True
|
||||||
|
|
||||||
|
@state
|
||||||
|
def infected(self):
|
||||||
|
if self.has_been_cured:
|
||||||
|
return self.cured
|
||||||
|
cured = max(self.count_neighbors(self.cured.id), 1.0)
|
||||||
|
infected = max(self.count_neighbors(self.infected.id), 1.0)
|
||||||
|
prob_cure = self.get("prob_neighbor_cure") * (cured / infected)
|
||||||
|
if self.prob(prob_cure):
|
||||||
|
return self.cured
|
||||||
|
|
||||||
|
|
||||||
|
class NewsSpread(Environment):
|
||||||
|
ratio_dumb: probability = 1,
|
||||||
|
ratio_herd: probability = 0,
|
||||||
|
ratio_wise: probability = 0,
|
||||||
|
prob_tv_spread: probability = 0.1,
|
||||||
|
prob_neighbor_spread: probability = 0.1,
|
||||||
|
prob_neighbor_cure: probability = 0.05,
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.populate_network([DumbViewer, HerdViewer, WiseViewer],
|
||||||
|
[self.ratio_dumb, self.ratio_herd, self.ratio_wise])
|
||||||
|
|
||||||
|
|
||||||
|
from itertools import product
|
||||||
|
from soil import Simulation
|
||||||
|
|
||||||
|
|
||||||
|
# We want to investigate the effect of different agent distributions on the spread of news.
|
||||||
|
# To do that, we will run different simulations, with a varying ratio of DumbViewers, HerdViewers, and WiseViewers
|
||||||
|
# Because the effect of these agents might also depend on the network structure, we will run our simulations on two different networks:
|
||||||
|
# one with a small-world structure and one with a connected structure.
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for [r1, r2] in product([0, 0.5, 1.0], repeat=2):
|
||||||
|
for (generator, netparams) in {
|
||||||
|
"barabasi_albert_graph": {"m": 5},
|
||||||
|
"erdos_renyi_graph": {"p": 0.1},
|
||||||
|
}.items():
|
||||||
|
print(r1, r2, 1-r1-r2, generator)
|
||||||
|
# Create new simulation
|
||||||
|
netparams["n"] = 500
|
||||||
|
Simulation(
|
||||||
|
name='newspread_sim',
|
||||||
|
model=NewsSpread,
|
||||||
|
model_params=dict(
|
||||||
|
ratio_dumb=r1,
|
||||||
|
ratio_herd=r2,
|
||||||
|
ratio_wise=1-r1-r2,
|
||||||
|
network_generator=generator,
|
||||||
|
network_params=netparams,
|
||||||
|
prob_neighbor_spread=0,
|
||||||
|
),
|
||||||
|
num_trials=5,
|
||||||
|
max_steps=300,
|
||||||
|
dump=False,
|
||||||
|
).run()
|
||||||
|
counter += 1
|
||||||
|
# Run all the necessary instances
|
||||||
|
|
||||||
|
print(f"A total of {counter} simulations were run.")
|
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: pubcrawl
|
|
||||||
num_trials: 3
|
|
||||||
max_steps: 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
|
|
||||||
description: Extroverted patron
|
|
||||||
state:
|
|
||||||
openness: 1.0
|
|
||||||
weight: 9
|
|
||||||
- agent_class: pubcrawl.Patron
|
|
||||||
description: Introverted patron
|
|
||||||
state:
|
|
||||||
openness: 0.1
|
|
||||||
weight: 1
|
|
||||||
environment_agents:
|
|
||||||
- agent_class: pubcrawl.Police
|
|
||||||
environment_class: pubcrawl.CityPubs
|
|
||||||
environment_params:
|
|
||||||
altercations: 0
|
|
||||||
number_of_pubs: 3
|
|
@ -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: {}
|
|
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
version: '2'
|
|
||||||
name: rabbits_improved
|
|
||||||
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: {}
|
|
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
sampler:
|
|
||||||
method: "SALib.sample.morris.sample"
|
|
||||||
N: 10
|
|
||||||
template:
|
|
||||||
group: simple
|
|
||||||
num_trials: 1
|
|
||||||
interval: 1
|
|
||||||
max_steps: 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 }}"
|
|
||||||
name: "{{ x3 }}"
|
|
||||||
skip_test: true
|
|
||||||
vars:
|
|
||||||
bounds:
|
|
||||||
x1: [0, 1]
|
|
||||||
x2: [1, 2]
|
|
||||||
fixed:
|
|
||||||
x3: ["a", "b", "c"]
|
|
@ -1,62 +0,0 @@
|
|||||||
name: TerroristNetworkModel_sim
|
|
||||||
max_steps: 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
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
skip_test: true # This simulation takes too long for automated tests.
|
|
@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
name: torvalds_example
|
|
||||||
max_steps: 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'
|
|
@ -0,0 +1,25 @@
|
|||||||
|
from soil import Environment, Simulation, CounterModel, report
|
||||||
|
|
||||||
|
|
||||||
|
# Get directory path for current file
|
||||||
|
import os, sys, inspect
|
||||||
|
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
|
|
||||||
|
class TorvaldsEnv(Environment):
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.create_network(path=os.path.join(currentdir, 'torvalds.edgelist'))
|
||||||
|
self.populate_network(CounterModel, skill_level='beginner')
|
||||||
|
self.agent(node_id="Torvalds").skill_level = 'God'
|
||||||
|
self.agent(node_id="balkian").skill_level = 'developer'
|
||||||
|
self.add_agent_reporter("times")
|
||||||
|
|
||||||
|
@report
|
||||||
|
def god_developers(self):
|
||||||
|
return self.count_agents(skill_level='God')
|
||||||
|
|
||||||
|
|
||||||
|
sim = Simulation(name='torvalds_example',
|
||||||
|
max_steps=10,
|
||||||
|
interval=2,
|
||||||
|
model=TorvaldsEnv)
|
File diff suppressed because one or more lines are too long
@ -1,267 +1,2 @@
|
|||||||
from __future__ import annotations
|
def load_config(cfg):
|
||||||
|
return cfg
|
||||||
from enum import Enum
|
|
||||||
from pydantic import BaseModel, ValidationError, validator, root_validator
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union, Type
|
|
||||||
from pydantic import BaseModel, Extra
|
|
||||||
|
|
||||||
from . import environment, utils
|
|
||||||
|
|
||||||
import networkx as nx
|
|
||||||
|
|
||||||
|
|
||||||
# Could use TypeAlias in python >= 3.10
|
|
||||||
nodeId = int
|
|
||||||
|
|
||||||
|
|
||||||
class Node(BaseModel):
|
|
||||||
id: nodeId
|
|
||||||
state: Optional[Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
|
|
||||||
class Edge(BaseModel):
|
|
||||||
source: nodeId
|
|
||||||
target: nodeId
|
|
||||||
value: Optional[float] = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Topology(BaseModel):
|
|
||||||
nodes: List[Node]
|
|
||||||
directed: bool
|
|
||||||
links: List[Edge]
|
|
||||||
|
|
||||||
|
|
||||||
class NetConfig(BaseModel):
|
|
||||||
params: Optional[Dict[str, Any]]
|
|
||||||
fixed: Optional[Union[Topology, nx.Graph]]
|
|
||||||
path: Optional[str]
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def default():
|
|
||||||
return NetConfig(topology=None, params=None)
|
|
||||||
|
|
||||||
@root_validator
|
|
||||||
def validate_all(cls, values):
|
|
||||||
if "params" not in values and "topology" not in values:
|
|
||||||
raise ValueError(
|
|
||||||
"You must specify either a topology or the parameters to generate a graph"
|
|
||||||
)
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
class EnvConfig(BaseModel):
|
|
||||||
@staticmethod
|
|
||||||
def default():
|
|
||||||
return EnvConfig()
|
|
||||||
|
|
||||||
|
|
||||||
class SingleAgentConfig(BaseModel):
|
|
||||||
agent_class: Optional[Union[Type, str]] = None
|
|
||||||
unique_id: Optional[int] = None
|
|
||||||
topology: Optional[bool] = False
|
|
||||||
node_id: Optional[Union[int, str]] = None
|
|
||||||
state: Optional[Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
|
|
||||||
class FixedAgentConfig(SingleAgentConfig):
|
|
||||||
n: Optional[int] = 1
|
|
||||||
hidden: Optional[bool] = False # Do not count this agent towards total agent count
|
|
||||||
|
|
||||||
@root_validator
|
|
||||||
def validate_all(cls, values):
|
|
||||||
if values.get("unique_id", None) is not None and values.get("n", 1) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
f"An unique_id can only be provided when there is only one agent ({values.get('n')} given)"
|
|
||||||
)
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
class OverrideAgentConfig(FixedAgentConfig):
|
|
||||||
filter: Optional[Dict[str, Any]] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Strategy(Enum):
|
|
||||||
topology = "topology"
|
|
||||||
total = "total"
|
|
||||||
|
|
||||||
|
|
||||||
class AgentDistro(SingleAgentConfig):
|
|
||||||
weight: Optional[float] = 1
|
|
||||||
strategy: Strategy = Strategy.topology
|
|
||||||
|
|
||||||
|
|
||||||
class AgentConfig(SingleAgentConfig):
|
|
||||||
n: Optional[int] = None
|
|
||||||
distribution: Optional[List[AgentDistro]] = None
|
|
||||||
fixed: Optional[List[FixedAgentConfig]] = None
|
|
||||||
override: Optional[List[OverrideAgentConfig]] = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def default():
|
|
||||||
return AgentConfig()
|
|
||||||
|
|
||||||
@root_validator
|
|
||||||
def validate_all(cls, values):
|
|
||||||
if "distribution" in values and (
|
|
||||||
"n" not in values and "topology" not in values
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"You need to provide the number of agents or a topology to extract the value from."
|
|
||||||
)
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel, extra=Extra.allow):
|
|
||||||
version: Optional[str] = "1"
|
|
||||||
|
|
||||||
name: str = "Unnamed Simulation"
|
|
||||||
description: Optional[str] = None
|
|
||||||
group: str = None
|
|
||||||
dir_path: Optional[str] = None
|
|
||||||
num_trials: int = 1
|
|
||||||
max_time: float = 100
|
|
||||||
max_steps: int = -1
|
|
||||||
num_processes: int = 1
|
|
||||||
interval: float = 1
|
|
||||||
seed: str = ""
|
|
||||||
dry_run: bool = False
|
|
||||||
skip_test: bool = False
|
|
||||||
|
|
||||||
model_class: Union[Type, str] = environment.Environment
|
|
||||||
model_params: Optional[Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
visualization_params: Optional[Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_raw(cls, cfg):
|
|
||||||
if isinstance(cfg, Config):
|
|
||||||
return cfg
|
|
||||||
if cfg.get("version", "1") == "1" and any(
|
|
||||||
k in cfg for k in ["agents", "agent_class", "topology", "environment_class"]
|
|
||||||
):
|
|
||||||
return convert_old(cfg)
|
|
||||||
return Config(**cfg)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_old(old, strict=True):
|
|
||||||
"""
|
|
||||||
Try to convert old style configs into the new format.
|
|
||||||
|
|
||||||
This is still a work in progress and might not work in many cases.
|
|
||||||
"""
|
|
||||||
|
|
||||||
utils.logger.warning(
|
|
||||||
"The old configuration format is deprecated. The converted file MAY NOT yield the right results"
|
|
||||||
)
|
|
||||||
|
|
||||||
new = old.copy()
|
|
||||||
|
|
||||||
network = {}
|
|
||||||
|
|
||||||
if "topology" in old:
|
|
||||||
del new["topology"]
|
|
||||||
network["topology"] = old["topology"]
|
|
||||||
|
|
||||||
if "network_params" in old and old["network_params"]:
|
|
||||||
del new["network_params"]
|
|
||||||
for (k, v) in old["network_params"].items():
|
|
||||||
if k == "path":
|
|
||||||
network["path"] = v
|
|
||||||
else:
|
|
||||||
network.setdefault("params", {})[k] = v
|
|
||||||
|
|
||||||
topology = None
|
|
||||||
if network:
|
|
||||||
topology = network
|
|
||||||
|
|
||||||
agents = {"fixed": [], "distribution": []}
|
|
||||||
|
|
||||||
def updated_agent(agent):
|
|
||||||
"""Convert an agent definition"""
|
|
||||||
newagent = dict(agent)
|
|
||||||
return newagent
|
|
||||||
|
|
||||||
by_weight = []
|
|
||||||
fixed = []
|
|
||||||
override = []
|
|
||||||
|
|
||||||
if "environment_agents" in new:
|
|
||||||
|
|
||||||
for agent in new["environment_agents"]:
|
|
||||||
agent.setdefault("state", {})["group"] = "environment"
|
|
||||||
if "agent_id" in agent:
|
|
||||||
agent["state"]["name"] = agent["agent_id"]
|
|
||||||
del agent["agent_id"]
|
|
||||||
agent["hidden"] = True
|
|
||||||
agent["topology"] = False
|
|
||||||
fixed.append(updated_agent(agent))
|
|
||||||
del new["environment_agents"]
|
|
||||||
|
|
||||||
if "agent_class" in old:
|
|
||||||
del new["agent_class"]
|
|
||||||
agents["agent_class"] = old["agent_class"]
|
|
||||||
|
|
||||||
if "default_state" in old:
|
|
||||||
del new["default_state"]
|
|
||||||
agents["state"] = old["default_state"]
|
|
||||||
|
|
||||||
if "network_agents" in old:
|
|
||||||
agents["topology"] = True
|
|
||||||
|
|
||||||
agents.setdefault("state", {})["group"] = "network"
|
|
||||||
|
|
||||||
for agent in new["network_agents"]:
|
|
||||||
agent = updated_agent(agent)
|
|
||||||
if "agent_id" in agent:
|
|
||||||
agent["state"]["name"] = agent["agent_id"]
|
|
||||||
del agent["agent_id"]
|
|
||||||
fixed.append(agent)
|
|
||||||
else:
|
|
||||||
by_weight.append(agent)
|
|
||||||
del new["network_agents"]
|
|
||||||
|
|
||||||
if "agent_class" in old and (not fixed and not by_weight):
|
|
||||||
agents["topology"] = True
|
|
||||||
by_weight = [{"agent_class": old["agent_class"], "weight": 1}]
|
|
||||||
|
|
||||||
# TODO: translate states properly
|
|
||||||
if "states" in old:
|
|
||||||
del new["states"]
|
|
||||||
states = old["states"]
|
|
||||||
if isinstance(states, dict):
|
|
||||||
states = states.items()
|
|
||||||
else:
|
|
||||||
states = enumerate(states)
|
|
||||||
for (k, v) in states:
|
|
||||||
override.append({"filter": {"node_id": k}, "state": v})
|
|
||||||
|
|
||||||
agents["override"] = override
|
|
||||||
agents["fixed"] = fixed
|
|
||||||
agents["distribution"] = by_weight
|
|
||||||
|
|
||||||
model_params = {}
|
|
||||||
if "environment_params" in new:
|
|
||||||
del new["environment_params"]
|
|
||||||
model_params = dict(old["environment_params"])
|
|
||||||
|
|
||||||
if "environment_class" in old:
|
|
||||||
del new["environment_class"]
|
|
||||||
new["model_class"] = old["environment_class"]
|
|
||||||
|
|
||||||
if "dump" in old:
|
|
||||||
del new["dump"]
|
|
||||||
new["dry_run"] = not old["dump"]
|
|
||||||
|
|
||||||
model_params["topology"] = topology
|
|
||||||
model_params["agents"] = agents
|
|
||||||
|
|
||||||
return Config(version="2", model_params=model_params, **new)
|
|
@ -0,0 +1,6 @@
|
|||||||
|
def report(f: property):
|
||||||
|
if isinstance(f, property):
|
||||||
|
setattr(f.fget, "add_to_report", True)
|
||||||
|
else:
|
||||||
|
setattr(f, "add_to_report", True)
|
||||||
|
return f
|
@ -0,0 +1,32 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
import annotated_types
|
||||||
|
from typing import *
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
class Parameter:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def floatrange(
|
||||||
|
*,
|
||||||
|
gt: Optional[float] = None,
|
||||||
|
ge: Optional[float] = None,
|
||||||
|
lt: Optional[float] = None,
|
||||||
|
le: Optional[float] = None,
|
||||||
|
multiple_of: Optional[float] = None,
|
||||||
|
) -> type[float]:
|
||||||
|
return Annotated[
|
||||||
|
float,
|
||||||
|
annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le),
|
||||||
|
annotated_types.MultipleOf(multiple_of) if multiple_of is not None else None,
|
||||||
|
]
|
||||||
|
|
||||||
|
function = Annotated[Callable, Parameter]
|
||||||
|
Integer = Annotated[int, Parameter]
|
||||||
|
Float = Annotated[float, Parameter]
|
||||||
|
|
||||||
|
|
||||||
|
probability = floatrange(ge=0, le=1)
|
@ -1,6 +0,0 @@
|
|||||||
from mesa.visualization.UserParam import UserSettableParameter
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettableParameter(UserSettableParameter):
|
|
||||||
def __str__(self):
|
|
||||||
return self.value
|
|
@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
version: '2'
|
|
||||||
name: simple
|
|
||||||
group: tests
|
|
||||||
dir_path: "/tmp/"
|
|
||||||
num_trials: 3
|
|
||||||
max_time: 100
|
|
||||||
interval: 1
|
|
||||||
seed: "CompleteSeed!"
|
|
||||||
model_class: Environment
|
|
||||||
model_params:
|
|
||||||
topology:
|
|
||||||
params:
|
|
||||||
generator: complete_graph
|
|
||||||
n: 4
|
|
||||||
agents:
|
|
||||||
agent_class: CounterModel
|
|
||||||
state:
|
|
||||||
group: network
|
|
||||||
times: 1
|
|
||||||
topology: true
|
|
||||||
distribution:
|
|
||||||
- agent_class: CounterModel
|
|
||||||
weight: 0.25
|
|
||||||
state:
|
|
||||||
state_id: 0
|
|
||||||
times: 1
|
|
||||||
- agent_class: AggregatedCounter
|
|
||||||
weight: 0.5
|
|
||||||
state:
|
|
||||||
times: 2
|
|
||||||
override:
|
|
||||||
- filter:
|
|
||||||
node_id: 1
|
|
||||||
state:
|
|
||||||
name: 'Node 1'
|
|
||||||
- filter:
|
|
||||||
node_id: 2
|
|
||||||
state:
|
|
||||||
name: 'Node 2'
|
|
||||||
fixed:
|
|
||||||
- agent_class: BaseAgent
|
|
||||||
hidden: true
|
|
||||||
topology: false
|
|
||||||
state:
|
|
||||||
name: 'Environment Agent 1'
|
|
||||||
times: 10
|
|
||||||
group: environment
|
|
||||||
am_i_complete: true
|
|
@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
name: simple
|
|
||||||
group: tests
|
|
||||||
dir_path: "/tmp/"
|
|
||||||
num_trials: 3
|
|
||||||
max_time: 100
|
|
||||||
interval: 1
|
|
||||||
seed: "CompleteSeed!"
|
|
||||||
network_params:
|
|
||||||
generator: complete_graph
|
|
||||||
n: 4
|
|
||||||
network_agents:
|
|
||||||
- agent_class: CounterModel
|
|
||||||
weight: 0.25
|
|
||||||
state:
|
|
||||||
state_id: 0
|
|
||||||
times: 1
|
|
||||||
- agent_class: AggregatedCounter
|
|
||||||
weight: 0.5
|
|
||||||
state:
|
|
||||||
times: 2
|
|
||||||
environment_agents:
|
|
||||||
- agent_id: 'Environment Agent 1'
|
|
||||||
agent_class: BaseAgent
|
|
||||||
state:
|
|
||||||
times: 10
|
|
||||||
environment_class: Environment
|
|
||||||
environment_params:
|
|
||||||
am_i_complete: true
|
|
||||||
agent_class: CounterModel
|
|
||||||
default_state:
|
|
||||||
times: 1
|
|
||||||
states:
|
|
||||||
1:
|
|
||||||
name: 'Node 1'
|
|
||||||
2:
|
|
||||||
name: 'Node 2'
|
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source_file: "../examples/torvalds_sim.py"
|
||||||
|
model: "TorvaldsEnv"
|
||||||
|
max_steps: 10
|
||||||
|
interval: 2
|
Loading…
Reference in New Issue