diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl index a8e70c00fe8c0b54563a7ca41f2008e6ae6d9c22..e80a1d9252aee85c2f5f6ddfb180264ef11157b2 100644 --- a/src/nature/lifehistory.jl +++ b/src/nature/lifehistory.jl @@ -1,12 +1,59 @@ ### Persephone - a socio-economic-ecological model of European agricultural landscapes. ### -### This file contains a set of utility functions needed by species. +### This file contains a set of life-history related utility functions needed by species. ### -#TODO initrandompopulation +""" + initrandompopulation(popsize, asexual=true) -#TODO killanimal! +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) + initfunc = function(species::Dict{String,Any}, model::AgentBasedModel) + if popsize < 1.0 + x, y = size(model.landscape) + popsize = Int(round(x*y*popsize)) + end + for i in 1:popsize + sex = asexual ? hermaphrodite : rand([male, female]) + add_agent!(Animal, model, species, sex, 0) + end + @debug "Initialised $(popsize) $(species["name"])s." + end + return initfunc +end -#TODO reproduce! +#TODO initpopulation -#TODO parentsalive? +""" + reproduce!(animal, model, n=1) + +Produce one or more offspring for the given animal at its current location. +""" +function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1) + # XXX at the moment we don't have intra-specific variation, so currently we + # don't need sexual recombination here + for i in 1:n + 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." +end + +""" + kill(animal, model, probability=1.0, cause="") + +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() < probability + postfix = isempty(cause) ? "." : " from $cause." + @debug "$(animal.traits["name"]) $(animal.id) has died$(postfix)" + kill_agent!(animal, model) + return true + end + return false +end diff --git a/src/nature/nature.jl b/src/nature/nature.jl index 70c13b3f77827918fe2ba521b432760645c3d695..71dad4008376f919443ea08195a26725fae6a019 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -6,6 +6,9 @@ ## 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 @@ -19,6 +22,7 @@ by trait dictionaries passed by them during initialisation. traits::Dict{String,Any} sex::Sex age::Int32 + #XXX keep track of parents and/or offspring? end @@ -33,7 +37,7 @@ custom syntax to describe species' biology: ```julia @species name begin - initialise! = initrandompopulation() + initialise! = initpopulation() phase = "phase1" ... @phase phase1 begin @@ -51,14 +55,18 @@ life cycle (see the documentation to `@phase` for details). There are two parameters that all species must define: - `initialise!` should specify a function that will be used to create the starting population for this species. This function must take - exactly one argument (an `AgentBasedModel` object). + two arguments, a species dict and an `AgentBasedModel` object. + The easiest way to create this function is by using `initpopultion()`. - `phase` should be a string specifying the name of the first phase that individuals of this species will be assigned to on birth. + +Access to the rest of the model is given by the `model` variable (an object +of type `AgentBasedModel`). """ macro species(name, body) - funcname = "speciesfunction_"*name + funcname = Symbol(SPECIESPREFIX*string(name)) quote - Core.@__doc__ function $(esc(funcname))() + Core.@__doc__ function $(esc(funcname))(model::AgentBasedModel) $(esc(:name)) = string($(QuoteNode(name))) $(esc(body)) vardict = Base.@locals @@ -91,13 +99,14 @@ variables: `model.landscape` (a two-dimensional array of pixels containing geographic information). -The utility macros `@respond`, `@trait`, and `@here` can be used within the body of -`@phase` as short-hand for common expressions. +Several utility macros can be used within the body of `@phase` as a short-hand for +common expressions: `@respond`, `@trait`, `@here`, `@kill`, `@reproduce` To transition an individual to another phase, simply redefine its phase variable: `@trait(phase) = "newphase"`. """ macro phase(name, body) + #XXX Using an undeclared variable in the body doesn't throw an error? :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end) end @@ -122,7 +131,11 @@ A utility macro to quickly access an animal's trait value. This can only be used nested within `@phase`. """ macro trait(traitname) - :(animal.traits[string($(QuoteNode(name)))]) + if traitname in fieldnames(Animal) + :(animal.$(traitname)) + else + :(animal.traits[string($(QuoteNode(traitname)))]) + end end """ @@ -135,6 +148,26 @@ macro here(property) :(model.landscape[animal.pos...].$(property)) end +""" + @kill + +Kill this animal. This is a thin wrapper around `kill!()`, and passes on any arguments. +This can only be used nested within `@phase`. +""" +macro kill(args...) + :(kill!(animal, model, $(args...))) +end + +""" + @reproduce + +Let this animal reproduce. This is a thin wrapper around `reproduce!()`, and passes on +any arguments. This can only be used nested within `@phase`. +""" +macro reproduce(args...) + :(reproduce!(animal, model, $(args...))) +end + ### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE @@ -156,9 +189,9 @@ 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("speciesfunction_"*s) - species = @eval $funcname() - species["initialise!"](model) + funcname = Symbol(SPECIESPREFIX*s) + species = @eval $funcname($model) + species["initialise!"](species, model) end # Initialise the data output initecologicaldata() diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl index 53a045382a40e1529bc0de5d3cd4362d08b74d49..4843816234b5afa6f9720df61a2a98d6303b7bea 100644 --- a/src/nature/species/skylark.jl +++ b/src/nature/species/skylark.jl @@ -8,18 +8,21 @@ @species Skylark begin - initialise! = initrandompopulation() - phase = "egg" - + popdensity = 1/10000 lifeexpectancy = 365*5 + eggharvestmortality = 0.9 + eggpredationmortality = 0.1 + + initialise! = initrandompopulation(popsize) + phase = "egg" @phase egg begin - if !parentsalive(animal) - killanimal(animal) - end + @kill(@trait(eggpredationmortality), "predation") - @respond harvest killanimal(animal, eggharvestmortality) + #TODO dies if parents die + + @respond harvest @kill(@trait(eggharvestmortality), "harvest") if animal.age == 14 @trait(phase) = "nestling"