diff --git a/src/core/simulation.jl b/src/core/simulation.jl index b77917b4cdd8d83f36e456938e27c5d1edaa94e2..1a6f390390e63f92fbae0cd8bd8272d962ee5559 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -59,6 +59,7 @@ Initialise a model object using a ready-made settings dict. This is a helper function for `initialise()`. """ function initmodel(settings::Dict{String, Any}) + #TODO catch exceptions and print them to the log file #TODO remove Agents.jl-related code, reimplement this more cleanly (#72) @debug "Initialising model object." createdatadir(settings["core.outdir"], settings["core.overwrite"]) @@ -127,6 +128,7 @@ end Execute one update of the model. """ function stepsimulation!(model::AgentBasedModel) + #TODO catch exceptions and print them to the log file with_logger(model.logger) do @info "Simulating day $(model.date)." #TODO remove Agents.jl-related code, reimplement this more cleanly (#72) @@ -135,6 +137,8 @@ function stepsimulation!(model::AgentBasedModel) stepagent!(model[a], model) catch exc # check if the KeyError comes from the `model[a]` or the function call + #FIXME this also silences KeyErrors caused in the species code (e.g. + # by accessing dead mates) - will be fixed once I reorganise the code isa(exc, KeyError) && isa(exc.key, Int) ? continue : throw(exc) end end diff --git a/src/nature/macros.jl b/src/nature/macros.jl index 6ca2086d267e72c429db3d6e5af3c66bc2109316..ccf9386f24e055cbd4e32a10df00b25aee6074a1 100644 --- a/src/nature/macros.jl +++ b/src/nature/macros.jl @@ -113,7 +113,8 @@ macro phase(name, body) #TODO the docstrings give a lot of warnings in the log - can I fix that? quote Core.@__doc__ function $(esc(name))($(esc(:animal))::Animal, $(esc(:model))::AgentBasedModel) - $(esc(:pos)) = $(esc(:animal)).pos + #TODO add `self` as a synonym for `animal` + $(esc(:pos)) = $(esc(:animal)).pos #XXX does this make sense? #$(esc(:date)) = $(esc(:model)).date #XXX does this make sense? $(esc(body)) end @@ -128,8 +129,7 @@ A utility macro to quickly access an animal's trait value. This can only be used nested within [`@phase`](@ref). """ macro trait(traitname) - #TODO provide a version that can access another animal's traits - #XXX replace with an @v macro? (shorter, and not all variables are "traits") + #FIXME actually, we can get rid of this altogether if we add a Python-style `self` #XXX This would error if called in the first part of a species definition block # (i.e. outside of a @phase block). Although this is specified in the documentation, # it is unexpected and liable to be overlooked. Can we add a third clause to @@ -145,7 +145,7 @@ This can only be used in a context where the `model` object is available (e.g. nested within [`@phase`](@ref)). """ macro animal(id) - :($(esc(:model))[$(id)]) + :($(esc(:model))[$(esc(id))]) end """ @@ -156,7 +156,7 @@ This can only be used in a context where the `model` object is available (e.g. nested within [`@phase`](@ref)). """ macro isalive(id) - :(isalive($(id), $(esc(:model)))) + :(isalive($(esc(id)), $(esc(:model)))) end """ diff --git a/src/nature/nature.jl b/src/nature/nature.jl index 4cd5599ec034e1f7140555702cdcc472f0cd2ccd..2edc9fa9617e7e0c216ed361c6d5d061d5a933ae 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -73,7 +73,7 @@ Update an animal by one day, executing it's currently active phase function. """ function stepagent!(animal::Animal, model::AgentBasedModel) animal.age += 1 - animal.traits[animal.phase](animal,model) #FIXME -> note to self: why? + animal.traits[animal.phase](animal,model) #FIXME -> note to self: why, what's wrong? end """ @@ -106,5 +106,3 @@ function updatenature!(model::AgentBasedModel) end #XXX what else needs to go here? end - -#TODO test migration diff --git a/src/nature/populations.jl b/src/nature/populations.jl index d7680ecf6f71c15cfece0be16bf03c8c09437ebf..2288ecde68081e7b59a248b60d1bdccee09ee907 100644 --- a/src/nature/populations.jl +++ b/src/nature/populations.jl @@ -39,7 +39,7 @@ This can be used to create the `initialise!` variable in a species definition bl 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 for the individual? + #TODO add a constructor function/macro for the individual function(species::Dict{String,Any}, model::AgentBasedModel) n = 0 lastn = 0 @@ -107,7 +107,7 @@ function reproduce!(animal::Animal, model::AgentBasedModel, mate::Int64, n::Int6 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.traits["name"]))($model) + species = @eval $(Symbol(animal.name))($model) a = add_agent!(animal.pos, Animal, model, species, (animal.id, mate), sex, 0) push!(offspring, a.id) end diff --git a/src/nature/species/skylark.jl b/src/nature/species/skylark.jl index 50268688c19e2deee6d777b459d6cd514f3d697f..f79c9fea027acd6a125d0c42925af8cef7df0309 100644 --- a/src/nature/species/skylark.jl +++ b/src/nature/species/skylark.jl @@ -55,7 +55,8 @@ At the moment, this implementation is still in development. # @distanceto(forest) > 5 && # at least 50m from forest edges # @distanceto(builtup) > 5) # and from anthropogenic structures - @initialise(habitats, popdensity=300, pairs=true, initfunction=initskylark) + @initialise(habitats, phase="mating", popdensity=300, pairs=true, + initfunction=initskylark) """ As an egg, simply check for mortality and hatching. @@ -101,7 +102,7 @@ At the moment, this implementation is still in development. @phase nonbreeding begin # flocking behaviour - follow a random neighbour or move randomly #TODO add feeding and mortality, respect habitat when moving - neighbours = map(a->a.id, @neighbours(10)) + neighbours = map(a->a.id, @neighbours(10)) #FIXME #isempty(neighbours) ? @randomwalk(5) : @follow(@rand(neighbours), 2) if isempty(neighbours) @randomwalk(5) @@ -133,17 +134,17 @@ At the moment, this implementation is still in development. return end m, d = monthday(model.date) - nest = ((m == @trait(nestingbegin)[1] && d > @trait(nestingbegin)[2] + nest = ((m == @trait(nestingbegin)[1] && d >= @trait(nestingbegin)[2] && @chance(0.05)) || (m > @trait(nestingbegin)[1])) nest && @setphase(nestbuilding) return end # look for a mate among the neighbouring birds, or move randomly for n in @neighbours(50) - if n.sex != @trait(sex) && n.mate == -1 + if n.sex != @trait(sex) && n.phase == "mating" && n.mate == -1 @trait(mate) = n.id n.mate = @trait(id) - @debug "$(animalid(@trait(id))) and $(animalid(n.id)) have mated." + @debug "$(animalid(animal)) and $(animalid(n)) have mated." return end end @@ -154,26 +155,35 @@ At the moment, this implementation is still in development. Females select a location and build a nest. Males do nothing. (Sound familiar?) """ @phase nestbuilding begin + if !@isalive(@trait(mate)) + @setphase(nonbreeding) + return + end if @trait(sex) == female if isempty(@trait(nest)) # try to find a nest in the neighbourhood, or move on - nest = @randompixel(10, @trait(habitats)) - if isnothing(nest) - nest = () + nestlocation = @randompixel(10, @trait(habitats)) + if isnothing(nestlocation) @randomwalk(20) else # if we've found a location, start the clock on the building time # (building time doubles for the first nest of the year) + @trait(nest) = nestlocation @trait(nestcompletion) = @rand(nestbuildingtime) month(model.date) == 4 && (@trait(nestcompletion) *= 2) + @debug "$(animalid(animal)) is building a nest." end else # wait while nest is being built, then lay eggs and go to next phase if @trait(nestcompletion) > 0 @trait(nestcompletion) -= 1 else + #XXX more accurately, a female lays one egg per day, not all at once @trait(clutch) = @reproduce(@trait(mate), @rand(eggsperclutch)) @animal(@trait(mate)).clutch = @trait(clutch) + for c in @trait(clutch) #FIXME find a cleaner solution for this + initskylark(@animal(c), model) + end @setphase(breeding) end end @@ -224,7 +234,7 @@ function initskylark(animal::Animal, model::AgentBasedModel) @migrate(returndate) end # set individual life-history parameters that are defined as ranges for the species - @trait(nestlingtime) = @rand(@trait(nestlingtime)) + @trait(nestlingtime) = @rand(@trait(nestlingtime)) #FIXME no effect? @trait(fledglingtime) = @rand(@trait(fledglingtime)) #TODO other stuff? end diff --git a/src/world/landscape.jl b/src/world/landscape.jl index b324298db50e8da1f8b917e4f20cd70edf8f5ab0..220e51f8979b431d5786943845fe054759dd5040 100644 --- a/src/world/landscape.jl +++ b/src/world/landscape.jl @@ -184,8 +184,8 @@ habitatdescriptor (create this using [`@habitat`](@ref)). """ function randompixel(pos::Tuple{Int64,Int64}, model::AgentBasedModel, range::Int64=1, habitatdescriptor::Function=(pos,model)->nothing) - for x in @shuffle!((pos[1]-range):(pos[1]+range)) - for y in @shuffle!((pos[2]-range):(pos[2]+range)) + for x in @shuffle!(collect((pos[1]-range):(pos[1]+range))) + for y in @shuffle!(collect((pos[2]-range):(pos[2]+range))) !inbounds((x,y), model) && continue habitatdescriptor((x,y), model) && return (x,y) end