Question

I've been working on a NetLogo model simulating home range (I use the terms territory and home range interchangeable) dynamics of territorial animals. In the model, females select patches with the highest prey value and add those patches to their territories. That works nicely and seems pretty fast. Males are limited by females and so will try to have their home ranges overlap multiple female home ranges. In the model, a male will occupy 90% of a single female's home range (i.e., add those same patches to his territory) and then move to the next closest female's home range (in which no other male is present) to begin occupying. This process seems to work fine but it runs exceedingly slow, to the point that I'm worried I won't be able to feasibly run many simulations. Any ideas on how to speed up this process? Am I doing something stupid, like asking all patches to do something when I don't have to? The model code is getting pretty long, so I just added the part related to the male home range growth. I can provide more code to someone if the problem seems to lie elsewhere. Any help would be much appreciated!

to update-male-home-range
  ask males ; the following procedure allows males to encompass 90% of a female's home range
  [
    let this-male self
    foreach [owner-fem] of females-in-my-territory ; the number of females in male territory is set when the males are dropped onto the landscape at tick 5. This number is updated at the end of this whole process.
    [
      let ? [owner-fem] of females-in-my-territory ; this ensures that the male will occupy each female territory sequentially instead of jumping back and forth
      while [((count patches with [(owner-fem = ?) and (owner-male = this-male)]) / (count [territory] of ?)) < 0.9] ; the male keeps adding cells to his home range from the home range of the female until he has encompassed 90% of them
      [ 
        let avail-fem-patches no-patches
        let target nobody
        let new-patches no-patches
        ask territory
          [
            ; Add unoccupied neighbor patches as potential targets:
            set avail-fem-patches (patch-set avail-fem-patches neighbors4 with [(owner-fem = ?) and (owner-male = nobody)])
          ]
        ; Set of all potential targets:
        set new-patches (patch-set new-patches avail-fem-patches) 

        ; Only chooses unoccupied neighbors 
        if any? new-patches
          [
            ask new-patches
              [
                if any? avail-fem-patches 
                [set target one-of avail-fem-patches]
              ]
            move-to target ; I let the animal move so you can see which patch he chose
          ]

        if target != nobody
          [
            ; Add target patch to territory of the current animal:
            set territory (patch-set territory target) ; this is the territory of the calling animal - we are still in its context
            ask target [ set owner-male myself ] ; Tell target patch that is has new owner-male:
          ]

        ask territory
        [
          set pcolor ([color] of myself) - 2
          if owner-male != myself [ user-message "Something wrong" ]
        ]

        set XY [list pxcor pycor] of territory
        set X map [(item 0 ?)] XY ;getting X coordinates for home range mapping later
        set Y map [(item 1 ?)] XY ;getting Y coordinates for home range mapping later

        let fem-patch-in-male-hr count patches with [(owner-fem = ?) and (owner-male = this-male)] ; this is for reporting purposes only. 
        ; This shows the number of patches belonging to the female home range that has also been claimed by the male
        let size-of-fem-hr (count [territory] of ?) ; this is for reporting purposes only
        ; This reports the total size of the female home range that the male has begun overlapping
        let fem-patch-hr-size-ratio fem-patch-in-male-hr / size-of-fem-hr ; this if for reporting purposes only
        ; This is  ratio of the number of patches claimed by the male that belongs to the home range of the female. 
        ; This ratio is the same as that in the "while" procedure above and shows whether the male has encompassed 90% or not.
      ]
      ;set total-females count females-in-my-territory
    ]

  ] ; ask males

  ask males ; After all males have encompassed their respective female home ranges, this procedure tells the males to find the closest female home range that is unoccupied and move to it to begin overlapping her home range
  [
    let other-females females with [not member? self ([females-in-my-territory] of myself)] ; females not in my territory
    let females-with-males females with [count males-in-my-territory != 0] ; female home ranges occupied by a male including myself
    let females-with-no-males females with [count males-in-my-territory = 0] ; female home ranges with no male
    let other-males-females females-with-males with [not member? myself ([males-in-my-territory] of self)] ; female home ranges occupied by a male not including myself 
    if count females-with-no-males > 0 ; if a female home range does not have a male occupying it
    [
      let target-patches no-patches
      set target-patches (patch-set target-patches ([territory] of females-with-no-males in-radius 10)) ; create patch-set that is all territories of females with no males occupying their home ranges
      let target-fem-patch no-patches
      ask territory 
      [
        set target-fem-patch min-one-of target-patches [distance myself] ; select the patch from female home ranges with no males that is closest to my home range.  This doesn't seem like it is working quite right.


      if target-fem-patch != nobody
      [
        move-to target-fem-patch ; move the male to this patch in the female home range previously unoccupied
        set territory (patch-set territory target-fem-patch) ; update the male's territory
        ;let old-owner-male [owner-male] of target-fem-patch
        ask target-fem-patch [ set owner-male myself ] ; update the owner
        ask territory ; update the color of the patch according to the owner of the territory
        [
          set pcolor ([color] of myself) - 2
          if owner-male != myself [ user-message "Something wrong" ]
        ]
        set females-in-my-territory females with [member? self ([owner-fem] of [territory] of myself)] ; update the number of female home ranges that the male occupies
        ask females 
        [
          set males-in-my-territory males with [member? self ([owner-male] of [territory] of myself)] ; update the number of males that occupy each female's home range
        ]
      ]
    ]

    ask territory ; update the patch color again because some males may not have been able to add any patches from the prior procedure
      [
        set pcolor ([color] of myself) - 2
        if owner-male != myself [ user-message "Something wrong" ]
      ]

    set XY [list pxcor pycor] of territory ; update the XY coordinates for the male territories for the home range calculation later
    set X map [(item 0 ?)] XY
    set Y map [(item 1 ?)] XY
  ]

end ; update-male-home-range
Was it helpful?

Solution 2

First thing that can easily be optimized:

    if any? new-patches
      [
        ask new-patches
          [
            if any? avail-fem-patches 
            [set target one-of avail-fem-patches]
          ]
        move-to target ; I let the animal move so you can see which patch he chose
      ]

You're having all new-patches set target. However, they just keep overwriting the same target variable. In fact, you don't even need the new-patches to do this. Thus, you can replace the above code with:

if any? avail-fem-patches [
  set target one-of avail-fem-patches
  move-to target
]

Next, this:

    ask territory
    [
      set pcolor ([color] of myself) - 2
      if owner-male != myself [ user-message "Something wrong" ]
    ]

keeps setting the entire territory to the same color over and over (each iteration of the loop), but you only need to set the color of the target patch, right? So this can become:

ask target [ set pcolor ([color] of myself) - 2 ]

It you keep doing maps over the territory's ycors (which is expensive in a tight loop like this), but you never actually use the results: X, Y, and XY. Bare minimum, you should do that only at the very end, outside of all the loops. But you may just be able to remove it completely.

A quick side note: new-patches effectively is just set to avail-fem-patches, since it's set to no-patches at the beginning of each iteration of the while loop, is then set to (patch-set avail-fem-patches new-patches). Is this a bug?

Finally, right now you're looking at at every single neighbor of every single territory patch to add a single patch to territory. Instead you could as many patches as possible each iteration of the loop. Something like (replacing target with targets):

let needed-patches 0.9 * (count [territory] of ?) - (count patches with [(owner-fem = ?) and (owner-male = this-male)])

ifelse count new-patches < needed-patches [
  set targets new-patches
] [
  set targets n-of needed-patches new-patches
]

Then you'd add all the targets to the territory. This would be behaviorally different. Right now, you can little trails cut into females' territories. Here, male's territories will expand as broadly as possible so to speak. Not sure if that's bad or not.

OTHER TIPS

This is an absolutely enormous chunk of code. I try not to let my procedures get longer than about 10 or 15 lines each (and that's just a ceiling, lots of good procedures just are just 1 or 3 or 5 lines each). Otherwise the complexity is just too much deal with and it becomes almost impossible to effectively understand or troubleshoot anything.

Also, if you broke this up into smaller procedures, that would enable you to use the profiler extension to measure which ones are taking all the time.

Re "asking all patches to do something when I don't have to", yes, you are. You're doing count patches with ..., which loops over all the patches, and you're doing it inside a while loop which is inside a foreach loop which is inside an ask loop...!

I don't understand your code well enough to suggest a fix, though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top