1
0
mirror of https://github.com/gsi-upm/soil synced 2025-08-24 03:52:20 +00:00

Large set of changes for v0.30

The examples weren't being properly tested in the last commit. When we fixed
that a lot of bugs in the new implementation of environment and agent were
found, which accounts for most of these changes.

The main difference is the mechanism to load simulations from a configuration
file. For that to work, we had to rework our module loading code in
`serialization` and add a `source_file` attribute to configurations (and
simulations, for that matter).
This commit is contained in:
J. Fernando Sánchez
2023-04-14 19:41:24 +02:00
parent 73282530fd
commit feab0ba79e
36 changed files with 739 additions and 875 deletions

View File

@@ -26,14 +26,14 @@ def mygenerator(n=5, n_edges=5):
class GeneratorEnv(Environment):
"""Using a custom generator for the network"""
generator: parameters.function = mygenerator
generator: parameters.function = staticmethod(mygenerator)
def init(self):
self.create_network(network_generator=self.generator, n=10, n_edges=5)
self.init_agents(CounterModel)
self.create_network(generator=self.generator, n=10, n_edges=5)
self.add_agents(CounterModel)
sim = Simulation(model=GeneratorEnv, max_steps=10, interval=1)
if __name__ == '__main__':
sim.run(dry_run=True)
sim.run(dump=False)

View File

@@ -30,7 +30,7 @@ from networkx import complete_graph
class TimeoutsEnv(Environment):
def init(self):
self.init_network(generator=complete_graph, n=2)
self.create_network(generator=complete_graph, n=2)
self.add_agent(agent_class=Fibonacci, node_id=0)
self.add_agent(agent_class=Odds, node_id=1)
@@ -38,4 +38,4 @@ class TimeoutsEnv(Environment):
sim = Simulation(model=TimeoutsEnv, max_steps=10, interval=1)
if __name__ == "__main__":
sim.run(dry_run=True)
sim.run(dump=False)

View File

@@ -56,41 +56,25 @@ class City(EventedEnvironment):
:param int height: Height of the internal grid
:param int width: Width of the internal grid
"""
n_cars = 1
n_passengers = 10
height = 100
width = 100
def init(self):
self.grid = MultiGrid(width=self.width, height=self.height, torus=False)
if not self.agents:
self.add_agents(Driver, k=self.n_cars)
self.add_agents(Passenger, k=self.n_passengers)
def __init__(
self,
*args,
n_cars=1,
n_passengers=10,
height=100,
width=100,
agents=None,
model_reporters=None,
**kwargs,
):
self.grid = MultiGrid(width=width, height=height, torus=False)
if agents is None:
agents = []
for i in range(n_cars):
agents.append({"agent_class": Driver})
for i in range(n_passengers):
agents.append({"agent_class": Passenger})
model_reporters = model_reporters or {
"earnings": "total_earnings",
"n_passengers": "number_passengers",
}
print("REPORTERS", model_reporters)
super().__init__(
*args, agents=agents, model_reporters=model_reporters, **kwargs
)
for agent in self.agents:
self.grid.place_agent(agent, (0, 0))
self.grid.move_to_empty(agent)
self.total_earnings = 0
self.add_model_reporter("total_earnings")
@property
def total_earnings(self):
return sum(d.earnings for d in self.agents(agent_class=Driver))
@report
@property
def number_passengers(self):
return self.count_agents(agent_class=Passenger)
@@ -150,6 +134,7 @@ class Driver(Evented, FSM):
while self.move_towards(self.journey.destination, with_passenger=True):
yield
self.earnings += self.journey.tip
self.model.total_earnings += self.journey.tip
self.check_passengers()
return self.wandering
@@ -228,13 +213,13 @@ class Passenger(Evented, FSM):
except events.TimedOut:
pass
self.info("Got home safe!")
self.die()
self.die("Got home safe!")
simulation = Simulation(name="RideHailing",
model=City,
seed="carsSeed",
max_time=1000,
model_params=dict(n_passengers=2))
if __name__ == "__main__":

View File

@@ -1,7 +1,7 @@
from soil import Simulation
from social_wealth import MoneyEnv, graph_generator
sim = Simulation(name="mesa_sim", dry_run=True, max_steps=10, interval=2, model=MoneyEnv, model_params=dict(generator=graph_generator, N=10, width=50, height=50))
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()

View File

@@ -53,7 +53,7 @@ class MoneyAgent(MesaAgent):
self.give_money()
class SocialMoneyAgent(NetworkAgent, MoneyAgent):
class SocialMoneyAgent(MoneyAgent, NetworkAgent):
wealth = 1
def give_money(self):

View File

@@ -91,10 +91,11 @@ class NewsSpread(Environment):
prob_neighbor_cure: probability = 0.05,
def init(self):
self.populate_network([DumbViewer, HerdViewer, WiseViewer], [self.ratio_dumb, self.ratio_herd, self.ratio_wise])
self.populate_network([DumbViewer, HerdViewer, WiseViewer],
[self.ratio_dumb, self.ratio_herd, self.ratio_wise])
from itertools import permutations
from itertools import product
from soil import Simulation
@@ -103,27 +104,31 @@ from soil import Simulation
# 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.
for [r1, r2, r3] in permutations([0, 0.5, 1.0], 3):
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, r3, generator)
print(r1, r2, 1-r1-r2, generator)
# Create new simulation
netparams["n"] = 500
sim = Simulation(
Simulation(
name='newspread_sim',
model=NewsSpread,
model_params={
"ratio_dumb": r1,
"ratio_herd": r2,
"ratio_wise": r3,
"network_generator": generator,
"network_params": netparams,
"prob_neighbor_spread": 0,
},
num_trials=50,
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,
dry_run=True,
)
dump=False,
).run()
counter += 1
# Run all the necessary instances
sim.run()
print(f"A total of {counter} simulations were run.")

View File

@@ -14,7 +14,7 @@ def mygenerator():
return G
class MyAgent(agents.FSM):
class MyAgent(agents.NetworkAgent, agents.FSM):
times_run = 0
@agents.default_state
@agents.state
@@ -29,6 +29,7 @@ class ProgrammaticEnv(Environment):
def init(self):
self.create_network(generator=mygenerator)
assert len(self.G)
self.populate_network(agent_class=MyAgent)
self.add_agent_reporter('times_run')
@@ -39,7 +40,7 @@ simulation = Simulation(
seed='Program',
num_trials=1,
max_time=100,
dry_run=True,
dump=False,
)
if __name__ == "__main__":

View File

@@ -14,7 +14,7 @@ class CityPubs(Environment):
pub_capacity: parameters.Integer = 10
def init(self):
pubs = {}
self.pubs = {}
for i in range(self.number_of_pubs):
newpub = {
"name": "The awesome pub #{}".format(i),
@@ -22,10 +22,11 @@ class CityPubs(Environment):
"capacity": self.pub_capacity,
"occupancy": 0,
}
pubs[newpub["name"]] = newpub
self.add_agent(agent_class=Police, node_id=0)
self["pubs"] = pubs
self.populate_network([{"openness": 0.1}, {"openness": 1}], [self.ratio_extroverted, 1-self.ratio_extroverted], agent_class=Patron)
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)])
def enter(self, pub_id, *nodes):
"""Agents will try to enter. The pub checks if it is possible"""
@@ -151,10 +152,10 @@ class Patron(FSM, NetworkAgent):
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.unique_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.unique_id))
return befriended
@@ -168,19 +169,20 @@ class Police(FSM):
def patrol(self):
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.unique_id))
drunk.kick_out()
else:
self.info("No trash to take out. Too bad.")
sim = Simulation(
model=CityPubs,
name="pubcrawl",
num_trials=3,
max_steps=10,
dry_run=True,
dump=False,
model_params=dict(
generator=nx.empty_graph,
network_generator=nx.empty_graph,
network_params={"n": 30},
model=CityPubs,
altercations=0,

View File

@@ -40,7 +40,7 @@ s = Simulation(
model=RandomEnv,
num_trials=1,
max_time=100,
dry_run=True,
dump=False,
)

View File

@@ -5,7 +5,6 @@ from soil.parameters import *
class TerroristEnvironment(Environment):
generator: function = nx.random_geometric_graph
n: Integer = 100
radius: Float = 0.2
@@ -37,8 +36,11 @@ class TerroristEnvironment(Environment):
TerroristNetworkModel.w(state_id='leader'),
TrainingAreaModel,
HavenModel
], [self.ratio_civil, self.ratio_leader, self.ratio_trainig, self.ratio_heaven])
], [self.ratio_civil, self.ratio_leader, self.ratio_training, self.ratio_haven])
@staticmethod
def generator(*args, **kwargs):
return nx.random_geometric_graph(*args, **kwargs)
class TerroristSpreadModel(FSM, Geo):
"""
@@ -50,10 +52,13 @@ class TerroristSpreadModel(FSM, Geo):
min_vulnerability (optional else zero)
max_vulnerability
prob_interaction
"""
information_spread_intensity = 0.1
terrorist_additional_influence = 0.1
min_vulnerability = 0
max_vulnerability = 1
def init(self):
if self.state_id == self.civilian.id: # Civilian
self.mean_belief = self.model.random.uniform(0.00, 0.5)
@@ -75,7 +80,7 @@ class TerroristSpreadModel(FSM, Geo):
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
n for n in neighbours if self.random.random() <= self.model.prob_interaction
)
influence = sum(self.degree(i) for i in interactions)
mean_belief = sum(
@@ -121,7 +126,7 @@ class TerroristSpreadModel(FSM, Geo):
)
# Check if there are any leaders in the group
leaders = list(filter(lambda x: x.state.id == self.leader.id, neighbours))
leaders = list(filter(lambda x: x.state_id == self.leader.id, neighbours))
if not leaders:
# Check if this is the potential leader
# Stop once it's found. Otherwise, set self as leader
@@ -132,12 +137,11 @@ class TerroristSpreadModel(FSM, Geo):
def ego_search(self, steps=1, center=False, agent=None, **kwargs):
"""Get a list of nodes in the ego network of *node* of radius *steps*"""
node = agent.node
node = agent.node_id
G = self.subgraph(**kwargs)
return nx.ego_graph(G, node, center=center, radius=steps).nodes()
def degree(self, agent, force=False):
node = agent.node
if (
force
or (not hasattr(self.model, "_degree"))
@@ -145,10 +149,9 @@ class TerroristSpreadModel(FSM, Geo):
):
self.model._degree = nx.degree_centrality(self.G)
self.model._last_step = self.now
return self.model._degree[node]
return self.model._degree[agent.node_id]
def betweenness(self, agent, force=False):
node = agent.node
if (
force
or (not hasattr(self.model, "_betweenness"))
@@ -156,7 +159,7 @@ class TerroristSpreadModel(FSM, Geo):
):
self.model._betweenness = nx.betweenness_centrality(self.G)
self.model._last_step = self.now
return self.model._betweenness[node]
return self.model._betweenness[agent.node_id]
class TrainingAreaModel(FSM, Geo):
@@ -169,13 +172,12 @@ class TrainingAreaModel(FSM, Geo):
Requires TerroristSpreadModel.
"""
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
training_influence = 0.1
min_vulnerability = 0
def init(self):
self.mean_believe = 1
self.vulnerability = 0
@default_state
@state
@@ -199,18 +201,19 @@ class HavenModel(FSM, Geo):
Requires TerroristSpreadModel.
"""
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"]
min_vulnerability = 0
haven_influence = 0.1
max_vulnerability = 0.5
def init(self):
self.mean_believe = 0
self.vulnerability = 0
def get_occupants(self, **kwargs):
return self.get_neighbors(agent_class=TerroristSpreadModel, **kwargs)
return self.get_neighbors(agent_class=TerroristSpreadModel,
**kwargs)
@default_state
@state
def civilian(self):
civilians = self.get_occupants(state_id=self.civilian.id)
@@ -246,13 +249,10 @@ class TerroristNetworkModel(TerroristSpreadModel):
weight_link_distance
"""
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"]
sphere_influence: float
vision_range: float
weight_social_distance: float
weight_link_distance: float
@state
def terrorist(self):
@@ -316,8 +316,8 @@ sim = Simulation(
num_trials=1,
name="TerroristNetworkModel_sim",
max_steps=150,
skip_test=True,
dry_run=True,
skip_test=False,
dump=False,
)
# TODO: integrate visualization

View File

@@ -1,14 +1,23 @@
from soil import Environment, Simulation, CounterModel
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='torvalds.edgelist')
self.create_network(path=os.path.join(currentdir, 'torvalds.edgelist'))
self.populate_network(CounterModel, skill_level='beginner')
print("Agentes: ", list(self.network_agents))
self.find_one(node_id="Torvalds").skill_level = 'God'
self.find_one(node_id="balkian").skill_level = 'developer'
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,

File diff suppressed because one or more lines are too long