mirror of
https://github.com/gsi-upm/soil
synced 2025-08-24 03:52:20 +00:00
WIP: all tests pass
This commit is contained in:
@@ -1,46 +1,54 @@
|
||||
---
|
||||
version: '2'
|
||||
general:
|
||||
id: simple
|
||||
group: tests
|
||||
dir_path: "/tmp/"
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
interval: 1
|
||||
seed: "CompleteSeed!"
|
||||
topologies:
|
||||
default:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 10
|
||||
another_graph:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 2
|
||||
environment:
|
||||
environment_class: Environment
|
||||
params:
|
||||
am_i_complete: true
|
||||
agents:
|
||||
# Agents are split several groups, each with its own definition
|
||||
default: # This is a special group. Its values will be used as default values for the rest of the groups
|
||||
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
|
||||
topologies:
|
||||
default:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 10
|
||||
another_graph:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 2
|
||||
environment:
|
||||
agents:
|
||||
agent_class: CounterModel
|
||||
topology: default
|
||||
state:
|
||||
times: 1
|
||||
environment:
|
||||
# In this group we are not specifying any topology
|
||||
topology: False
|
||||
# In this group we are not specifying any topology
|
||||
fixed:
|
||||
- name: 'Environment Agent 1'
|
||||
agent_class: CounterModel
|
||||
agent_class: BaseAgent
|
||||
group: environment
|
||||
topology: null
|
||||
hidden: true
|
||||
state:
|
||||
times: 10
|
||||
general_counters:
|
||||
topology: default
|
||||
- agent_class: CounterModel
|
||||
id: 0
|
||||
group: other_counters
|
||||
topology: another_graph
|
||||
state:
|
||||
times: 1
|
||||
total: 0
|
||||
- agent_class: CounterModel
|
||||
topology: another_graph
|
||||
group: other_counters
|
||||
id: 1
|
||||
distribution:
|
||||
- agent_class: CounterModel
|
||||
weight: 1
|
||||
group: general_counters
|
||||
state:
|
||||
times: 3
|
||||
- agent_class: AggregatedCounter
|
||||
@@ -51,16 +59,3 @@ agents:
|
||||
n: 2
|
||||
state:
|
||||
times: 5
|
||||
|
||||
other_counters:
|
||||
topology: another_graph
|
||||
fixed:
|
||||
- agent_class: CounterModel
|
||||
id: 0
|
||||
state:
|
||||
times: 1
|
||||
total: 0
|
||||
- agent_class: CounterModel
|
||||
id: 1
|
||||
# If not specified, it will use the state set in the default
|
||||
# state:
|
||||
|
63
examples/complete_opt2.yml
Normal file
63
examples/complete_opt2.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
version: '2'
|
||||
id: simple
|
||||
group: tests
|
||||
dir_path: "/tmp/"
|
||||
num_trials: 3
|
||||
max_steps: 100
|
||||
interval: 1
|
||||
seed: "CompleteSeed!"
|
||||
model_class: "soil.Environment"
|
||||
model_params:
|
||||
topologies:
|
||||
default:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 10
|
||||
another_graph:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 2
|
||||
agents:
|
||||
# The values here will be used as default values for any agent
|
||||
agent_class: CounterModel
|
||||
topology: default
|
||||
state:
|
||||
times: 1
|
||||
# This specifies a distribution of agents, each with a `weight` or an explicit number of agents
|
||||
distribution:
|
||||
- agent_class: CounterModel
|
||||
weight: 1
|
||||
# This is inherited from the default settings
|
||||
#topology: default
|
||||
state:
|
||||
times: 3
|
||||
- agent_class: AggregatedCounter
|
||||
topology: default
|
||||
weight: 0.2
|
||||
fixed:
|
||||
- name: 'Environment Agent 1'
|
||||
# All the other agents will assigned to the 'default' group
|
||||
group: environment
|
||||
# Do not count this agent towards total limits
|
||||
hidden: true
|
||||
agent_class: soil.BaseAgent
|
||||
topology: null
|
||||
state:
|
||||
times: 10
|
||||
- agent_class: CounterModel
|
||||
topology: another_graph
|
||||
id: 0
|
||||
state:
|
||||
times: 1
|
||||
total: 0
|
||||
- agent_class: CounterModel
|
||||
topology: another_graph
|
||||
id: 1
|
||||
override:
|
||||
# 2 agents that match this filter will be updated to match the state {times: 5}
|
||||
- filter:
|
||||
agent_class: AggregatedCounter
|
||||
n: 2
|
||||
state:
|
||||
times: 5
|
@@ -2,7 +2,7 @@
|
||||
name: custom-generator
|
||||
description: Using a custom generator for the network
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
max_steps: 100
|
||||
interval: 1
|
||||
network_params:
|
||||
generator: mymodule.mygenerator
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from networkx import Graph
|
||||
import random
|
||||
import networkx as nx
|
||||
|
||||
def mygenerator(n=5, n_edges=5):
|
||||
@@ -13,9 +14,9 @@ def mygenerator(n=5, n_edges=5):
|
||||
|
||||
for i in range(n_edges):
|
||||
nodes = list(G.nodes)
|
||||
n_in = self.random.choice(nodes)
|
||||
n_in = random.choice(nodes)
|
||||
nodes.remove(n_in) # Avoid loops
|
||||
n_out = self.random.choice(nodes)
|
||||
n_out = random.choice(nodes)
|
||||
G.add_edge(n_in, n_out)
|
||||
return G
|
||||
|
||||
|
@@ -3,17 +3,21 @@ name: mesa_sim
|
||||
group: tests
|
||||
dir_path: "/tmp"
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
max_steps: 100
|
||||
interval: 1
|
||||
seed: '1'
|
||||
network_params:
|
||||
generator: social_wealth.graph_generator
|
||||
n: 5
|
||||
network_agents:
|
||||
- agent_class: social_wealth.SocialMoneyAgent
|
||||
weight: 1
|
||||
environment_class: social_wealth.MoneyEnv
|
||||
environment_params:
|
||||
model_class: social_wealth.MoneyEnv
|
||||
model_params:
|
||||
topologies:
|
||||
default:
|
||||
params:
|
||||
generator: social_wealth.graph_generator
|
||||
n: 5
|
||||
agents:
|
||||
distribution:
|
||||
- agent_class: social_wealth.SocialMoneyAgent
|
||||
topology: default
|
||||
weight: 1
|
||||
mesa_agent_class: social_wealth.MoneyAgent
|
||||
N: 10
|
||||
width: 50
|
||||
|
@@ -5,7 +5,7 @@ environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 300
|
||||
max_steps: 300
|
||||
name: Sim_all_dumb
|
||||
network_agents:
|
||||
- agent_class: newsspread.DumbViewer
|
||||
@@ -28,7 +28,7 @@ environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 300
|
||||
max_steps: 300
|
||||
name: Sim_half_herd
|
||||
network_agents:
|
||||
- agent_class: newsspread.DumbViewer
|
||||
@@ -59,7 +59,7 @@ environment_params:
|
||||
prob_neighbor_spread: 0.0
|
||||
prob_tv_spread: 0.01
|
||||
interval: 1
|
||||
max_time: 300
|
||||
max_steps: 300
|
||||
name: Sim_all_herd
|
||||
network_agents:
|
||||
- agent_class: newsspread.HerdViewer
|
||||
@@ -85,7 +85,7 @@ environment_params:
|
||||
prob_tv_spread: 0.01
|
||||
prob_neighbor_cure: 0.1
|
||||
interval: 1
|
||||
max_time: 300
|
||||
max_steps: 300
|
||||
name: Sim_wise_herd
|
||||
network_agents:
|
||||
- agent_class: newsspread.HerdViewer
|
||||
@@ -110,7 +110,7 @@ environment_params:
|
||||
prob_tv_spread: 0.01
|
||||
prob_neighbor_cure: 0.1
|
||||
interval: 1
|
||||
max_time: 300
|
||||
max_steps: 300
|
||||
name: Sim_all_wise
|
||||
network_agents:
|
||||
- agent_class: newsspread.WiseViewer
|
||||
|
@@ -16,13 +16,13 @@ class DumbViewer(FSM, NetworkAgent):
|
||||
@state
|
||||
def neutral(self):
|
||||
if self['has_tv']:
|
||||
if prob(self.env['prob_tv_spread']):
|
||||
if self.prob(self.model['prob_tv_spread']):
|
||||
return self.infected
|
||||
|
||||
@state
|
||||
def infected(self):
|
||||
for neighbor in self.get_neighboring_agents(state_id=self.neutral.id):
|
||||
if prob(self.env['prob_neighbor_spread']):
|
||||
if self.prob(self.model['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.env['prob_neighbor_spread'] * infected/total
|
||||
prob_infect = self.model['prob_neighbor_spread'] * infected/total
|
||||
self.debug('prob_infect', prob_infect)
|
||||
if prob(prob_infect):
|
||||
if self.prob(prob_infect):
|
||||
self.set_state(self.infected)
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@ class WiseViewer(HerdViewer):
|
||||
|
||||
@state
|
||||
def cured(self):
|
||||
prob_cure = self.env['prob_neighbor_cure']
|
||||
prob_cure = self.model['prob_neighbor_cure']
|
||||
for neighbor in self.get_neighboring_agents(state_id=self.infected.id):
|
||||
if prob(prob_cure):
|
||||
if self.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.env['prob_neighbor_cure'] * (cured/infected)
|
||||
if prob(prob_cure):
|
||||
prob_cure = self.model['prob_neighbor_cure'] * (cured/infected)
|
||||
if self.prob(prob_cure):
|
||||
return self.cured
|
||||
return self.set_state(super().infected)
|
||||
|
@@ -60,12 +60,10 @@ class Patron(FSM, NetworkAgent):
|
||||
'''
|
||||
level = logging.DEBUG
|
||||
|
||||
defaults = {
|
||||
'pub': None,
|
||||
'drunk': False,
|
||||
'pints': 0,
|
||||
'max_pints': 3,
|
||||
}
|
||||
pub = None
|
||||
drunk = False
|
||||
pints = 0
|
||||
max_pints = 3
|
||||
|
||||
@default_state
|
||||
@state
|
||||
@@ -89,9 +87,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.env.available_pubs():
|
||||
for pub in self.model.available_pubs():
|
||||
self.debug('We\'re trying to get into {}: total: {}'.format(pub, len(group)))
|
||||
if self.env.enter(pub, self, *group):
|
||||
if self.model.enter(pub, self, *group):
|
||||
self.info('We\'re all {} getting in {}!'.format(len(group), pub))
|
||||
return self.sober_in_pub
|
||||
|
||||
@@ -128,7 +126,7 @@ class Patron(FSM, NetworkAgent):
|
||||
success depend on both agents' openness.
|
||||
'''
|
||||
if force or self['openness'] > self.random.random():
|
||||
self.env.add_edge(self, other_agent)
|
||||
self.model.add_edge(self, other_agent)
|
||||
self.info('Made some friend {}'.format(other_agent))
|
||||
return True
|
||||
return False
|
||||
@@ -150,7 +148,7 @@ class Patron(FSM, NetworkAgent):
|
||||
return befriended
|
||||
|
||||
|
||||
class Police(FSM, NetworkAgent):
|
||||
class Police(FSM):
|
||||
'''Simple agent to take drunk people out of pubs.'''
|
||||
level = logging.INFO
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: pubcrawl
|
||||
num_trials: 3
|
||||
max_time: 10
|
||||
max_steps: 10
|
||||
dump: false
|
||||
network_params:
|
||||
# Generate 100 empty nodes. They will be assigned a network agent
|
||||
|
4
examples/rabbits/README.md
Normal file
4
examples/rabbits/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
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.
|
130
examples/rabbits/basic/rabbit_agents.py
Normal file
130
examples/rabbits/basic/rabbit_agents.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
|
||||
from soil.time import Delta
|
||||
from enum import Enum
|
||||
from collections import Counter
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
||||
class RabbitModel(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(RabbitModel):
|
||||
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(RabbitModel):
|
||||
gestation = 100
|
||||
|
||||
@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())
|
||||
self.debug('I am pregnant')
|
||||
|
||||
@state
|
||||
def pregnant(self):
|
||||
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,
|
||||
topology=self.topology,
|
||||
**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.topology.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=RabbitModel):
|
||||
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))
|
41
examples/rabbits/basic/rabbits.yml
Normal file
41
examples/rabbits/basic/rabbits.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
version: '2'
|
||||
name: rabbits_basic
|
||||
num_trials: 1
|
||||
seed: MySeed
|
||||
description: null
|
||||
group: null
|
||||
interval: 1.0
|
||||
max_time: 100
|
||||
model_class: soil.environment.Environment
|
||||
model_params:
|
||||
agents:
|
||||
topology: default
|
||||
agent_class: rabbit_agents.RabbitModel
|
||||
distribution:
|
||||
- agent_class: rabbit_agents.Male
|
||||
topology: default
|
||||
weight: 1
|
||||
- agent_class: rabbit_agents.Female
|
||||
topology: default
|
||||
weight: 1
|
||||
fixed:
|
||||
- agent_class: rabbit_agents.RandomAccident
|
||||
topology: null
|
||||
hidden: true
|
||||
state:
|
||||
group: environment
|
||||
state:
|
||||
group: network
|
||||
mating_prob: 0.1
|
||||
prob_death: 0.001
|
||||
topologies:
|
||||
default:
|
||||
topology:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
extra:
|
||||
visualization_params: {}
|
130
examples/rabbits/improved/rabbit_agents.py
Normal file
130
examples/rabbits/improved/rabbit_agents.py
Normal file
@@ -0,0 +1,130 @@
|
||||
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()
|
41
examples/rabbits/improved/rabbits.yml
Normal file
41
examples/rabbits/improved/rabbits.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
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: default
|
||||
agent_class: rabbit_agents.RabbitModel
|
||||
distribution:
|
||||
- agent_class: rabbit_agents.Male
|
||||
topology: default
|
||||
weight: 1
|
||||
- agent_class: rabbit_agents.Female
|
||||
topology: default
|
||||
weight: 1
|
||||
fixed:
|
||||
- agent_class: rabbit_agents.RandomAccident
|
||||
topology: null
|
||||
hidden: true
|
||||
state:
|
||||
group: environment
|
||||
state:
|
||||
group: network
|
||||
mating_prob: 0.1
|
||||
prob_death: 0.001
|
||||
topologies:
|
||||
default:
|
||||
topology:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
extra:
|
||||
visualization_params: {}
|
@@ -1,133 +0,0 @@
|
||||
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
|
||||
from enum import Enum
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
||||
class Genders(Enum):
|
||||
male = 'male'
|
||||
female = 'female'
|
||||
|
||||
|
||||
class RabbitModel(FSM, NetworkAgent):
|
||||
|
||||
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_class=Female,
|
||||
limit_neighbors=False,
|
||||
limit=self.max_females):
|
||||
r = self.random.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*self.random.random())
|
||||
self.info('Having {} babies'.format(number_of_babies))
|
||||
for i in range(number_of_babies):
|
||||
state = {}
|
||||
state['gender'] = self.random.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(BaseAgent):
|
||||
|
||||
level = logging.DEBUG
|
||||
|
||||
def step(self):
|
||||
rabbits_total = self.env.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
|
||||
if self.prob(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.env.count_agents(state_id=RabbitModel.dead.id) == self.env.topology.number_of_nodes():
|
||||
self.die()
|
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: rabbits_example
|
||||
max_time: 100
|
||||
interval: 1
|
||||
seed: MySeed
|
||||
agent_class: rabbit_agents.RabbitModel
|
||||
environment_agents:
|
||||
- agent_class: rabbit_agents.RandomAccident
|
||||
environment_params:
|
||||
prob_death: 0.001
|
||||
default_state:
|
||||
mating_prob: 0.1
|
||||
topology:
|
||||
nodes:
|
||||
- id: 1
|
||||
agent_class: rabbit_agents.Male
|
||||
- id: 0
|
||||
agent_class: rabbit_agents.Female
|
||||
directed: true
|
||||
links: []
|
@@ -6,20 +6,20 @@ template:
|
||||
group: simple
|
||||
num_trials: 1
|
||||
interval: 1
|
||||
max_time: 2
|
||||
max_steps: 2
|
||||
seed: "CompleteSeed!"
|
||||
dump: false
|
||||
network_params:
|
||||
generator: complete_graph
|
||||
n: 10
|
||||
network_agents:
|
||||
- agent_class: CounterModel
|
||||
weight: "{{ x1 }}"
|
||||
state:
|
||||
state_id: 0
|
||||
- agent_class: AggregatedCounter
|
||||
weight: "{{ 1 - x1 }}"
|
||||
environment_params:
|
||||
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:
|
||||
|
@@ -81,6 +81,26 @@ 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):
|
||||
"""
|
||||
@@ -194,14 +214,14 @@ class TerroristNetworkModel(TerroristSpreadModel):
|
||||
break
|
||||
|
||||
def get_distance(self, 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]
|
||||
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]
|
||||
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.topology, self.id, target)
|
||||
return nx.shortest_path_length(self.G, self.id, target)
|
||||
except nx.NetworkXNoPath:
|
||||
return float('inf')
|
||||
|
@@ -1,31 +1,31 @@
|
||||
name: TerroristNetworkModel_sim
|
||||
max_time: 150
|
||||
max_steps: 150
|
||||
num_trials: 1
|
||||
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
|
||||
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
|
||||
|
||||
environment_params:
|
||||
# TerroristSpreadModel
|
||||
information_spread_intensity: 0.7
|
||||
terrorist_additional_influence: 0.035
|
||||
|
@@ -1,14 +1,15 @@
|
||||
---
|
||||
name: torvalds_example
|
||||
max_time: 10
|
||||
max_steps: 10
|
||||
interval: 2
|
||||
agent_class: CounterModel
|
||||
default_state:
|
||||
skill_level: 'beginner'
|
||||
network_params:
|
||||
path: 'torvalds.edgelist'
|
||||
states:
|
||||
Torvalds:
|
||||
skill_level: 'God'
|
||||
balkian:
|
||||
skill_level: 'developer'
|
||||
model_params:
|
||||
agent_class: CounterModel
|
||||
default_state:
|
||||
skill_level: 'beginner'
|
||||
network_params:
|
||||
path: 'torvalds.edgelist'
|
||||
states:
|
||||
Torvalds:
|
||||
skill_level: 'God'
|
||||
balkian:
|
||||
skill_level: 'developer'
|
||||
|
Reference in New Issue
Block a user