From da0b2c84176d42b9864a828264afe197f4cb6d62 Mon Sep 17 00:00:00 2001 From: Daniel Vedder <daniel.vedder@idiv.de> Date: Thu, 22 Dec 2022 10:00:08 +0100 Subject: [PATCH] Changed the events system to give better performance --- src/core/landscape.jl | 72 +++++++++++++++++++++++---------------- src/core/simulation.jl | 13 ++++--- src/crop/crops.jl | 13 ------- src/nature/lifehistory.jl | 2 +- src/nature/nature.jl | 2 +- 5 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/core/landscape.jl b/src/core/landscape.jl index 959aef5..665e94f 100644 --- a/src/core/landscape.jl +++ b/src/core/landscape.jl @@ -1,14 +1,14 @@ - ### Persephone - a socio-economic-ecological model of European agricultural landscapes. ### ### This file manages the landscape maps that underlie the model. ### -## The land cover classes encoded in the Mundialis Sentinel data. -## Do not change the order of this enum, or initlandscape() will break! +"The land cover classes encoded in the Mundialis Sentinel data." @enum LandCover nodata forest grass water builtup soil agriculture +## Do not change the order of this enum, or initlandscape() will break! -@enum LandscapeEvent test tillage fertiliser pesticide harvest +"The types of landscape event that can be simulated" +@enum EventType tillage sowing fertiliser pesticide harvest """ Pixel @@ -20,7 +20,19 @@ in a single object. The model landscape consists of a matrix of pixels. mutable struct Pixel landcover::LandCover fieldid::Union{Missing,Int64} - events::Vector{Tuple{LandscapeEvent,Int64}} + events::Vector{EventType} +end + +""" + FarmEvent + +A data structure to define a landscape event, giving its type, +spatial extent, and duration. +""" +mutable struct FarmEvent + type::EventType + pixels::Vector{Tuple{Int64,Int64}} + duration::Int64 end """ @@ -51,25 +63,37 @@ function initlandscape() end """ - updatelandscape!(model) + updateevents!(model) -Update the model landscape. (Currently only removes old events.) +Cycle through the list of events, removing those that have expired. """ -function updatelandscape!(model::AgentBasedModel) - #FIXME This is really slow?! - # Instead of cycling through every pixel every time, I should keep a list of active - # events, and cycle through those - width, height = size(model.landscape) - for x in 1:width - for y in 1:height - newevents::Vector{Tuple{LandscapeEvent,Int64}} = [] - for e in model.landscape[x,y].events - # only keep events that still have a duration > 1 day - (e[2] > 1) && push!(newevents, (e[1], e[2]-1)) +function updateevents!(model::AgentBasedModel) + expiredevents = [] + for e in 1:length(model.events) + event = model.events[e] + event.duration -= 1 + if event.duration <= 0 + push!(expiredevents, e) + for p in event.pixels + i = findnext(x -> x == event.type, model.landscape[p...].events, 1) + deleteat!(model.landscape[p...].events, i) end - model.landscape[x,y].events = newevents end end + deleteat!(model.events, expiredevents) +end + +""" + createevent(model, pos, name, duration=1) + +Add a farm event to the specified pixels for a given duration. +""" +function createevent!(model::AgentBasedModel, pixels::Vector{Tuple{Int64,Int64}}, + name::EventType, duration::Int64=1) + push!(model.events, FarmEvent(name, pixels, duration)) + for p in pixels + push!(model.landscape[p...].events, name) + end end """ @@ -90,13 +114,3 @@ function farmplot(model::AgentBasedModel, pos::Tuple{Int64,Int64}) model[model.landscape[pos...].fieldid] end -""" - createevent(model, pos, name, duration=1) - -Add a landscape event to the pixel at the specified site. -""" -function createevent(model::AgentBasedModel, pos::Tuple{Int64,Int64}, - name::LandscapeEvent, duration::Int64=1) - push!(model.landscape[pos...].events, (name, duration)) -end - diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 525e76e..6440f57 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -4,21 +4,24 @@ ### """ - initialise(config) + initialise(config, seed) Initialise the model: read in parameters, create the output data directory, and instantiate the AgentBasedModel object. """ function initialise(config::String=PARAMFILE) + #XXX add a seed parameter? # do some housekeeping initsettings(config) Random.seed!(param("core.seed")) setupdatadir() # initialise world-level properties landscape = initlandscape() + events = Vector{FarmEvent}() space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:date=>param("core.startdate"), - :landscape=>landscape) + :landscape=>landscape, + :events=>events) @debug "Setting up model now" model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, rng=Random.Xoshiro(param("core.seed")), warn=false) @@ -41,7 +44,7 @@ function stepsimulation!(model::AgentBasedModel) #The animal may have been killed, so we need a try/catch try stepagent!(model[a], model) catch keyerror end end - #updatelandscape!(model) + updateevents!(model) outputdata(model) model.date += Day(1) end @@ -54,8 +57,8 @@ Wrap up the simulation. Output all remaining data and exit. function finalise(model::AgentBasedModel) @info "Simulated $(model.date-param("core.startdate"))." @info "Simulation completed at $(Dates.now()),\nwrote output to $(param("core.outdir"))." - #TODO - genocide!(model) + #XXX is there anything to do here? + #genocide!(model) end """ diff --git a/src/crop/crops.jl b/src/crop/crops.jl index 709a203..ccae89b 100644 --- a/src/crop/crops.jl +++ b/src/crop/crops.jl @@ -86,16 +86,3 @@ function averagefieldsize(model::AgentBasedModel) round(sum(sizes)/size(sizes)[1], digits=2) #sizes end - - -""" - applyevent(model, farmplotid, name, duration=1) - -Apply an event to all pixels in a farm plot. -""" -function applyevent(model::AgentBasedModel, farmplotid::Int64, - name::LandscapeEvent, duration::Int64=1) - for p in model[farmplotid].pixels - createevent(model, p, name, duration) - end -end diff --git a/src/nature/lifehistory.jl b/src/nature/lifehistory.jl index c15f759..c48618a 100644 --- a/src/nature/lifehistory.jl +++ b/src/nature/lifehistory.jl @@ -36,7 +36,7 @@ function reproduce!(animal::Animal, model::AgentBasedModel, n::Int64=1) # XXX at the moment we don't have intra-specific variation, so currently we # don't need sexual recombination here for i in 1:n - sex = animal.sex == hermaphrodite ? hermaphrodite : rand([male, female]) + sex = (animal.sex == hermaphrodite) ? hermaphrodite : rand([male, female]) add_agent!(animal.pos, Animal, model, animal.traits, sex, 0) end @debug "$(animalid(animal)) has reproduced." diff --git a/src/nature/nature.jl b/src/nature/nature.jl index 9fe3f27..a13faef 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -123,7 +123,7 @@ This can only be used nested within `@phase`. macro respond(eventname, body) quote #TODO test this - if any(e -> e[1] == $(esc(eventname)), @here(events)) + if $(esc(eventname)) in @here(events) $body end end -- GitLab