My colleague Dr. Julia K. Clark and I recently published a model in the multidisciplinary journal land based on household fission/fusion dynamics in Mongolia. It’s entitled “Examining Social Adaptations in a Volatile Landscape in Northern Mongolia via the Agent-Based Model Ger Grouper”and you can read it here. We present a fairly streamlined model with a clear goal.
The question is, though, how do you get from point A to point X?
A secret is, most models do not spring forth from a modeler’s head fully formed. They are created in increments and along the way things change, plans evolve, and workflow shifts. In this post today I aim to show you how we created a model from a sketchy start to published article.
The beginning:
Dr. Clark is an archaeologist working on Bronze Age habitations in Northern Mongolia. You can read her dissertation here. I have had the privilege of working with her for many years, recently going to Mongolia with her to help survey and excavate archaeological cites, and do ethnoarchaeologial work with modern nomads. From these experiences we developed an agent-based model.
One thing we saw happen in Mongolia was the combining of households to reduce risk. When I was interviewing a family I learned that the daughter’s house (a ger, the Mongolian name for yurt) had burnt down, so she had come to live with her parents. This risk mitigation strategy meant that the two households combined—the flocks of sheep and goats combined, the amount of owned resources combined. When the daughter, her kids and her husband eventually were able to move, the resources would have been divided between parent and child household.
Events like this are virtually impossible to see in the archaeological record, but we realized that these strategies have lasted for at least 100 years, and likely for many centuries previously. People in Mongolia have been dealing with unpredictable weather events for as long as they’ve lived there. There are large winter events known as dzuds: imagine the worst blizzard you’ve been in, and make it worse. The Mongolian people have come up with a complex system of exchange, kinship, and pasture management to deal with the harsh climate. We wanted to know what rules evolved over time to ensure survival. We decided to model it.
We created all of our models in Net Logo. All of the code for these models is at the end of this tutorial so you can see our thought process.
Model 1: Static Population Random Walk Model

This first model looks almost nothing like the finished model. It’s really a messy toy model that was used to begin to explore the problem. Our first question was:
Do we see households aggregated near each other, and can this aggregation tell us anything about household sharing?
To create a simplified model we allowed population to remain static. We gave the households very simplified rules, allowing for movement in spring and winter, reproduction, and resource acquisition. The household ate the grass it landed on (simulating herds eating grass) and they would move again in the next season. The amount of energy a Ger received from consuming grass was static and set within the model.
We ran this model multiple times to look at aggregation. We realized that we essentially just created a model of random walks, but this random walk model provided the basis for where we went from there.
Model 2: Changing population and the emergence of a useful model

This model looks very similar to the above model, but instead of having population replacement we allow for ‘sexual’ reproduction. When a household accrues above a certain storage threshold they probabilistically reproduce, dividing their resources between themselves and their offspring.
We also allowed a new parameter which controlled how much of the landscape would be productive. It would be set at the beginning of the model and would stay the same throughout the run. While this was an improvement over Model 1, we realized that with this model the landscape wasn’t really reacting to household usage: it was fairly static from start to finish. There were patches of productivity and patches of no productivity. We wanted to see what would happen with unpredictable events, like the gigantic Mongolian winter storms known as dzuds.
We did some quick analyses with the “Index of Dispersion” statistic (for more info look here) and the following graphic shows a scientific poster we created for the presentation of this simplified model.

We realized we essentially created the nullest of null models. Nothing in reality looked like what we produced in this model, but it created a great stepping-stone to adding on complexity. Moreover, by doing multiple tests on the model we were able to ensure we truly understood what we had coded.
Model 3: The Final Model

There were several things we were unsatisfied with Model 2.
First, we wanted to know if people would group together, but in the previous model groupage was not intentional. It was completely random. While this is useful for letting us know if households being close together is random or a product of social choice, it didn’t help us get at risk management. For that we needed a way for households to intentionally choose to cooperate with another household.
We realized we needed to explore creating different strategies for different groups of agents. Thus we used the “breeds” option native in NetLogo to create four strategies related to sharing. These strategies corresponded to the probability of a household asking for help from another household when their storage got below a certain level. We created four strategies:
Lineage A: 100% cooperation
Lineage B: 50% cooperation
Lineage C: 25% cooperation
Lineage D: 0% cooperation
This way we could directly examine the survival of different sharing strategies in different environments. Would always sharing ever be a good option? Likewise, would never sharing be a good option? You can read the paper to see our results, but by creating different lineages we could examine this.
Second, in our research we realized that people could live pretty much anywhere during the summer. In the winter, however, people couldn’t live just anywhere; during ethnographic interviews families talked about areas they would go to when winters weren’t harsh, and areas they’d go to when winters were harsh. For our model we wanted to create distinct areas for people to live in. Thus we divided the landscape into two halves, summer and winter, and allowed the Gers to ‘teleport’ between the two halves, and then use their usual move function once in the summer or winter patches. This, then, mimics the long-distance movement we noted in ethnographic interviews. In the winter patches we allowed for ‘green’ patches (healthy grass), ‘brown’ patches (patches that grass could grow on but were currently denuded), and ‘grey’ patches (to symbolize areas where grass couldn’t grow—rocky outcrops, frozen lakes, etc). By changing the parameter that corresponds to the amount of grey patches we can simulate different productive or unproductive winter environments.
We also allowed the households to use memory to return to previously good winter patches. This meant that a household that previously teleported on a green patch would remember it. This reduces the probability of landing on a grey patch. While it’s true that they could return to that patch and it would be dead, a green or brown patch is likely to abut another green patch, reducing the odds of landing in a truly bad patch.
We added multiple variables for the final version of Ger Grouper:
Ger reproduce (the probability of reproducing at any timestep)
N (The number of each lineage to be seeded at the start of the simulation)
Patch-variability (the amount of winter patches we allow to be dead)
Ger-gain from food: The amount of energy that each household gets from consuming grass
Grass-regrowth time: how long it takes for grass to regrow once eaten
Energy-loss from dead patches: How much each household is charged for landing on unproductive patches.
Results:
Sharing strategies are stable and useful for different environments, and ‘restricted sharing’ practices are the most optimal. However, in some environments all-share and no-share are better than restricted sharing.
For the full results and for what that might mean for Mongolia read the article here.
Now here’s the massive amounts of code. Be gentle; some of this is poorly documented or not exactly what we were hoping for when we began. But hopefully it shows you my thought process, and you should be able to cut and paste into NetLogo and get it to run.
Model 1:
globals [
run-count
]
breed [
ger
gers
]
turtles-own [
trait
energy
]
patches-own [ ]
to setup
let saved-run-count run-count
ca ;; shorthand for “clear all”
set run-count saved-run-count + 1
crt N ;; creating N turtles; this will be controlled by a slider at the GUI
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set label trait
]
;; this section randomizes the patches so that at initialization ‘patchiness’ is set randomly
;; The “Z” slider at the getgo makes it so the user can make the land more lush (a lower number for Z) or more sparse (a higher number)
;; this is based on “patch clusters” in the netlogo code library
;; this identifies one patch, colors it green, repeats this 50 times randomly in the landscape and asks
;; random patches to color as their neighbors are
ask patches
[ set pcolor green + green * random Z ]
repeat 50
[ ask patches
[set pcolor [pcolor] of one-of neighbors4 ] ]
ask patches
[
if pcolor != green [set pcolor brown]
]
reset-ticks
end
to go
if not any? turtles [ stop ] ;; this is so that if all our turtles die the sim doesn’t keep moving
move_spring
move_autumn
if ticks >= 10
[ stop ] ;; since population is held constant, just having a few ticks is fine to test fission/fusion
end
;; set heading randomly away from current patch
;; this is done because in the ethnography, Mongolians are much less constrained to where they can live in spring
;; to simulate moving to a completely different area, they randomly move in a direction
;; and are not constrained as to what productivity the patches has
;; instead of making the world flip between green in spring and a patchy green/brown they can simply live anywhere in spring
to move_spring
ask turtles
[
left random 360
forward 10
]
;; because movement is divided into two seasons, spring and winter, file writing has to happen in each “move” space
;; this file writes both the spring patterns
;; and the autumn patterns of the turtles
;; by recording the tick
;; the individual Ger
;; and the x and y coordinates
;; if ticks = 8 [
;; ask turtles
;; [
;; file-open “GerGrouper1.txt”
;; file-type (word ticks “,”)
;; file-type (word who “,”)
;; file-type (word xcor “,”)
;; file-print ycor
;; file-close
;; ]
;; ]
tick
end
to move_autumn
;; Here, the turtles move in a random direction away from the patch they were on during the spring.
;; They are constrained during autumn to try and live on a green patch.
;; If the patch is brown, a.k.a dead
;; The turtle looks at its Von Neuman neighborhood.
;; If they find a patch in the neighborhood that’s green, then they move there and are rewarded 1 energy.
;; If they don’t, they are deducted 1 energy.
;; Turtles die by having their energy dip below 0 (defined below under “death”).
ask turtles
[
left random 360
forward 10
]
ask turtles [
;; if previous tick
;; pcolor = green [
;;move to that patch
;; ]
rt random 360
forward 30
]
ask turtles [
if pcolor = brown [
ifelse any? neighbors4 with [ pcolor = green ]
[
move-to one-of neighbors4 with [ pcolor = green ]
set energy energy + 1 ;; add energy if they land on a green patch
]
[
set energy energy – 1 ;; deduct energy if they don’t end up on a green patch
]
]
death
;; this file writes both the autumn patterns of the turtles
;; by recording the run count, the seed, number of turtles, tick
;; the individual Ger ID
;; and the x and y coordinates
]
ask turtles [
if ticks = 9 [
file-open “GerGrouper_Final.txt”
file-type (word run-count “,”)
file-type (word seed “,”)
file-type (word N “,”)
file-type (word ticks “,”)
file-type (word who “,”)
file-type (word xcor “,”)
file-print ycor
file-close
]
]
tick
end
;; this is how turtles die at the end of autumn and how they reproduce
;; since reproduction is just replacement
to death ;; turtle procedure
; when energy dips below zero, die
if energy < 0 [
hatch 1
setxy random-xcor random-ycor
set trait random 1000
set label trait
die
]
end
Model 2:
globals [ ]
breed [
ger
gers
]
turtles-own [
trait
energy
]
patches-own [ ]
to setup
ca ;; shorthand for “clear all”
crt N ;; creating N turtles; this will be controlled by a slider at the GUI
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set label trait
]
;; this section randomizes the patches so that at initialization ‘patchiness’ is set randomly
;; The “Z” slider at the getgo makes it so the user can make the land more lush (a lower number for Z) or more sparse (a higher number)
ask patches
[ set pcolor green + green * random Z ]
repeat 50
[ ask patches
[set pcolor [pcolor] of one-of neighbors4 ] ]
ask patches
[
if pcolor != green [set pcolor brown]
]
reset-ticks
end
to go
if not any? turtles [ stop ] ;; this is so that if all our turtles die the sim doesn’t keep moving
move_spring
move_autumn
if ticks >= 100
[ stop ] ;; 100 ticks = 50 years, a suitable amount of time to see fission/fusion
end
;; set heading randomly away from current patch
;; this is done because in the ethnography, Mongolians are much less constrained to where they can live in spring
;; to simulate moving to a completely different area, they randomly move in a direction
;; and are not constrained as to what productivity the patches has
;; instead of making the world flip between green in spring and a patchy green/brown they can simply live anywhere in spring
to move_spring
ask turtles
[
left random 360
forward 20
]
;; because movement is divided into two seasons, spring and winter, file writing has to happen in each “move” space
;; this file writes both the spring patterns
;; and the autumn patterns of the turtles
;; by recording the tick
;; the individual Ger
;; and the x and y coordinates
ask turtles
[
file-open “GerGrouper.txt”
file-type (word ticks “,”)
file-type (word who “,”)
file-type (word xcor “,”)
file-print ycor
file-close
]
tick
end
to move_autumn
;; Here, the turtles move in a random direction away from the patch they were on during the spring.
;; They are constrained during autumn to try and live on a green patch.
;; If the patch is brown, a.k.a dead
;; The turtle looks at its Von Neuman neighborhood.
;; If they find a patch in the neighborhood that’s green, then they move there and are rewarded 1 energy.
;; If they don’t, they are deducted 1 energy.
;; Turtles die by having their energy dip below 0 (defined below under “death”).
ask turtles
[
rt random 360
forward 10
]
ask turtles [
if pcolor = brown [
ifelse any? neighbors4 with [ pcolor = green ]
[
move-to one-of neighbors4 with [ pcolor = green ]
set energy energy + 1 ;; add energy if they land on a green patch
]
[
set energy energy – 1 ;; deduct energy if they don’t end up on a green patch
]
death
reproduce-gers
;; this is the same file writing procedure as above, but instantiated in autumn after death and reproduction have happened
file-open “GerGrouper.txt”
file-type (word ticks “,”)
file-type (word who “,”)
file-type (word xcor “,”)
file-print ycor
file-close
]
]
tick
end
;; this procedure is how a turtle reproduces. The “gers-reproduce” slider is on the GUI and shows the percent likelihood of reproduction
to reproduce-gers ;; procedure for turtles
if random-float 100 < gers-reproduce [ ;; throw “dice” to see if you will reproduce
set energy (energy / 2) ;; divide energy between parent and offspring
hatch 1 [ rt random-float 360 fd 1 ]
set trait random 1000
set label trait ;; hatch an offspring and move it forward 1 step, then give it a random label so it isn’t labeled like its parents
]
end
;; this is how turtles die at the end of autumn
to death ;; turtle procedure
; when energy dips below zero, die
if energy < 0 [ die ]
end
Model 3:
globals [
grass
summer-patches
winter-patches
]
breed [
lineageA ;; 100% cooperation
]
breed [
lineageB ;; 50% cooperation
]
breed [
lineageC ;; 25% cooperation
]
breed [
lineageD ;; 0% cooperation
]
turtles-own [
visited-patches
energy
trait
]
patches-own [countdown]
to setup
ca ;; shorthand for “clear all”
create-lineageA N
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set label lineageA
set energy 20
set color blue
set visited-patches (list patch-here)
]
create-lineageB N
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set label lineageB
set energy 20
set color pink
set visited-patches (list patch-here)
]
create-lineageC N
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set label trait
set label lineageC
set energy 20
set color red
set visited-patches (list patch-here)
]
create-lineageD N
[
set shape “ger”
set size 3
setxy random-xcor random-ycor
set trait random 1000
set energy 20
set label lineageD
set color yellow
set visited-patches (list patch-here)
]
set summer-patches patches with [ pxcor < 0 ]
ask summer-patches [ set pcolor green ] ;;one-of [ green brown ]]
set winter-patches patches with [ pxcor >= 0 ]
ask winter-patches [ set countdown random grass-regrowth-time ;; initialize grass grow clocks randomly
set pcolor one-of [green brown brown grey grey grey] ;;equal amount dead patches as patches that can be living
]
ask patches [
set countdown random grass-regrowth-time ;; initialize grass grow clocks randomly
]
;; ]
file-open “ger_grouper2.csv”
file-close
reset-ticks
;; tick
end
to go
if not any? turtles [ stop ]
if ticks >= 500 [ stop ]
move_spring
ask turtles [
set energy energy – energy-loss-from-dead-patches ;; deduct energy if they land on a bad patch
eat-grass
]
write-spring
move_autumn
ask turtles[
set energy energy – energy-loss-from-dead-patches ;; deduct energy if they land on a bad patch
eat-grass
update-history ]
ask lineageA [
if energy <= 10 [
ask one-of lineageA in-radius 5 [
set energy energy + 10 ]
set energy energy – 10
]
]
ask lineageB [
if energy <= 10 [
if random-float 100 < 50 [
ask one-of lineageB in-radius 5 [
set energy energy + 10 ]
set energy energy – 10
]
]
]
ask lineageC [
if energy <= 10 [
if random-float 100 < 25 [
ask one-of lineageC in-radius 5 [
set energy energy + 10 ]
set energy energy – 10
]
]
]
write-autumn
ask patches
[ grow-grass ]
set grass count patches with [pcolor = green]
if count turtles >= 500
[ stop ]
ask patches
[ variability ]
end
to move_spring
ask turtles
[
move-to-empty-one-of summer-patches
if pcolor != green [
ifelse any? neighbors with [ pcolor = green ]
[
move-to one-of neighbors with [ pcolor = green ]
set energy energy – 1 + ger-gain-from-food
]
[
set energy energy – energy-loss-from-dead-patches ;; deduct energy if they don’t end up on a green patch
]
]
]
tick
end
to move_autumn
ask turtles [
if ticks >= 2 [
move-to one-of visited-patches
move-to-empty-one-of winter-patches
]
]
; ask patch
; if the patch is brown, a.k.a dead
; look at your neighborhood
; if you find a patch in the neighborhood that’s green move there
ask turtles [
if pcolor != green [
ifelse any? neighbors with [ pcolor = green ]
[
move-to one-of neighbors with [ pcolor = green ]
set energy energy – 1 + ger-gain-from-food
]
[
set energy energy – energy-loss-from-dead-patches ;; deduct energy if they don’t end up on a green patch
]
set label round energy
death
reproduce-gers
]
]
tick
end
to eat-grass ;; get procedure
;; get eat grass, turn the patch brown
if pcolor = green [
set pcolor brown
set energy energy + ger-gain-from-food ;; ger gain energy by eating
]
end
to variability
if random-float 100 < patch-variability
[
if pcolor = green [
set pcolor brown ]
]
;; ]
end
to reproduce-gers ;; ger procedure
if energy >= 20 [
if random-float 100 < gerreproduce [ ;; throw “dice” to see if you will reproduce
set energy (energy / 2) ;; divide energy between parent and offspring
hatch 1 [ rt random-float 360 fd 1 ]
]
]
end
to death ;; turtle procedure
; when energy dips below zero, die
if energy < 5 [ die ]
end
to grow-grass ;; patch procedure
;; countdown on brown patches: if reach 0, grow some grass
if pcolor = brown [
ifelse countdown <= 0
[ set pcolor green
set countdown grass-regrowth-time ]
[ set countdown countdown – 1 ]
]
end
to move-to-empty-one-of [locations] ;; turtle procedure
move-to one-of locations
while [any? other turtles-here] [
move-to one-of locations
]
end
;; here the gers remember the last 4 patches they went to that were green
;; since update-history is only called in the winter
;; the gers only remember the last few winter camps that were productive
to update-history
if pcolor = green
[
set visited-patches (lput patch-here visited-patches)
]
end
to write-autumn
file-open “ger_grouper2.csv”
file-type (word behaviorspace-run-number “,”)
file-type (word ticks “,”)
file-type (word seed “,”)
file-type (word count lineageA”,”)
file-type (word count lineageB”,”)
file-type (word count lineageC”,”)
file-type (word count lineageD”,”)
file-type (word N “,”)
file-type (word gerreproduce “,”)
file-type (word patch-variability “,”)
file-type (word ger-gain-from-food “,”)
file-type (word grass-regrowth-time “,”)
file-print (word energy-loss-from-dead-patches “,”)
file-close
end
to write-spring
file-open “ger_grouper2.csv”
file-type (word behaviorspace-run-number “,”)
file-type (word ticks “,”)
file-type (word seed “,”)
file-type (word count lineageA”,”)
file-type (word count lineageB”,”)
file-type (word count lineageC”,”)
file-type (word count lineageD”,”)
file-type (word N “,”)
file-type (word gerreproduce “,”)
file-type (word patch-variability “,”)
file-type (word ger-gain-from-food “,”)
file-type (word grass-regrowth-time “,”)
file-print (word energy-loss-from-dead-patches “,”)
file-close
end