mirror of
https://github.com/gsi-upm/soil
synced 2025-08-23 19:52:19 +00:00
WIP: all tests pass
Documentation needs some improvement The API has been simplified to only allow for ONE topology per NetworkEnvironment. This covers the main use case, and simplifies the code.
This commit is contained in:
@@ -10,19 +10,14 @@ 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
|
||||
topology:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 12
|
||||
environment:
|
||||
agents:
|
||||
agent_class: CounterModel
|
||||
topology: default
|
||||
topology: true
|
||||
state:
|
||||
times: 1
|
||||
# In this group we are not specifying any topology
|
||||
@@ -30,25 +25,23 @@ model_params:
|
||||
- name: 'Environment Agent 1'
|
||||
agent_class: BaseAgent
|
||||
group: environment
|
||||
topology: null
|
||||
topology: false
|
||||
hidden: true
|
||||
state:
|
||||
times: 10
|
||||
- agent_class: CounterModel
|
||||
id: 0
|
||||
group: other_counters
|
||||
topology: another_graph
|
||||
group: fixed_counters
|
||||
state:
|
||||
times: 1
|
||||
total: 0
|
||||
- agent_class: CounterModel
|
||||
topology: another_graph
|
||||
group: other_counters
|
||||
group: fixed_counters
|
||||
id: 1
|
||||
distribution:
|
||||
- agent_class: CounterModel
|
||||
weight: 1
|
||||
group: general_counters
|
||||
group: distro_counters
|
||||
state:
|
||||
times: 3
|
||||
- agent_class: AggregatedCounter
|
||||
|
@@ -1,63 +0,0 @@
|
||||
---
|
||||
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
|
@@ -15,6 +15,7 @@ class Fibonacci(FSM):
|
||||
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'''
|
||||
@default_state
|
||||
@@ -23,9 +24,8 @@ class Odds(FSM):
|
||||
self.log('Stopping at {}'.format(self.now))
|
||||
return None, self.env.timeout(1+self.now%2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
from soil import Simulation
|
||||
s = Simulation(network_agents=[{'ids': [0], 'agent_class': Fibonacci},
|
||||
{'ids': [1], 'agent_class': Odds}],
|
||||
|
@@ -8,17 +8,12 @@ interval: 1
|
||||
seed: '1'
|
||||
model_class: social_wealth.MoneyEnv
|
||||
model_params:
|
||||
topologies:
|
||||
default:
|
||||
params:
|
||||
generator: social_wealth.graph_generator
|
||||
n: 5
|
||||
generator: social_wealth.graph_generator
|
||||
agents:
|
||||
topology: true
|
||||
distribution:
|
||||
- agent_class: social_wealth.SocialMoneyAgent
|
||||
topology: default
|
||||
weight: 1
|
||||
mesa_agent_class: social_wealth.MoneyAgent
|
||||
N: 10
|
||||
width: 50
|
||||
height: 50
|
||||
|
@@ -2,6 +2,7 @@ from mesa.visualization.ModularVisualization import ModularServer
|
||||
from soil.visualization import UserSettableParameter
|
||||
from mesa.visualization.modules import ChartModule, NetworkModule, CanvasGrid
|
||||
from social_wealth import MoneyEnv, graph_generator, SocialMoneyAgent
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class MyNetwork(NetworkModule):
|
||||
@@ -13,15 +14,16 @@ 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)}
|
||||
portrayal["nodes"] = [
|
||||
{
|
||||
"id": agent_id,
|
||||
"size": env.get_agent(agent_id).wealth,
|
||||
# "color": "#CC0000" if not agents or agents[0].wealth == 0 else "#007959",
|
||||
"color": "#CC0000",
|
||||
"label": f"{agent_id}: {env.get_agent(agent_id).wealth}",
|
||||
}
|
||||
for (agent_id) in env.G.nodes
|
||||
"id": node_id,
|
||||
"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()
|
||||
|
||||
]
|
||||
|
||||
portrayal["edges"] = [
|
||||
@@ -29,7 +31,6 @@ def network_portrayal(env):
|
||||
for edge_id, (source, target) in enumerate(env.G.edges)
|
||||
]
|
||||
|
||||
|
||||
return portrayal
|
||||
|
||||
|
||||
@@ -55,7 +56,7 @@ def gridPortrayal(agent):
|
||||
}
|
||||
|
||||
|
||||
grid = MyNetwork(network_portrayal, 500, 500, library="sigma")
|
||||
grid = MyNetwork(network_portrayal, 500, 500)
|
||||
chart = ChartModule(
|
||||
[{"Label": "Gini", "Color": "Black"}], data_collector_name="datacollector"
|
||||
)
|
||||
@@ -70,7 +71,6 @@ model_params = {
|
||||
1,
|
||||
description="Choose how many agents to include in the model",
|
||||
),
|
||||
"network_agents": [{"agent_class": SocialMoneyAgent}],
|
||||
"height": UserSettableParameter(
|
||||
"slider",
|
||||
"height",
|
||||
@@ -89,12 +89,15 @@ model_params = {
|
||||
1,
|
||||
description="Grid width",
|
||||
),
|
||||
"network_params": {
|
||||
'generator': graph_generator
|
||||
},
|
||||
"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(
|
||||
|
@@ -10,7 +10,7 @@ from mesa.batchrunner import BatchRunner
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from soil import NetworkAgent, Environment
|
||||
from soil import NetworkAgent, Environment, serialization
|
||||
|
||||
def compute_gini(model):
|
||||
agent_wealths = [agent.wealth for agent in model.agents]
|
||||
@@ -19,15 +19,16 @@ def compute_gini(model):
|
||||
B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x))
|
||||
return (1 + (1/N) - 2*B)
|
||||
|
||||
|
||||
class MoneyAgent(MesaAgent):
|
||||
"""
|
||||
A MESA agent with fixed initial wealth.
|
||||
It will only share wealth with neighbors based on grid proximity
|
||||
"""
|
||||
|
||||
def __init__(self, unique_id, model):
|
||||
def __init__(self, unique_id, model, wealth=1):
|
||||
super().__init__(unique_id=unique_id, model=model)
|
||||
self.wealth = 1
|
||||
self.wealth = wealth
|
||||
|
||||
def move(self):
|
||||
possible_steps = self.model.grid.get_neighborhood(
|
||||
@@ -45,7 +46,7 @@ class MoneyAgent(MesaAgent):
|
||||
self.wealth -= 1
|
||||
|
||||
def step(self):
|
||||
self.info("Crying wolf", self.pos)
|
||||
print("Crying wolf", self.pos)
|
||||
self.move()
|
||||
if self.wealth > 0:
|
||||
self.give_money()
|
||||
@@ -58,8 +59,8 @@ class SocialMoneyAgent(NetworkAgent, MoneyAgent):
|
||||
cellmates = set(self.model.grid.get_cell_list_contents([self.pos]))
|
||||
friends = set(self.get_neighboring_agents())
|
||||
self.info("Trying to give money")
|
||||
self.debug("Cellmates: ", cellmates)
|
||||
self.debug("Friends: ", friends)
|
||||
self.info("Cellmates: ", cellmates)
|
||||
self.info("Friends: ", friends)
|
||||
|
||||
nearby_friends = list(cellmates & friends)
|
||||
|
||||
@@ -68,14 +69,29 @@ class SocialMoneyAgent(NetworkAgent, MoneyAgent):
|
||||
other.wealth += 1
|
||||
self.wealth -= 1
|
||||
|
||||
def graph_generator(n=5):
|
||||
G = nx.Graph()
|
||||
for ix in range(n):
|
||||
G.add_edge(0, ix)
|
||||
return G
|
||||
|
||||
|
||||
class MoneyEnv(Environment):
|
||||
"""A model with some number of agents."""
|
||||
def __init__(self, width, height, *args, topologies, **kwargs):
|
||||
def __init__(self, width, height, N, generator=graph_generator,
|
||||
agent_class=SocialMoneyAgent,
|
||||
topology=None, **kwargs):
|
||||
|
||||
super().__init__(*args, topologies=topologies, **kwargs)
|
||||
generator = serialization.deserialize(generator)
|
||||
agent_class = serialization.deserialize(agent_class, globs=globals())
|
||||
topology = generator(n=N)
|
||||
super().__init__(topology=topology,
|
||||
N=N,
|
||||
**kwargs)
|
||||
self.grid = MultiGrid(width, height, False)
|
||||
|
||||
self.populate_network(agent_class=agent_class)
|
||||
|
||||
# Create agents
|
||||
for agent in self.agents:
|
||||
x = self.random.randrange(self.grid.width)
|
||||
@@ -87,17 +103,9 @@ class MoneyEnv(Environment):
|
||||
agent_reporters={"Wealth": "wealth"})
|
||||
|
||||
|
||||
def graph_generator(n=5):
|
||||
G = nx.Graph()
|
||||
for ix in range(n):
|
||||
G.add_edge(0, ix)
|
||||
return G
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
G = graph_generator()
|
||||
fixed_params = {"topology": G,
|
||||
fixed_params = {"generator": nx.complete_graph,
|
||||
"width": 10,
|
||||
"network_agents": [{"agent_class": SocialMoneyAgent,
|
||||
'weight': 1}],
|
||||
@@ -116,4 +124,3 @@ if __name__ == '__main__':
|
||||
run_data = batch_run.get_model_vars_dataframe()
|
||||
run_data.head()
|
||||
print(run_data.Gini)
|
||||
|
||||
|
@@ -126,7 +126,7 @@ class Patron(FSM, NetworkAgent):
|
||||
success depend on both agents' openness.
|
||||
'''
|
||||
if force or self['openness'] > self.random.random():
|
||||
self.model.add_edge(self, other_agent)
|
||||
self.add_edge(self, other_agent)
|
||||
self.info('Made some friend {}'.format(other_agent))
|
||||
return True
|
||||
return False
|
||||
|
@@ -57,7 +57,7 @@ class Male(RabbitModel):
|
||||
|
||||
|
||||
class Female(RabbitModel):
|
||||
gestation = 100
|
||||
gestation = 30
|
||||
|
||||
@state
|
||||
def fertile(self):
|
||||
@@ -72,10 +72,10 @@ class Female(RabbitModel):
|
||||
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.debug('I am pregnant')
|
||||
self.age += 1
|
||||
self.pregnancy += 1
|
||||
|
||||
@@ -88,7 +88,6 @@ class Female(RabbitModel):
|
||||
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:
|
||||
@@ -113,7 +112,7 @@ class RandomAccident(BaseAgent):
|
||||
level = logging.INFO
|
||||
|
||||
def step(self):
|
||||
rabbits_alive = self.model.topology.number_of_nodes()
|
||||
rabbits_alive = self.model.G.number_of_nodes()
|
||||
|
||||
if not rabbits_alive:
|
||||
return self.die()
|
||||
@@ -121,10 +120,15 @@ class RandomAccident(BaseAgent):
|
||||
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:
|
||||
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))
|
||||
|
||||
if __name__ == '__main__':
|
||||
from soil import easy
|
||||
sim = easy('rabbits.yml')
|
||||
sim.run()
|
||||
|
@@ -10,18 +10,16 @@ max_time: 100
|
||||
model_class: soil.environment.Environment
|
||||
model_params:
|
||||
agents:
|
||||
topology: default
|
||||
topology: true
|
||||
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
|
||||
topology: false
|
||||
hidden: true
|
||||
state:
|
||||
group: environment
|
||||
@@ -29,13 +27,12 @@ model_params:
|
||||
group: network
|
||||
mating_prob: 0.1
|
||||
prob_death: 0.001
|
||||
topologies:
|
||||
default:
|
||||
topology:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
topology:
|
||||
fixed:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
extra:
|
||||
visualization_params: {}
|
||||
|
@@ -10,18 +10,16 @@ max_time: 100
|
||||
model_class: soil.environment.Environment
|
||||
model_params:
|
||||
agents:
|
||||
topology: default
|
||||
topology: true
|
||||
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
|
||||
topology: false
|
||||
hidden: true
|
||||
state:
|
||||
group: environment
|
||||
@@ -29,13 +27,12 @@ model_params:
|
||||
group: network
|
||||
mating_prob: 0.1
|
||||
prob_death: 0.001
|
||||
topologies:
|
||||
default:
|
||||
topology:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
topology:
|
||||
fixed:
|
||||
directed: true
|
||||
links: []
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
extra:
|
||||
visualization_params: {}
|
||||
|
@@ -4,7 +4,6 @@ Example of a fully programmatic simulation, without definition files.
|
||||
'''
|
||||
from soil import Simulation, agents
|
||||
from soil.time import Delta
|
||||
import logging
|
||||
|
||||
|
||||
|
||||
@@ -40,5 +39,4 @@ s = Simulation(name='Programmatic',
|
||||
dry_run=True)
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
envs = s.run()
|
||||
|
Reference in New Issue
Block a user