Skip to content
Snippets Groups Projects
Commit 2673d3ba authored by xo30xoqa's avatar xo30xoqa
Browse files

Started upgrading animal tests

parent 3ef13ba4
Branches
Tags
No related merge requests found
......@@ -4,7 +4,7 @@ SHELL = /bin/bash
run:
# run an example simulation
if [ -d "example_results" ]; then rm -r example_results; fi
./run.jl -o example_results
julia run.jl -o example_results
#src/analysis/analyse_nature.R example_results
test:
......
......@@ -79,6 +79,7 @@ export
@distancetoedge,
@randompixel,
@randomdirection,
@nearby_animals,
@neighbours,
@move,
@walk,
......@@ -132,18 +133,13 @@ include("nature/nature.jl")
include("nature/macros.jl")
include("nature/populations.jl")
include("nature/ecologicaldata.jl")
#TODO loop over files in nature/species directory
# (the below doesn't work yet)
# for f in readdir("nature/species", join=true)
# endswith(f, ".jl") && include(f)
# end
include("nature/species/skylark.jl")
include("nature/species/wolpertinger.jl")
include("nature/species/wyvern.jl")
include("core/simulation.jl") #this must be last
# precompile important functions - XXX use PrecompileTools.jl
# precompile important functions - TODO use PrecompileTools.jl
precompile(initialise, (String,Int))
precompile(stepsimulation!, (SimulationModel,))
......
......@@ -180,12 +180,12 @@ end
"""
cropname(model, position)
Return the name of the crop at this position, or nothing if there is no crop here
Return the name of the crop at this position, or an empty string if there is no crop here
(utility wrapper).
"""
function cropname(pos::Tuple{Int64,Int64}, model::SimulationModel)
ismissing(model.landscape[pos...].fieldid) ? nothing :
model.farmplots[model.landscape[pos...].fieldid].croptype.name
field = model.landscape[pos...].fieldid
ismissing(field) ? "" : model.farmplots[field].croptype.name
end
"""
......
......@@ -47,7 +47,9 @@ the `model` variable (an object of type `SimulationModel`).
"""
macro species(name, body)
quote
@kwdef mutable struct $(name) <: Animal
@kwdef mutable struct $name <: Animal
#FIXME once Julia 1.11 is released, escape $name above
#(https://discourse.julialang.org/t/kwdef-constructor-not-available-outside-of-module/114675/4)
const id::Int64
const sex::Sex = hermaphrodite
const parents::Tuple{Int64,Int64} = (-1, -1) #XXX assumes sexual reprod.
......@@ -63,9 +65,6 @@ macro species(name, body)
$(esc(name))(id=id, sex=sex, parents=parents, pos=pos, phase=phase)
# define a single-argument constructor for utility purposes (especially testing)
$(esc(name))(id) = $(esc(name))(id=id, parents=(-1, -1), pos=(-1, -1))
# allow species to be defined outside of the Persefone module, but still
# available inside it (needed by `initnature!()` and `reproduce!()`)
(@__MODULE__() != $(esc(:Persefone))) && ($(esc(:Persefone)).$name = $(name))
end
end
......@@ -309,7 +308,7 @@ end
"""
@cropname
Return the name of the local croptype, or nothing if there is no crop here.
Return the name of the local croptype, or an empty string if there is no crop here.
This is a utility wrapper that can only be used nested within [`@phase`](@ref)
or [`@habitat`](@ref).
"""
......@@ -383,6 +382,17 @@ macro randomdirection(args...)
:(randomdirection($(esc(:model)), $(map(esc, args)...)))
end
"""
@nearby_animals(radius=0, species="")
Return an iterator over all animals in the given radius around the current position.
This can only be used nested within [`@phase`](@ref) or [`@habitat`](@ref).
"""
macro nearby_animals(args...)
#XXX does it make sense to use `pos` here? What if an an animal wants to look at another place?
:(nearby_animals($(esc(:pos)), $(esc(:model)), $(map(esc, args)...))) #FIXME
end
"""
@neighbours(radius=0, conspecifics=true)
......
......@@ -76,7 +76,10 @@ function initpopulation!(speciesname::String, model::SimulationModel)
for x in @shuffle!(Vector(1:width))
for y in @shuffle!(Vector(1:height))
if p.habitat((x,y), model) &&
(p.popdensity <= 0 || @chance(1/p.popdensity)) #XXX what if pd==0?
(p.popdensity <= 0 || n == 0 || @chance(1/p.popdensity)) #XXX what if pd==0?
#XXX `n==0` above guarantees that at least one individual is created, even
# in a landscape that is otherwise too small for the specified popdensity -
# do we want this?
if p.pairs
a1 = species(length(model.animals)+1, male, (-1, -1), (x,y), p.initphase)
a2 = species(length(model.animals)+2, female, (-1, -1), (x,y), p.initphase)
......@@ -105,7 +108,7 @@ function initpopulation!(speciesname::String, model::SimulationModel)
end
lastn = n
end
@info "Initialised $(n) $(species)s."
@info "Initialised $(n) $(speciesname)s."
end
#XXX initpopulation with dispersal from an original source?
......@@ -209,7 +212,7 @@ end
Return a list of animals in the given radius around this position, optionally filtering by species.
"""
function nearby_animals(pos::Tuple{Int64,Int64}, model::SimulationModel;
radius::Int64=0, species="")
radius::Int64=0, species="") #XXX add type for species
neighbours = nearby_ids(pos, model, radius)
isempty(neighbours) && return neighbours
if isempty(species)
......@@ -225,7 +228,7 @@ end
Return a list of animals in the given radius around this animal, excluding itself. By default,
only return conspecific animals.
"""
function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0, conspecifics=true)
function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0, conspecifics::Bool=true)
filter(a -> a.id != animal.id,
nearby_animals(animal.pos, model, radius = radius,
species = conspecifics ? speciesof(animal) : ""))
......
......@@ -6,7 +6,7 @@
skylarkhabitat = @habitat((@landcover() == grass ||
# settle on grass or arable land (but not maize)
(@landcover() == agriculture && @cropname() != "maize")) &&
@distancetoedge() > 5) # at least 50m from other habitats
@distancetoedge() >= 5) # at least 50m from other habitats
#XXX this ought to check for distance to forest and builtup,
# but that's very expensive (see below)
# @distanceto(forest) > 5 && # at least 50m from forest edges
......@@ -32,6 +32,7 @@ At the moment, this implementation is still in development.
@species Skylark begin
#XXX use Unitful.jl
#XXX add type annotations
eggtime = 11 # 11 days from laying to hatching
eggpredationmortality = 0.03 # per-day egg mortality from predation
......
......@@ -30,7 +30,8 @@ weatherfile = "data/regions/jena-small/weather.csv" # location of the weather da
farmmodel = "FieldManager" # which version of the farm model to use (not yet implemented)
[nature]
targetspecies = ["Skylark"] #["Wolpertinger", "Wyvern"] # list of target species to simulate
#targetspecies = ["Wolpertinger", "Wyvern"] # list of target species to simulate - example species
targetspecies = ["Skylark"] # list of target species to simulate
popoutfreq = "daily" # output frequency population-level data, daily/monthly/yearly/end/never
indoutfreq = "end" # output frequency individual-level data, daily/monthly/yearly/end/never
insectmodel = ["season", "habitat", "pesticides", "weather"] # factors affecting insect growth
......
......@@ -5,10 +5,29 @@
## Test species definition
@species Mermaid begin
#FIXME reactivate once Julia 1.11 is released and @species is fixed
#(https://discourse.julialang.org/t/kwdef-constructor-not-available-outside-of-module/114675/4)
# @species Mermaid begin
# ageofmaturity = 2
# pesticidemortality = 1.0
# end
#FIXME remove manual definition of Mermaid once Julia 1.11 is released (see above)
@kwdef mutable struct Mermaid <: Animal
const id::Int64
const sex::Persefone.Sex = Persefone.hermaphrodite
const parents::Tuple{Int64,Int64} = (-1, -1)
pos::Tuple{Int64,Int64}
phase::Function = (self,model)->nothing
age::Int = 0
energy::Union{Persefone.EnergyBudget,Nothing} = nothing
offspring::Vector{Int64} = Vector{Int64}()
ageofmaturity = 2
pesticidemortality = 1.0
end
Mermaid(id, sex, parents, pos, phase) =
Mermaid(id=id, sex=sex, parents=parents, pos=pos, phase=phase)
Mermaid(id) = Mermaid(id=id, parents=(-1, -1), pos=(-1, -1))
@create Mermaid begin
@debug "Created $(animalid(self))."
......@@ -40,7 +59,6 @@ end
## Test sets
#FIXME
@testset "Habitat macros" begin
# set up the testing landscape
model = inittestmodel()
......@@ -49,7 +67,7 @@ end
FarmPlot(1, [(6,6)], model.crops["winter wheat"], Ps.janfirst,
0.0, 0.0, 0.0, 0.0, Vector{Ps.EventType}()))
push!(model.animals,
Mermaid(1, Ps.male, (-1,-1), (3,3), life), #FIXME unsupported keyword arguments?
Mermaid(1, Ps.male, (-1,-1), (3,3), life),
Mermaid(2, Ps.female, (-1,-1), (4,4), life))
# create a set of habitat descriptors
h1 = @habitat(@landcover() == Ps.water)
......@@ -57,7 +75,7 @@ end
@cropheight() < 2)
h3 = @habitat(@distanceto(Ps.water) > 2 &&
@distancetoedge() <= 2)
h4 = @habitat(length(@neighbours(1)) == 1)
h4 = @habitat(length(@nearby_animals(radius=1)) == 1) #FIXME defining radius doesn't work
# test the descriptors
@test h1((6,4), model) == true
@test h1((5,4), model) == false
......@@ -66,9 +84,9 @@ end
@test h3((3,3), model) == true
@test h3((5,4), model) == false
@test h3((6,1), model) == false
@test h4((2,2), model) == true
@test h4((3,3), model) == false
@test h4((1,1), model) == false
@test_broken h4((2,2), model) == true
@test_broken h4((3,3), model) == false
@test_broken h4((1,1), model) == false
end
#FIXME the way initialisation works has completely changed...
......@@ -213,31 +231,35 @@ end
end
# @testset "Skylark submodel" begin
# # set up a modified test landscape
# model = inittestmodel()
# for x in 1:4
# model.landscape[x,4] = Pixel(Ps.agriculture, missing, [], [])
# end
# # test migration
# @test_logs((:info, "Initialised 2 Skylarks."),
# (:debug, "Skylark 1 has migrated."),
# (:debug, "Skylark 2 has migrated."),
# min_level=Logging.Debug, match_mode=:any,
# Ps.initpopulation!("Skylark", Ps.withtestlogger(model)))
# @test length(model.animals) == 2
# @test all(isnothing, model.animals)
# @test length(model.migrants) == 2
# @test model.migrants[1].first.sex != model.migrants[2].first.sex
# for a in model.migrants
# leave, arrive = a.first.migrationdates
# @test leave[1] in (9, 10) || (leave[1] == 11 && leave[2] <= 15)
# @test (arrive[1] == 2 && arrive[2] >= 15) || (arrive[1] == 3 && arrive[2] <= 15)
# end
# model.date = Date(year(model.date), 3, 17)
# @test_logs((:debug, "Skylark 1 has returned."),
# (:debug, "Skylark 2 has returned."),
# min_level=Logging.Debug, match_mode=:any,
# Ps.updatenature!(Ps.withtestlogger(model)))
# #TODO
# end
#TODO test Wolpertinger/Wyvern?
@testset "Skylark submodel" begin
# set up a modified test landscape
model = inittestmodel()
for x in 1:6
for y in 5:6
model.landscape[x,y] = Pixel(Ps.agriculture, missing, [], [])
end
end
# test migration
@test_logs((:info, "Initialised 2 Skylarks."),
(:debug, "Skylark 1 has migrated."),
(:debug, "Skylark 2 has migrated."),
min_level=Logging.Debug, match_mode=:any,
Ps.initpopulation!("Skylark", Ps.withtestlogger(model)))
@test length(model.animals) == 2
@test all(isnothing, model.animals)
@test length(model.migrants) == 2
@test model.migrants[1].first.sex != model.migrants[2].first.sex
for a in model.migrants
leave, arrive = a.first.migrationdates
@test leave[1] in (9, 10) || (leave[1] == 11 && leave[2] <= 15)
@test (arrive[1] == 2 && arrive[2] >= 15) || (arrive[1] == 3 && arrive[2] <= 15)
end
model.date = Date(year(model.date), 3, 17)
@test_logs((:debug, "Skylark 1 has returned."),
(:debug, "Skylark 2 has returned."),
min_level=Logging.Debug, match_mode=:any,
Ps.updatenature!(Ps.withtestlogger(model)))
#TODO
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment