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

Chooser.png

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.

Without_all_houses.png

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.

Switch_territorial.png

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  

  ]

profiler_button.png

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!

Final.png