-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathGraphSimulation.jl
More file actions
157 lines (130 loc) · 6.2 KB
/
GraphSimulation.jl
File metadata and controls
157 lines (130 loc) · 6.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
GraphSimulation(graph, mapping)
GraphSimulation(graph, statuses, dependency_graph, models, outputs)
A type that holds all information for a simulation over a graph.
# Arguments
- `graph`: an graph, such as an MTG
- `mapping`: a dictionary of model mapping
- `statuses`: a structure that defines the status of each node in the graph
- `status_templates`: a dictionary of status templates
- `reverse_multiscale_mapping`: a dictionary of mapping for other scales
- `var_need_init`: a dictionary indicating if a variable needs to be initialized
- `dependency_graph`: the dependency graph of the models applied to the graph
- `models`: a dictionary of models
- `model_specs`: a dictionary of normalized model usage specifications
- `outputs`: a dictionary of outputs
- `temporal_state`: multi-rate temporal storage used at runtime for producer streams,
input resolution caches, run clocks, and requested output export buffers
"""
struct GraphSimulation{T,S,U,O,V,TS,MS}
graph::T
statuses::S
status_templates::Dict{String,Dict{Symbol,Any}}
reverse_multiscale_mapping::Dict{String,Dict{String,Dict{Symbol,Any}}}
var_need_init::Dict{String,V}
dependency_graph::DependencyGraph
models::Dict{String,U}
model_specs::MS
outputs::Dict{String,O}
outputs_index::Dict{String, Int}
temporal_state::TS
is_multirate::Bool
end
function GraphSimulation(graph, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false)
mapping_checked = mapping isa ModelMapping ? mapping : ModelMapping(mapping)
GraphSimulation(init_simulation(graph, mapping_checked; nsteps=nsteps, outputs=outputs, type_promotion=type_promotion, check=check, verbose=verbose)...)
end
dep(g::GraphSimulation) = g.dependency_graph
status(g::GraphSimulation) = g.statuses
status_template(g::GraphSimulation) = g.status_templates
reverse_mapping(g::GraphSimulation) = g.reverse_multiscale_mapping
var_need_init(g::GraphSimulation) = g.var_need_init
get_models(g::GraphSimulation) = g.models
get_model_specs(g::GraphSimulation) = g.model_specs
outputs(g::GraphSimulation) = g.outputs
temporal_state(g::GraphSimulation) = g.temporal_state
is_multirate(g::GraphSimulation) = g.is_multirate
"""
convert_outputs(sim_outputs::Dict{String,O} where O, sink; refvectors=false, no_value=nothing)
convert_outputs(sim_outputs::TimeStepTable{T} where T, sink)
Convert the outputs returned by a simulation made on a plant graph into another format.
# Details
The first method operates on the outputs of a multiscale simulation, the second one on those of a typical single-scale simulation.
The sink function determines the format used, for exemple a `DataFrame`.
# Arguments
- `sim_outputs : the outputs of a prior simulation, typically returned by `run!`.
- `sink`: a sink compatible with the Tables.jl interface (*e.g.* a `DataFrame`)
- `refvectors`: if `false` (default), the function will remove the RefVector values, otherwise it will keep them
- `no_value`: the value to replace `nothing` values. Default is `nothing`. Usually used to replace `nothing` values
by `missing` in DataFrames.
# Examples
```@example
using PlantSimEngine, MultiScaleTreeGraph, DataFrames, PlantSimEngine.Examples
```
Import example models (can be found in the `examples` folder of the package, or in the `Examples` sub-modules):
```jldoctest mylabel
julia> using PlantSimEngine.Examples;
```
$MAPPING_EXAMPLE
```@example
mtg = import_mtg_example();
```
```@example
out = run!(mtg, mapping, meteo, tracked_outputs = Dict(
"Leaf" => (:carbon_assimilation, :carbon_demand, :soil_water_content, :carbon_allocation),
"Internode" => (:carbon_allocation,),
"Plant" => (:carbon_allocation,),
"Soil" => (:soil_water_content,),
));
```
```@example
convert_outputs(out, DataFrames)
```
"""
# Another, possibly better way would be to just create the DataFrame directly from the outputs
# and then remove the RefVector columns and replace the node one, hmm
function convert_outputs(outs::Dict{String,O} where O, sink; refvectors=false, no_value=nothing)
ret = Dict{String, sink}()
for (organ, status_vector) in outs
# remove RefVector variables
refv = ()
if length(status_vector) > 0
for (var, val) in pairs(status_vector[1])
if !refvectors && isa(val, RefVector)
refv = (refv..., var)
end
if var == :node
refv = (refv..., var)
end
end
else
@warn "No instance found at the $organ scale, no output available, removing it from the Dict"
continue
end
# Get the new NamedTuple type
refv_nt = NamedTuple{refv}
# Piddle around with the first element to get the final type to be able to allocate the exact vector size with a definite element type
vector_named_tuple_1 = NamedTuple(status_vector[1])
# replace the MTG node var with the id (MTG nodes aren't CSV-friendly)
filtered_named_tuple = (;node=MultiScaleTreeGraph.node_id(vector_named_tuple_1.node),Base.structdiff(vector_named_tuple_1, refv_nt)...)
filtered_vector_named_tuple = Vector{typeof(filtered_named_tuple)}(undef, length(status_vector))
for i in 1:length(status_vector)
vector_named_tuple_i = NamedTuple(status_vector[i])
filtered_vector_named_tuple[i] = (;node=MultiScaleTreeGraph.node_id(vector_named_tuple_i.node), Base.structdiff(vector_named_tuple_i, refv_nt)...)
end
ret[organ] = sink(filtered_vector_named_tuple)
end
return ret
end
# TODO adapt these to new output structure or remove them
function outputs(outs::Dict{String, O} where O, key::Symbol)
Tables.columns(convert_outputs(outs, Vector{NamedTuple}))[key]
end
function outputs(outs::Dict{String, O} where O, i::T) where {T<:Integer}
Tables.columns(convert_outputs(outs, Vector{NamedTuple}))[i]
end
# ModelLists now return outputs as a TimeStepTable{Status}, conversion is straightforward
function convert_outputs(out::TimeStepTable{T} where T, sink)
@assert Tables.istable(sink) "The sink argument must be compatible with the Tables.jl interface (`Tables.istable(sink)` must return `true`, *e.g.* `DataFrame`)"
return sink(out)
end