Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sugarscape Chapter 3- Draft Pull Request #106

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions examples/sugarscape_cg/sugarscape_cg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ def __init__(self, width=50, height=50, initial_population=100):
import numpy as np

sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
agent_id = 0
for _, (x, y) in self.grid.coord_iter():
max_sugar = sugar_distribution[x, y]
sugar = Sugar(agent_id, (x, y), self, max_sugar)
agent_id += 1
sugar = Sugar(self.next_id(), (x, y), self, max_sugar)
self.grid.place_agent(sugar, (x, y))
self.schedule.add(sugar)

Expand All @@ -62,8 +60,9 @@ def __init__(self, width=50, height=50, initial_population=100):
sugar = self.random.randrange(6, 25)
metabolism = self.random.randrange(2, 4)
vision = self.random.randrange(1, 6)
ssa = SsAgent(agent_id, (x, y), self, False, sugar, metabolism, vision)
agent_id += 1
ssa = SsAgent(
self.next_id(), (x, y), self, False, sugar, metabolism, vision
)
self.grid.place_agent(ssa, (x, y))
self.schedule.add(ssa)

Expand Down
43 changes: 43 additions & 0 deletions examples/sugarscape_scc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Sugarscape: Sex, Culture, and Conflict : The Emergence of History

## Summary

This is a work-in-progress implementation of Chapter 3 from *Growing Artificial Societies: Social Science from the Bottom Up.*, adding mechanics such as sexual reproduction, cultural exchange and combat.

This model greatly increases the complexity of interactions between agents, following on from Chapter 2's model.

## Mechanics Introduced

### Sexual Reproduction

We assign agents a trait, **Fertility**, according to which they can perform Agent Rule **S**:
- Select a neighbour at random
- If the neighbor is fertile and of the opposite sex and at least one of the agents has an empty neighboring site (for the baby), then a child is born
- Repeat for all neighbors

### Cultural Processes (WIP)

### Combat (WIP)

This model demonstrates visualisation of agent attributes.
## How to Run

To run the model interactively, run ``mesa runserver`` in this directory. e.g.

`$ mesa runserver`

Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press **Reset**, then **Run**.

## Files

* `sugarscape_scc/agents.py`: Adds chapter-specific mechanics to `sugarscape_cg/agent.py`
* `sugarscape_scc/server.py`: Sets up the interactive server with charts visualising data.
* `sugarscape_scc/model.py`: Defines the Sugarscape model itself
* `run.py`: Launches a model visualization server.

## Further Reading

Epstein, J. M., & Axtell, R. (n.d.). *Growing Artificial Societies*: Social science from the bottom up. Brookings Institution Press, Chapter 3.


The ant sprite is taken from https://openclipart.org/detail/229519/ant-silhouette, with CC0 1.0 license.
3 changes: 3 additions & 0 deletions examples/sugarscape_scc/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from sugarscape_scc.server import server

server.launch(open_browser=True)
Empty file.
146 changes: 146 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import math

import mesa


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(mesa.Agent):
def __init__(
self, unique_id, pos, model, moore=False, sugar=0, metabolism=0, vision=0
):
# first generation agents are randomly assigned metabolism and vision,
# children get a contribution from their parents

super().__init__(unique_id, model)
self.pos = pos
self.moore = moore
self.sugar = sugar
self.metabolism = metabolism
self.vision = vision
self.age = 0
self.age_of_death = self.random.randrange(60, 100)
self.fertility = self.random.randrange(10, 40)
self.gender = self.random.randint(0, 1) # 0 is FEMALE, 1 is MALE

def is_occupied(self, pos):
this_cell = self.model.grid.get_cell_list_contents([pos])
return any(isinstance(agent, SsAgent) for agent in this_cell)

def childbearing_years(self) -> bool:
rht marked this conversation as resolved.
Show resolved Hide resolved
return True

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 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
]
self.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 sex(self):
potential_mates = [
i
for i in self.model.grid.get_neighbors(
self.pos, self.moore, include_center=False, radius=self.vision
)
if
( # also check for childbearing age
(type(i) is SsAgent)
and (i.sugar >= i.fertility)
and (i.gender != self.gender)
)
]

# check for empty spots next to self
empty_cells = [
i
for i in self.model.grid.get_neighborhood(
self.pos, self.moore, include_center=False, radius=1
)
if not self.is_occupied(i)
]
self.random.shuffle(empty_cells)
for neighbor in potential_mates:
if self.sugar < self.fertility:
break

if len(empty_cells) == 0:
continue # placeholder
# check for first empty spot next to neighbor
# if none found iterate to next neighbor
endowment = (self.sugar / 2) + (neighbor.sugar / 2)
self.sugar -= self.sugar / 2
neighbor.sugar -= neighbor.sugar / 2
ssa = SsAgent(
self.model.next_id(),
empty_cells[0],
self.model,
False,
sugar=endowment,
metabolism=self.random.choice([neighbor.metabolism, self.metabolism]),
vision=self.random.choice([neighbor.vision, self.vision]),
)
self.model.grid.place_agent(ssa, empty_cells[0])
self.model.schedule.add(ssa)

# iterate through list

def step(self):
self.move()
self.eat()
if self.sugar >= self.fertility: # also check for childbearing age
self.sex() # sex condition(s)

if (self.sugar <= 0) or (self.age == self.age_of_death):
self.model.grid.remove_agent(self)
self.model.schedule.remove(self) # death conditions
self.age += 1


class Sugar(mesa.Agent):
def __init__(self, unique_id, pos, model, max_sugar, growback_rule=1):
super().__init__(unique_id, model)
self.amount = max_sugar
self.max_sugar = max_sugar
self.growback = growback_rule

def step(self):
self.amount = min([self.max_sugar, self.amount + self.growback])
95 changes: 95 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
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.
"""

from pathlib import Path

import mesa

from .agents import SsAgent, Sugar


class SugarscapeScc(mesa.Model):
"""
Sugarscape 3 SCC
"""

verbose = True # Print-monitoring

def __init__(self, width=50, height=50, initial_population=100):
"""
Create a new Constant Growback model with the given parameters.

Args:
initial_population: Number of population to start with
"""
super().__init__()

# Set parameters
self.width = width
self.height = height
self.initial_population = initial_population
self.schedule = mesa.time.RandomActivationByType(self)
self.grid = mesa.space.MultiGrid(self.width, self.height, torus=False)
self.datacollector = mesa.DataCollector(
model_reporters={"SsAgent": lambda m: m.schedule.get_type_count(SsAgent)},
agent_reporters={
"Age": lambda a: a.age if (isinstance(a, SsAgent)) else None
},
)
# Create sugar
import numpy as np

sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
for _, (x, y) in self.grid.coord_iter():
max_sugar = sugar_distribution[x, y]
sugar = Sugar(self.next_id(), (x, y), self, max_sugar)
self.grid.place_agent(sugar, (x, y))
self.schedule.add(sugar)

# Create agent:
for _ in range(self.initial_population):
x = self.random.randrange(self.width)
y = self.random.randrange(self.height)
sugar = self.random.randrange(50, 100)
metabolism = self.random.randrange(2, 4)
vision = self.random.randrange(1, 6)
ssa = SsAgent(
self.next_id(), (x, y), self, False, sugar, metabolism, vision
)
self.grid.place_agent(ssa, (x, y))
self.schedule.add(ssa)

self.running = True
self.datacollector.collect(self)

def step(self):
self.schedule.step()
# collect data
self.datacollector.collect(self)
if self.verbose:
print([self.schedule.time, self.schedule.get_type_count(SsAgent)])

def run_model(self, step_count=200):
if self.verbose:
print(
"Initial number Sugarscape Agent: ",
self.schedule.get_type_count(SsAgent),
)

for i in range(step_count):
self.step()

if self.verbose:
print("")
print(
"Final number Sugarscape Agent: ",
self.schedule.get_type_count(SsAgent),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import mesa

# TODO implement age graph as well
from .agents import SsAgent, Sugar
from .model import SugarscapeScc

color_dic = {4: "#005C00", 3: "#008300", 2: "#00AA00", 1: "#00F800"}


def SsAgent_portrayal(agent):
if agent is None:
return

if type(agent) is SsAgent:
return {"Shape": "sugarscape_scc/resources/ant.png", "scale": 0.9, "Layer": 1}

elif type(agent) is Sugar:
color = color_dic[agent.amount] if agent.amount != 0 else "#D6F5D6"
return {
"Color": color,
"Shape": "rect",
"Filled": "true",
"Layer": 0,
"w": 1,
"h": 1,
}

return {}


canvas_element = mesa.visualization.CanvasGrid(SsAgent_portrayal, 50, 50, 500, 500)
chart_element = mesa.visualization.ChartModule(
[{"Label": "SsAgent", "Color": "#AA0000"}], data_collector_name="datacollector"
)
bar_graph = mesa.visualization.BarChartModule(
[{"Label": "age", "Color": "#AAAAAA"}], data_collector_name="datacollector"
)
server = mesa.visualization.ModularServer(
SugarscapeScc, [canvas_element, chart_element, bar_graph], "Sugarscape 3 SCC"
)
Loading