You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
soil/examples/pubcrawl/pubcrawl_sim.py

195 lines
6.2 KiB
Python

from soil.agents import FSM, NetworkAgent, state, default_state
from soil import Environment, Simulation, parameters
6 years ago
from itertools import islice
import networkx as nx
6 years ago
import logging
class CityPubs(Environment):
2 years ago
"""Environment with Pubs"""
6 years ago
level = logging.INFO
number_of_pubs: parameters.Integer = 3
ratio_extroverted: parameters.probability = 0.1
pub_capacity: parameters.Integer = 10
def init(self):
self.pubs = {}
for i in range(self.number_of_pubs):
6 years ago
newpub = {
2 years ago
"name": "The awesome pub #{}".format(i),
"open": True,
"capacity": self.pub_capacity,
2 years ago
"occupancy": 0,
6 years ago
}
self.pubs[newpub["name"]] = newpub
self.add_agent(agent_class=Police)
self.populate_network([Patron.w(openness=0.1), Patron.w(openness=1)],
[self.ratio_extroverted, 1-self.ratio_extroverted])
assert all(["agent" in node and isinstance(node["agent"], Patron) for (_, node) in self.G.nodes(data=True)])
6 years ago
def enter(self, pub_id, *nodes):
2 years ago
"""Agents will try to enter. The pub checks if it is possible"""
6 years ago
try:
2 years ago
pub = self["pubs"][pub_id]
6 years ago
except KeyError:
2 years ago
raise ValueError("Pub {} is not available".format(pub_id))
if not pub["open"] or (pub["capacity"] < (len(nodes) + pub["occupancy"])):
6 years ago
return False
2 years ago
pub["occupancy"] += len(nodes)
6 years ago
for node in nodes:
2 years ago
node["pub"] = pub_id
6 years ago
return True
def available_pubs(self):
2 years ago
for pub in self["pubs"].values():
if pub["open"] and (pub["occupancy"] < pub["capacity"]):
yield pub["name"]
6 years ago
def exit(self, pub_id, *node_ids):
2 years ago
"""Agents will notify the pub they want to leave"""
6 years ago
try:
2 years ago
pub = self["pubs"][pub_id]
6 years ago
except KeyError:
2 years ago
raise ValueError("Pub {} is not available".format(pub_id))
6 years ago
for node_id in node_ids:
node = self.get_agent(node_id)
2 years ago
if pub_id == node["pub"]:
del node["pub"]
pub["occupancy"] -= 1
6 years ago
class Patron(FSM, NetworkAgent):
2 years ago
"""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
6 years ago
pub = None
drunk = False
pints = 0
max_pints = 3
2 years ago
kicked_out = False
6 years ago
@default_state
@state
def looking_for_friends(self):
2 years ago
"""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)
)
6 years ago
if not available_friends:
2 years ago
self.info("Life sucks and I'm alone!")
6 years ago
return self.at_home
befriended = self.try_friends(available_friends)
if befriended:
return self.looking_for_pub
@state
def looking_for_pub(self):
2 years ago
"""Look for a pub that accepts me and my friends"""
if self["pub"] != None:
6 years ago
return self.sober_in_pub
2 years ago
self.debug("I am looking for a pub")
group = list(self.get_neighbors())
for pub in self.model.available_pubs():
2 years ago
self.debug("We're trying to get into {}: total: {}".format(pub, len(group)))
if self.model.enter(pub, self, *group):
2 years ago
self.info("We're all {} getting in {}!".format(len(group), pub))
6 years ago
return self.sober_in_pub
@state
def sober_in_pub(self):
2 years ago
"""Drink up."""
6 years ago
self.drink()
2 years ago
if self["pints"] > self["max_pints"]:
6 years ago
return self.drunk_in_pub
@state
def drunk_in_pub(self):
2 years ago
"""I'm out. Take me home!"""
self.info("I'm so drunk. Take me home!")
self["drunk"] = True
2 years ago
if self.kicked_out:
return self.at_home
pass # out drun
6 years ago
@state
def at_home(self):
2 years ago
"""The end"""
others = self.get_agents(state_id=Patron.at_home.id, limit_neighbors=True)
2 years ago
self.debug("I'm home. Just like {} of my friends".format(len(others)))
6 years ago
def drink(self):
2 years ago
self["pints"] += 1
self.debug("Cheers to that")
6 years ago
def kick_out(self):
2 years ago
self.kicked_out = True
6 years ago
def befriend(self, other_agent, force=False):
2 years ago
"""
6 years ago
Try to become friends with another agent. The chances of
success depend on both agents' openness.
2 years ago
"""
if force or self["openness"] > self.random.random():
self.add_edge(self, other_agent)
2 years ago
self.info("Made some friend {}".format(other_agent))
6 years ago
return True
return False
def try_friends(self, others):
2 years ago
"""Look for random agents around me and try to befriend them"""
6 years ago
befriended = False
2 years ago
k = int(10 * self["openness"])
2 years ago
self.random.shuffle(others)
6 years ago
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.unique_id))
6 years ago
befriended = True
else:
self.debug("{} does not want to be friends".format(friend.unique_id))
6 years ago
return befriended
class Police(FSM):
2 years ago
"""Simple agent to take drunk people out of pubs."""
6 years ago
level = logging.INFO
@default_state
@state
def patrol(self):
2 years ago
drunksters = list(self.get_agents(drunk=True, state_id=Patron.drunk_in_pub.id))
6 years ago
for drunk in drunksters:
self.info("Kicking out the trash: {}".format(drunk.unique_id))
6 years ago
drunk.kick_out()
else:
2 years ago
self.info("No trash to take out. Too bad.")
6 years ago
sim = Simulation(
model=CityPubs,
name="pubcrawl",
iterations=3,
max_steps=10,
dump=False,
parameters=dict(
network_generator=nx.empty_graph,
network_params={"n": 30},
model=CityPubs,
altercations=0,
number_of_pubs=3,
)
)
2 years ago
if __name__ == "__main__":
sim.run(parallel=False)