VirtualPlantLab / PlantSimEngine.jl

A simulation engine for models related to plants
https://virtualplantlab.github.io/PlantSimEngine.jl/stable/
MIT License
14 stars 1 forks source link

Multiple variables with shared name #78

Closed VEZY closed 1 month ago

VEZY commented 1 month ago

There is an issue when a variable at a given scale is needed at the current time-step for one model and at the previous time-step for another one.

You can look in XPalm to reproduce the error. The model XPalm.OrganReserveFilling needs the current values for reserve_organs, but the model XPalm.OrgansCarbonAllocationModel needs the values from the previous day to avoid a cyclic dependency.

If we get the full model mapping and then compute it, we get:

using PlantMeteo, PlantSimEngine, DataFrames, CSV
using XPalm

meteo = CSV.read(joinpath(dirname(dirname(pathof(XPalm))), "0-data/meteo.csv"), DataFrame)
meteo.T = meteo.Taverage
meteo.Rh .= (meteo.Rh_max .- meteo.Rh_min) ./ 2 ./ 100
m = Weather(meteo)

p = Palm(nsteps=nrow(m))

mapping = Dict(
    "Plant" => (
        MultiScaleModel(
            model=XPalm.OrgansCarbonAllocationModel(p.parameters[:carbon_demand][:reserves][:cost_reserve_mobilization]),
            mapping=[
                PreviousTimeStep(:reserve_organs),
                PreviousTimeStep(:reserve),
            ]
        ),
        XPalm.OrganReserveFilling(),
    ),
)

full_vars_mapping = Dict(first(mod) => Dict(PlantSimEngine.get_mapping(last(mod))) for mod in mapping)

julia> full_vars_mapping["Plant"]
Dict{Union{Symbol, PreviousTimeStep}, Union{Pair{String, Symbol}, Vector{Pair{String, Symbol}}}} with 15 entries:
  :biomass_stalk_harvested_organs                       => ["Female"=>:biomass_stalk_harvested]
  :carbon_allocation_organs                             => ["Leaf"=>:carbon_allocation, "Internode"=>:carbon_allocation, "Male"=>:carbon_allocation, "Female"=>:carbon_allocation]
  :reserve_organs                                       => ["Internode"=>:reserve, "Leaf"=>:reserve]
  :carbon_demand_organs                                 => ["Leaf"=>:carbon_demand, "Internode"=>:carbon_demand, "Male"=>:carbon_demand, "Female"=>:carbon_demand]
  :ftsw                                                 => "Soil"=>:ftsw
  :biomass_fruit_harvested_organs                       => ["Female"=>:biomass_fruit_harvested]
  :Rm_organs                                            => ["Leaf"=>:Rm, "Internode"=>:Rm, "Male"=>:Rm, "Female"=>:Rm]
  PreviousTimeStep(:reserve, :carbon_allocation)        => ""=>:reserve
  PreviousTimeStep(:reserve_organs, :carbon_allocation) => ["Leaf"=>:reserve, "Internode"=>:reserve]
  :scene_leaf_area                                      => "Scene"=>:scene_leaf_area
  :graph_node_count                                     => "Scene"=>:graph_node_count
  :aPPFD                                                => "Scene"=>:aPPFD
  :leaf_area                                            => ["Leaf"=>:leaf_area]
  :potential_reserve_organs                             => ["Internode"=>:potential_reserve, "Leaf"=>:potential_reserve]
  :biomass_bunch_harvested_organs                       => ["Female"=>:biomass_bunch_harvested]

Note that :reserve_organs appears twice, once as is, and once wrapped in PreviousTimeStep(:reserve_organs, :carbon_allocation).

We have to think about how we can manage such issue. For now, I changed the name of the variable for OrgansCarbonAllocationModel into :reserve_organs_all.

VEZY commented 1 month ago

Hmmm, it is working for the biomass with the simulation below. We have:

So, based on the assumption from before, it shouldn't work. The problem probably happens for a different reason than I thought.

Simulation code:

using PlantMeteo, PlantSimEngine, DataFrames, CSV
using XPalm

meteo = CSV.read(joinpath(dirname(dirname(pathof(XPalm))), "0-data/meteo.csv"), DataFrame)
meteo.T = meteo.Taverage
meteo.Rh .= (meteo.Rh_max .- meteo.Rh_min) ./ 2 ./ 100
m = Weather(meteo)

p = Palm(nsteps=nrow(m))
mapping = Dict(
    "Leaf" => (
        XPalm.RankLeafPruning(p.parameters[:rank_leaf_pruning]),
        MultiScaleModel(
            model=XPalm.LeafAreaModel(
                p.parameters[:lma_min],
                p.parameters[:leaflets_biomass_contribution],
                p.parameters[:potential_area][:leaf_area_first_leaf],
            ),
            mapping=[PreviousTimeStep(:biomass),],
        ),
        MultiScaleModel(
            model=XPalm.RmQ10FixedN(
                p.parameters[:respiration][:Leaf][:Q10],
                p.parameters[:respiration][:Leaf][:Rm_base],
                p.parameters[:respiration][:Leaf][:T_ref],
                p.parameters[:respiration][:Leaf][:P_alive],
                p.parameters[:nitrogen_content][:Leaf]
            ),
            mapping=[PreviousTimeStep(:biomass),],
        ),
        XPalm.LeafBiomass(
            initial_biomass=p.parameters[:potential_area][:leaf_area_first_leaf] * p.parameters[:lma_min] /
                            p.parameters[:leaflets_biomass_contribution],
            respiration_cost=p.parameters[:carbon_demand][:leaf][:respiration_cost],
        ),
        Status(carbon_allocation=1.0, state="undetermined", rank=1)
    )
)

dep(mapping)
to_initialize(mapping, p.mtg)

outs = Dict{String,Any}(
    "Leaf" => (:biomass, :Rm, :leaf_area),
)

p = Palm(nsteps=nrow(m))
sim = run!(p.mtg, mapping, m, outputs=outs, executor=SequentialEx());
outputs(sim, DataFrame)