diff --git a/src/core/landscape.jl b/src/core/landscape.jl index 7672839848aa54d9182d01534c71f4dbda18bee4..aff4e570b5550f40cb1c07de6d48fb3564b26940 100644 --- a/src/core/landscape.jl +++ b/src/core/landscape.jl @@ -15,9 +15,9 @@ A pixel is a simple data structure to combine land use and ownership information in a single object. The model landscape consists of a matrix of pixels. (Note: further landscape information may be added here in future.) """ -struct Pixel +mutable struct Pixel landcover::LandCover - fieldid::Union{Missing, UInt32} + fieldid::Union{Missing,Int64} events::Vector{Symbol} #TODO implement the rest of the events system #FIXME actually this is stupid - I don't want a field ID, I want a field object end @@ -41,7 +41,9 @@ function initlandscape() lc = landcover[x,y][1] (ismissing(lc)) && (lc = 0) lcv = LandCover(Int(lc/10)) - landscape[x,y] = Pixel(lcv, farmfields[x,y][1], Vector{Symbol}()) + ff = farmfields[x,y][1] + !(ismissing(ff)) && (ff = Int64(ff)) + landscape[x,y] = Pixel(lcv, ff, Vector{Symbol}()) end end return landscape diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 7ef064e39bbd24f3ad0c4b5b47d036a51b033a53..3ee8fe7ca8bf2352082215f19d50717a6c6a264f 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -19,8 +19,9 @@ function initialise(config::String=PARAMFILE) space = GridSpace(size(landscape), periodic=false) properties = Dict{Symbol,Any}(:date=>param("core.startdate"), :landscape=>landscape) - model = AgentBasedModel(Union{Farmer,Animal,CropPlot}, space, properties=properties, - rng=Random.Xoshiro(param("core.seed"))) + @debug "Setting up model now" + model = AgentBasedModel(Union{Farmer,Animal,FarmPlot}, space, properties=properties, + rng=Random.Xoshiro(param("core.seed")), warn=false) # initialise submodels initfarms!(model) initfields!(model) @@ -36,7 +37,7 @@ Execute one update of the model. """ function stepsimulation!(model::AgentBasedModel) @info "Simulating day $(model.date)." - for a in Schedulers.ByType((Farmer,Animal,CropPlot), true)(model) + for a in Schedulers.ByType((Farmer,Animal,FarmPlot), true)(model) #The animal may have been killed, so we need a try/catch try stepagent!(getindex(model, a), model) catch keyerror end end diff --git a/src/crop/crops.jl b/src/crop/crops.jl index 0db809b6159f46e7f0a40145a8be362344fef84e..ccae89b5abe3497475fbf1dbb4d7f1fa58ab496b 100644 --- a/src/crop/crops.jl +++ b/src/crop/crops.jl @@ -6,24 +6,83 @@ #XXX not sure whether it makes sense to have this as an agent type, # or perhaps better a grid property? -@agent CropPlot GridAgent{2} begin +""" +This is a docstring +""" +@agent FarmPlot NoSpaceAgent begin + pixels::Vector{Tuple{Int64, Int64}} + croptype::String + height::Float64 + biomass::Float64 #TODO end """ - stepagent!(cropplot, model) + stepagent!(farmplot, model) -Update a crop plot by one day. +Update a farm plot by one day. """ -function stepagent!(cropplot::CropPlot, model::AgentBasedModel) +function stepagent!(farmplot::FarmPlot, model::AgentBasedModel) #TODO end """ initfields!(model) -Initialise the model with its crop plots. +Initialise the model with its farm plots. """ function initfields!(model::AgentBasedModel) - #TODO + n = 0 + convertid = Dict{Int64,Int64}() + width, height = size(model.landscape) + for x in 1:width + for y in 1:height + # for each pixel, we need to extract the field ID given by the map input file, + # and convert it into the internal object ID used by Agents.jl, creating a new + # agent object if necessary + rawid = model.landscape[x,y].fieldid + (ismissing(rawid)) && continue + if rawid in keys(convertid) + objectid = convertid[rawid] + model.landscape[x,y].fieldid = objectid + push!(model[objectid].pixels, (x,y)) + else + fp = add_agent!(FarmPlot, model, [(x,y)], "fallow", 0.0, 0.0) + model.landscape[x,y].fieldid = fp.id + convertid[rawid] = fp.id + n += 1 + end + end + end + @debug "Initialised $n farm plots." +end + +#XXX only needed during development, can be deleted again +function countfields(model::AgentBasedModel) + ids::Vector{Int64} = [] + width, height = size(model.landscape) + for x in 1:width + for y in 1:height + i = model.landscape[x,y].fieldid + (ismissing(i)) && continue + push!(ids, i) + end + end + ids |> sort! |> unique! |> length +end + +""" + averagefieldsize(model) + +Calculate the average field size in hectares for the model landscape. +""" +function averagefieldsize(model::AgentBasedModel) + conversionfactor = 100 #our pixels are currently 10x10m, so 100 pixels per hectare + sizes::Vector{Float64} = [] + for a in allagents(model) + (typeof(a) != FarmPlot) && continue + push!(sizes, size(a.pixels)[1]/conversionfactor) + end + round(sum(sizes)/size(sizes)[1], digits=2) + #sizes end diff --git a/src/nature/nature.jl b/src/nature/nature.jl index abc2043ad0c0dcbc32cfad8f5e34632d55adcb88..39e388ecaabe5a8f33401c92c518c005eca10479 100644 --- a/src/nature/nature.jl +++ b/src/nature/nature.jl @@ -12,7 +12,6 @@ This is the generic agent type for all animals. Species are differentiated by trait dictionaries passed by them during initialisation. """ -#XXX fix the @agent docstring issue (https://github.com/JuliaDynamics/Agents.jl/issues/715) @agent Animal GridAgent{2} begin #XXX is it (performance-)wise to use a dict for the traits? # Doesn't this rather obviate the point of having an agent struct? @@ -111,7 +110,7 @@ To transition an individual to another phase, simply redefine its phase variable `@trait(phase) = "newphase"`. """ macro phase(name, body) - #XXX Using an undeclared variable in the body doesn't throw an error? + #FIXME Somehow, errors in the phase body are not shown? :($(esc(name)) = function(animal::Animal, model::AgentBasedModel) $body end) end @@ -173,6 +172,8 @@ macro reproduce(args...) :(reproduce!(animal, model, $(args...))) end +#XXX add a macro @f to call a function with animal and model as first parameters? +# e.g. @f(nearby_agents, distance) ### FUNCTIONS INTEGRATING THE NATURE MODEL WITH THE REST OF PERSEPHONE