Skip to content
Snippets Groups Projects
Commit 2db97204 authored by xo30xoqa's avatar xo30xoqa
Browse files

Implemented animal territories.

(Not yet tested.)
parent 46ab9b84
No related branches found
No related tags found
No related merge requests found
...@@ -45,8 +45,9 @@ macro species(name, body) ...@@ -45,8 +45,9 @@ macro species(name, body)
pos::Tuple{Int64,Int64} pos::Tuple{Int64,Int64}
phase::Function = (self,model)->nothing phase::Function = (self,model)->nothing
age::Int = 0 age::Int = 0
energy::Union{EnergyBudget,Nothing} = nothing # DEB is optional energy::Union{EnergyBudget,Nothing} = nothing # DEB is optional #TODO remove?
offspring::Vector{Int64} = Vector{Int64}() offspring::Vector{Int64} = Vector{Int64}()
territory::Vector{Tuple{Int64,Int64}} = Vector{Tuple{Int64,Int64}}()
$(body.args...) $(body.args...)
end end
# define a constructor giving the minimum necessary arguments as positional arguments # define a constructor giving the minimum necessary arguments as positional arguments
...@@ -106,7 +107,7 @@ variables: ...@@ -106,7 +107,7 @@ variables:
Many macros are available to make the code within the body of `@phase` more succinct. Many macros are available to make the code within the body of `@phase` more succinct.
Some of the most important of these are: [`@setphase`](@ref), [`@respond`](@ref), Some of the most important of these are: [`@setphase`](@ref), [`@respond`](@ref),
[`@kill`](@ref), [`@reproduce`](@ref), [`@neighbours`](@ref), [`@migrate`](@ref), [`@kill`](@ref), [`@reproduce`](@ref), [`@neighbours`](@ref), [`@migrate`](@ref),
[`@move`](@ref), [`@rand`](@ref). [`@move`](@ref), [`@occupy`](@ref), [`@rand`](@ref).
""" """
macro phase(species, phase, body) macro phase(species, phase, body)
quote quote
...@@ -237,6 +238,46 @@ macro migrate(arrival) ...@@ -237,6 +238,46 @@ macro migrate(arrival)
:(migrate!($(esc(:self)), $(esc(:model)), $(esc(arrival)))) :(migrate!($(esc(:self)), $(esc(:model)), $(esc(arrival))))
end end
"""
@occupy(position)
Add the given position to this animal's territory. Use [`@vacate`](@ref) to
remove positions from the territory again. This can only be used nested within [`@phase`](@ref).
"""
macro occupy(position)
:(occupy!($(esc(:self)), $(esc(:model)), $(esc(position))))
end
"""
@isoccupied(position)
Test whether this position is already occupied by an animal of this species.
This can only be used nested within [`@phase`](@ref).
"""
macro isoccupied(position)
:(isoccupied($(esc(:model)), speciesof($(esc(:self))), $(esc(position))))
end
"""
@vacate(position)
Remove the given position from this animal's territory.
This can only be used nested within [`@phase`](@ref).
"""
macro vacate(position)
:(vacate!($(esc(:self)), $(esc(:model)), $(esc(position))))
end
"""
@vacate()
Remove this animal's complete territory.
This can only be used nested within [`@phase`](@ref).
"""
macro vacate()
:(vacate!($(esc(:self)), $(esc(:model))))
end
""" """
@habitat @habitat
......
...@@ -24,6 +24,7 @@ fields, all species contain the following fields: ...@@ -24,6 +24,7 @@ fields, all species contain the following fields:
- `phase` The update function to be called during the individual's current life phase. - `phase` The update function to be called during the individual's current life phase.
- `energy` A [DEBparameters](@ref) struct for calculating energy budgets. - `energy` A [DEBparameters](@ref) struct for calculating energy budgets.
- `offspring` A vector containing the IDs of an individual's children. - `offspring` A vector containing the IDs of an individual's children.
- `territory` A vector of coordinates that comprise the individual's territory.
""" """
abstract type Animal <: ModelAgent end abstract type Animal <: ModelAgent end
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
### reproduction, and mortality. ### reproduction, and mortality.
### ###
##TODO move the life-history functions into a new file `individuals.jl`
""" """
PopInitParams PopInitParams
...@@ -175,9 +177,14 @@ Returns true if the animal dies, false if not. ...@@ -175,9 +177,14 @@ Returns true if the animal dies, false if not.
function kill!(animal::Animal, model::SimulationModel, function kill!(animal::Animal, model::SimulationModel,
probability::Float64=1.0, cause::String="") probability::Float64=1.0, cause::String="")
if @rand() < probability 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." postfix = isempty(cause) ? "." : " from $cause."
@debug "$(animalid(animal)) has died$(postfix)" @debug "$(animalid(animal)) has died$(postfix)"
filter!(x -> x!=animal.id, model.landscape[animal.pos...].animals)
model.animals[animal.id] = nothing model.animals[animal.id] = nothing
return true return true
end end
...@@ -204,6 +211,53 @@ function migrate!(animal::Animal, model::SimulationModel, arrival::Date) ...@@ -204,6 +211,53 @@ function migrate!(animal::Animal, model::SimulationModel, arrival::Date)
@debug "$(animalid(animal)) has migrated." @debug "$(animalid(animal)) has migrated."
end end
"""
occupy!(animal, model, position)
Add the given location to the animal's territory.
"""
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))."
end
push!(animal.territories, position)
push!(model.landscape[position...], animal.id)
end
"""
isoccupied(model, position, species)
Test whether this location is part of the territory of an animal of the given species.
"""
function isoccupied(model::SimulationModel, species::String, position::Tuple{Int64,Int64})
for terr in model.landscape[position...].territories
(speciesof(model.animals[terr]) == species) && return true
end
return false
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
""" """
isalive(id, model) isalive(id, model)
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
"The types of management event that can be simulated" "The types of management event that can be simulated"
@enum Management tillage sowing fertiliser pesticide harvesting @enum Management tillage sowing fertiliser pesticide harvesting
#XXX rename to Management or similar?
""" """
Pixel Pixel
...@@ -19,12 +18,17 @@ in a single object. The model landscape consists of a matrix of pixels. ...@@ -19,12 +18,17 @@ in a single object. The model landscape consists of a matrix of pixels.
(Note: further landscape information may be added here in future.) (Note: further landscape information may be added here in future.)
""" """
mutable struct Pixel mutable struct Pixel
landcover::LandCover landcover::LandCover # land cover class at this position
fieldid::Union{Missing,Int64} fieldid::Union{Missing,Int64} # ID of the farmplot (if any) at this position
events::Vector{Management} events::Vector{Management} # management events that have been applied to this pixel
animals::Vector{Int64} animals::Vector{Int64} # IDs of animals currently at this position
territories::Vector{Int64} # IDs of animals that claim this pixel as part of their territory
end end
Pixel(landcover::LandCover, fieldid::Union{Missing,Int64}) =
Pixel(landcover, fieldid, Vector{Management}(), Vector{Int64}(), Vector{Int64}())
Pixel(landcover::LandCover) = Pixel(landcover, missing)
""" """
FarmEvent FarmEvent
...@@ -63,7 +67,7 @@ function initlandscape(directory::String, landcovermap::String, farmfieldsmap::S ...@@ -63,7 +67,7 @@ function initlandscape(directory::String, landcovermap::String, farmfieldsmap::S
lcv = LandCover(Int(landcover[x,y][1]/10)) lcv = LandCover(Int(landcover[x,y][1]/10))
ff = Int64(farmfields[x,y][1]) ff = Int64(farmfields[x,y][1])
(iszero(ff)) && (ff = missing) (iszero(ff)) && (ff = missing)
landscape[x,y] = Pixel(lcv, ff, Vector{Symbol}(), []) landscape[x,y] = Pixel(lcv, ff)
end end
end end
return landscape return landscape
...@@ -236,6 +240,18 @@ function randomdirection(model::SimulationModel, distance::Length) ...@@ -236,6 +240,18 @@ function randomdirection(model::SimulationModel, distance::Length)
Tuple(@rand(-range:range, 2)) Tuple(@rand(-range:range, 2))
end end
"""
bounds(x; max=Inf, min=0)
A utility function to make sure that a number is within a given set of bounds.
Returns `max`/`min` if `x` is greater/less than this.
"""
function bounds(x::Number; max::Number=Inf, min::Number=0)
x > max ? max :
x < min ? min :
x
end
""" """
inbounds(pos, model) inbounds(pos, model)
...@@ -252,11 +268,6 @@ end ...@@ -252,11 +268,6 @@ end
Make sure that a given position is within the bounds of the model landscape. Make sure that a given position is within the bounds of the model landscape.
""" """
function safebounds(pos::Tuple{Int64,Int64}, model::SimulationModel) function safebounds(pos::Tuple{Int64,Int64}, model::SimulationModel)
dims = size(model.landscape) width, height = size(model.landscape)
x, y = pos (bounds(pos[1], max=width, min=1), bounds(pos[2], max=height, min=1))
x <= 0 && (x = 1)
x > dims[1] && (x = dims[1])
y <= 0 && (y = 1)
y > dims[2] && (y = dims[2])
(x,y)
end end
...@@ -47,7 +47,7 @@ end) # end eval ...@@ -47,7 +47,7 @@ end) # end eval
@testset "Habitat macros" begin @testset "Habitat macros" begin
# set up the testing landscape # set up the testing landscape
model = inittestmodel() model = inittestmodel()
model.landscape[6,6] = Pixel(Ps.agriculture, 1, [], []) model.landscape[6,6] = Pixel(Ps.agriculture, 1)
push!(model.farmplots, push!(model.farmplots,
FarmPlot(1, [(6,6)], model.crops["winter wheat"], Ps.janfirst, FarmPlot(1, [(6,6)], model.crops["winter wheat"], Ps.janfirst,
0.0, 0.0, 0.0, 0.0, Vector{Ps.Management}())) 0.0, 0.0, 0.0, 0.0, Vector{Ps.Management}()))
...@@ -165,6 +165,7 @@ end ...@@ -165,6 +165,7 @@ end
@test typeof(@rand()) == Float64 @test typeof(@rand()) == Float64
@test @rand([true, true]) @test @rand([true, true])
#TODO test movement macros #TODO test movement macros
#TODO test territory macros
end end
#XXX I'm unsure whether to keep the insect submodel at all, so rather than #XXX I'm unsure whether to keep the insect submodel at all, so rather than
...@@ -175,12 +176,12 @@ end ...@@ -175,12 +176,12 @@ end
# date1 = Date("2023-05-08") # day 128 (season begin) # date1 = Date("2023-05-08") # day 128 (season begin)
# date2 = Date("2023-07-06") # day 187 (insect max) # date2 = Date("2023-07-06") # day 187 (insect max)
# date3 = Date("2023-09-27") # day 270 (season end) # date3 = Date("2023-09-27") # day 270 (season end)
# p1 = Pixel(Ps.agriculture, 1, [], []) # p1 = Pixel(Ps.agriculture, 1)
# p2 = Pixel(Ps.agriculture, 1, [Ps.pesticide], []) # p2 = Pixel(Ps.agriculture, 1, [Ps.pesticide], [], [])
# p3 = Pixel(Ps.grass, 1, [], []) # p3 = Pixel(Ps.grass, 1)
# p4 = Pixel(Ps.soil, 1, [Ps.fertiliser, Ps.pesticide], []) # p4 = Pixel(Ps.soil, 1, [Ps.fertiliser, Ps.pesticide], [], [])
# p5 = Pixel(Ps.forest, 1, [], []) # p5 = Pixel(Ps.forest, 1)
# p6 = Pixel(Ps.water, 1, [], []) # p6 = Pixel(Ps.water, 1)
# # check whether the model calculates the same numbers I did by hand # # check whether the model calculates the same numbers I did by hand
# model.date = date1 # model.date = date1
# #FIXME these tests are still broken because insectbiomass throws a unit error # #FIXME these tests are still broken because insectbiomass throws a unit error
...@@ -239,7 +240,7 @@ end ...@@ -239,7 +240,7 @@ end
# as no individuals are initialised # as no individuals are initialised
for x in 1:6 for x in 1:6
for y in 5:6 for y in 5:6
model.landscape[x,y] = Pixel(Ps.agriculture, missing, [], []) model.landscape[x,y] = Pixel(Ps.agriculture)
end end
end end
# test migration # test migration
......
...@@ -84,10 +84,10 @@ function smalltestlandscape() ...@@ -84,10 +84,10 @@ function smalltestlandscape()
(x in (1:2)) ? lc = Ps.forest : (x in (1:2)) ? lc = Ps.forest :
(y == 8) ? lc = Ps.builtup : (y == 8) ? lc = Ps.builtup :
lc = Ps.grass lc = Ps.grass
landscape[x,y] = Pixel(lc, missing, [], []) landscape[x,y] = Pixel(lc)
end end
end end
landscape[6,4] = Pixel(Ps.water, 0, [], []) landscape[6,4] = Pixel(Ps.water, 0)
landscape landscape
end end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment