Skip to content

Commit 8595cf5

Browse files
committed
Merge branch 'multiple-timesteps-take-3' of https://github.com/VirtualPlantLab/PlantSimEngine.jl into multiple-timesteps-take-3
2 parents 5ae99df + 2b871de commit 8595cf5

17 files changed

Lines changed: 512 additions & 86 deletions

docs/src/API/API_public.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,13 @@ Scope selection detail:
6767
```julia
6868
req_hold = OutputRequest("Leaf", :A; name=:A_hourly, process=:assim, policy=HoldLast())
6969
req_day = OutputRequest("Leaf", :A; name=:A_daily_sum, process=:assim, policy=Integrate(), clock=ClockSpec(24.0, 1.0))
70-
run!(sim, meteo; multirate=true, tracked_outputs=[req_hold, req_day], executor=SequentialEx())
70+
run!(sim, meteo; tracked_outputs=[req_hold, req_day], executor=SequentialEx())
7171
out = collect_outputs(sim; sink=DataFrame)
7272

7373
# or directly:
7474
out_status, out = run!(
7575
sim,
7676
meteo;
77-
multirate=true,
7877
tracked_outputs=[req_hold, req_day],
7978
return_requested_outputs=true,
8079
)
@@ -116,6 +115,8 @@ MeteoBindings(
116115
### Parameterized window reducers
117116

118117
`Integrate()` defaults to `SumReducer()`; `Aggregate()` defaults to `MeanReducer()`.
118+
With the same reducer, they are runtime-equivalent.
119+
Use `Integrate` for accumulation semantics and `Aggregate` for summary-statistics semantics.
119120

120121
```julia
121122
ModelSpec(DailyModel()) |>

docs/src/model_execution.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Inspection helpers:
5252
Policy parameterization:
5353
- `Integrate()` defaults to `SumReducer()`; you can pass another reducer, e.g. `Integrate(MeanReducer())` or `Integrate(vals -> maximum(vals) - minimum(vals))`.
5454
- `Aggregate()` defaults to `MeanReducer()`; you can pass reducers such as `Aggregate(MaxReducer())`.
55+
- Difference between `Integrate` and `Aggregate`: with the same reducer they are runtime-equivalent.
56+
In practice, only defaults and naming intent differ (`Integrate` for accumulation, `Aggregate` for summary statistics).
5557
- `Interpolate()` defaults to `mode=:linear, extrapolation=:linear`; use `Interpolate(; mode=:hold, extrapolation=:hold)` for hold behavior.
5658
- The same reducer objects are reused by meteo sampling (`MeteoBindings`) and by windowed policies (`Integrate`, `Aggregate`).
5759

@@ -160,7 +162,7 @@ mapping = ModelMapping(
160162
)
161163
```
162164

163-
When `multirate=true` is passed to `run!`, the runtime resolves inputs from producer temporal streams according to these policies.
165+
When the `ModelMapping` declares multirate configuration, the runtime resolves inputs from producer temporal streams according to these policies.
164166
Meteo rows are also sampled at each model clock. By default, meteo variables are aggregated from
165167
the finest weather step (for example `T` and `Rh` as weighted means, `Tmin/Tmax`, and radiation
166168
quantity aliases such as `Ri_SW_q` in MJ m-2). You can override these rules with `MeteoBindings(...)`
@@ -183,7 +185,7 @@ req = OutputRequest("Leaf", :carbon_assimilation;
183185
clock=ClockSpec(24.0, 1.0)
184186
)
185187

186-
run!(sim, meteo; multirate=true, tracked_outputs=[req], executor=SequentialEx())
188+
run!(sim, meteo; tracked_outputs=[req], executor=SequentialEx())
187189
exported = collect_outputs(sim; sink=DataFrame)
188190
```
189191

@@ -194,7 +196,6 @@ You can also return them directly from `run!`:
194196
out_status, exported = run!(
195197
sim,
196198
meteo;
197-
multirate=true,
198199
tracked_outputs=[req],
199200
return_requested_outputs=true,
200201
)

docs/src/multirate/multirate_tutorial.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,6 @@ out_status, exported = run!(
268268
mtg,
269269
mapping,
270270
meteo_hourly;
271-
multirate=true,
272271
executor=SequentialEx(),
273272
tracked_outputs=[req_leaf_hourly, req_plant_daily, req_plant_daily_T, req_plant_weekly],
274273
return_requested_outputs=true,

docs/src/planned_features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### Varying timesteps
66

77
Model-level varying timesteps are now available experimentally for MTG simulations
8-
through multi-rate execution (`multirate=true`) and mapping-level `ModelSpec` transforms
8+
through mapping-declared multi-rate execution and mapping-level `ModelSpec` transforms
99
such as `TimeStepModel`, `InputBindings`, `OutputRouting`, and `ScopeModel`.
1010

1111
Current remaining gaps for this area are:

src/mtg/GraphSimulation.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct GraphSimulation{T,S,U,O,V,TS,MS}
3131
outputs::Dict{String,O}
3232
outputs_index::Dict{String, Int}
3333
temporal_state::TS
34+
is_multirate::Bool
3435
end
3536

3637
function GraphSimulation(graph, mapping; nsteps=1, outputs=nothing, type_promotion=nothing, check=true, verbose=false)
@@ -47,6 +48,7 @@ get_models(g::GraphSimulation) = g.models
4748
get_model_specs(g::GraphSimulation) = g.model_specs
4849
outputs(g::GraphSimulation) = g.outputs
4950
temporal_state(g::GraphSimulation) = g.temporal_state
51+
is_multirate(g::GraphSimulation) = g.is_multirate
5052

5153
"""
5254
convert_outputs(sim_outputs::Dict{String,O} where O, sink; refvectors=false, no_value=nothing)

src/mtg/initialisation.jl

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,25 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
315315
end
316316

317317
models = Dict(first(m) => parse_models(get_models(last(m))) for m in mapping)
318-
model_specs = Dict(first(m) => parse_model_specs(last(m)) for m in mapping)
319-
scale_reachability = _scale_reachability_from_mtg(mtg)
320-
infer_model_specs_configuration!(model_specs; scale_reachability=scale_reachability)
321-
validate_model_specs_configuration(model_specs)
318+
model_specs = if mapping isa ModelMapping && !isempty(mapping.info.model_specs)
319+
deepcopy(mapping.info.model_specs)
320+
else
321+
Dict(first(m) => parse_model_specs(last(m)) for m in mapping)
322+
end
322323

323324
soft_dep_graphs_roots, hard_dep_dict = hard_dependencies(mapping; verbose=false)
324325

326+
scale_reachability = _scale_reachability_from_mtg(mtg)
327+
_infer_timestep_hints!(model_specs)
328+
ignored_same_rate_hard_children = _same_rate_hard_dependency_children(model_specs, soft_dep_graphs_roots)
329+
active_processes_by_scale = _active_processes_for_inference(model_specs, ignored_same_rate_hard_children)
330+
infer_model_specs_configuration!(
331+
model_specs;
332+
scale_reachability=scale_reachability,
333+
active_processes_by_scale=active_processes_by_scale
334+
)
335+
validate_model_specs_configuration(model_specs)
336+
325337
# Get the status of each node by node type, pre-initialised considering multi-scale variables:
326338
statuses, status_templates, reverse_multiscale_mapping, vars_need_init =
327339
init_statuses(mtg, mapping, soft_dep_graphs_roots; type_promotion=type_promotion, verbose=verbose, check=check)
@@ -356,5 +368,19 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
356368

357369
outputs_index = Dict{String, Int}(s => 1 for s in keys(outputs))
358370
temporal_state = TemporalState()
359-
return (; mtg, statuses, status_templates, reverse_multiscale_mapping, vars_need_init, dependency_graph=dep_graph, models, model_specs, outputs, outputs_index, temporal_state)
371+
mapping_is_multirate = mapping isa ModelMapping ? is_multirate(mapping) : false
372+
return (;
373+
mtg,
374+
statuses,
375+
status_templates,
376+
reverse_multiscale_mapping,
377+
vars_need_init,
378+
dependency_graph=dep_graph,
379+
models,
380+
model_specs,
381+
outputs,
382+
outputs_index,
383+
temporal_state,
384+
is_multirate=mapping_is_multirate
385+
)
360386
end

0 commit comments

Comments
 (0)