1

I have a problem with accelerating a code in NetLogo. I tried a few things and they didn't work. The problem is in the "setup-patches" procedure that refers to the creation of turtles (please, see the code).

I have a map (I use the GIS extension, but to be a replicable example I didn't use it here) with 7 classes (which in the code is called habitatcover). 6 habitatcover have vegetation and therefore is used by turtles (mobile agent) and 1 class has no vegetation and therefore is not used by turtles (habitatcover 7). Turtles can born in the 6 types of habitatcover that have vegetation and their combinations result in 63 items (please, see the variable in the code called ValidHabs). Also, I have 3 types of metabolism and 3 types of reproduction, which results in 9 combinations, that is, 9 types of turtles.

For each ValidHabs item (which has a total of 63 items) 50 individuals of each of the 9 types of turtles are born. Therefore, for item 0 of ValidHabs which is [1] 450 individuals will be born (= 9 * 50) it executes all the procedures and goes to the next item in the ValidHabs list until it goes through all the items in this list.

The problem is that as the world is 1000x1000 and 450 turtles are born per list item (which has 63 items in total), using GIS, as well, I have several maps to perform these procedures, in addition to having minimum 15 repetitions per map (using BehaviourSpace) takes a lot of time do execute one map (almost 5 days per map). I've already tried to make modifications to speed it up and haven't had much success. I know the problem is in the "setup-patches" procedure, but I'm not quite sure how to solve it. Can anybody help me?

Thanks in advance!

The code below:

globals 
[   
  RandomSeeds
  HotPatchSet
  ValidHabs
  ValidHabsItem
  CurrentHotPatchSet
]

turtles-own
[
  turtle-profiles-habitat 
  energy-reproduction
  turtle-metabolism
  turtle-profiles-code
  turtle-profiles-reproduction-code
  turtle-profiles-metabolism-code 
  turtle-profiles-habitat-code
]

patches-own
[
  habitatcover 
]

to setup
  clear-all 
  set RandomSeeds 1
  random-seed RandomSeeds
  resize-world 999 * 0 ( 999 * 1 )  ( 999 * -1 ) 999 * 0 ;; defines the edge size of the world and location of origin: corner top left. The world is 1000x1000
  read-legal-habs ;; create list of valid habitats for each turtle profile
  set ValidHabsItem 0
  setup-layers 
  setup-patches 
  reset-ticks
end

to read-legal-habs ;; create list of valid habitats for each turtle profile
  set ValidHabs [[1] [2] [3] [4] [5] [6] [1 2] [1 3] [1 4] [1 5] [1 6] [2 3] [2 4] [2 5] [2 6] [3 4] [3 5] [3 6] [4 5] [4 6] [5 6] [1 2 3] [1 2 4] [1 2 5] [1 2 6] [1 3 4] [1 3 5] [1 3 6] [1 4 5] [1 4 6] [1 5 6] [2 3 4] [2 3 5] [2 3 6] [2 4 5] [2 4 6] [2 5 6] [3 4 5] [3 4 6] [3 5 6] [4 5 6] [1 2 3 4] [1 2 3 5] [1 2 3 6] [1 2 4 5] [1 2 4 6] [1 2 5 6] [1 3 4 5] [1 3 4 6] [1 3 5 6] [1 4 5 6] [2 3 4 5] [2 3 4 6] [2 3 5 6] [2 4 5 6] [3 4 5 6] [1 2 3 4 5] [1 2 3 4 6] [1 2 3 5 6] [1 2 4 5 6] [1 3 4 5 6] [2 3 4 5 6] [1 2 3 4 5 6]]
end

to setup-layers   ;; the original code I use GIS. And depending on how the class distributions (habitatcover) are on the map, it takes longer to create the turtles 
  let pcolors [ ]
  set pcolors [ 25 65 23 53 105 10 2 ]
  ask patches 
  [
    set pcolor item ( random 7 ) pcolors
  ]
  
  ask patches 
  [
    if pcolor = 25 [ set habitatcover 1 ]
    if pcolor = 65 [ set habitatcover 2 ]
    if pcolor = 23 [ set habitatcover 3 ]
    if pcolor = 53 [ set habitatcover 4 ]
    if pcolor = 105 [ set habitatcover 5 ]
    if pcolor = 10 [ set habitatcover 6 ]
    if pcolor = 2 [ set habitatcover 7 ]
  ]
end

to setup-patches
  let list1 ( list 2 4 8 ) ;; metabolism types
  let list2 ( list 10 15 20 ) ;; reproduction types
  let n0 count turtles
  set HotPatchSet patches with [ ( habitatcover != 7 ) ] ;; referring to all 6 habitatcovers that have some type of vegetation removing habitatcover 7 that has no vegetation
  set CurrentHotPatchSet HotPatchSet with [ habitatcover = one-of item ValidHabsItem ValidHabs ] ;; CurrentHotPatchSet will be the HotPatchSet with the current habitatcover ;; ValidHabsItem is the item that goes from 0 to 62 since there are 63 habitacover combinations (see the list ValidHabs has 63 items)
  let s ( length list1 ) * ( length list2 ) * 50 ;; 50 individuals per profile = 3 * 3 * 50 = 450 turtles

  while [ n0 < s ]
  [
    (
      foreach list1
      [
        this-metabolism ->
        foreach list2
        [
          this-reproduction ->
          let c count CurrentHotPatchSet
          if c = 0 [ stop ]
          ask one-of CurrentHotPatchSet
          [
            sprout 1
            [
              set turtle-profiles-habitat item ValidHabsItem ValidHabs ;; I need the ValidHabsItem and turtle-profiles-habitat, because in the "go" procedure I'm calling each item (ValidHabsItem) from the ValidHabs list to execute all the procedures
              set turtle-metabolism this-metabolism
              set energy-reproduction this-reproduction
              setup-turtles
            ]
            set CurrentHotPatchSet CurrentHotPatchSet with [ not any? turtles in-radius 3 ] ;; each turtle must be a minimum distance of 3 patches and can't have more than one turtle in each patch
          ]
        ]
      ]
    )
    set n0 count turtles
  ]
end

to setup-turtles
  set turtle-profiles-habitat-code reduce_list turtle-profiles-habitat
  (
    ifelse
    turtle-metabolism = 2 [ set turtle-profiles-metabolism-code "_M1" ]
    turtle-metabolism = 4 [ set turtle-profiles-metabolism-code "_M2" ]
    turtle-metabolism = 6 [ set turtle-profiles-metabolism-code "_M3" ]
  )

  (
    ifelse
    energy-reproduction = 5 [ set turtle-profiles-reproduction-code "_R1" ]
    energy-reproduction = 10 [ set turtle-profiles-reproduction-code "_R2" ]
    energy-reproduction = 15 [ set turtle-profiles-reproduction-code "_R3" ]
  )
  set turtle-profiles-code ( word turtle-profiles-habitat-code turtle-profiles-reproduction-code turtle-profiles-metabolism-code )
end

to-report reduce_list [ a_list ]
  report read-from-string word reduce word a_list ""
end
Rafaela
  • 419
  • 2
  • 8

1 Answers1

1

Opening note: In no way I am assuming that my suggestions are exhaustive, as other people might see other things to improve.

I intend this question to be exclusively on code, i.e. assuming that it is not addressing non-code options such as computer cluster etc.

Eliminate the time needed to upload GIS data

Importing GIS data in NetLogo is a lengthy process. You can get rid of this process by using export-world and import-world (see here and here). These commands will let you export to a file the current state of your NetLogo world, and then import into NetLogo that state from the file. You can perform the export once when the GIS data has been loaded (so that the GIS information is stored in patches) and store the file somewhere: this means that you won't need to upload GIS data again but will only need to perform import-world.

My personal suggestion is that you create a separate NetLogo file to do this: only in this separate file you will have the GIS extension's procedures and the export-world command. In the file with your main model, you will only have the import-world part.

Of course you will need to create a new/different file for each new/different set of GIS files you use.

Reduce the amount of times you need to perform import-world

Your model is quite big, so it might be the case that import-world too takes some time. You can adjust your code such that, when using BeahviorSpace, you will only have to run import-world once.

See here for some previous discussion.

The idea is that in your setup, instead of using clear-all (which would also clear all of that imported data which is stored inside patches), you manually clear everything apart from patches (i.e. you use clear-globals, clear-ticks, clear-turtles, clear-drawing, clear-all-plots and clear-output). This will allow you to check if the data has already been imported: if not, then it means that this is the first run and you need to use import-world; if yes, it means that patches already have that information stored and thus import-world needs not to be performed.

I don't know if in your model the patch variables coming from GIS (i.e. coming from import-world) might be changed during the simulation. If this is the case, it means that you need to store the initial state of these variables as a special patch variable, which will be used to reset the initial state when you do not use import-world.

Some code will make all of this clearer. For example, in case the GIS information imported through import-world is stored as a vegetation patch variable, you can create an initial-vegetation variable for this purpose:

patches-own [
  initial-vegetation
  vegetation
]

to setup
  clear-globals
  clear-ticks
  clear-turtles
  clear-drawing
  clear-all-plots
  clear-output

  ifelse (all? patches [initial-vegetation = 0])
    [import-world "initial-state.csv"]
    [ask patches [set vegetation initial-vegetation]]
end

Otherwise, if in your model the variable of interest can never be changed (following my example: if vegetation never changes during the simulation...) things are even simpler:

patches-own [
  vegetation
]

to setup
  clear-globals
  clear-ticks
  clear-turtles
  clear-drawing
  clear-all-plots
  clear-output

  ifelse (all? patches [initial-vegetation = 0])
    [import-world "initial-state.csv"]
    []
end

Looking at your setup-patches procedure, I don't see any code that is obviously inefficient.

(probably set n0 n0 + 1 would be faster than set n0 count turtles - but this is just for the sake of noting it, as it surely has a negligible effect).


Possible unintended aspects in your code? (1)

ValidHabsItem is used twice but it is never set to anything different than zero. In setup there is set ValidHabsItem 0 (anyway note that this is superflous, as zero is the default value), and the value of ValidHabsItem is never changed. I don't know if this is used in some other way in the full version of your code (e.g. if the value of ValidHabsItem iterates from 0 to 62) but note that, looking exclusively at the code in your question, this means that item ValidHabsItem ValidHabs is always [1]. Which in turn, looking at the two instances where you use it in setup-patches, means that:

  • The statement:
set CurrentHotPatchSet HotPatchSet with [habitatcover = one-of item ValidHabsItem ValidHabs]

is equivalent to:

set CurrentHotPatchSet HotPatchSet with [habitatcover = 1]
  • The statement:
set turtle-profiles-habitat item ValidHabsItem ValidHabs

is equivalent to:

set turtle-profiles-habitat [1]

Overall this means that, by using the code in your question, turtles will only be born on orange patches (those with habitatcover = 1) and they will only have turtle-profiles-habitat = [1]. Is this intended? Is it not? I am not sure, but it looks peculiar enough for me to point it out.

Possible unintended aspects in your code? (2)

Let's imagine that ValidHabsItem is not constantly 0 but it goes from 0 to 62, with the effect that item ValidHabsItem ValidHabs is always a different list containing a varying number of numbers.

What do you want your code to do when you write:

set CurrentHotPatchSet HotPatchSet with [habitatcover = one-of item ValidHabsItem ValidHabs]

?

If you want to check that habitatcover of patches is one of the values in the current list of habitats, then this is not what your code is doing.

Let's take an example: the current list being represented by item ValidHabsItem ValidHabs is [2 4 5].

The one-of item ValidHabsItem ValidHabs statement is literally one of [2 4 5]. Given that one-of is a random reporter, let's say that in this occasion it extracts the value of 4. This means that your:

set CurrentHotPatchSet HotPatchSet with [habitatcover = one-of item ValidHabsItem ValidHabs]

is

set CurrentHotPatchSet HotPatchSet with [habitatcover = 4]

even if item ValidHabsItem ValidHabs = [2 4 5]; with the result that all patches having habitatcover = 2 or habitatcover = 5 will be excluded.

If it is correct to say that this is not what you wanted, it means that what you are looking for is the member? reporter (see here):

set CurrentHotPatchSet HotPatchSet with [member? (habitatcover) (item ValidHabsItem ValidHabs)]

In the example where item ValidHabsItem ValidHabs = [2 4 5], the code above will set CurrentHotPatchSet as every patch having habitatcover as either 2, 4 or 5.

Matteo
  • 2,774
  • 1
  • 6
  • 22