Skip to content
Snippets Groups Projects
Select Git revision
  • 3001a4e544e66f5844ed60bfbb04cf0e13ad12b2
  • master default protected
  • development
  • precompile-statements
  • precompile-tools
  • tmp-faster-loading
  • skylark
  • testsuite
  • code-review
  • v0.7.0
  • v0.6.1
  • v0.6.0
  • v0.5.5
  • v0.5.4
  • v0.5.3
  • v0.5.2
  • v0.2
  • v0.3.0
  • v0.4.1
  • v0.5
20 results

individuals.jl

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    individuals.jl 7.19 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 #FIXME what if we want to force all females?
                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?)