Skip to content

Commit

Permalink
Merge pull request #385 from rht/sugarscape
Browse files Browse the repository at this point in the history
Add Sugarscape Constant Growback example
  • Loading branch information
dmasad committed Jul 2, 2017
2 parents ba1058c + 330adbb commit c38681a
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 1 deletion.
4 changes: 3 additions & 1 deletion examples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ Completed code to go along with the [tutorial]() on making a simple model of how
### WolfSheep
Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation model](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation).


### Sugarscape CG
Implementation of Sugarscape 2 Constant Growback model, based on the Netlogo
[Sugarscape 2 Constant Growback](http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback)
61 changes: 61 additions & 0 deletions examples/sugarscape_cg/Readme.md
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.
1 change: 1 addition & 0 deletions examples/sugarscape_cg/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mesa
3 changes: 3 additions & 0 deletions examples/sugarscape_cg/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from sugarscape.server import server

server.launch()
Empty file.
76 changes: 76 additions & 0 deletions examples/sugarscape_cg/sugarscape/agents.py
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])
88 changes: 88 additions & 0 deletions examples/sugarscape_cg/sugarscape/model.py
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))
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions examples/sugarscape_cg/sugarscape/schedule.py
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])
43 changes: 43 additions & 0 deletions examples/sugarscape_cg/sugarscape/server.py
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()

0 comments on commit c38681a

Please sign in to comment.