1
0
mirror of https://github.com/gsi-upm/soil synced 2024-11-21 18:52:28 +00:00

black formatting

This commit is contained in:
J. Fernando Sánchez 2022-10-17 20:23:57 +02:00
parent 227fdf050e
commit 880a9f2a1c
19 changed files with 431 additions and 329 deletions

View File

@ -2,16 +2,17 @@ from networkx import Graph
import random
import networkx as nx
def mygenerator(n=5, n_edges=5):
'''
"""
Just a simple generator that creates a network with n nodes and
n_edges edges. Edges are assigned randomly, only avoiding self loops.
'''
"""
G = nx.Graph()
for i in range(n):
G.add_node(i)
for i in range(n_edges):
nodes = list(G.nodes)
n_in = random.choice(nodes)
@ -19,9 +20,3 @@ def mygenerator(n=5, n_edges=5):
n_out = random.choice(nodes)
G.add_edge(n_in, n_out)
return G

View File

@ -2,34 +2,37 @@ from soil.agents import FSM, state, default_state
class Fibonacci(FSM):
'''Agent that only executes in t_steps that are Fibonacci numbers'''
"""Agent that only executes in t_steps that are Fibonacci numbers"""
defaults = {
'prev': 1
}
defaults = {"prev": 1}
@default_state
@state
def counting(self):
self.log('Stopping at {}'.format(self.now))
prev, self['prev'] = self['prev'], max([self.now, self['prev']])
self.log("Stopping at {}".format(self.now))
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'''
"""Agent that only executes in odd t_steps"""
@default_state
@state
def odds(self):
self.log('Stopping at {}'.format(self.now))
return None, self.env.timeout(1+self.now%2)
self.log("Stopping at {}".format(self.now))
return None, self.env.timeout(1 + self.now % 2)
if __name__ == '__main__':
if __name__ == "__main__":
from soil import Simulation
s = Simulation(network_agents=[{'ids': [0], 'agent_class': Fibonacci},
{'ids': [1], 'agent_class': Odds}],
network_params={"generator": "complete_graph", "n": 2},
max_time=100,
)
s = Simulation(
network_agents=[
{"ids": [0], "agent_class": Fibonacci},
{"ids": [1], "agent_class": Odds},
],
network_params={"generator": "complete_graph", "n": 2},
max_time=100,
)
s.run(dry_run=True)

View File

@ -14,16 +14,18 @@ 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)}
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),
"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()
}
for (node_id, wealth) in wealths.items()
]
portrayal["edges"] = [
@ -41,7 +43,7 @@ def gridPortrayal(agent):
:param agent: the agent in the simulation
:return: the portrayal dictionary
"""
color = max(10, min(agent.wealth*10, 100))
color = max(10, min(agent.wealth * 10, 100))
return {
"Shape": "rect",
"w": 1,
@ -52,7 +54,7 @@ def gridPortrayal(agent):
"Text": agent.unique_id,
"x": agent.pos[0],
"y": agent.pos[1],
"Color": f"rgba(31, 10, 255, 0.{color})"
"Color": f"rgba(31, 10, 255, 0.{color})",
}
@ -79,7 +81,7 @@ model_params = {
10,
1,
description="Grid height",
),
),
"width": UserSettableParameter(
"slider",
"width",
@ -88,16 +90,20 @@ model_params = {
10,
1,
description="Grid width",
),
"agent_class": UserSettableParameter('choice', 'Agent class', value='MoneyAgent',
choices=['MoneyAgent', 'SocialMoneyAgent']),
),
"agent_class": UserSettableParameter(
"choice",
"Agent class",
value="MoneyAgent",
choices=["MoneyAgent", "SocialMoneyAgent"],
),
"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

@ -1,9 +1,10 @@
'''
"""
This is an example that adds soil agents and environment in a normal
mesa workflow.
'''
"""
from mesa import Agent as MesaAgent
from mesa.space import MultiGrid
# from mesa.time import RandomActivation
from mesa.datacollection import DataCollector
from mesa.batchrunner import BatchRunner
@ -12,12 +13,13 @@ import networkx as nx
from soil import NetworkAgent, Environment, serialization
def compute_gini(model):
agent_wealths = [agent.wealth for agent in model.agents]
x = sorted(agent_wealths)
N = len(list(model.agents))
B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x))
return (1 + (1/N) - 2*B)
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x))
return 1 + (1 / N) - 2 * B
class MoneyAgent(MesaAgent):
@ -32,9 +34,8 @@ class MoneyAgent(MesaAgent):
def move(self):
possible_steps = self.model.grid.get_neighborhood(
self.pos,
moore=True,
include_center=False)
self.pos, moore=True, include_center=False
)
new_position = self.random.choice(possible_steps)
self.model.grid.move_agent(self, new_position)
@ -69,6 +70,7 @@ class SocialMoneyAgent(NetworkAgent, MoneyAgent):
other.wealth += 1
self.wealth -= 1
def graph_generator(n=5):
G = nx.Graph()
for ix in range(n):
@ -78,16 +80,22 @@ def graph_generator(n=5):
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,
width,
height,
N,
generator=graph_generator,
agent_class=SocialMoneyAgent,
topology=None,
**kwargs
):
generator = serialization.deserialize(generator)
agent_class = serialization.deserialize(agent_class, globs=globals())
topology = generator(n=N)
super().__init__(topology=topology,
N=N,
**kwargs)
super().__init__(topology=topology, N=N, **kwargs)
self.grid = MultiGrid(width, height, False)
self.populate_network(agent_class=agent_class)
@ -99,26 +107,29 @@ class MoneyEnv(Environment):
self.grid.place_agent(agent, (x, y))
self.datacollector = DataCollector(
model_reporters={"Gini": compute_gini},
agent_reporters={"Wealth": "wealth"})
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
)
if __name__ == '__main__':
if __name__ == "__main__":
fixed_params = {"generator": nx.complete_graph,
"width": 10,
"network_agents": [{"agent_class": SocialMoneyAgent,
'weight': 1}],
"height": 10}
fixed_params = {
"generator": nx.complete_graph,
"width": 10,
"network_agents": [{"agent_class": SocialMoneyAgent, "weight": 1}],
"height": 10,
}
variable_params = {"N": range(10, 100, 10)}
batch_run = BatchRunner(MoneyEnv,
variable_parameters=variable_params,
fixed_parameters=fixed_params,
iterations=5,
max_steps=100,
model_reporters={"Gini": compute_gini})
batch_run = BatchRunner(
MoneyEnv,
variable_parameters=variable_params,
fixed_parameters=fixed_params,
iterations=5,
max_steps=100,
model_reporters={"Gini": compute_gini},
)
batch_run.run_all()
run_data = batch_run.get_model_vars_dataframe()

View File

@ -4,24 +4,26 @@ from mesa.time import RandomActivation
from mesa.datacollection import DataCollector
from mesa.batchrunner import BatchRunner
def compute_gini(model):
agent_wealths = [agent.wealth for agent in model.schedule.agents]
x = sorted(agent_wealths)
N = model.num_agents
B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x))
return (1 + (1/N) - 2*B)
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x))
return 1 + (1 / N) - 2 * B
class MoneyAgent(Agent):
""" An agent with fixed initial wealth."""
"""An agent with fixed initial wealth."""
def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.wealth = 1
def move(self):
possible_steps = self.model.grid.get_neighborhood(
self.pos,
moore=True,
include_center=False)
self.pos, moore=True, include_center=False
)
new_position = self.random.choice(possible_steps)
self.model.grid.move_agent(self, new_position)
@ -37,8 +39,10 @@ class MoneyAgent(Agent):
if self.wealth > 0:
self.give_money()
class MoneyModel(Model):
"""A model with some number of agents."""
def __init__(self, N, width, height):
self.num_agents = N
self.grid = MultiGrid(width, height, True)
@ -55,29 +59,29 @@ class MoneyModel(Model):
self.grid.place_agent(a, (x, y))
self.datacollector = DataCollector(
model_reporters={"Gini": compute_gini},
agent_reporters={"Wealth": "wealth"})
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
)
def step(self):
self.datacollector.collect(self)
self.schedule.step()
if __name__ == '__main__':
if __name__ == "__main__":
fixed_params = {"width": 10,
"height": 10}
fixed_params = {"width": 10, "height": 10}
variable_params = {"N": range(10, 500, 10)}
batch_run = BatchRunner(MoneyModel,
variable_params,
fixed_params,
iterations=5,
max_steps=100,
model_reporters={"Gini": compute_gini})
batch_run = BatchRunner(
MoneyModel,
variable_params,
fixed_params,
iterations=5,
max_steps=100,
model_reporters={"Gini": compute_gini},
)
batch_run.run_all()
run_data = batch_run.get_model_vars_dataframe()
run_data.head()
print(run_data.Gini)

View File

@ -3,84 +3,83 @@ 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.
'''
"""
defaults = {
'prob_neighbor_spread': 0.5,
'prob_tv_spread': 0.1,
"prob_neighbor_spread": 0.5,
"prob_tv_spread": 0.1,
}
@default_state
@state
def neutral(self):
if self['has_tv']:
if self.prob(self.model['prob_tv_spread']):
if self["has_tv"]:
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 self.prob(self.model['prob_neighbor_spread']):
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.set_state(self.infected)
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'''
"""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
self.debug('prob_infect', prob_infect)
prob_infect = self.model["prob_neighbor_spread"] * infected / total
self.debug("prob_infect", prob_infect)
if self.prob(prob_infect):
self.set_state(self.infected)
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,
"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']
prob_cure = self.model["prob_neighbor_cure"]
for neighbor in self.get_neighboring_agents(state_id=self.infected.id):
if self.prob(prob_cure):
try:
neighbor.cure()
except AttributeError:
self.debug('Viewer {} cannot be cured'.format(neighbor.id))
self.debug("Viewer {} cannot be cured".format(neighbor.id))
def cure(self):
self.set_state(self.cured.id)
@state
def infected(self):
cured = max(self.count_neighboring_agents(self.cured.id),
1.0)
infected = max(self.count_neighboring_agents(self.infected.id),
1.0)
prob_cure = self.model['prob_neighbor_cure'] * (cured/infected)
cured = max(self.count_neighboring_agents(self.cured.id), 1.0)
infected = max(self.count_neighboring_agents(self.infected.id), 1.0)
prob_cure = self.model["prob_neighbor_cure"] * (cured / infected)
if self.prob(prob_cure):
return self.cured
return self.set_state(super().infected)

View File

@ -1,6 +1,6 @@
'''
"""
Example of a fully programmatic simulation, without definition files.
'''
"""
from soil import Simulation, agents
from networkx import Graph
import logging
@ -14,21 +14,22 @@ def mygenerator():
class MyAgent(agents.FSM):
@agents.default_state
@agents.state
def neutral(self):
self.debug('I am running')
self.debug("I am running")
if agents.prob(0.2):
self.info('This runs 2/10 times on average')
self.info("This runs 2/10 times on average")
s = Simulation(name='Programmatic',
network_params={'generator': mygenerator},
num_trials=1,
max_time=100,
agent_class=MyAgent,
dry_run=True)
s = Simulation(
name="Programmatic",
network_params={"generator": mygenerator},
num_trials=1,
max_time=100,
agent_class=MyAgent,
dry_run=True,
)
# By default, logging will only print WARNING logs (and above).

View File

@ -5,7 +5,8 @@ import logging
class CityPubs(Environment):
'''Environment with Pubs'''
"""Environment with Pubs"""
level = logging.INFO
def __init__(self, *args, number_of_pubs=3, pub_capacity=10, **kwargs):
@ -13,51 +14,52 @@ class CityPubs(Environment):
pubs = {}
for i in range(number_of_pubs):
newpub = {
'name': 'The awesome pub #{}'.format(i),
'open': True,
'capacity': pub_capacity,
'occupancy': 0,
"name": "The awesome pub #{}".format(i),
"open": True,
"capacity": pub_capacity,
"occupancy": 0,
}
pubs[newpub['name']] = newpub
self['pubs'] = pubs
pubs[newpub["name"]] = newpub
self["pubs"] = pubs
def enter(self, pub_id, *nodes):
'''Agents will try to enter. The pub checks if it is possible'''
"""Agents will try to enter. The pub checks if it is possible"""
try:
pub = self['pubs'][pub_id]
pub = self["pubs"][pub_id]
except KeyError:
raise ValueError('Pub {} is not available'.format(pub_id))
if not pub['open'] or (pub['capacity'] < (len(nodes) + pub['occupancy'])):
raise ValueError("Pub {} is not available".format(pub_id))
if not pub["open"] or (pub["capacity"] < (len(nodes) + pub["occupancy"])):
return False
pub['occupancy'] += len(nodes)
pub["occupancy"] += len(nodes)
for node in nodes:
node['pub'] = pub_id
node["pub"] = pub_id
return True
def available_pubs(self):
for pub in self['pubs'].values():
if pub['open'] and (pub['occupancy'] < pub['capacity']):
yield pub['name']
for pub in self["pubs"].values():
if pub["open"] and (pub["occupancy"] < pub["capacity"]):
yield pub["name"]
def exit(self, pub_id, *node_ids):
'''Agents will notify the pub they want to leave'''
"""Agents will notify the pub they want to leave"""
try:
pub = self['pubs'][pub_id]
pub = self["pubs"][pub_id]
except KeyError:
raise ValueError('Pub {} is not available'.format(pub_id))
raise ValueError("Pub {} is not available".format(pub_id))
for node_id in node_ids:
node = self.get_agent(node_id)
if pub_id == node['pub']:
del node['pub']
pub['occupancy'] -= 1
if pub_id == node["pub"]:
del node["pub"]
pub["occupancy"] -= 1
class Patron(FSM, NetworkAgent):
'''Agent that looks for friends to drink with. It will do three things:
1) Look for other patrons to drink with
2) Look for a bar where the agent and other agents in the same group can get in.
3) While in the bar, patrons only drink, until they get drunk and taken home.
'''
"""Agent that looks for friends to drink with. It will do three things:
1) Look for other patrons to drink with
2) Look for a bar where the agent and other agents in the same group can get in.
3) While in the bar, patrons only drink, until they get drunk and taken home.
"""
level = logging.DEBUG
pub = None
@ -69,13 +71,13 @@ class Patron(FSM, NetworkAgent):
@default_state
@state
def looking_for_friends(self):
'''Look for friends to drink with'''
self.info('I am looking for friends')
available_friends = list(self.get_agents(drunk=False,
pub=None,
state_id=self.looking_for_friends.id))
"""Look for friends to drink with"""
self.info("I am looking for friends")
available_friends = list(
self.get_agents(drunk=False, pub=None, state_id=self.looking_for_friends.id)
)
if not available_friends:
self.info('Life sucks and I\'m alone!')
self.info("Life sucks and I'm alone!")
return self.at_home
befriended = self.try_friends(available_friends)
if befriended:
@ -83,93 +85,91 @@ class Patron(FSM, NetworkAgent):
@state
def looking_for_pub(self):
'''Look for a pub that accepts me and my friends'''
if self['pub'] != None:
"""Look for a pub that accepts me and my friends"""
if self["pub"] != None:
return self.sober_in_pub
self.debug('I am looking for a pub')
self.debug("I am looking for a pub")
group = list(self.get_neighboring_agents())
for pub in self.model.available_pubs():
self.debug('We\'re trying to get into {}: total: {}'.format(pub, len(group)))
self.debug("We're trying to get into {}: total: {}".format(pub, len(group)))
if self.model.enter(pub, self, *group):
self.info('We\'re all {} getting in {}!'.format(len(group), pub))
self.info("We're all {} getting in {}!".format(len(group), pub))
return self.sober_in_pub
@state
def sober_in_pub(self):
'''Drink up.'''
"""Drink up."""
self.drink()
if self['pints'] > self['max_pints']:
if self["pints"] > self["max_pints"]:
return self.drunk_in_pub
@state
def drunk_in_pub(self):
'''I'm out. Take me home!'''
self.info('I\'m so drunk. Take me home!')
self['drunk'] = True
"""I'm out. Take me home!"""
self.info("I'm so drunk. Take me home!")
self["drunk"] = True
if self.kicked_out:
return self.at_home
pass # out drun
@state
def at_home(self):
'''The end'''
"""The end"""
others = self.get_agents(state_id=Patron.at_home.id, limit_neighbors=True)
self.debug('I\'m home. Just like {} of my friends'.format(len(others)))
self.debug("I'm home. Just like {} of my friends".format(len(others)))
def drink(self):
self['pints'] += 1
self.debug('Cheers to that')
self["pints"] += 1
self.debug("Cheers to that")
def kick_out(self):
self.kicked_out = True
def befriend(self, other_agent, force=False):
'''
"""
Try to become friends with another agent. The chances of
success depend on both agents' openness.
'''
if force or self['openness'] > self.random.random():
"""
if force or self["openness"] > self.random.random():
self.add_edge(self, other_agent)
self.info('Made some friend {}'.format(other_agent))
self.info("Made some friend {}".format(other_agent))
return True
return False
def try_friends(self, others):
''' Look for random agents around me and try to befriend them'''
"""Look for random agents around me and try to befriend them"""
befriended = False
k = int(10*self['openness'])
k = int(10 * self["openness"])
self.random.shuffle(others)
for friend in islice(others, k): # random.choice >= 3.7
if friend == self:
continue
if friend.befriend(self):
self.befriend(friend, force=True)
self.debug('Hooray! new friend: {}'.format(friend.id))
self.debug("Hooray! new friend: {}".format(friend.id))
befriended = True
else:
self.debug('{} does not want to be friends'.format(friend.id))
self.debug("{} does not want to be friends".format(friend.id))
return befriended
class Police(FSM):
'''Simple agent to take drunk people out of pubs.'''
"""Simple agent to take drunk people out of pubs."""
level = logging.INFO
@default_state
@state
def patrol(self):
drunksters = list(self.get_agents(drunk=True,
state_id=Patron.drunk_in_pub.id))
drunksters = list(self.get_agents(drunk=True, state_id=Patron.drunk_in_pub.id))
for drunk in drunksters:
self.info('Kicking out the trash: {}'.format(drunk.id))
self.info("Kicking out the trash: {}".format(drunk.id))
drunk.kick_out()
else:
self.info('No trash to take out. Too bad.')
self.info("No trash to take out. Too bad.")
if __name__ == '__main__':
if __name__ == "__main__":
from soil import simulation
simulation.run_from_config('pubcrawl.yml',
dry_run=True,
dump=None,
parallel=False)
simulation.run_from_config("pubcrawl.yml", dry_run=True, dump=None, parallel=False)

View File

@ -5,7 +5,6 @@ import math
class RabbitEnv(Environment):
@property
def num_rabbits(self):
return self.count_agents(agent_class=Rabbit)
@ -27,7 +26,7 @@ class Rabbit(NetworkAgent, FSM):
@default_state
@state
def newborn(self):
self.info('I am a newborn.')
self.info("I am a newborn.")
self.age = 0
self.offspring = 0
return self.youngling
@ -36,7 +35,7 @@ class Rabbit(NetworkAgent, FSM):
def youngling(self):
self.age += 1
if self.age >= self.sexual_maturity:
self.info(f'I am fertile! My age is {self.age}')
self.info(f"I am fertile! My age is {self.age}")
return self.fertile
@state
@ -60,11 +59,11 @@ class Male(Rabbit):
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']):
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
@ -83,14 +82,14 @@ class Female(Rabbit):
return self.pregnant
def impregnate(self, male):
self.info(f'impregnated by {repr(male)}')
self.info(f"impregnated by {repr(male)}")
self.mate = male
self.pregnancy = 0
self.number_of_babies = int(8+4*self.random.random())
self.number_of_babies = int(8 + 4 * self.random.random())
@state
def pregnant(self):
self.info('I am pregnant')
self.info("I am pregnant")
self.age += 1
if self.age >= self.life_expectancy:
@ -100,18 +99,17 @@ class Female(Rabbit):
self.pregnancy += 1
return
self.info('Having {} babies'.format(self.number_of_babies))
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 = 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.debug("The father has passed away")
self.offspring += 1
self.mate = None
@ -119,32 +117,34 @@ class Female(Rabbit):
return self.fertile
def die(self):
if 'pregnancy' in self and self['pregnancy'] > -1:
self.info('A mother has died carrying a baby!!')
if "pregnancy" in self and self["pregnancy"] > -1:
self.info("A mother has died carrying a baby!!")
return super().die()
class RandomAccident(BaseAgent):
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))
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))
self.info("I killed a rabbit: {}".format(i.id))
rabbits_alive -= 1
i.die()
self.debug('Rabbits alive: {}'.format(rabbits_alive))
self.debug("Rabbits alive: {}".format(rabbits_alive))
if __name__ == '__main__':
if __name__ == "__main__":
from soil import easy
with easy('rabbits.yml') as sim:
with easy("rabbits.yml") as sim:
sim.run()

View File

@ -7,7 +7,6 @@ import math
class RabbitEnv(Environment):
@property
def num_rabbits(self):
return self.count_agents(agent_class=Rabbit)
@ -36,7 +35,7 @@ class Rabbit(FSM, NetworkAgent):
@default_state
@state
def newborn(self):
self.info('I am a newborn.')
self.info("I am a newborn.")
self.birth = self.now
self.offspring = 0
return self.youngling, Delta(self.sexual_maturity - self.age)
@ -44,7 +43,7 @@ class Rabbit(FSM, NetworkAgent):
@state
def youngling(self):
if self.age >= self.sexual_maturity:
self.info(f'I am fertile! My age is {self.age}')
self.info(f"I am fertile! My age is {self.age}")
return self.fertile
@state
@ -66,11 +65,11 @@ class Male(Rabbit):
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']):
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 # Do not try to impregnate other females
@ -94,32 +93,31 @@ class Female(Rabbit):
return self.now - self.conception
def impregnate(self, male):
self.info(f'impregnated by {repr(male)}')
self.info(f"impregnated by {repr(male)}")
self.mate = male
self.conception = self.now
self.number_of_babies = int(8+4*self.random.random())
self.number_of_babies = int(8 + 4 * self.random.random())
@state
def pregnant(self):
self.debug('I am pregnant')
self.debug("I am pregnant")
if self.age > self.life_expectancy:
self.info("Dying before giving birth")
return self.die()
if self.pregnancy >= self.gestation:
self.info('Having {} babies'.format(self.number_of_babies))
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 = self.model.add_node(agent_class=agent_class, **state)
child.add_edge(self)
if self.mate:
child.add_edge(self.mate)
self.mate.offspring += 1
else:
self.debug('The father has passed away')
self.debug("The father has passed away")
self.offspring += 1
self.mate = None
@ -127,31 +125,33 @@ class Female(Rabbit):
def die(self):
if self.pregnancy is not None:
self.info('A mother has died carrying a baby!!')
self.info("A mother has died carrying a baby!!")
return super().die()
class RandomAccident(BaseAgent):
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))
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))
self.info("I killed a rabbit: {}".format(i.id))
rabbits_alive -= 1
i.die()
self.debug('Rabbits alive: {}'.format(rabbits_alive))
self.debug("Rabbits alive: {}".format(rabbits_alive))
if __name__ == '__main__':
if __name__ == "__main__":
from soil import easy
with easy('rabbits.yml') as sim:
with easy("rabbits.yml") as sim:
sim.run()

View File

@ -1,42 +1,43 @@
'''
"""
Example of setting a
Example of a fully programmatic simulation, without definition files.
'''
"""
from soil import Simulation, agents
from soil.time import Delta
class MyAgent(agents.FSM):
'''
"""
An agent that first does a ping
'''
"""
defaults = {'pong_counts': 2}
defaults = {"pong_counts": 2}
@agents.default_state
@agents.state
def ping(self):
self.info('Ping')
return self.pong, Delta(self.random.expovariate(1/16))
self.info("Ping")
return self.pong, Delta(self.random.expovariate(1 / 16))
@agents.state
def pong(self):
self.info('Pong')
self.info("Pong")
self.pong_counts -= 1
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(self.random.expovariate(1 / 16))
s = Simulation(name='Programmatic',
network_agents=[{'agent_class': MyAgent, 'id': 0}],
topology={'nodes': [{'id': 0}], 'links': []},
num_trials=1,
max_time=100,
agent_class=MyAgent,
dry_run=True)
s = Simulation(
name="Programmatic",
network_agents=[{"agent_class": MyAgent, "id": 0}],
topology={"nodes": [{"id": 0}], "links": []},
num_trials=1,
max_time=100,
agent_class=MyAgent,
dry_run=True,
)
envs = s.run()

View File

@ -20,56 +20,83 @@ class TerroristSpreadModel(FSM, Geo):
def __init__(self, model=None, unique_id=0, state=()):
super().__init__(model=model, unique_id=unique_id, state=state)
self.information_spread_intensity = model.environment_params['information_spread_intensity']
self.terrorist_additional_influence = model.environment_params['terrorist_additional_influence']
self.prob_interaction = model.environment_params['prob_interaction']
self.information_spread_intensity = model.environment_params[
"information_spread_intensity"
]
self.terrorist_additional_influence = model.environment_params[
"terrorist_additional_influence"
]
self.prob_interaction = model.environment_params["prob_interaction"]
if self['id'] == self.civilian.id: # Civilian
if self["id"] == self.civilian.id: # Civilian
self.mean_belief = self.random.uniform(0.00, 0.5)
elif self['id'] == self.terrorist.id: # Terrorist
elif self["id"] == self.terrorist.id: # Terrorist
self.mean_belief = self.random.uniform(0.8, 1.00)
elif self['id'] == self.leader.id: # Leader
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'] )
else :
self.vulnerability = self.random.uniform( 0, model.environment_params['max_vulnerability'] )
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"],
)
else:
self.vulnerability = self.random.uniform(
0, model.environment_params["max_vulnerability"]
)
@state
def civilian(self):
neighbours = list(self.get_neighboring_agents(agent_class=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)
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 )
self.mean_belief = mean_belief * self.vulnerability + self.mean_belief * ( 1 - self.vulnerability )
interactions = list(
n for n in neighbours if self.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)
)
self.mean_belief = mean_belief * self.vulnerability + self.mean_belief * (
1 - self.vulnerability
)
if self.mean_belief >= 0.8:
return self.terrorist
@state
def leader(self):
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
for neighbour in self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]):
self.mean_belief = self.mean_belief ** (1 - self.terrorist_additional_influence)
for neighbour in self.get_neighboring_agents(
state_id=[self.terrorist.id, self.leader.id]
):
if self.betweenness(neighbour) > self.betweenness(self):
return self.terrorist
@state
def terrorist(self):
neighbours = self.get_agents(state_id=[self.terrorist.id, self.leader.id],
agent_class=TerroristSpreadModel,
limit_neighbors=True)
neighbours = self.get_agents(
state_id=[self.terrorist.id, self.leader.id],
agent_class=TerroristSpreadModel,
limit_neighbors=True,
)
if len(neighbours) > 0:
influence = sum( self.degree(n) for n in neighbours )
mean_belief = sum( n.mean_belief * self.degree(n) / influence for n in neighbours )
mean_belief = mean_belief * self.vulnerability + self.mean_belief * ( 1 - self.vulnerability )
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
influence = sum(self.degree(n) for n in neighbours)
mean_belief = sum(
n.mean_belief * self.degree(n) / influence for n in neighbours
)
mean_belief = mean_belief * self.vulnerability + self.mean_belief * (
1 - self.vulnerability
)
self.mean_belief = self.mean_belief ** (
1 - self.terrorist_additional_influence
)
# Check if there are any leaders in the group
leaders = list(filter(lambda x: x.state.id == self.leader.id, neighbours))
@ -82,21 +109,29 @@ class TerroristSpreadModel(FSM, Geo):
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*'''
"""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:
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:
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]
@ -114,17 +149,20 @@ class TrainingAreaModel(FSM, Geo):
def __init__(self, model=None, unique_id=0, state=()):
super().__init__(model=model, unique_id=unique_id, state=state)
self.training_influence = model.environment_params['training_influence']
if 'min_vulnerability' in model.environment_params:
self.min_vulnerability = model.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
self.training_influence = model.environment_params["training_influence"]
if "min_vulnerability" in model.environment_params:
self.min_vulnerability = model.environment_params["min_vulnerability"]
else:
self.min_vulnerability = 0
@default_state
@state
def terrorist(self):
for neighbour in self.get_neighboring_agents(agent_class=TerroristSpreadModel):
if neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.training_influence )
neighbour.vulnerability = neighbour.vulnerability ** (
1 - self.training_influence
)
class HavenModel(FSM, Geo):
@ -141,11 +179,12 @@ class HavenModel(FSM, Geo):
def __init__(self, model=None, unique_id=0, state=()):
super().__init__(model=model, unique_id=unique_id, state=state)
self.haven_influence = model.environment_params['haven_influence']
if 'min_vulnerability' in model.environment_params:
self.min_vulnerability = model.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
self.max_vulnerability = model.environment_params['max_vulnerability']
self.haven_influence = model.environment_params["haven_influence"]
if "min_vulnerability" in model.environment_params:
self.min_vulnerability = model.environment_params["min_vulnerability"]
else:
self.min_vulnerability = 0
self.max_vulnerability = model.environment_params["max_vulnerability"]
def get_occupants(self, **kwargs):
return self.get_neighboring_agents(agent_class=TerroristSpreadModel, **kwargs)
@ -158,14 +197,18 @@ class HavenModel(FSM, Geo):
for neighbour in self.get_occupants():
if neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability * ( 1 - self.haven_influence )
neighbour.vulnerability = neighbour.vulnerability * (
1 - self.haven_influence
)
return self.civilian
@state
def terrorist(self):
for neighbour in self.get_occupants():
if neighbour.vulnerability < self.max_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.haven_influence )
neighbour.vulnerability = neighbour.vulnerability ** (
1 - self.haven_influence
)
return self.terrorist
@ -184,10 +227,10 @@ class TerroristNetworkModel(TerroristSpreadModel):
def __init__(self, model=None, unique_id=0, state=()):
super().__init__(model=model, unique_id=unique_id, state=state)
self.vision_range = model.environment_params['vision_range']
self.sphere_influence = model.environment_params['sphere_influence']
self.weight_social_distance = model.environment_params['weight_social_distance']
self.weight_link_distance = model.environment_params['weight_link_distance']
self.vision_range = model.environment_params["vision_range"]
self.sphere_influence = model.environment_params["sphere_influence"]
self.weight_social_distance = model.environment_params["weight_social_distance"]
self.weight_link_distance = model.environment_params["weight_link_distance"]
@state
def terrorist(self):
@ -201,27 +244,48 @@ 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_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
)
)
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:
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
):
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]
dx = abs( source_x - target_x )
dy = abs( source_y - target_y )
return ( dx ** 2 + dy ** 2 ) ** ( 1 / 2 )
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.G, self.id, target)
except nx.NetworkXNoPath:
return float('inf')
return float("inf")

View File

@ -225,6 +225,7 @@ def easy(cfg, pdb=False, debug=False, **kwargs):
except Exception as e:
if os.environ.get("SOIL_POSTMORTEM"):
from .debugging import post_mortem
print(traceback.format_exc())
post_mortem()
ex = e
@ -232,5 +233,6 @@ def easy(cfg, pdb=False, debug=False, **kwargs):
if ex:
raise ex
if __name__ == "__main__":
main(do_run=True)

View File

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

View File

@ -43,9 +43,9 @@ class MetaAgent(ABCMeta):
}
for attr, func in namespace.items():
if attr == 'step' and inspect.isgeneratorfunction(func):
if attr == "step" and inspect.isgeneratorfunction(func):
orig_func = func
new_nmspc['_MetaAgent__coroutine'] = None
new_nmspc["_MetaAgent__coroutine"] = None
@wraps(func)
def func(self):
@ -62,10 +62,10 @@ class MetaAgent(ABCMeta):
func.is_default = False
new_nmspc[attr] = func
elif (
isinstance(func, types.FunctionType)
or isinstance(func, property)
or isinstance(func, classmethod)
or attr[0] == "_"
isinstance(func, types.FunctionType)
or isinstance(func, property)
or isinstance(func, classmethod)
or attr[0] == "_"
):
new_nmspc[attr] = func
elif attr == "defaults":
@ -303,7 +303,7 @@ class NetworkAgent(BaseAgent):
return G
def remove_node(self):
print(f'Removing node for {self.unique_id}: {self.node_id}')
print(f"Removing node for {self.unique_id}: {self.node_id}")
self.G.remove_node(self.node_id)
self.node_id = None

View File

@ -146,7 +146,7 @@ class BaseEnvironment(Model):
if unique_id is None:
unique_id = self.next_id()
kwargs['unique_id'] = unique_id
kwargs["unique_id"] = unique_id
a = self._agent_from_dict(kwargs)
self.schedule.add(a)
@ -169,7 +169,9 @@ class BaseEnvironment(Model):
Advance one step in the simulation, and update the data collection and scheduler appropriately
"""
super().step()
self.logger.info(f"--- Step: {self.schedule.steps:^5} - Time: {self.now:^5} ---")
self.logger.info(
f"--- Step: {self.schedule.steps:^5} - Time: {self.now:^5} ---"
)
self.schedule.step()
self.datacollector.collect(self)
@ -236,7 +238,7 @@ class NetworkEnvironment(BaseEnvironment):
node_id = agent.get("node_id", None)
if node_id is None:
node_id = network.find_unassigned(self.G, random=self.random)
self.G.nodes[node_id]['agent'] = None
self.G.nodes[node_id]["agent"] = None
agent["node_id"] = node_id
agent["unique_id"] = unique_id
agent["topology"] = self.G
@ -271,7 +273,7 @@ class NetworkEnvironment(BaseEnvironment):
G=self.G, shuffle=True, random=self.random
)
if node_id is None:
node_id = f'node_for_{unique_id}'
node_id = f"node_for_{unique_id}"
if node_id not in self.G.nodes:
self.G.add_node(node_id)
@ -280,17 +282,23 @@ class NetworkEnvironment(BaseEnvironment):
self.G.nodes[node_id]["agent"] = None # Reserve
a = self.add_agent(
unique_id=unique_id, agent_class=agent_class, topology=self.G, node_id=node_id, **kwargs
unique_id=unique_id,
agent_class=agent_class,
topology=self.G,
node_id=node_id,
**kwargs,
)
a["visible"] = True
return a
def add_agent(self, *args, **kwargs):
a = super().add_agent(*args, **kwargs)
if 'node_id' in a:
if "node_id" in a:
if a.node_id == 24:
import pdb;pdb.set_trace()
assert self.G.nodes[a.node_id]['agent'] == a
import pdb
pdb.set_trace()
assert self.G.nodes[a.node_id]["agent"] == a
return a
def agent_for_node_id(self, node_id):

View File

@ -202,7 +202,12 @@ class summary(Exporter):
for (t, df) in self.get_dfs(env):
if not len(df):
continue
msg = indent(str(df.describe()), ' ')
logger.info(dedent(f'''
msg = indent(str(df.describe()), " ")
logger.info(
dedent(
f"""
Dataframe {t}:
''') + msg)
"""
)
+ msg
)

View File

@ -226,7 +226,9 @@ Model stats:
)
model.step()
if model.schedule.time < until: # Simulation ended (no more steps) before the expected time
if (
model.schedule.time < until
): # Simulation ended (no more steps) before the expected time
model.schedule.time = until
return model

View File

@ -174,7 +174,6 @@ class TimedActivation(BaseScheduler):
agent.alive = False
continue
if not getattr(agent, "alive", True):
self.remove(agent)
continue