Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to manage computations over an MTG, considering scales, symbols... #38

Closed
VEZY opened this issue Apr 24, 2023 · 2 comments
Closed
Labels
enhancement New feature or request

Comments

@VEZY
Copy link
Member

VEZY commented Apr 24, 2023

We need to be able to manage how models are applied over an MTG, eventually by tweaking the way we traverse the MTG.
For example, we can have the photosynthesis computed for each leaf, and then a common pool of carbon at the plant scale. Obviously the photosynthesis must be run first.

We have different ways of managing this (and I'm not sure how they are done or if they are possible):

  • traverse the MTG to make a dependency graph at the scale of the MTG itself, based on the dependency graphs of each node. This can be computationnally intensive as we must traverse the whole MTG first. Or, we can also compute the dependency graph outside the MTG based on the dictionnay of models we give at initialization ? In any case, we must traverse the MTG several times, one time for each node dependency.
  • Use futures ? I don't know how these are handled in Julia, but it could potentially be helpful to traverse the MTG once, and that the results of a given scale (e.g. the plant) are just waiting for the computation of the other scale it depends on (e.g. the leaf). And in the end everything is available.

In any case, we first need to define a way of declaring that a model depends on the computation of another scale, e.g. for the carbon pool we could have:

PlantSimEngine.mtg_dep(::CommonCPool) = (photosynthesis=(scale = 1,),)

This code would define a dependency on the photosynthesis model from nodes that are of scale 1.
We would need ways of defining dependencies for the symbol or the id of a node too, e.g.:

PlantSimEngine.mtg_dep(::CommonCPool) = (photosynthesis=(scale = 1, id=1,symbol="Leaf"),)

We could also think about including a dependency on:

  • any node in the mtg
  • ancestors
  • descendants

Maybe the best way would be to visit the MTG from the node we are computing. If a ModelList from the current node has a dependency on a model in its descendants, then we could go an visit them, identify where is the model in their ModelList, and run the root model of this dependency. Then we would have to mark it as already simulated somehow to avoid re-running it when visiting these nodes (we can have a new field in the dependency tree that marks the id of the simulated time-step, if this id is already there, we don't simulate the model). And then continue with the node we were simulating already.

@VEZY VEZY added the enhancement New feature or request label Apr 24, 2023
@VEZY
Copy link
Member Author

VEZY commented Aug 15, 2023

Instead of defining it in e.g. PlantSimEngine.mtg_dep, we could define the dependency of variables from other scales in the ModelList directly, something along this:

m = 
ModelList(
    photosynthesis = FvCB() => "Leaf", # 1
    carbon_offer = ("Leaf" => :A,) => CommonCPool() => "Plant", # 2
    carbon_allocation = ("Plant" => :carbon_offer => first,) => CAllocationRelativeToDemand() => ["Leaf", "Internode"], # 3
)

Details:

  1. FvCB() => "Leaf" means that we give the FvCB() to all leaves (node symbol == "Leaf") for photosynthesis;
  2. ("Leaf" => :A,) => CommonCPool() => "Plant" means that the input :A for the model CommonCPool() comes from the "Leaf" scale, and that CommonCPool() is applied at the plant node
  3. ("Plant" => :carbon_offer => first,) => CAllocationRelativeToDemand() => ["Leaf", "Internode"] means that we take the :carbon_offer from all nodes at the plant scale, take the first value only (this is to transform the vector of one into a single value) and give it as an input to CAllocationRelativeToDemand(), that is itself applied at both leaf and internode nodes

This way we can take inputs from other scales and give them to a given node, and we can define on which nodes a model is applied.

Then we can make the code that will build the simulation, grouping the model calls into scales whenever possible (so we traverse the mtg as little as possible), of course taking into account the dependencies between the models for the simulation order.

In the end, the code would be built automatically, and would look like something along this:

init_mtg_models!(mtg, m)

function PlantSimEngine.run!(m, mtg, meteo, [...])
    # Caching the traversals in the mtg root:
    traversal_cache!(mtg, symbol = "Leaf")
    traversal_cache!(mtg, symbol = ["Leaf", "Internode"])

    for (i, meteo_) in enumerate(Tables.rows(meteo))

        # Call the photosynthesis model over all leaves
        MultiScaleTreeGraph.traverse!(plant, symbol="Leaf") do leaf
            PlantSimEngine.run!(leaf[:models].models.photosynthesis, leaf[:models].models, leaf[:models].status[i], meteo_, constants, nothing)
        end

        
        mtg[:models].status[i].A = descendants(mtg, :A, symbol = "Leaf")
        PlantSimEngine.run!(mtg[:models].models.carbon_offer, mtg[:models].models, mtg[:models].status[i], meteo_, constants, nothing)

        # Here we get the values of the carbon_offer by visiting the plant, this is then used by the `carbon_allocation`:
        var_carbon_offer = typeof(inputs_(m.models.carbon_offer).carbon_offer)[] # Or something like this, here we have to pre-allocate a vector for the values of the carbon offer of the plant (may have several values if several plants)
        MultiScaleTreeGraph.traverse!(plant, symbol="Plant") do plant
            push!(var_carbon_offer, mtg[:models].status[i].carbon_offer)
        end
        var_carbon_offer = first(var_carbon_offer)

        MultiScaleTreeGraph.traverse!(plant, symbol=["Leaf", "Internode"]) do leaf_or_internode
            leaf_or_internode[:models].status[i].A = var_carbon_offer
            PlantSimEngine.run!(leaf_or_internode[:models].models.carbon_allocation, leaf_or_internode[:models].models, leaf_or_internode[:models].status[i], meteo_, constants, nothing)
        end
    end
end

@VEZY
Copy link
Member Author

VEZY commented Nov 24, 2023

This is fixed in #51

@VEZY VEZY closed this as completed Nov 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant