Something went wrong on our end
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
individuals.jl 7.14 KiB
### 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?)