listforeachiterationnetlogo

Netlogo: efficient iteration using 'foreach'


I want to calculate and store two certain quantities associated with turtles' properties, namely count how many turtles meet a condition and the sum of these turtles' specific attribute. That specific attribute takes discrete, integer, non-sequential values.

A minimal example code of the model would look like this:

globals [ N-x1 N-x2, N-x5 sum-A-x1 sum-A-x2 sum-A-x5 x-list ]

turtles-own [ condition1? A x ]

set x-list ( list 1 2 5 )

Given that in the actual model the x-list takes 88 discrete values I thought using a 'foreach' loop would be an appropriate way to handle this. Note that the number of turtles in the actual model is around 75k so efficiency matters.

Following a combination from previous answers (here and here), I came up with the following solution:

  foreach x-list
  [ x ->
    
    ; Count turtles by x
    let temp1 (word "set N-x" x " count turtles with [ condition1? = true and x = " x "]" )
    run temp1
    
    ; Calculate sum of A by x
    let temp2 (word "set sum-A-x" x " sum [ A ] of turtles with [ condition1? = true and x = " x "]" ) 
   
   ; Capture errors due to empty list in calculating the sum
    carefully [ run temp2 ] 
    [ let temp3 (word "set sum-A-x" x " 0" )
      run temp3
    ]
  ]

Although it works, each run of the model was substantially slowed down. A simple (yet crude) solution that I thought it would help to speed up the code is to first define the turtle agentset on which the subsequent commands (counting and summation) will be performed. Since I could not find a way to use let inside the 'foreach' loop to do that, I went with the manual solution of defining everything by hand (for every single of the 88 values of x):

; Define needed agentsets
let turtles-x1 with [ condition1? = true and x = 1 ]
let turtles-x2 with [ condition1? = true and x = 2 ]
let turtles-x5 with [ condition1? = true and x = 5 ]

; Count turtles meeting condition1
set N-x1 count turtles-x1
set N-x2 count turtles-x2
set N-x5 count turtles-x5

; Sum A over turtles meeting condition1
if N-x1 > 0 [ set sum-A-x1 sum [ A ] of turtles-x1 ]
if N-x2 > 0 [ set sum-A-x2 sum [ A ] of turtles-x2 ]
if N-x5 > 0 [ set sum-A-x5 sum [ A ] of turtles-x5 ]

That seemed to improve performance by a small margin. Could anyone think of a more efficient (i.e. smarter) way to do the same? E.g. using map, which I can't my head around of, or the table extension?

Edit: While answers are still welcome, I've found a way to solve the issue using table extension and map. I'm leaving it here in case someone stumbles upon a similar case.


Solution

  • Following the answers found here and here the -much more efficient- solution is:

    globals [ x-keys x-N turtle-list turtle-A ]
    
    ; Store table of relevant turtles and their x's (to preserve order)
    let temp-table table:group-agents turtles with [ condition1? = true ] [ x ] 
    
    ; Relevant turtles' Xs (in the order inserted)
    set x-keys table:keys temp-table
      
    ; Counts of relevant turtles per x (in the order inserted)
    set x-N map count table:values temp-table
      
    ; Relevant turtles in the order inserted (in an agentset list)
    set turtle-list table:values temp-table
    
    ; Calculate sum of A per x
    set turtle-A map [ y -> precision ( sum [ A ] of y ) 3 ] turtle-list
    

    This solves many issues such as predefining every variable, executing multiple if's to check whether an x is populated or not (i.e. N > 0) for the sum to run and storing unneeded information (e.g. manually setting the sum of cases with N = 0, to zero). This greatly improved speed and halved the size of generated output (in a BehaviorSpace experiment).