From 0e3c37c58cfc22a0d7c24a2493569ff2108ac407 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Sat, 17 Dec 2022 14:15:31 +0100 Subject: [PATCH] Rewrote the wyvern --- src/Persephone.jl | 2 +- src/nature/lifehistory.jl | 6 +- src/nature/nature.jl | 22 +++-- src/nature/species/wolpertinger.jl | 7 +- src/nature/species/wyvern.jl | 128 +++++++++++++++-------------- src/parameters.toml | 7 +- 6 files changed, 91 insertions(+), 81 deletions(-) diff --git a/src/Persephone.jl b/src/Persephone.jl index 3a978d7..2470a0c 100644 --- a/src/Persephone.jl +++ b/src/Persephone.jl @@ -47,7 +47,7 @@ include("nature/lifehistory.jl") include("nature/ecologicaldata.jl") #include("nature/species/skylark.jl") include("nature/species/wolpertinger.jl") -#include("nature/species/wyvern.jl") +include("nature/species/wyvern.jl") include("core/simulation.jl") end diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl index e80a1d9..c15f759 100644 --- a/src/nature/lifehistory.jl +++ b/src/nature/lifehistory.jl @@ -10,7 +10,7 @@ A simplified version of `initpopulation()`. Creates a function that initialises `popsize` individuals, spread at random across the landscape. If `popsize` is less than 1, it is interpreted as a population density (i.e. 1 animal per `popsize` pixels). """ -function initrandompopulation(popsize::Float64, asexual::Bool=true) +function initrandompopulation(popsize::Union{Int64,Float64}, asexual::Bool=true) initfunc = function(species::Dict{String,Any}, model::AgentBasedModel) if popsize < 1.0 x, y = size(model.landscape) @@ -39,7 +39,7 @@ function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1) sex = animal.sex == hermaphrodite ? hermaphrodite : rand([male, female]) add_agent!(animal.pos, Animal, model, animal.traits, sex, 0) end - @debug "$(animal.traits["name"]) $(animal.id) has reproduced." + @debug "$(animalid(animal)) has reproduced." end """ @@ -51,7 +51,7 @@ Returns true if the animal dies, false if not. function kill!(animal::Animal, model::AgentBasedModel, probability::Float64=1.0, cause::String="") if rand() < probability postfix = isempty(cause) ? "." : " from $cause." - @debug "$(animal.traits["name"]) $(animal.id) has died$(postfix)" + @debug "$(animalid(animal)) has died$(postfix)" kill_agent!(animal, model) return true end diff --git a/src/nature/nature.jl b/src/nature/nature.jl index 71dad40..abc2043 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -6,9 +6,6 @@ ## An enum used to assign a sex to each animal @enum Sex hermaphrodite male female -## This will be prepended to the names of the functions created by @species -const SPECIESPREFIX = "speciesfunction_" - """ Animal @@ -25,6 +22,15 @@ by trait dictionaries passed by them during initialisation. #XXX keep track of parents and/or offspring? end +""" + animalid(animal) + +A small utility function to return a string with the species name and ID of an animal. +""" +function animalid(a::Animal) + return "$(a.traits["name"]) $(a.id)" +end + ### MACROS IMPLEMENTING THE DOMAIN-SPECIFIC LANGUAGE FOR DEFINING SPECIES @@ -64,9 +70,8 @@ Access to the rest of the model is given by the `model` variable (an object of type `AgentBasedModel`). """ macro species(name, body) - funcname = Symbol(SPECIESPREFIX*string(name)) quote - Core.@__doc__ function $(esc(funcname))(model::AgentBasedModel) + Core.@__doc__ function $(esc(name))(model::AgentBasedModel) $(esc(:name)) = string($(QuoteNode(name))) $(esc(body)) vardict = Base.@locals @@ -174,7 +179,7 @@ end """ stepagent!(animal, model) -Update an animal by one day. +Update an animal by one day, executing it's currently active phase function. """ function stepagent!(animal::Animal, model::AgentBasedModel) animal.age += 1 @@ -188,9 +193,8 @@ Initialise the model with all simulated animal populations. """ function initnature!(model::AgentBasedModel) # The config file determines which species are simulated in this run - for s in param("nature.targetspecies") - funcname = Symbol(SPECIESPREFIX*s) - species = @eval $funcname($model) + for speciesname in param("nature.targetspecies") + species = @eval $(Symbol(speciesname))($model) species["initialise!"](species, model) end # Initialise the data output diff --git a/src/nature/species/wolpertinger.jl b/src/nature/species/wolpertinger.jl index a8956f2..67a4914 100644 --- a/src/nature/species/wolpertinger.jl +++ b/src/nature/species/wolpertinger.jl @@ -13,8 +13,9 @@ of a deer. @species Wolpertinger begin popdensity = 1/100000 fecundity = 0.02 - mortality = 0.01 + mortality = 0.015 maxspeed = 5 + crowdingfactor = maxspeed*2 initialise! = initrandompopulation(popdensity) phase = "lifephase" @@ -29,7 +30,9 @@ of a deer. walk!(animal, direction, model; ifempty=false) end - if rand() < @trait(fecundity) + #TODO density-dependent reproduction + #neighbours = length((n.id for n in nearby_agents(animal, model, @trait(maxspeed)))) + if rand() < @trait(fecundity) # && neighbours < @trait(crowdingfactor) @reproduce end diff --git a/src/nature/species/wyvern.jl b/src/nature/species/wyvern.jl index 2c96df9..c852d69 100644 --- a/src/nature/species/wyvern.jl +++ b/src/nature/species/wyvern.jl @@ -5,75 +5,77 @@ ### Thankfully, wyverns are not a species we have to manage for... ### -##TODO convert to @species syntax - """ - initwyvern!(model) - -Initialise a population of Wyverns in pairs locations around the landscape. +Beware the wyvern! This evolutionary cousin of the dragon may only have two +legs, but that doesn't make it any less dangerous... """ -function initwyvern!(model::AgentBasedModel) - species = getspecies("Wyvern") - x, y = size(model.landscape) - for i in 1:species.traits["initpop"] - add_agent!(Animal, model, species, hermaphrodite, 0, species.traits["birthenergy"]) - end - @debug "$(species.traits["initpop"]) wyverns are now lying in wait in the landscape." -end +@species Wyvern begin + popsize = 10 + fecundity = 0.02 + maxage = 365 + speed = 20 + vision = 50 + aggression = 0.2 + huntsuccess = 0.8 -""" - updatewyvern!(animal, model) + initialise! = initrandompopulation(popsize) + phase = "winter" -Wyverns are ferocious hunters, scouring the landscape for their favourite -prey: wolpertingers... -""" -function updatewyvern!(w::Animal, model::AgentBasedModel) - # check if the wyvern has reached its end-of-life - if w.age == trait(w, "maxage") - kill_agent!(w, model) - return - end - # check if a wolpertinger is in pouncing distance - for a in nearby_agents(w, model, trait(w, "speed")) - (a.species.name != "Wolpertinger") && continue - move_agent!(w, a.pos, model) - w.energy -= trait(w, "pounceenergy") - if rand() < trait(w, "huntsuccess") - @debug "Wyvern $(w.id) killed wolpertinger $(a.id)." - w.energy += Int(round(a.energy/2)) - kill_agent!(a, model) - @goto reproduce + """ + Wyverns are ferocious hunters, scouring the landscape for their favourite + prey: wolpertingers... + """ + @phase summer begin + for a in nearby_agents(animal, model, @trait(speed)) + # check if a wolpertinger is in pouncing distance + if a.traits["name"] == "Wolpertinger" + move_agent!(animal, a.pos, model) + if rand() < @trait(huntsuccess) + @debug "$(animalid(animal)) killed $(animalid(a))." + kill_agent!(a, model) + @goto reproduce + end + elseif a.traits["name"] == "Wyvern" && rand() < @trait(aggression) + # wyverns also fight against each other if they get too close + move_agent!(animal, a.pos, model) + outcome = rand() + if outcome < 0.4 + @debug "$(animalid(animal)) killed $(animalid(a)) in a fight." + kill_agent!(a, model) + elseif outcome < 0.8 + @kill 1.0 "wounds sustained in a fight" + end + @goto reproduce + end end - end - # check if a wolpertinger is in seeing distance, or walk in a random direction - direction = Tuple(rand([-1,1], 2)) - for a in nearby_agents(w, model, trait(w, "vision")) - if a.species.name == "Wolpertinger" - direction = get_direction(w.pos, a.pos, model) - break + # check if a wolpertinger is in seeing distance, or walk in a random direction + direction = Tuple(rand([-1,1], 2)) + for a in nearby_agents(animal, model, @trait(vision)) + if a.traits["name"] == "Wolpertinger" + direction = get_direction(animal.pos, a.pos, model) + break + end + end + for i in 1:@trait(speed) + walk!(animal, direction, model; ifempty=false) + end + # reproduce every once in a blue moon + @label reproduce + (rand() < @trait(fecundity)) && @reproduce + # hibernate from November to March + if monthday(model.date) == (11,1) + @trait(phase) = "winter" end - end - for i in 1:trait(w, "speed") - walk!(w, direction, model; ifempty=false) - w.energy -= 1 + (@trait(age) == maxage) && @kill(1.0, "old age") end - # reproduce every once in a blue moon - @label reproduce - if rand() < trait(w, "fecundity") - @debug "Wyvern $(w.id) has reproduced." - add_agent!(w.pos, Animal, model, getspecies("Wyvern"), - hermaphrodite, 0, trait(w, "birthenergy")) + + """ + Fortunately, wyverns hibernate in winter. + """ + @phase winter begin + # hibernate from November to March + if monthday(model.date) == (3,1) + @trait(phase) = "summer" + end end end - -newspecies("Wyvern", - initwyvern!, - updatewyvern!, - Dict("initpop"=>100, - "birthenergy"=>1000, - "fecundity"=>0.01, - "maxage"=>365, - "speed"=>10, - "vision"=>50, - "pounceenergy"=>20, - "huntsuccess"=>0.3)) diff --git a/src/parameters.toml b/src/parameters.toml index b24fe1e..69677aa 100644 --- a/src/parameters.toml +++ b/src/parameters.toml @@ -15,13 +15,14 @@ loglevel = "debug" # verbosity level: "debug", "info", "quiet" seed = 0 # seed value for the RNG (0 -> random value) # dates to start and end the simulation startdate = 2020-01-01 -enddate = 2020-12-31 +#enddate = 2020-01-01 +enddate = 2021-12-31 [farm] -farmmodel = "FieldManager" # which version of the farm model to use +farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented) [nature] -targetspecies = ["Wolpertinger"] # list of target species to simulate +targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never -- GitLab