### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe. ### ### This file is responsible for managing the animal modules and contains functions ### and types integrating the nature submodel with the rest of Persefone. ### ## An enum used to assign a sex to each animal @enum Sex hermaphrodite male female #XXX hermaphrodite probably is not needed and its implementation is iffy """ Animal This is the generic agent type for all animals. Individual species are created using the [`@species`](@ref) macro. In addition to user-defined, species-specific fields, all species contain the following fields: - `id` An integer unique identifier for this individual. - `sex` male, female, or hermaphrodite. - `parents` The IDs of the individual's parents. - `pos` An (x, y) coordinate tuple. - `age` The age of the individual in days. - `phase` The update function to be called during the individual's current life phase. - `energy` A [DEBparameters](@ref) struct for calculating energy budgets. - `offspring` A vector containing the IDs of an individual's children. """ abstract type Animal <: ModelAgent end """ speciesof(animal) Return the species name of this animal as a string. """ function speciesof(a::Union{Animal,Type}) # strip out the module name if necessary (`Persefone.<species>`) (a isa Animal) && (a = typeof(a)) spstrings = split(string(a), ".") length(spstrings) == 1 ? spstrings[1] : spstrings[2] end """ speciestype(name) Return the Type of this species. """ function speciestype(name::String) # get the species Type from its namestring by looking in the module namespace speciestype = nothing for var in names(Persefone, all=true) if string(var) == name && getfield(Persefone, var) isa Type speciestype = getfield(Persefone, var) break end end isnothing(speciestype) && @error("Species $name is not defined.") speciestype 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 "$(speciesof(a)) $(a.id)" end """ create!(animal, model) The `create!` function is called for every individual at birth or at model initialisation. Species must use [`@create`](@ref) to define a species-specific method. This is the fall- back method, in case none is implemented for a species. """ function create!(a::Animal, model::SimulationModel) @warn "Species $(speciesof(a)) has no create!() method. Use `@create` to add one." end """ stepagent!(animal, model) Update an animal by one day, executing it's currently active phase function. """ function stepagent!(animal::Animal, model::SimulationModel) animal.age += 1 animal.phase(animal, model) end """ initnature!(model) Initialise the model with all simulated animal populations. """ function initnature!(model::SimulationModel) # The config file determines which species are simulated in this run for speciesname in @param(nature.targetspecies) # Call each species' initialisation function initpopulation!(speciesname, model) end # Initialise the data output initecologicaldata(model) end """ updatenature!(model) Run processes that affect all animals. """ function updatenature!(model::SimulationModel) # Update all animals. Dead animals are replaced by `nothing`, we can skip these. #XXX I assume that keeping all those `nothing`s in the animal vector won't lead # to memory bloat, but I will have to check that. for a in model.animals !isnothing(a) && stepagent!(a, model) end # The migrant pool is sorted by date of return, so we can simply look at the top # of the stack to check whether any animals are returning today. while !isempty(model.migrants) && model.migrants[1].second <= model.date returnee = model.migrants[1].first model.animals[returnee.id] = returnee @debug "$(animalid(returnee)) has returned." deleteat!(model.migrants, 1) end end """ killallanimals!(model) Remove all animal individuals from the simulation. """ function killallanimals!(model) for a in model.animals kill!(a, model) end model.migrants = Vector{Pair{Animal, Date}}() model.animals = Vector{Union{Animal,Nothing}}() return end