Step 6. Change population size
1. Let's do some clean up!
This step is pretty simple. Here, you will simply use the population data included in the political vector file you already imported to change the number of people created in each territory.
​
Given that this is a relatively short section, let's take this occasion to do some preliminary clean-up of Interface settings we no longer need.
​
On the Interface, select the show-energy? chooser, as well as the three monitors that show the number of sheep, wolves, and grass. Delete them.
​
This will create an error because the code has an If statement calling the show-energy? chooser. Delete the whole display-labels procedure and its call within the setup and go procedures.
​
Finally, as we no longer count grass patches, delete the reporter that counts grass.
to display-labels
ask turtles [ set label "" ] ; set labels to blank
if show-energy? [ ; if show-energy? is TRUE, the code within brackets will be run...
ask wolves [ set label round energy ] ; ... then the wolves will show their energy (round means no decimals)
ask sheep [ set label round energy ] ; sheep always have energy when there is grass
]
end
to setup
[ ... ]
​
add-humans
display-labels
reset-ticks
to go
[ ... ]
​
tick
display-labels ; calls the display-labels procedure
end
to-report grass
report patches with [ pcolor = green ] ; reports the number of green patches
end
2. Set up the population variable
Given that the population impacts kingdoms, we decided to give the houses their own population variable. That number is stored per polygon in the political vector file you already imported.
​
To include population size in setup, first create a house population variable.
​
In the Interface, create a chooser called population-size that determines if population size is random or based on the story. Write the two options shown here.
​
Then, edit the setup-house procedure to upload the population size to the new house population variable. This should be added after calling setup-got-houses because you need to create houses before asking them to set their population.
houses-own [
name ; house name to call specific ones
my-color ; house color
throne-score ; score of the prisoner's dilemma game
cooperate? ; determines the probability of cooperation
rival-list ; list of neighbors against which they will play
defeated? ; will record if the house still holds cities
population ; takes in the population of each territory
]
houses-own [
name
my-color
throne-score
cooperate?
rival-list
defeated?
population
]
to setup-houses ; observer procedure
[ ... ]
setup-got-houses ; call the setup-got-houses procedure
if population-size = "faithful to the story" [ ; If the chooser is set to "faithfyl to the story"
foreach gis:feature-list-of houses-basemap [ ; This iterates through each polygon in the shapefile
x -> ask houses [ ; Each polygon (x) asks the houses that intersect with it to ...
if gis:intersects? self x [
set population gis:property-value x "POPULATION" ; ... set their population to the vector "POPULATION" value
]
]
]
]
end
to setup-houses
[ ... ]
setup-got-houses
if population-size = "faithful to the story" [
foreach gis:feature-list-of houses-basemap [
x -> ask houses [
if gis:intersects? self x [
set population gis:property-value x "POPULATION"
]
]
]
]
end
3. Change the population size
You now need to take this new data into consideration when creating humans.
​
Edit the add-humans procedure so that it creates different number of humans for each kingdom.
​
Notice that we divide the population number by 100 here because the populations are too big for this simple model.
​
To avoid repeating code, move the part of the code that sets humans attributes (ask humans) outside of the "sprout x" brackets.
to add-humans ; observer procedure
ifelse population-size = "faithful to the story" [
ask houses [
let reduced-pop population / 100 ; reduce the number of millions because the map would not hold them all
if reduced-pop < 1 [ ; to make small populations visible and somewhat sustainable
set reduced-pop 2
]
ask up-to-n-of reduced-pop land-patches with [ house-color = [ my-color ] of myself ][
sprout-humans 1 ; the number of patches that correspond to reduced-pop sprout 1 human each
]
]
][
; Create as many humans as the slider (if population-size "random"
ask n-of initial-number-humans land-patches [ ; This way of creating humans prevents having to give them coordinates
sprout-humans 1 ; Create humans
]
]
ask humans [ ; initialize humans' variables
set shape "person" ; humans are "person" shaped
set size 5 ; a bit bigger than turtles and wolves
set color house-color ; humans take the house color of the patch on which they are created
set energy random ( 2 * wolf-gain-from-food ) ; the initial energy is up to the gain that wolves get * 2
]
end
to add-humans
ifelse population-size = "faithful to the story" [
ask houses [
let reduced-pop population / 100
if reduced-pop < 1 [
set reduced-pop 2
]
ask up-to-n-of reduced-pop land-patches with [ house-color = [ my-color ] of myself ][
sprout-humans 1
]
]
][
ask n-of initial-number-humans land-patches [
sprout-humans 1
]
]
ask humans [
set shape "person"
set size 5
set color house-color
set energy random ( 2 * wolf-gain-from-food )
]
end
4. Add the Greyjoys, Wildlings, and Night's Watch back
Press setup. What do you see? Some kingdoms are very crowded (the Reach), others not so much (the North). But do you notice that some people are missing? Where are the Greyjoys? And the Wildlings?
​
Remember that when we created the GoT houses (in step 5), we removed the Greyjoys, Wildlings, and the Night's Watch from the list of potential players of the prisoner's dilemma game. Here, the code uses the players list to create the houses, which then create people under this new setting. Therefore, when population size conforms with the story, people are not created North of the Wall, on the Wall, or even on the Iron Islands. We need to fix that by temporarily adding those back up to the list of players in the setup-got-houses procedure.
​
Create a temporary variable that merges the players list with this new list of 'forgotten' houses, and use that temporary list to create initial houses.
​
to setup-got-houses ; observer procedure
set players identify-houses ; creates the list of potential players of the prisoner's dilemma
; for this particular part, we need to add back Greyjoys and, Wildlings, and the Night's watch (as they have populations)
let players-start sentence players [ 32 8 0 ] ; 'sentence' merges the players list with this new list
; the foreach loop iterates through the list. x represents each entry of the list as it goes through.
foreach players-start [
x -> create-houses 1 [
to setup-got-houses
set players identify-houses
let players-start sentence players [ 32 8 0 ]
foreach players-start [
x -> create-houses 1 [
5. Change mobility
Press setup again. You should now see a few Wildlings and Greyjoys. But the prisoner's dilemma game players remain the same houses as before. This is great! We can now move on to add complexity to the model.
While we want everybody to be able to walk around the 7 Kingdoms freely, let's change people's mobility a bit so that they will most often choose to move through their own territory rather than move away from it.
​
To do this, we will use weighted probabilities. As they move, humans will have choose to walk onto patches that are from their house with higher probabilities. This adds a little bit of computational complexity, but makes things a bit more interesting.
​
The first thing to do is to load the rnd extension, which you will use to create weighted probabilities. Then, create a new move-humans procedure because we do not want the wolves and sheep (remember those, even if we haven't really done much with them for a while?) to base their movement on their house, because... well animals do not belong to houses here.
​
In the move-humans procedure, start by identifying a set of patches within walking distance (distance <1.5 to account for diagonals) and in a cone of vision of 180 degrees to keep their focus forward. Then, compute the probabilities of choosing each of the visible patches and use those probabilities to move the human to one of them.
​
There are two ways you could do this part. The first is to create a list of patches' probabilities and choose from that list, the second is to create a prob variable for each patch, update them as humans move and choose from those variables. While the first requires more coding, it may be slightly faster than the second one, which is why this is the one we choose here. If you want to play around, read the description of the rnd:weighted-one-of agentset reporter procedure in the Rnd Extension manual and try to replace our code to use patch variables instead of a list.
​
extensions [ gis nw rnd ] ; activates the gis, nw, and rnd extensions
extensions [ gis nw rnd ]
to move-humans ; human procedure
; humans first wiggle a bit, then try to step ahead if the patch ahead is land
rt random 50 ; turn right a random number of degrees below 50
lt random 50 ; turn left a random number of degrees below 50
ifelse patch-ahead 1 != nobody [ ; check if the patch in front of the agent is something (to turn at the edges)
ifelse [ land? ] of patch-ahead 1 = true [ ; if the patch in front of the agent is land (not water or the wall)...
let patches-within-vision patches with [ land? ] in-cone 1.5 180 ; ...identify patches within distance 1.5...
; ...and a cone of vision of 180 degrees
let chosen-patch 0 ; temporary variable that will identify the patch to walk to
let cone-colors [] ; temporary empty list that will take the color of each house within vision
ask patches-within-vision [
set cone-colors lput [ house-color ] of self cone-colors ; each patch adds its color to the list
]
set cone-colors remove-duplicates cone-colors ; removes duplicate colors to speed computation below
; to speed up computation, the probabilities are computed only if there are more than 1 territory possible
ifelse length cone-colors > 1 [
let human-color [ color ] of self
let cone-colors-prob cone-colors ; duplicates the list of colors to create the probability that each will be chosen.
foreach range length cone-colors-prob [ ; iterates through each item in the list
x -> let current-value item x cone-colors-prob ; x is the item number in the list. This identifies the house-color of item x
ifelse current-value = human-color [ ; if the house-color is the same as the human's color...
set cone-colors-prob replace-item x cone-colors-prob 3 ; ... the probability is higher
][
set cone-colors-prob replace-item x cone-colors-prob 2 ; if it is different (new territory), the probability is lower
]
]
let pairs ( map list cone-colors cone-colors-prob ) ; this pairs the house-colors with their probability
let i rnd:weighted-one-of-list pairs [[ p ] -> last p ] ; this chooses one of the house-colors based on its probability
; chooses one of the patch with the chosen house-color
set chosen-patch one-of patches-within-vision with [ house-color = first i ]
][
set chosen-patch one-of patches-within-vision ; if all patches are of the same house, the human chooses one
]
face chosen-patch ; the human faces the patch it chose
fd 1 ; if it is land, the agent move one step ahead
][
rt 180 ; if the patch is water, the agent turns 180 degrees
]
][
rt 180 ; if the patch is nothing (edge), the agent turns 180 degrees
]
end
to move-humans
rt random 50
lt random 50
ifelse patch-ahead 1 != nobody [
ifelse [ land? ] of patch-ahead 1 = true [
let patches-within-vision patches with [ land? ] in-cone 1.5 180
let chosen-patch 0
let cone-colors []
ask patches-within-vision [
set cone-colors lput [ house-color ] of self cone-colors
]
set cone-colors remove-duplicates cone-colors
ifelse length cone-colors > 1 [
let human-color [ color ] of self
let cone-colors-prob cone-colors
foreach range length cone-colors-prob [
x -> let current-value item x cone-colors-prob
ifelse current-value = human-color [
set cone-colors-prob replace-item x cone-colors-prob 3
][
set cone-colors-prob replace-item x cone-colors-prob 2
]
]
let pairs ( map list cone-colors cone-colors-prob )
let i rnd:weighted-one-of-list pairs [[ p ] -> last p ]
set chosen-patch one-of patches-within-vision with [ house-color = first i ]
][
set chosen-patch one-of patches-within-vision
]
face chosen-patch
fd 1
][
rt 180
]
][
rt 180
]
end
6. Integrate it to Go
Now that the new procedure is written, you can update the go procedure so that humans call this new move-humans procedure instead of the general move. BUT... if you do so, you will notice that the code becomes much slower, and slows down considerably as the number of humans increase. Why is that?
Dealing with a lot of short lists is computationally demanding, as every time a list is changed, a new list is created. That's a lot of information. So, to insure that you can still run the model fast (to test other functions, for example), let's create a switch that determines which movement occurs.
​
On the Interface, create a switch called territorial-movement. In the code, change the go procedure so that humans choose the appropriate move procedure based on that switch.
​
to go
[ ... ]
​
ask humans [ ; human actions, almost all the same as sheep and wolves
ifelse territorial-movement [
move-humans ; calls the "move-humans" procedure if there is territorial movement
][
move ; calls the "move" procedure
]
set energy energy - 1 ; humans also lose energy as they move
humans-eat ; Humans eat some of the sheep that are on their patch or the grass if available
domesticate-wolves ; Humans domesticate some of the wolves they encounter
humans-feed-animals ; humans feed their domesticated wolves
death ; humans die when they run out of energy
reproduce humans-reproduce ; wolves reproduce at a random rate governed by a slider
]
to go
[ ... ]
​
ask humans [
ifelse territorial-movement [
move-humans
][
move
]
set energy energy - 1
humans-eat
domesticate-wolves
humans-feed-animals
death
reproduce humans-reproduce
]
7. How did we know what was faster?
While you code, you may notice that small additions make your model much slower. Sometimes, it is difficult to identify which part of the code is the culprit. Enters another extension called profiler.
​
The profiler extension is well explained in this blog. It calculates the time it takes to run each procedure called by your model. Here, we will use the two possible mobility procedures to show how it works.
​
First thing first: add profiler to the list of extensions at the top of your code.
​
Then, create a button on the Interface and copy the code provided below (and shown here).
​
Now, press the profiler button. You will see that your model will run for 30 ticks, stop, and then a bunch of data will get printed in the Command Center.
​
You will see 3 tables, which are basically the same thing ordered differently in each table. The first column identifies the name of each procedure called at least once (so if you think one procedure should be called but it's not there, it's a good sign that something is wrong). The second column shows you how many times each procedure is called (a great column to look at if you suspect that duplicate calls happen). The third column shows the total time spent within that procedure inclusive of any sub-procedure called by it. The fourth column shows the time spent within that procedure exclusive of the time spent within any sub-procedure. Finally, the fifth column shows the time each call took.
​
Now, change the status of the territorial-movement switch and press the profiler button again. Can you see the difference in speed?
​
extensions [
gis
nw
rnd
profiler
] ; activates the gis, nw, rnd, and profiler extensions
extensions [
gis
nw
rnd
profiler
]
setup ; set up the model
profiler:start ; start profiling
repeat 30 [ go ] ; run something you want to measure
profiler:stop ; stop profiling
print profiler:report ; view the results
profiler:reset ; clear the data
setup
profiler:start
repeat 30 [ go ]
profiler:stop
print profiler:report
profiler:reset
8. Another step bites the dust!
Congratulations on finishing step 6.
​
Here, you have learned a few skills, such as using weighted probabilities to choose a patch, as well as how to use profiler to identify slow parts in your code.
​
If you want to see the finished model for this step, download it here​.
​
In the next step, you will set up erratic seasons, as well as introduce the model to Whitewalkers, which come with winter and try to suck the life off of everything that comes their way.
​
See you there!