Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/src/API/API_public.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,13 @@ Scope selection detail:
```julia
req_hold = OutputRequest("Leaf", :A; name=:A_hourly, process=:assim, policy=HoldLast())
req_day = OutputRequest("Leaf", :A; name=:A_daily_sum, process=:assim, policy=Integrate(), clock=ClockSpec(24.0, 1.0))
run!(sim, meteo; multirate=true, tracked_outputs=[req_hold, req_day], executor=SequentialEx())
run!(sim, meteo; tracked_outputs=[req_hold, req_day], executor=SequentialEx())
out = collect_outputs(sim; sink=DataFrame)

# or directly:
out_status, out = run!(
sim,
meteo;
multirate=true,
tracked_outputs=[req_hold, req_day],
return_requested_outputs=true,
)
Expand Down Expand Up @@ -109,6 +108,8 @@ MeteoBindings(
### Parameterized window reducers

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

```julia
ModelSpec(DailyModel()) |>
Expand Down
7 changes: 4 additions & 3 deletions docs/src/model_execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Inspection helpers:
Policy parameterization:
- `Integrate()` defaults to `SumReducer()`; you can pass another reducer, e.g. `Integrate(MeanReducer())` or `Integrate(vals -> maximum(vals) - minimum(vals))`.
- `Aggregate()` defaults to `MeanReducer()`; you can pass reducers such as `Aggregate(MaxReducer())`.
- Difference between `Integrate` and `Aggregate`: with the same reducer they are runtime-equivalent.
In practice, only defaults and naming intent differ (`Integrate` for accumulation, `Aggregate` for summary statistics).
- `Interpolate()` defaults to `mode=:linear, extrapolation=:linear`; use `Interpolate(; mode=:hold, extrapolation=:hold)` for hold behavior.
- The same reducer objects are reused by meteo sampling (`MeteoBindings`) and by windowed policies (`Integrate`, `Aggregate`).

Expand Down Expand Up @@ -131,7 +133,7 @@ mapping = ModelMapping(
)
```

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

run!(sim, meteo; multirate=true, tracked_outputs=[req], executor=SequentialEx())
run!(sim, meteo; tracked_outputs=[req], executor=SequentialEx())
exported = collect_outputs(sim; sink=DataFrame)
```

Expand All @@ -165,7 +167,6 @@ You can also return them directly from `run!`:
out_status, exported = run!(
sim,
meteo;
multirate=true,
tracked_outputs=[req],
return_requested_outputs=true,
)
Expand Down
1 change: 0 additions & 1 deletion docs/src/multirate/multirate_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ req_plant_weekly = OutputRequest("Plant", :plant_assim_w;
out_status, exported = run!(
sim,
meteo_hourly;
multirate=true,
executor=SequentialEx(),
tracked_outputs=[req_leaf_hourly, req_plant_daily, req_plant_daily_T, req_plant_weekly],
return_requested_outputs=true,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/planned_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
### Varying timesteps

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

Current remaining gaps for this area are:
Expand Down
2 changes: 2 additions & 0 deletions src/mtg/GraphSimulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct GraphSimulation{T,S,U,O,V,TS,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)
Expand All @@ -47,6 +48,7 @@ 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)
Expand Down
36 changes: 31 additions & 5 deletions src/mtg/initialisation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,25 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion
end

models = Dict(first(m) => parse_models(get_models(last(m))) for m in mapping)
model_specs = Dict(first(m) => parse_model_specs(last(m)) for m in mapping)
scale_reachability = _scale_reachability_from_mtg(mtg)
infer_model_specs_configuration!(model_specs; scale_reachability=scale_reachability)
validate_model_specs_configuration(model_specs)
model_specs = if mapping isa ModelMapping && !isempty(mapping.info.model_specs)
deepcopy(mapping.info.model_specs)
else
Dict(first(m) => parse_model_specs(last(m)) for m in mapping)
end

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

scale_reachability = _scale_reachability_from_mtg(mtg)
_infer_timestep_hints!(model_specs)
ignored_same_rate_hard_children = _same_rate_hard_dependency_children(model_specs, soft_dep_graphs_roots)
active_processes_by_scale = _active_processes_for_inference(model_specs, ignored_same_rate_hard_children)
infer_model_specs_configuration!(
model_specs;
scale_reachability=scale_reachability,
active_processes_by_scale=active_processes_by_scale
)
validate_model_specs_configuration(model_specs)

# Get the status of each node by node type, pre-initialised considering multi-scale variables:
statuses, status_templates, reverse_multiscale_mapping, vars_need_init =
init_statuses(mtg, mapping, soft_dep_graphs_roots; type_promotion=type_promotion, verbose=verbose, check=check)
Expand Down Expand Up @@ -356,5 +368,19 @@ function init_simulation(mtg, mapping; nsteps=1, outputs=nothing, type_promotion

outputs_index = Dict{String, Int}(s => 1 for s in keys(outputs))
temporal_state = TemporalState()
return (; mtg, statuses, status_templates, reverse_multiscale_mapping, vars_need_init, dependency_graph=dep_graph, models, model_specs, outputs, outputs_index, temporal_state)
mapping_is_multirate = mapping isa ModelMapping ? is_multirate(mapping) : false
return (;
mtg,
statuses,
status_templates,
reverse_multiscale_mapping,
vars_need_init,
dependency_graph=dep_graph,
models,
model_specs,
outputs,
outputs_index,
temporal_state,
is_multirate=mapping_is_multirate
)
end
Loading
Loading