### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe. ### ### This file contains life-history and other ecological functions that apply to ### all animal individuals, such reproduction, death, and movement. ### """ reproduce!(animal, model, mate, n=1) Produce one or more offspring for the given animal at its current location. The `mate` argument gives the ID of the reproductive partner. """ function reproduce!(animal::Animal, model::SimulationModel, n::Int64=1, mate::Int64=-1) (animal.sex == male) && @warn "Male $(animalid(animal)) is reproducing." for i in 1:n if animal.sex == hermaphrodite sex = hermaphrodite else sex = @rand([male, female]) end bphase = populationparameters(typeof(animal)).birthphase child = typeof(animal)(length(model.animals)+1, sex, (animal.id, mate), animal.pos, bphase) push!(model.animals, child) push!(animal.offspring, child.id) mate > 0 && push!(model.animals[mate].offspring, child.id) push!(model.landscape[child.pos...].animals, child.id) create!(child, model) end @debug "$(animalid(animal)) 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::SimulationModel, probability::Float64=1.0, cause::String="") if @rand() < probability # remove the animal's location and territory pointers from the landscape filter!(x -> x!=animal.id, model.landscape[animal.pos...].animals) for pos in animal.territory filter!(x -> x!=animal.id, model.landscape[pos...].territories) end # print the epitaph and remove the animal from the model postfix = isempty(cause) ? "." : " from $cause." @debug "$(animalid(animal)) has died$(postfix)" @record("mortality", [model.date, speciesof(animal), cause]) model.animals[animal.id] = nothing return true end return false end """ migrate!(animal, model, arrival) Remove this animal from the map and add it to the migrant species pool. It will be returned to its current location at the specified `arrival` date. """ function migrate!(animal::Animal, model::SimulationModel, arrival::AnnualDate) # keep model.migrants sorted by inserting the new migrant after migrants # that will return earlier than it i = findfirst(m -> m.second >= arrival, model.migrants) if isnothing(i) push!(model.migrants, Pair(animal, arrival)) else insert!(model.migrants, i, Pair(animal, arrival)) end filter!(x -> x!=animal.id, model.landscape[animal.pos...].animals) model.animals[animal.id] = nothing @debug "$(animalid(animal)) has migrated." end """ occupy!(animal, model, position) Add the given location to the animal's territory. Returns `true` if successful (i.e. if the location was not already occupied by a conspecific), `false` if not. """ function occupy!(animal::Animal, model::SimulationModel, position::Tuple{Int64,Int64}) if isoccupied(model, speciesof(animal), position) #XXX should this be an error? @warn "Position $position is already occupied by a $(speciesof(animal))." animal return false else push!(animal.territory, position) push!(model.landscape[position...].territories, animalid(animal)) return true end end """ vacate!(animal, model, position) Remove this position from the animal's territory. """ function vacate!(animal::Animal, model::SimulationModel, position::Tuple{Int64,Int64}) filter!(x -> x!=position, animal.territory) filter!(x -> x!=animal.id, model.landscape[position...].territories) end """ vacate!(animal, model) Remove the animal's complete territory. """ function vacate!(animal::Animal, model::SimulationModel) for pos in animal.territory filter!(x -> x!=animal.id, model.landscape[pos...].territories) end animal.territory = Vector{Tuple{Int64,Int64}}() end """ followanimal!(follower, leader, model, distance=0) Move the follower animal to a location near the leading animal. """ function followanimal!(follower::Animal, leader::Animal, model::SimulationModel, distance::Length=0m) #TODO test function dist = Int(floor(distance / @param(world.mapresolution))) spacing = Tuple(@rand(-dist:dist, 2)) targetposition = safebounds(spacing .+ leader.pos, model) move!(follower, model, targetposition) end """ move!(animal, model, position) Move the animal to the given position, making sure that this is in-bounds. If the position is out of bounds, the animal stops at the map edge. """ function move!(animal::Animal, model::SimulationModel, position::Tuple{Int64,Int64}) #XXX should this function give some sort of warning (e.g. return false) # if the original position is not reachable? filter!(x -> x != animal.id, model.landscape[animal.pos...].animals) animal.pos = safebounds(position, model) push!(model.landscape[animal.pos...].animals, animal.id) end """ walk!(animal, model, direction, distance=1pixel) Let the animal move a given number of steps in the given direction ("north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest", "random"). """ function walk!(animal::Animal, model::SimulationModel, direction::String, distance::Length=-1m) steps = 1 if distance > @param(world.mapresolution) steps = Int(floor(distance/@param(world.mapresolution))) end if direction == "north" shift = (0,-steps) elseif direction == "northeast" shift = (steps,-steps) elseif direction == "east" shift = (steps,0) elseif direction == "southeast" shift = (steps,steps) elseif direction == "south" shift = (0,steps) elseif direction == "southwest" shift = (-steps,steps) elseif direction == "west" shift = (-steps,0) elseif direction == "northwest" shift = (-steps,-steps) elseif direction == "random" shift = Tuple(@rand(-steps:steps, 2)) else @error "Invalid direction in @walk: "*direction end walk!(animal, model, shift) end """ walk!(animal, model, direction, distance=-1) Let the animal move in the given direction, where the direction is defined by an (x, y) tuple to specify the shift in coordinates. If maxdist >= 0, move no further than the specified distance. """ function walk!(animal::Animal, model::SimulationModel, direction::Tuple{Int64,Int64}, maxdist::Length=-1m) #TODO test distance = Int(floor(maxdist/@param(world.mapresolution))) if distance >= 0 direction[1] > distance && (direction[1] = distance) direction[2] > distance && (direction[2] = distance) direction[1] < -distance && (direction[1] = -distance) direction[2] < -distance && (direction[2] = -distance) end newpos = animal.pos .+ direction move!(animal, model, newpos) end #TODO add a walk function with a habitat descriptor ##TODO add walktoward or similar function (incl. pathfinding?)