diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac4e365..95527f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # Changelog +## v0.14.1 + +Changes in this section are based on the git history since [`v0.14.0`](https://github.com/VirtualPlantLab/PlantSimEngine.jl/releases/tag/v0.14.0), corresponding to the GitHub compare view for [`v0.14.1`](https://github.com/VirtualPlantLab/PlantSimEngine.jl/compare/v0.14.0...v0.14.1). + +### Summary + +This release adds a dependency graph visualizer and an interactive graph editor. +The new tooling makes it possible to inspect a `ModelMapping`, see which models +produce and consume each variable, diagnose missing initialization values, and +interactively build or revise a mapping from a browser UI. + +The static graph viewer is available directly from `PlantSimEngine` through +`write_graph_view`, `graph_view`, and `graph_view_json`. The live editor is +provided by the `PlantSimEngineGraphEditorExt` package extension and is loaded +when `HTTP.jl` is available in the session. The release also includes benchmark +and CI maintenance, updated developer guidance, and a new agent skill for +working with PlantSimEngine internals. + +### Added + +- New dependency graph visualization API: + `graph_view`, `graph_view_json`, `compile_graph_view`, and + `write_graph_view`. +- A standalone HTML graph viewer with model nodes, variable ports, hard-call + edges, variable dependency edges, scale filters, relationship filters, search, + overview/detail modes, and an inspector. +- An interactive browser-based graph editor through `edit_graph`, backed by a + local `HTTP.jl` server and WebSocket session. +- The live graph editor runs as a package extension that depends on + `HTTP.jl`; static graph visualization is available without loading + `HTTP`. +- Editing support for adding, updating, removing, and reconnecting models in a + `ModelMapping`. +- Support for starting the editor from a blank mapping with `edit_graph()`. +- Mapping-code generation from the editor, including `using PackageName` + statements for loaded model packages and a top-level `mapping` variable. +- Save, autosave, recent-file reopening, undo, and redo support in live graph + editing sessions. +- Model discovery helpers: + `available_processes`, `available_models`, `model_descriptor`, and + `model_constructor_descriptor`. +- Editor suggestions for adding producer models from input variables and + consumer models from output variables. +- Cycle diagnostics in the graph UI, including highlighted cycle edges and an + interactive way to wrap selected inputs in `PreviousTimeStep`. +- Visualization of required initialization values and graph diagnostics even + when a mapping is incomplete or cyclic. +- A new documentation page for graph visualization and editing: + [`docs/src/step_by_step/graph_visualization_editor.md`](docs/src/step_by_step/graph_visualization_editor.md). +- Playwright end-to-end tests for the browser editor and new Julia tests for + the static graph viewer and editor extension. +- A local `plantsimengine` Codex skill describing the package architecture and + contributor workflow. + +### Fixed + +- Fixed type promotion behavior with `ModelMapping`. +- Fixed benchmark and documentation issues that were blocking CI. + ## v0.14.0 Changes in this section are based on the git history since [`v0.13.2`](https://github.com/VirtualPlantLab/PlantSimEngine.jl/releases/tag/v0.13.2), corresponding to the GitHub compare view for [`v0.14.0`](https://github.com/VirtualPlantLab/PlantSimEngine.jl/compare/v0.13.2...v0.14.0). diff --git a/skills/plantsimengine/SKILL.md b/skills/plantsimengine/SKILL.md index ebf01e04..405f1cf3 100644 --- a/skills/plantsimengine/SKILL.md +++ b/skills/plantsimengine/SKILL.md @@ -1,59 +1,115 @@ --- name: plantsimengine -description: Use PlantSimEngine.jl to compose existing process models with ModelMapping, spatial multiscale MTG mappings, multirate ModelSpec configuration, and to implement or wrap new models by defining processes, inputs_, outputs_, run!, hard dependencies, and model traits. +description: Use PlantSimEngine.jl from either the user perspective, composing existing models into single-scale, multiscale, vector-of-object, or multirate simulations, or the modeler perspective, implementing, wrapping, translating, testing, and documenting models with processes, inputs_, outputs_, run!, hard dependencies, traits, and ModelMapping integration. --- # PlantSimEngine Skill -Use this skill when helping with PlantSimEngine.jl simulations, model mappings, multiscale MTG coupling, multirate execution, or implementing/wrapping models. +Use this skill when helping with PlantSimEngine.jl simulations, model mappings, +multiscale MTG coupling, vector-of-object workflows, multirate execution, or +implementing/wrapping process models. -PlantSimEngine has two main user roles: +PlantSimEngine work usually has one of two viewpoints: -- **Users** compose existing models. They mostly need `ModelMapping`, `MultiScaleModel`/`ModelSpec`, status initialization, variable mappings, spatial scale symbols, and multirate policies. -- **Modelers** implement or wrap models. They need process identity, `inputs_`, `outputs_`, `run!`, hard dependencies, model traits, and tests that prove the model composes correctly. +- **User viewpoint**: compose existing process models into a bigger simulation. + The main tools are `ModelMapping`, `Status`, `MultiScaleModel`, `ModelSpec`, + `InputBindings`, `to_initialize`, `dep`, `graph_view`, and `run!`. +- **Modeler viewpoint**: define, wrap, or translate models so users can compose + them. The main tools are `@process`, `inputs_`, `outputs_`, `run!`, `dep`, + model traits, and tests that prove the model works in mappings. -Prefer current APIs: `ModelMapping`, `ModelSpec`, `MultiScaleModel`, `Status`, `PreviousTimeStep`, and `run!`. Treat legacy `ModelList` as compatibility plumbing unless the user is working on legacy code. +Prefer current APIs: `ModelMapping`, `ModelSpec`, `MultiScaleModel`, `Status`, +`PreviousTimeStep`, and `run!`. Treat `ModelList` as legacy compatibility unless +the user is explicitly working on old code. For Julia execution, use Kaimon. -## First Steps +## First Decision -1. Identify whether the request is user-side mapping work or modeler-side implementation work. -2. Inspect existing model declarations before inventing names: - - Search for process definitions with `rg "@process|abstract type Abstract.*Model" src examples docs test`. - - Search for model APIs with `rg "inputs_\\(|outputs_\\(|PlantSimEngine.run!|dep\\(" src examples test`. -3. Check model IO with `inputs(model)`, `outputs(model)`, `variables(model)`, and process identity with `process(model)` when available. -4. Validate mappings early with `dep(mapping)`, `to_initialize(mapping[, mtg])`, `resolved_model_specs(mapping)`, and `explain_model_specs(mapping)` when relevant. +Before editing or answering, decide which role the request is about. -## User Workflow: Existing Models +Use the **user workflow** when the user says things like: -### Single-scale mapping +- "couple these models" +- "build a plant/scene model" +- "convert this single-scale simulation to multiscale" +- "map leaves into a plant model" +- "run one model hourly and another daily" +- "why does this mapping ask me to initialize this variable?" -Use `ModelMapping` when all models share one status. +Use the **modeler workflow** when the user says things like: + +- "implement a new model/process" +- "wrap this equation/code/package" +- "translate this model to PlantSimEngine" +- "make this model usable in multiscale simulations" +- "add hard dependencies or traits" +- "write tests for this model" + +When in doubt, start by inspecting existing declarations: + +```sh +rg "@process|abstract type Abstract.*Model|inputs_\\(|outputs_\\(|PlantSimEngine.run!|dep\\(" src examples docs test +``` + +Useful runtime queries are `process(model)`, `inputs(model)`, `outputs(model)`, +`variables(model)`, `to_initialize(mapping[, mtg])`, `dep(mapping)`, +`graph_view(mapping)`, `resolved_model_specs(mapping)`, and +`explain_model_specs(mapping)`. + +## User Workflow: Compose Existing Models + +The user is trying to make existing models work together. Optimize for a +working mapping, then for clarity. + +### 1. Inventory model IO + +For each model, identify: + +- the process name: `process(model)`; +- required inputs: `inputs(model)`; +- produced outputs: `outputs(model)`; +- variables still needing user initialization after coupling: + `to_initialize(mapping)`. + +Do not decide initialization from `inputs(model)` alone. In a coupled mapping, +some inputs are computed by upstream models and should not be put in `Status`. + +### 2. Single-scale coupling + +Use `ModelMapping` when every model shares one status. ```julia mapping = ModelMapping( ModelA(args...), ModelB(args...); - status=(x=1.0, y=0.0), + status=(driver=1.0,), ) +to_initialize(mapping) out = run!(mapping, meteo) ``` Rules: -- Inputs are matched to outputs by variable name after model declarations are flattened. -- Variables not produced by another model must be initialized through `status`, model defaults, meteo, or another supported input source. -- If two models produce the same canonical variable, inspect the graph and disambiguate before relying on incidental order. -- `Status` values are reference-backed. Writing `status.x = value` mutates the cell used by coupled models. +- Soft dependencies are inferred by matching input names to output names. +- The order in the `ModelMapping` is not the semantic model order; the + dependency graph controls execution. +- Variables not produced by another model must come from `status`, model + defaults, meteo, constants, or another supported source. +- In single-scale runs, vector values in `status` are usually timestep series: + the runtime updates the current value each timestep. +- If two models publish the same canonical output, inspect the graph and + disambiguate before relying on incidental behavior. -### Spatial multiscale mapping +### 3. Move from single-scale to multiscale -Use `ModelMapping` keyed by scale symbols when running on an MTG. Prefer symbol scales such as `:Scene`, `:Plant`, `:Leaf`, `:Internode`; string scale names are deprecated. +Use a scale-keyed `ModelMapping` when running on an MTG. Scales should be +symbols such as `:Scene`, `:Plant`, `:Leaf`, and `:Internode`; string scale +names are deprecated. ```julia mapping = ModelMapping( :Scene => ( - SceneModel(), + SceneDriverModel(), ), :Plant => ( MultiScaleModel( @@ -67,72 +123,115 @@ mapping = ModelMapping( ), ) +to_initialize(mapping) out = run!(mtg, mapping, meteo) ``` -Each scale tuple can contain models, `ModelSpec`s, `MultiScaleModel`s, and optional `Status(...)` initializers for variables local to that scale. A model only sees variables in its local status unless they are mapped from another scale or supplied by runtime input binding. +Core rule: a model sees only its local scale status unless an input is mapped +from another scale or supplied by multirate input binding. A scale in the +mapping does not run unless the MTG contains nodes with that scale symbol. -### Variable mapping forms +### 4. Choose scalar or vector mappings deliberately -Use `MultiScaleModel(model, mapped_variables)` or pipe through `ModelSpec(model) |> MultiScaleModel(mapped_variables)`. +Use `MultiScaleModel(model, mapped_variables)` or: + +```julia +ModelSpec(model) |> MultiScaleModel(mapped_variables) +``` -Common forms: +Common mapping forms: ```julia -:x => :Plant # scalar read from :Plant, same variable name +:x => :Plant # scalar read from :Plant, same variable :x => (:Plant => :y) # scalar read from :Plant variable :y :x => [:Leaf] # vector read from all :Leaf nodes :x => [:Leaf, :Internode] # vector read from several scales :x => [:Leaf => :a, :Internode => :b] # vector read with per-scale renaming :x => (Symbol("") => :y) # same-scale rename or alias -PreviousTimeStep(:x) # break current-step dependency inference +PreviousTimeStep(:x) # remove current-step dependency edge PreviousTimeStep(:x) => (:Plant => :y) ``` Semantics: -- Scalar cross-scale mappings share a `Ref`; the source scale is expected to be unique at runtime. -- Vector mappings create a `RefVector`; models must handle vector inputs, and order follows MTG traversal order. -- Same-scale renaming creates a per-status alias, not a graph-wide shared variable. -- `PreviousTimeStep` prevents same-step dependency edges and is the standard way to break cycles. - -### Multirate configuration - -Use `ModelSpec` when models run at different clocks, consume streams with temporal policies, aggregate meteo, or need scoped streams. +- Scalar cross-scale mappings use shared `Ref`s and assume the source scale is + unique enough for the scenario. +- Vector mappings create a `RefVector`, even when there is currently one source + node. Treat it as a vector-like object, not as a scalar. +- `RefVector` reads dereference source statuses; writes mutate the source + status cells. Use broadcasting for in-place vector outputs when appropriate. +- `RefVector` order follows MTG traversal/initialization order, not biological + priority. +- Same-scale renaming creates a per-status alias, not a graph-wide shared + variable. +- `PreviousTimeStep` is the standard way to break a same-step dependency cycle, + but only use it when the initial value and lag semantics are intentional. + +Vector-of-object mappings are for models such as carbon allocation at the plant +scale consuming values from many leaf or internode objects. The model +implementation must tolerate vector inputs, dynamic length changes in growing +MTGs, and traversal-order semantics. + +### 5. Use ModelSpec for scenario configuration + +`ModelSpec` is user-side configuration around an existing model. Use it when +the scenario needs runtime policy, not when the model definition itself should +change. ```julia -daily = ClockSpec(24.0, 1.0) - plant_spec = ModelSpec(PlantDailyModel()) |> MultiScaleModel([:leaf_assim => [:Leaf => :A]]) |> - TimeStepModel(daily) |> + TimeStepModel(ClockSpec(24.0, 1.0)) |> InputBindings(; leaf_assim=(process=:leafassimilation, scale=:Leaf, var=:A, policy=Integrate()), ) |> ScopeModel(:plant) ``` -Policies: +Use explicit `InputBindings` when: + +- several producers can provide the same variable; +- a producer is cross-scale and inference is ambiguous; +- variable names differ; +- the temporal policy must be `Integrate`, `Aggregate`, or `Interpolate` + instead of default `HoldLast`. -- `HoldLast()` uses the latest producer value. -- `Interpolate()` interpolates or holds/extrapolates producer streams. -- `Integrate()` reduces over the consumer window, usually for fluxes or accumulations. -- `Aggregate()` reduces over the consumer window, usually for means, extrema, or summaries. +Policy precedence: -Precedence: +1. Input policy: explicit `InputBindings(..., policy=...)` > producer + `output_policy` > `HoldLast()`. +2. Timestep: `TimeStepModel(...)` > non-default `timespec(model)` > meteo base + step. +3. Meteo sampling: explicit `MeteoBindings(...)`/`MeteoWindow(...)` > + `meteo_hint(...)` > runtime defaults. -1. Input policy: explicit `InputBindings(..., policy=...)` > producer `output_policy` > `HoldLast()`. -2. Timestep: `TimeStepModel(...)` > non-default `timespec(model)` > meteo base step. -3. Meteo sampling: explicit `MeteoBindings(...)`/`MeteoWindow(...)` > `meteo_hint(...)` > runtime defaults. +Use `OutputRouting(; x=:stream_only)` when a model should publish a temporal +stream without becoming the canonical status owner for `x`. -Use explicit `InputBindings` when several models/scales can produce the same variable, names differ, or the default temporal policy is not correct. Use `OutputRouting(; x=:stream_only)` when a producer should publish a stream without becoming the canonical status owner for `x`. +### 6. Validate before long runs -## Modeler Workflow: New Or Wrapped Models +Recommended order: -### Choose or create the process +1. Build the smallest mapping that should work. +2. Run `to_initialize(mapping)` or `to_initialize(mapping, mtg)`. +3. Run `dep(mapping)` to check graph construction and cycles. +4. Use `graph_view(mapping)` or `write_graph_view(...)` for visual debugging. +5. For multirate mappings, inspect `resolved_model_specs(mapping)` and + `explain_model_specs(mapping)`. +6. Run a short simulation with minimal `tracked_outputs`. -Process identity is the abstract process type, not the concrete model name. Before adding a process, search for an existing one with the same biological or physical meaning. Reuse it when the new model is an alternative implementation of the same process. +## Modeler Workflow: Implement, Wrap, Or Translate Models + +The modeler is creating code that users will compose later. Optimize for a +clear model contract and predictable coupling behavior. + +### 1. Choose the process + +Process identity is the abstract process type, not the concrete model name. +Search before adding a new process. Reuse an existing process when the new +model is an alternative implementation of the same biological or physical +process. Create a new process only when the simulated process is genuinely new: @@ -140,9 +239,11 @@ Create a new process only when the simulated process is genuinely new: PlantSimEngine.@process "maintenance_respiration" verbose=false ``` -This creates an abstract process type such as `AbstractMaintenance_RespirationModel`. Concrete implementations subtype that abstract process. +This creates an abstract process type such as +`AbstractMaintenance_RespirationModel`. Concrete implementations subtype that +abstract process. -### Implement the model contract +### 2. Implement the model contract ```julia struct MyModel{T} <: AbstractSome_ProcessModel @@ -160,27 +261,82 @@ end Rules: -- `inputs_` and `outputs_` are authoritative. Defaults are also initialization hints. +- `inputs_` and `outputs_` are authoritative and must return `NamedTuple`s. - Use `NamedTuple()` for no inputs or no outputs. -- Read and write model state through `status`. Do not store timestep-varying state in the model object. -- Read weather through `meteo` and physical constants through `constants`. -- In MTG runs, `extra` is the `GraphSimulation`; do not use user-defined `extra` arguments for MTG APIs. -- If a variable appears in both `inputs_` and `outputs_` with the same name, remember that `variables(model)` merges declarations and later output declarations win. +- Defaults are initialization hints; choose values that make missing data + obvious unless a true default is meaningful. +- `variables(model)` merges inputs and outputs; if a variable appears in both, + the output declaration wins. +- `run!` is one timestep for one status. Do not loop over timesteps inside a + normal model `run!`. +- Read and write timestep-varying state through `status`, not model fields. +- Put fixed parameters in the model struct. +- Read weather through `meteo` and constants through `constants`. +- In MTG runs, `extra` is the `GraphSimulation`; do not rely on user-defined + `extra` arguments for MTG APIs. + +Prefer parametric fields and promotion/default constructors when useful: + +```julia +struct MyModel{T} <: AbstractSome_ProcessModel + p1::T + p2::T +end -### Wrapping existing code +MyModel(p1, p2) = MyModel(promote(p1, p2)...) +MyModel(; p1=1.0, p2=2.0) = MyModel(p1, p2) +``` -When wrapping an external or existing model: +### 3. Wrap or translate existing code -1. Identify its true inputs, outputs, parameters, weather needs, and mutable state. +When wrapping an existing model, first separate its contract from its +implementation details: + +1. Identify true inputs, outputs, parameters, weather needs, constants, units, + timestep assumptions, and mutable state. 2. Put fixed parameters in the struct. -3. Put timestep-varying inputs and outputs in `status`. +3. Put timestep-varying state, intermediate variables that must be coupled, and + outputs in `Status`. 4. Convert internal side effects into explicit `status` assignments. -5. Keep units and timestep assumptions in docstrings and traits. -6. If the external model computes several processes internally, split it into several PlantSimEngine models when users need to couple or replace those subprocesses independently. Keep it as one model only when the subprocesses are inseparable implementation details. +5. Keep unit and timestep assumptions in docstrings and traits. +6. Split a large external model into several PlantSimEngine models when users + need to couple, replace, or inspect subprocesses independently. +7. Keep it as one model only when subprocesses are inseparable implementation + details. + +Do not preserve an external model's timestep loop inside `run!` unless you are +intentionally implementing a model that internally solves a subproblem at a +finer scale and exposes only one PlantSimEngine timestep result. -### Hard dependencies +### 4. Write vector-aware models carefully -Use hard dependencies when a parent model directly calls a required submodel inside its own `run!`. The runtime records the dependency but does not automatically execute it for the parent. +A model that consumes or produces values across many MTG objects should declare +vector defaults: + +```julia +PlantSimEngine.inputs_(::AllocationModel) = + (carbon_assimilation=[-Inf], carbon_demand=[-Inf]) + +PlantSimEngine.outputs_(::AllocationModel) = + (carbon_allocation=[-Inf],) +``` + +Implementation rules: + +- Expect `status.x` to be a `RefVector` or another vector-like object. +- Use `sum`, broadcasting, `map`, and generic `AbstractVector` operations. +- For vector outputs backed by source statuses, mutate in place: + `status.carbon_allocation .= values`. +- In dynamic MTGs, source and target vector lengths can temporarily differ. + Handle shared prefixes and initialize remaining values deliberately. +- Do not attach semantics to vector order unless the MTG traversal order is part + of the model contract and tested. + +### 5. Declare hard dependencies only for direct calls + +Use hard dependencies when a parent model directly calls another process inside +its own `run!`. The runtime records the dependency but the parent model is +responsible for executing it. ```julia PlantSimEngine.dep(::ParentModel) = ( @@ -190,10 +346,11 @@ PlantSimEngine.dep(::ParentModel) = ( function PlantSimEngine.run!(m::ParentModel, models, status, meteo, constants, extra=nothing) run!(models.child_process, models, status, meteo, constants, extra) status.parent_output = g(status.child_output) + return nothing end ``` -For multiscale hard dependencies, declare the target scale: +For multiscale hard dependencies, declare target scale(s): ```julia PlantSimEngine.dep(::ParentModel) = ( @@ -201,11 +358,18 @@ PlantSimEngine.dep(::ParentModel) = ( ) ``` -Then call the child model explicitly on the correct target status, usually via `extra.statuses[:Leaf]` and `extra.models[:Leaf]`. Be careful: hard-dependency IO still participates in graph compilation through the owning soft node. +Then call the child on the correct scale status, usually via +`extra.statuses[:Leaf]` and `extra.models[:Leaf]`, or through the status +returned by dynamic MTG helpers such as `add_organ!`. Do not call a child model +with the parent's status when the child lives at another scale. + +Hard-dependency IO still participates in graph compilation through the owning +soft node. Test both the manual call and the graph shape. -### Model traits +### 6. Add traits only when they are true -Add traits only when they are true for the model implementation, not merely convenient for one scenario. +Traits describe model behavior. `ModelSpec` describes scenario configuration. +Do not use traits to hide a one-off scenario choice. ```julia PlantSimEngine.TimeStepDependencyTrait(::Type{<:MyModel}) = @@ -229,32 +393,75 @@ PlantSimEngine.meteo_hint(::Type{<:MyModel}) = ( ) ``` -Parallel traits are mainly for single-scale execution. Multirate MTG runs are currently sequential. +Parallel traits are mainly for single-scale execution. Multirate MTG execution +is currently sequential. ## Validation Checklist For user mappings: -- `to_initialize(mapping)` or `to_initialize(mapping, mtg)` lists only variables the user should really provide. -- `dep(mapping)` succeeds and the dependency graph matches the expected coupling. -- `explain_model_specs(mapping)` is sensible for multirate runs. -- Cycles are either absent or intentionally broken with `PreviousTimeStep`. +- `to_initialize(mapping)` or `to_initialize(mapping, mtg)` lists only variables + the user should provide. +- `dep(mapping)` succeeds and the graph matches the intended coupling. +- `graph_view(mapping)` makes producer/consumer relationships clear. +- Cycles are absent or intentionally broken with `PreviousTimeStep`. +- Scalar mappings point to genuinely unique sources for the scenario. +- Vector mappings are consumed by models that expect vector-like values. - Ambiguous multirate producers are resolved with `InputBindings`. +- Duplicate publishers are either removed or routed with `OutputRouting`. For model implementations: -- Unit-test `inputs_`, `outputs_`, and a direct `run!` call with a minimal `Status`. -- Test single-scale composition when the model is meant to couple by variable name. -- Test MTG/multiscale mapping when the model expects scalar refs, `RefVector` inputs, or cross-scale writes. -- Test multirate behavior when traits, `InputBindings`, `MeteoBindings`, or `OutputRouting` matter. -- Check hard dependencies by proving the parent actually calls the child and uses the child's outputs. +- Test `inputs_`, `outputs_`, and `variables`. +- Test a direct `run!` call with a minimal `Status`. +- Test single-scale composition when the model should couple by variable name. +- Test MTG scalar mapping when the model reads from another scale. +- Test vector mapping when the model expects `RefVector` inputs or outputs. +- Test cycle breaking when `PreviousTimeStep` is part of the workflow. +- Test multirate behavior when traits, bindings, meteo sampling, output routing, + or scopes matter. +- Test hard dependencies by proving the parent declares and calls the child. ## Common Pitfalls -- Do not confuse hard dependencies with soft dependency scheduling. Hard dependencies are manual calls. -- Do not rely on MTG topology for model execution order. Soft dependency order controls model order. -- Do not assume `RefVector` order has biological meaning. -- Do not map scalar reads from a scale that can have several runtime nodes unless the model really expects the chosen unique source behavior. -- Do not use strings for new scale declarations. Use symbols. -- Do not mutate MTG topology after status initialization unless you reinitialize or use supported dynamic helpers. -- Do not use `PreviousTimeStep` as a numerical lag unless the initial value and expected temporal semantics are explicit. +- Using `inputs(model)` instead of `to_initialize(mapping)` to decide what users + must provide. +- Putting a model at a scale that does not exist in the MTG and expecting it to + run. +- Passing a multiscale mapping to `run!(mapping, meteo)` instead of + `run!(mtg, mapping, meteo)`. +- Using strings for new scale declarations. Use symbols. +- Treating vector mappings as copied arrays instead of shared `RefVector`s. +- Assuming `RefVector` order has biological meaning. +- Mapping scalar reads from a scale that can have several runtime nodes. +- Putting timestep vectors directly in multiscale `Status` when a driver model + or generated status-vector helper would be clearer. +- Using `PreviousTimeStep` as a casual numerical lag without specifying the + initial value and intended lag semantics. +- Declaring `dep` for a soft dependency that should be inferred from + input/output names. +- Forgetting that hard dependencies are manual calls. +- Storing timestep-varying state in model fields. +- Omitting explicit `InputBindings` in ambiguous multirate cases. +- Forgetting `OutputRouting(; x=:stream_only)` when duplicate producers should + publish streams but not own the canonical status variable. + +## High-Signal Local References + +- User coupling: `docs/src/step_by_step/simple_model_coupling.md`, + `docs/src/model_coupling/model_coupling_user.md`. +- Single to multiscale: `docs/src/multiscale/single_to_multiscale.md`. +- Multiscale mapping and vector inputs: `docs/src/multiscale/multiscale.md`, + `docs/src/multiscale/multiscale_coupling.md`, + `examples/ToyCAllocationModel.jl`. +- Cycles: `docs/src/multiscale/multiscale_cyclic.md`. +- Multirate: `docs/src/multirate/introduction.md`, + `docs/src/multirate/multirate_tutorial.md`, + `docs/src/multirate/advanced_configuration.md`. +- Model implementation: `docs/src/step_by_step/implement_a_model.md`, + `docs/src/step_by_step/implement_a_model_additional.md`, + `docs/src/FAQ/translate_a_model.md`. +- Internals: `src/component_models/Status.jl`, + `src/component_models/RefVector.jl`, `src/mtg/MultiScaleModel.jl`, + `src/mtg/ModelSpec.jl`, `src/mtg/mapping/compute_mapping.jl`, + `src/run.jl`.