cesaraustralia / Dispersal.jl

Tools for simulating organism dispersal
Other
21 stars 3 forks source link

Idea to better implement subpopulation functions | enhancement #63

Closed virgile-baudrot closed 3 years ago

virgile-baudrot commented 3 years ago

Hi Raf,

this is an implementation of a discrete growth with 3 subpopulations (here sharing carying capacity) which work pretty well. If you have any idea to implement something generic for N populations:

@mix @Timestep struct InstrinsicGrowthRate3{GR1, GR2, GR3}
    intrinsicrate1::GR1 | 0.1      | true  | (0.0, 10.0) | "Intrinsic rate of growth per timestep"
    intrinsicrate2::GR2 | 0.1      | true  | (0.0, 10.0) | "Intrinsic rate of growth per timestep"
    intrinsicrate3::GR3 | 0.1      | true  | (0.0, 10.0) | "Intrinsic rate of growth per timestep"
end

@InstrinsicGrowthRate3  @CarryCap struct DiscreteGrowth3{R,W} <: GrowthRule{R,W} end

@inline applyrule(data, rule::DiscreteGrowth3, (population1, population2,population3), args...) = begin
    sumPop = population1+population2+population3
    ( rule.intrinsicrate1 * population1 * (1-sumPop/rule.carrycap),
      rule.intrinsicrate2 * population2 * (1-sumPop/rule.carrycap),
      rule.intrinsicrate3 * population3 * (1-sumPop/rule.carrycap) )
end

A generic could potentially look like this:

@Timestep struct InstrinsicGrowthRateN{GR}
    intrinsicrate::Vector{GR}
    numberSupPop::Int
   numberSupPop = length(intrinsicrate)
end

# I feel like popState would be like the "hood" is a dispersal context?
@inline applyrule(data, rule::DiscreteGrowth, popState, args...) = begin
    sumPop = sum(popState)
    ...
end

EDIT: I edit typos

rafaqz commented 3 years ago

First we need to get your code updated to get rid of all those macros! Can you push your latest code? then I can convert it and put in a PR. It will be so much easier to discuss after that.

For having multiple sub-populations in the grid, for intrinsicrate use a Tuple. (but you might not even need to lock it like this)

struct DiscreteGrowthRate{R,W,GR<:Tuple,I}
    intrinsicrate::GR
    numberSupPop::I
end

With a Tuple you can just map over the populations and growth rates, and it will handle any number of sub populations making this generic to all similar problems:

@inline function applyrule(data, rule::DiscreteGrowthRate, pops, index)
    map(pops, rule.intrinsicrate) do pop, ir
         ir * pop * (1 - sum(pops) / rule.carrycap)
    end
end

(map with a Vector is allocating and much slower, and can't be optimised the same way a Tuple can be. We also need to return a Tuple to be written back to the N separate grids, which just works using a Tuple for intrinsicrate)

Edit: you could get rid of the <:Tuple limitation, then a single instrinsicrate would also work

virgile-baudrot commented 3 years ago

Great. I just send a PR #64