Skip to content
Snippets Groups Projects
Select Git revision
  • f6fdd9017e4e71297d30495a4b57a4a2af0f27d4
  • master default protected
  • marco-development
  • development
  • fix-missing-weatherdata
  • fix-eichsfeld-weather
  • marco/dev-aquacrop
  • 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
24 results

populations.jl

Blame
  • Daniel Vedder's avatar
    xo30xoqa authored
    Still need to write tests to make sure it works properly, but at least
    it runs without errors now and seems to do what it's supposed to.
    faed2eae
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    populations.jl 8.20 KiB
    ### Persefone.jl - a model of agricultural landscapes and ecosystems in Europe.
    ###
    ### This file contains a set of utility functions for species, including initialisation,
    ### reproduction, and mortality.
    ###
    
    """
        initpopulation(habitatdescriptor; popsize=-1, pairs=false, asexual=false)
    
    Creates a function that initialises individuals at random locations across the landscape.
    This can be used to create the `initialise!` variable in a species definition block.
    
    - `habitatdescriptor` is a function that determines whether a given location is suitable
        or not (create this using [`@habitat`](@ref)).
    
    - `phase` determines which life phase individuals will be assigned to. If this is `nothing`,
        the species' default post-natal life stage will be used (although note that this is
        probably not what you want).
    
    - `popsize` determines the number of individuals that will be created. If this is zero or
        negative, one individual will be created in every suitable location in the landscape.
        If `popsize` is greater than the number of suitable locations, multiple individuals
        will be created in one place.
    
    - `popdensity`: if this is greater than zero, the chance of creating an individual (or
        pair of individuals) at a suitable location is 1/popdensity.
    
    - If `pairs` is true, a male and a female individual will be created in each selected
        location, otherwise, only one individual will be created at a time.
    
    - If `asexual` is true, all created individuals are assigned the sex `hermaphrodite`,
        otherwise, they are randomly assigned male of female. (If `pairs` is true, `asexual`
        is ignored.)
    
    - `initfunction` is an optional function that takes an animal object and the model,
        and performs individual-specific initialisation tasks. It is called after the
        individual has been created and placed.
    """
    function initpopulation(habitatdescriptor::Function; phase::Union{String,Nothing}=nothing,
                            popsize::Int64=-1, popdensity::Int64=-1, pairs::Bool=false,
                            asexual::Bool=false, initfunction::Function=(a,m)->nothing)
        #TODO add a constructor function/macro for the individual
        function(species::Dict{String,Any}, model::AgentBasedModel)
            n = 0
            lastn = 0
            specname = species["name"]
            (!isnothing(phase)) && (species["phase"] = phase)
            width, height = size(model.landscape)
            while n == 0 || n < popsize
                for x in @shuffle!(Vector(1:width))
                    for y in @shuffle!(Vector(1:height))
                        if habitatdescriptor((x,y), model) &&
                            (popdensity <= 0 || @chance(1/popdensity)) #XXX what if pd==0?
                            if pairs
                                a1 = add_agent!((x,y), Animal, model, deepcopy(species),
                                                (-1,-1), female, 0)
                                a2 = add_agent!((x,y), Animal, model, deepcopy(species),
                                                (-1, -1), male, 0)
                                initfunction(a1, model)
                                initfunction(a2, model)
                                n += 2
                            else
                                sex = asexual ? hermaphrodite : @rand([male, female])
                                a = add_agent!((x,y), Animal, model, deepcopy(species),
                                               (-1, -1), sex,0)
                                initfunction(a, model)
                                n += 1
                            end
                        end
                        (popsize > 0 && n >= popsize) && break
                    end
                    (popsize > 0 && n >= popsize) && break
                end
                if lastn == n # prevent an infinite loop - we don't have a Cray...
                    @warn "There are not enough suitable locations for $(specname) in the landscape."
                    break
                end
                lastn = n
            end
            @info "Initialised $(n) $(specname)s."
        end
    end
    
    """
        initrandompopulation(popsize; kwargs...)
    
    A simplified version of [`initpopulation`](@ref). Creates a function that initialises
    `popsize` individuals, spread at random across the landscape.
    """
    function initrandompopulation(popsize::Int64; kwargs...)
        #XXX How should this be called if users are supposed to use @initialise?
        initpopulation(@habitat(true); popsize=popsize, kwargs...)
    end
    
    #XXX initpopulation with dispersal from an original source?
    #XXX initpopulation based on known occurences in real-life?
    
    """
        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::AgentBasedModel, mate::Int64, n::Int64=1)
        (animal.sex == male) && @warn "Male $(animalid(animal)) is reproducing."
        offspring = []
        for i in 1:n
            sex = (animal.sex == hermaphrodite) ? hermaphrodite : @rand([male, female])
            # We need to generate a fresh species dict here
            species = @eval $(Symbol(animal.name))($model)
            a = add_agent!(animal.pos, Animal, model, species, (animal.id, mate), sex, 0)
            push!(offspring, a.id)
        end
        @debug "$(animalid(animal)) has reproduced."
        offspring
    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 "$(animalid(animal)) has died$(postfix)"
            kill_agent!(animal, model)
            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::AgentBasedModel, arrival::Date)
        i = 1
        while i <= length(model.migrants) && model.migrants[i].second < arrival
            i += 1
        end
        i <= length(model.migrants) ?
            insert!(model.migrants, i, Pair(animal, arrival)) :
            push!(model.migrants, Pair(animal, arrival))
        remove_agent!(animal, model)
        @debug "$(animalid(animal)) has migrated."
    end
    
    """
       isalive(id, model)
    
    Test whether the animal with the given ID is still alive.
    """
    function isalive(animalid::Int64, model::AgentBasedModel)
        animalid in allids(model)
    end
    
    """
        nearby_animals(pos, model, radius)
    
    Return an iterator over all animals in the given radius around this position.
    """
    function nearby_animals(pos::Tuple{Int64,Int64}, model::AgentBasedModel, radius::Int64)
        #TODO enable filtering by species
        neighbours = (model[id] for id in nearby_ids(pos, model, radius))
        Iterators.filter(a -> typeof(a) == Animal, neighbours)
    end
    
    """
        nearby_animals(animal, model, radius)
    
    Return an iterator over all animals in the given radius around this animal, excluding itself.
    """
    function nearby_animals(animal::Animal, model::AgentBasedModel, radius::Int64)
        #TODO enable filtering by species
        neighbours = (model[id] for id in nearby_ids(animal.pos, model, radius))
        Iterators.filter(a -> typeof(a) == Animal && a.id != animal.id, neighbours)
    end
    
    """
        countanimals(pos, model; species="", radius=0)
    
    Count the number of animals in this location (optionally supplying a species name and radius).
    """
    function countanimals(pos::Tuple{Int64,Int64}, model::AgentBasedModel;
                          species::String="", radius::Int64=0)
        n = 0
        #XXX can we ignore capitalisation in the spelling of `species`?
        for a in nearby_animals(pos, model, radius)
            (species == "" || a.traits["name"] == species) && (n += 1)
        end
        return n
    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::AgentBasedModel,
                           distance::Int64=0)
        #TODO test function
        spacing = Tuple(@rand(-distance:distance, 2))
        targetposition = safebounds(spacing .+ leader.pos, model)
        move_agent!(follower, targetposition, model)
    end
    
    ##TODO add random walk with habitat descriptor