forked from projectmesa/mesa
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Sugarscape Constant Growback example
- Loading branch information
Showing
11 changed files
with
404 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Sugarscape Constant Growback model | ||
|
||
## Summary | ||
|
||
This is Epstein & Axtell's Sugarscape Constant Growback model, with a detailed | ||
description in the chapter 2 of Growing Artificial Societies: Social Science from the Bottom Up | ||
|
||
A simple ecological model, consisting of two agent types: ants, and sugar | ||
patches. | ||
|
||
The ants wander around according to Epstein's rule M: | ||
- Look out as far as vision pennits in the four principal lattice directions and identify the unoccupied site(s) having the most sugar. The order in which each agent search es the four directions is random. | ||
- If the greatest sugar value appears on multiple sites then select the nearest one. That is, if the largest sugar within an agent s vision is four, but the value occurs twice, once at a lattice position two units away and again at a site three units away, the former is chosen. If it appears at multiple sites the same distance away, the first site encountered is selected (the site search order being random). | ||
- Move to this site. Notice that there is no distinction between how far an agent can move and how far it can see. So, if vision equals 5, the agent can move up to 5 lattice positions north , south, east, or west. | ||
- Collect all the sugar at this new position. | ||
|
||
The sugar patches grow at a constant rate of 1 until it reaches maximum capacity. If ant metabolizes to the point it has zero or negative sugar, it dies. | ||
|
||
|
||
The model is tests and demonstrates several Mesa concepts and features: | ||
- MultiGrid | ||
- Multiple agent types (ants, sugar patches) | ||
- Overlay arbitrary text (wolf's energy) on agent's shapes while drawing on CanvasGrid | ||
- Dynamically removing agents from the grid and schedule when they die | ||
|
||
## Installation | ||
|
||
To install the dependencies use pip and the requirements.txt in this directory. e.g. | ||
|
||
``` | ||
$ pip install -r requirements.txt | ||
``` | ||
|
||
## How to Run | ||
|
||
To run the model interactively, run ``run.py`` in this directory. e.g. | ||
|
||
``` | ||
$ python run.py | ||
``` | ||
|
||
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run. | ||
|
||
## Files | ||
|
||
* ``sugarscape/agents.py``: Defines the SsAgent, and Sugar agent classes. | ||
* ``sugarscape/schedule.py``: This is exactly based on wolf_sheep/schedule.py. | ||
* ``sugarscape/model.py``: Defines the Sugarscape Constant Growback model itself | ||
* ``sugarscape/server.py``: Sets up the interactive visualization server | ||
* ``run.py``: Launches a model visualization server. | ||
|
||
## Further Reading | ||
|
||
This model is based on the Netlogo Sugarscape 2 Constant Growback: | ||
|
||
Li, J. and Wilensky, U. (2009). NetLogo Sugarscape 2 Constant Growback model. | ||
http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback. | ||
Center for Connected Learning and Computer-Based Modeling, | ||
Northwestern University, Evanston, IL. | ||
|
||
The ant sprite is taken from https://openclipart.org/detail/229519/ant-silhouette, with CC0 1.0 license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mesa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from sugarscape.server import server | ||
|
||
server.launch() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import random | ||
import math | ||
|
||
from mesa import Agent | ||
|
||
|
||
def get_distance(pos_1, pos_2): | ||
""" Get the distance between two point | ||
Args: | ||
pos_1, pos_2: Coordinate tuples for both points. | ||
""" | ||
x1, y1 = pos_1 | ||
x2, y2 = pos_2 | ||
dx = x1 - x2 | ||
dy = y1 - y2 | ||
return math.sqrt(dx ** 2 + dy ** 2) | ||
|
||
|
||
class SsAgent(Agent): | ||
def __init__(self, pos, model, moore=False, sugar=0, metabolism=0, vision=0): | ||
super().__init__(pos, model) | ||
self.pos = pos | ||
self.moore = moore | ||
self.sugar = sugar | ||
self.metabolism = metabolism | ||
self.vision = vision | ||
|
||
def get_sugar(self, pos): | ||
this_cell = self.model.grid.get_cell_list_contents([pos]) | ||
for agent in this_cell: | ||
if type(agent) is Sugar: | ||
return agent | ||
|
||
def is_occupied(self, pos): | ||
this_cell = self.model.grid.get_cell_list_contents([pos]) | ||
return len(this_cell) > 1 | ||
|
||
def move(self): | ||
# Get neighborhood within vision | ||
neighbors = [i for i in self.model.grid.get_neighborhood(self.pos, self.moore, | ||
False, radius=self.vision) if not self.is_occupied(i)] | ||
neighbors.append(self.pos) | ||
# Look for location with the most sugar | ||
max_sugar = max([self.get_sugar(pos).amount for pos in neighbors]) | ||
candidates = [pos for pos in neighbors if self.get_sugar(pos).amount == | ||
max_sugar] | ||
# Narrow down to the nearest ones | ||
min_dist = min([get_distance(self.pos, pos) for pos in candidates]) | ||
final_candidates = [pos for pos in candidates if get_distance(self.pos, | ||
pos) == min_dist] | ||
random.shuffle(final_candidates) | ||
self.model.grid.move_agent(self, final_candidates[0]) | ||
|
||
def eat(self): | ||
sugar_patch = self.get_sugar(self.pos) | ||
self.sugar = self.sugar - self.metabolism + sugar_patch.amount | ||
sugar_patch.amount = 0 | ||
|
||
def step(self): | ||
self.move() | ||
self.eat() | ||
if self.sugar <= 0: | ||
self.model.grid._remove_agent(self.pos, self) | ||
self.model.schedule.remove(self) | ||
|
||
|
||
class Sugar(Agent): | ||
def __init__(self, pos, model, max_sugar): | ||
super().__init__(pos, model) | ||
self.amount = max_sugar | ||
self.max_sugar = max_sugar | ||
|
||
def step(self): | ||
self.amount = min([self.max_sugar, self.amount + 1]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
''' | ||
Sugarscape Constant Growback Model | ||
================================ | ||
Replication of the model found in Netlogo: | ||
Li, J. and Wilensky, U. (2009). NetLogo Sugarscape 2 Constant Growback model. | ||
http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback. | ||
Center for Connected Learning and Computer-Based Modeling, | ||
Northwestern University, Evanston, IL. | ||
''' | ||
|
||
import random | ||
|
||
from mesa import Model | ||
from mesa.space import MultiGrid | ||
from mesa.datacollection import DataCollector | ||
|
||
from sugarscape.agents import SsAgent, Sugar | ||
from sugarscape.schedule import RandomActivationByBreed | ||
|
||
|
||
class Sugarscape2ConstantGrowback(Model): | ||
''' | ||
Sugarscape 2 Constant Growback | ||
''' | ||
|
||
verbose = True # Print-monitoring | ||
|
||
def __init__(self, height=50, width=50, | ||
initial_population=100): | ||
''' | ||
Create a new Constant Growback model with the given parameters. | ||
Args: | ||
initial_population: Number of population to start with | ||
''' | ||
|
||
# Set parameters | ||
self.height = height | ||
self.width = width | ||
self.initial_population = initial_population | ||
|
||
self.schedule = RandomActivationByBreed(self) | ||
self.grid = MultiGrid(self.height, self.width, torus=False) | ||
self.datacollector = DataCollector({"SsAgent": lambda m: m.schedule.get_breed_count(SsAgent), }) | ||
|
||
# Create sugar | ||
import numpy as np | ||
sugar_distribution = np.genfromtxt("sugarscape/sugar-map.txt") | ||
for _, x, y in self.grid.coord_iter(): | ||
max_sugar = sugar_distribution[x, y] | ||
sugar = Sugar((x, y), self, max_sugar) | ||
self.grid.place_agent(sugar, (x, y)) | ||
self.schedule.add(sugar) | ||
|
||
# Create agent: | ||
for i in range(self.initial_population): | ||
x = random.randrange(self.width) | ||
y = random.randrange(self.height) | ||
sugar = random.randrange(6, 25) | ||
metabolism = random.randrange(2, 4) | ||
vision = random.randrange(1, 6) | ||
ssa = SsAgent((x, y), self, False, sugar, metabolism, vision) | ||
self.grid.place_agent(ssa, (x, y)) | ||
self.schedule.add(ssa) | ||
|
||
self.running = True | ||
|
||
def step(self): | ||
self.schedule.step() | ||
self.datacollector.collect(self) | ||
if self.verbose: | ||
print([self.schedule.time, | ||
self.schedule.get_breed_count(SsAgent)]) | ||
|
||
def run_model(self, step_count=200): | ||
|
||
if self.verbose: | ||
print('Initial number Sugarscape Agent: ', | ||
self.schedule.get_breed_count(SsAgent)) | ||
|
||
for i in range(step_count): | ||
self.step() | ||
|
||
if self.verbose: | ||
print('') | ||
print('Final number Sugarscape Agent: ', | ||
self.schedule.get_breed_count(SsAgent)) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import random | ||
from collections import defaultdict | ||
|
||
from mesa.time import RandomActivation | ||
|
||
|
||
class RandomActivationByBreed(RandomActivation): | ||
''' | ||
A scheduler which activates each type of agent once per step, in random | ||
order, with the order reshuffled every step. | ||
This is equivalent to the NetLogo 'ask breed...' and is generally the | ||
default behavior for an ABM. | ||
Assumes that all agents have a step() method. | ||
''' | ||
agents_by_breed = defaultdict(list) | ||
|
||
def __init__(self, model): | ||
super().__init__(model) | ||
self.agents_by_breed = defaultdict(list) | ||
|
||
def add(self, agent): | ||
''' | ||
Add an Agent object to the schedule | ||
Args: | ||
agent: An Agent to be added to the schedule. | ||
''' | ||
|
||
self.agents.append(agent) | ||
agent_class = type(agent) | ||
self.agents_by_breed[agent_class].append(agent) | ||
|
||
def remove(self, agent): | ||
''' | ||
Remove all instances of a given agent from the schedule. | ||
''' | ||
|
||
while agent in self.agents: | ||
self.agents.remove(agent) | ||
|
||
agent_class = type(agent) | ||
while agent in self.agents_by_breed[agent_class]: | ||
self.agents_by_breed[agent_class].remove(agent) | ||
|
||
def step(self, by_breed=True): | ||
''' | ||
Executes the step of each agent breed, one at a time, in random order. | ||
Args: | ||
by_breed: If True, run all agents of a single breed before running | ||
the next one. | ||
''' | ||
if by_breed: | ||
for agent_class in self.agents_by_breed: | ||
self.step_breed(agent_class) | ||
self.steps += 1 | ||
self.time += 1 | ||
else: | ||
super().step() | ||
|
||
def step_breed(self, breed): | ||
''' | ||
Shuffle order and run all agents of a given breed. | ||
Args: | ||
breed: Class object of the breed to run. | ||
''' | ||
agents = self.agents_by_breed[breed] | ||
random.shuffle(agents) | ||
for agent in agents: | ||
agent.step() | ||
|
||
def get_breed_count(self, breed_class): | ||
''' | ||
Returns the current number of agents of certain breed in the queue. | ||
''' | ||
return len(self.agents_by_breed[breed_class]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from mesa.visualization.ModularVisualization import ModularServer | ||
from mesa.visualization.modules import CanvasGrid, ChartModule | ||
|
||
from sugarscape.agents import SsAgent, Sugar | ||
from sugarscape.model import Sugarscape2ConstantGrowback | ||
|
||
color_dic = {4: "#005C00", | ||
3: "#008300", | ||
2: "#00AA00", | ||
1: "#00F800"} | ||
|
||
|
||
def SsAgent_portrayal(agent): | ||
if agent is None: | ||
return | ||
|
||
portrayal = {} | ||
|
||
if type(agent) is SsAgent: | ||
portrayal["Shape"] = "sugarscape/resources/ant.png" | ||
portrayal["scale"] = 0.9 | ||
portrayal["Layer"] = 1 | ||
|
||
elif type(agent) is Sugar: | ||
if agent.amount != 0: | ||
portrayal["Color"] = color_dic[agent.amount] | ||
else: | ||
portrayal["Color"] = "#D6F5D6" | ||
portrayal["Shape"] = "rect" | ||
portrayal["Filled"] = "true" | ||
portrayal["Layer"] = 0 | ||
portrayal["w"] = 1 | ||
portrayal["h"] = 1 | ||
|
||
return portrayal | ||
|
||
|
||
canvas_element = CanvasGrid(SsAgent_portrayal, 50, 50, 500, 500) | ||
chart_element = ChartModule([{"Label": "SsAgent", "Color": "#AA0000"}]) | ||
|
||
server = ModularServer(Sugarscape2ConstantGrowback, [canvas_element, chart_element], | ||
"Sugarscape 2 Constant Growback") | ||
# server.launch() |
Oops, something went wrong.