From 7c8b7287d910eab1b31de858161d5c154e2d1f98 Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Fri, 3 Feb 2023 12:22:44 +0100
Subject: [PATCH] Added @shuffle! macro

---
 src/Persephone.jl         |  1 +
 src/core/input.jl         | 30 ++++++++++++++++++++++++++----
 src/nature/nature.jl      | 11 -----------
 src/nature/populations.jl | 10 +++++-----
 4 files changed, 32 insertions(+), 20 deletions(-)

diff --git a/src/Persephone.jl b/src/Persephone.jl
index cdf484f..58c1b36 100644
--- a/src/Persephone.jl
+++ b/src/Persephone.jl
@@ -60,6 +60,7 @@ export
     @distancetoedge,
     @countanimals,
     @rand,
+    @shuffle!,
     #functions
     simulate,
     simulate!,
diff --git a/src/core/input.jl b/src/core/input.jl
index d950738..7b39605 100644
--- a/src/core/input.jl
+++ b/src/core/input.jl
@@ -7,7 +7,7 @@
 ## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/input.jl)
 
 """
-The file that stores all default parameters.
+The file that stores all default parameters: `src/parameters.toml`
 """
 const PARAMFILE = joinpath(pkgdir(Persephone), "src/parameters.toml")
 ## (DO NOT CHANGE THIS VALUE! Instead, specify simulation-specific configuration files
@@ -19,11 +19,11 @@ const PARAMFILE = joinpath(pkgdir(Persephone), "src/parameters.toml")
 Return a configuration parameter from the global settings.
 The argument should be in the form `<domain>.<parameter>`,
 for example `@param(core.outdir)`. Possible values for
-<domain> are `core`, `nature`, `farm`, or `crop`. For a full
+`<domain>` are `core`, `nature`, `farm`, or `crop`. For a full
 list of parameters, see `src/parameters.toml`.
 
-Note that this macro only works in a context where the `model`
-object is available.
+Note: this macro only works in a context where the `model`
+object is available!
 """
 macro param(domainparam)
     domain = String(domainparam.args[1])
@@ -112,6 +112,28 @@ function flattenTOML(tomldict)
     flatdict
 end
 
+"""
+    @rand(args...)
+
+Return a random number or element from the sample, using the model RNG.
+This is a utility wrapper that can only be used a context where the
+`model` object is available.
+"""
+macro rand(args...)
+    :($(esc(:rand))($(esc(:model)).rng, $(map(esc, args)...)))
+end
+
+"""
+    @shuffle!(collection)
+
+Shuffle the given collection in place, using the model RNG.
+This is a utility wrapper that can only be used a context where the
+`model` object is available.
+"""
+macro shuffle!(collection)
+    :($(esc(:shuffle!))($(esc(:model)).rng, $(esc(collection))))
+end
+
 """
     parsecommandline()
 
diff --git a/src/nature/nature.jl b/src/nature/nature.jl
index a02f984..97885d0 100644
--- a/src/nature/nature.jl
+++ b/src/nature/nature.jl
@@ -375,17 +375,6 @@ macro countanimals(args...)
     :(countanimals($(esc(:pos)), $(esc(:model)); $(map(esc, args)...)))
 end
 
-"""
-    @rand(args...)
-
-Return a random number or element from the sample, using the model RNG.
-This is a utility wrapper that can only be used nested within `@phase` or `@habitat`
-(or in other contexts where the `model` object is available).
-"""
-macro rand(args...)
-    :($(esc(:rand))($(esc(:model)).rng, $(map(esc, args)...)))
-end
-
 ##TODO @chance macro: @chance(0.5) => rand(model.rng) < 0.5
 
 ##TODO add movement macros
diff --git a/src/nature/populations.jl b/src/nature/populations.jl
index a760088..4e213e3 100644
--- a/src/nature/populations.jl
+++ b/src/nature/populations.jl
@@ -40,15 +40,15 @@ function initpopulation(habitatdescriptor::Function; phase::Union{String,Nothing
         (!isnothing(phase)) && (species["phase"] = phase)
         width, height = size(model.landscape)
         while n == 0 || n < popsize
-            for x in shuffle!(model.rng, Vector(1:width))
-                for y in shuffle!(model.rng, Vector(1:height))
+            for x in @shuffle!(Vector(1:width))
+                for y in @shuffle!(Vector(1:height))
                     if habitatdescriptor((x,y), model)
                         if pairs
                             add_agent!((x,y), Animal, model, deepcopy(species), female, 0)
                             add_agent!((x,y), Animal, model, deepcopy(species), male, 0)
                             n += 2
                         else
-                            sex = asexual ? hermaphrodite : rand(model.rng, [male, female])
+                            sex = asexual ? hermaphrodite : @rand([male, female])
                             add_agent!((x,y), Animal, model, deepcopy(species), sex, 0)
                             n += 1
                         end
@@ -88,7 +88,7 @@ Produce one or more offspring for the given animal at its current location.
 """
 function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1)
     for i in 1:n
-        sex = (animal.sex == hermaphrodite) ? hermaphrodite : rand(model.rng, [male, female])
+        sex = (animal.sex == hermaphrodite) ? hermaphrodite : @rand([male, female])
         # We need to generate a fresh species dict here
         species = @eval $(Symbol(animal.traits["name"]))($model)
         add_agent!(animal.pos, Animal, model, species, sex, 0)
@@ -103,7 +103,7 @@ Kill this animal, optionally with a given percentage probability.
 Returns true if the animal dies, false if not.
 """
 function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0, cause::String="")
-    if rand(model.rng) < probability
+    if @rand() < probability
         postfix = isempty(cause) ? "." : " from $cause."
         @debug "$(animalid(animal)) has died$(postfix)"
         kill_agent!(animal, model)
-- 
GitLab