This reference lists the functions for building, running, and inspecting a simulation. For a first example, start with Couple Models On One Object.
An object is one part of your simulated system, such as a plant or leaf. Its status holds its changing values. A model application is a model configured with a name, selected objects, and input or timing settings. An input binding connects a model to the value it reads. The carrier holds that connection, usually as a shared reference to the source value; time-based inputs can instead read saved results.
CompositeModel stores the objects, their model applications, reusable plant instances, and environmental data or providers.
CompositeModel(model, models...; status=..., timestep=..., type_promotion=..., status_transform=...) is the concise one-object form and creates one object and its model applications.
Object represents one entity, such as a plant or organ, with an identifier that stays the same and its own status.
object_id(model, source) finds an object's identifier in the model's registry, the collection of registered objects. source can be an ObjectId, registered Object or Status, MTG node, or raw identifier. MTG nodes keep the identifier assigned by the model's id= function when the MTG was imported or the organ was created. object_id does not call that function again, and rejects copied nodes or nodes from another model. The same methods accept a RunContext or Simulation.
CompositeModelTemplate and ObjectInstance reuse a set of model definitions for several plants or other repeated objects.
ModelSpec(model; name=..., on=..., inputs=..., calls=..., outputs_to=..., every=..., environment=..., output_routing=..., updates=...) configures a model application.
ModelSpec(...; inputs=...) describes where a model's inputs come from.
bound_input(context, :name) gives a model access to both the values and source objects of a declared Many input through a BoundMany view. object_ids(view) returns the source identifiers in the same order as the values, without copying them.
ModelSpec(...; calls=...) declares models that this model can run from inside its own calculation.
ModelSpec(...; outputs_to=(name=OutputTo(selector; vars=...),)) declares variables that this application writes into other selected objects' statuses. Each variable uses Required(T) or Default(value). Before initializing the statuses, PlantSimEngine finds the destination objects and checks that competing applications do not write the same value.
output_targets(context, :name) returns an OutputTargets view for one named outputs_to group. Read or write a variable's destination values through targets.columns.<variable>. object_ids(targets) returns their identifiers in the same order; those identifiers are read-only.
assign_outputs!(targets, table; id=:object_id) assigns a Tables.jl-compatible result to objects using their identifiers. The assign_outputs!(targets, ids, columns) overload accepts an ID vector and a NamedTuple of columns directly.
Updates(:variable; after=:application_id) sets the order when several applications deliberately update the same variable.
Input(...) and Call(...) describe a model's default input connections and calls through dep(model).
Initializer(One(application=:name, ...)) declares one normally scheduled application that may initialize a newly registered object during its creation event.
run_call!(context, :name; publish=false) runs every model and object selected by that named call. Each such pair is a call target. The result is always a vector-like CallTargets collection.
run_call!(context, :name; sampled_environment=value) forwards one already prepared set of environmental values to the selected models. It uses the execution groups already prepared by PlantSimEngine.
call_model(context, :name) returns the model when a call selects exactly one target.
call_targets(context, :name) returns those targets without running them, so you can choose individual targets to run with run_call!(target; ...).
run_initializer!(context, :name, object) runs a declared Initializer once on a newly created object. It sets values in that object's main stored status and returns that Status, without adding an output-history sample partway through the step. Use it only for initialization during object creation, not for trial calls or existing objects.
When assigning outputs to several objects, include every selected object ID exactly once and provide every declared output column. Additional table or NamedTuple columns are treated as metadata and ignored. A result column may share storage with a destination column only when assigning that column to itself in exactly the destination order.
Obtain OutputTargets each time the model runs. Do not keep it for later calls after changes to the objects have been processed. If you reuse the same ID-column object, PlantSimEngine reuses the correspondence it calculated between result rows and destination objects. This requires the IDs and their order to remain unchanged. Supply a new ID-column object if either changes.
Required(T) declares an input that you must supply in the object's status or connect to another model. T is the expected type and may be generic.
Default(value) supplies a model's fallback value when no input is provided.
inputs_(model) uses only these explicit declarations; plain literals are rejected.
outputs_(model) gives the initial values of outputs.
init_variables(model) returns only input defaults and initial output values.
VariableContract describes units and physical meaning: for example, whether a variable is per plant or per square metre, a rate or a daily total, and whether amounts from several objects can be added. The description is stored separately from the numerical value.
variable_contracts(model) returns checked declarations from PlantSimEngine.variable_contracts_, which model packages implement. When an input is connected to another model's output, their contracts must be identical if either model declares one.
CompositeModel(...; type_promotion=Dict(Float64 => Float32)) converts every matching status value with convert when the value's storage is created.
CompositeModel(...; status_transform=(variable, value) -> ...) applies a precise transformation based on the status variable name and value. The returned value is then checked against the type_promotion mapping, so status_transform always runs first.
Ordinary numeric arrays are converted element by element when their elements match a mapping rule. Their shape is preserved.
The policy covers supplied object statuses, model input and output defaults, and statuses of objects added later through the object-creation functions.
The policy is limited to status values. Model parameters, environment values, constants, object labels, and topology are not converted.
Conversion occurs when status storage is created or an object is registered, not every time the model's equation runs.
Diagnostics.explain_initialization(model) reports declared_type, original_type, transformed_type, and effective_type, plus flags and the selected mapping rule for each initialized value.
The model's calculation must support the resulting numerical type. Keeping Required declarations and equations open to different types allows the same model to use Float32, numbers with uncertainty estimates, or another compatible numerical type. See Numerical Reliability for complete examples.
Number of matches: One(...), OptionalOne(...), and Many(...).
Where to look: SceneScope(), Self(), Subtree(), SelfPlant(), Ancestor(...), and Scope(name).
Label criteria: kind=..., species=..., scale=..., and name=....
Connections between objects: Relation(...).
Self() always means the current object: the object on which the model reading the input runs. It means a plant only when that object is itself the plant.
Selector fields are checked where the selector is used:
| Context | Accepted criteria |
|---|---|
ModelSpec(...; on=...) | kind, species, scale, name, and a scene or named scope |
ModelSpec(...; inputs=...) | object criteria plus process, application, var, policy, window, from_status, and after |
ModelSpec(...; calls=...) | object criteria plus process and application |
OutputTo(...) in ModelSpec(...; outputs_to=...) | object criteria only |
object queries and OutputRequest selectors | object criteria only |
Unsupported or misspelled fields fail when the selector is constructed. Selections such as descendants or ancestors need a current object to start from. Use them in inputs, calls, or object/output queries with that context. They cannot select where an application runs through ModelSpec(...; on=...).
ModelSpec(...; every=period) sets how often an application runs.
HoldLast, Interpolate, Integrate, and Aggregate describe how an input uses results over time: keep the last value, interpolate, integrate, or combine values over an interval.
Environment(...) chooses where environmental data comes from and can map source variable names to the names a model expects.
Models list the environmental variables they read in environment_inputs_.
A model that tries changes to its environment passes trial values with run_call!(context, name; environment=trial_state). It stores the accepted values with commit_environment!.
OutputRequest(selector, variable; ...) selects results to save and can resample them at a chosen interval. It uses the same selectors as object queries.
objects_from_mtg and CompositeModel(mtg; ...) create registered objects from a MultiScaleTreeGraph (MTG).
add_organ! creates and initializes a new organ when the model uses an MTG.
runtime_model(context) gives a model's run! function access to the running CompositeModel, for example to add or remove objects.
object_id(context), model_object(context), model_status(context), and source_node(context) return the current object's identifier, object, status, and source MTG node, respectively. model_status returns the main status stored for that object. The status argument passed to run! is instead the view of variables used by that particular application.
register_object!, remove_object!, and reparent_object! change the objects and their parent relationships.
move_object! and update_geometry! change position or geometry.
These functions mark affected connections for updating. Changes to object relationships are processed after the application that made them, so new objects can run applications still remaining in the same time step. Changes made between steps are processed before the next step.
Initializer lets a model initialize an object it has just created, using an application that already ran on existing objects. Its restrictions are detailed below.
run!(model; steps=..., outputs=:none) starts a fresh simulation history and returns a Simulation.
continue!(simulation; steps=...) and step!(simulation) advance an existing simulation, preserving the values needed for time-based inputs.
current_step(simulation) reports the latest completed time step.
final_state(simulation) returns a snapshot of the latest values even when output history was not saved. Pass an object id or selector for multi-object simulations.
collect_outputs(sim) gathers saved results into rows for analysis.
Initializing objects during growth. An Initializer application runs before the model that creates new objects. Models that directly read the new values run after the creator in the same step. The following restrictions keep those new values consistent with the rest of the simulation:
run_initializer! accepts exactly one target from the current creation event, and that event must only add objects. It rejects repeated initialization, existing or reparented objects, and ordinary manual-call bindings. It also rejects cases that require a full connection rebuild instead of the update limited to the new objects.
Each initialized output must have exactly one possible application writing its main stored value. This check counts both local outputs and outputs written to other objects.
Initialization adds no output sample partway through a step. Models that read the new values directly can use them in that step, but PlantSimEngine rejects downstream time-based inputs that could read a newly created object's output. The initializer itself can still use a PreviousTimeStep input.
Use the Diagnostics namespace instead of inspecting internals:
Diagnostics.explain_objects
Diagnostics.explain_instances
Diagnostics.explain_scopes
Diagnostics.explain_applications
Diagnostics.explain_bindings
Diagnostics.explain_calls
Diagnostics.explain_output_bindings
Diagnostics.explain_environment_bindings
Diagnostics.explain_schedule
Diagnostics.explain_writers
Diagnostics.explain_execution_plan
Diagnostics.explain_output_retention
Diagnostics.explain_outputs
Diagnostics.explain_initialization
Diagnostics.input_carrier, Diagnostics.input_value, and Diagnostics.has_reference_carrier
Diagnostics.object_address
See Migrating To The CompositeModel/Object API for translations from removed APIs.
Use Authoring to find models and inspect their definitions. Its reports can also be read by tools such as an AI coding agent:
Authoring.available_processes() and Authoring.available_models(...) find models in loaded Julia modules;
Authoring.describe_model(instance) reports the model's process, current parameter values, inputs and outputs, variable contracts, input connections and calls, execution settings, descriptive information, and source location. Its nested field_provenance explains where each fact came from: the actual model, an explicit declaration, or information inferred from the source. The origins of constructor fields, defaults, and methods are reported separately;
Authoring.describe_model(ModelType) reports whatever can be determined from the type. If it has no constructor that can be called without arguments, the report is incomplete. It never invents parameter values or creates a dummy instance;
current values from Authoring.describe_model(instance) stay in parameters; they are never relabeled as constructor defaults. Defaults are inspected from a type description only when a constructor can actually be called without arguments;
Authoring.model_interface(instance) returns the declarations checked when replacing a model with Override for an object or plant instance;
Authoring.model_interface(ModelType) attempts the same inspection using a constructor with no arguments. If no such constructor exists, it raises ArgumentError rather than inventing parameter values;
Authoring.compare_models(a, b) reports whether two models share a process, whether one can directly replace the other, and what settings would need to change. requires_binding_changes reports differences in inputs, outputs, variable contracts, dependencies, output policies, or environmental connections. requires_reconfiguration covers every difference preventing a direct override. Each reported difference records its path, kind, values, affects_override, and affects_bindings;
Authoring.validate_model(model; strict=false) checks declarations without running the equation. Strict mode requires a complete VariableContract for every declared input and output, including environmental variables;
Authoring.validate_scenario(model; strict=false) checks the simulation setup. If it is incomplete, the result still includes a partial report and diagnostic information;
Authoring.to_dict(report) and Authoring.to_json(report) convert reports to dictionaries or JSON using a versioned format. They do not include internal compiler objects;
Authoring.scenario_source(model; environments=...) reconstructs readable, editable Julia simulation setup code. Pass named environment values through environments so the generated code can refer to them explicitly;
Authoring.compiled_model_source(model_or_simulation) produces readable, executable Julia code showing the chosen application order, selected objects, input sources, model calls, and calculation functions;
Authoring.write_compiled_model_source(path, value) writes that view explicitly.
These functions check the declared models and their connections. They do not guess units, assumptions, references, valid use conditions, or whether two equations are scientifically equivalent. The generated execution code uses the normal runtime; it does not create a separate way to schedule the models.
Use scenario_source to edit or save the simulation setup in version control. Use compiled_model_source to inspect the calculations and connections PlantSimEngine prepared from that setup.
A model package can implement Authoring.model_metadata(model) to supply a summary, hypothesis, references, and development or validation status. It can implement Authoring.parameter_metadata(model) to describe each parameter's meaning, units, valid range, defaults, references, or other constraints.
GraphEditor.model_graph_view(model; level=:applications) returns graph data describing the models and their connections.
GraphEditor.model_graph_view_json(model) converts the same graph data used by the browser to JSON.
GraphEditor.write_model_graph_view(path, model) writes a self-contained static viewer.
GraphEditor.edit_graph(model; templates=..., environments=...) starts the optional HTTP editor after using HTTP. The editor uses the templates and environment values supplied from Julia.
GraphEditor.current_model(session), GraphEditor.undo!(session), GraphEditor.redo!(session), and close(session) control an interactive session from Julia.
See Visualize And Edit A CompositeModel for the runnable workflow, finding models, previewing selected objects, handling dependency cycles, and embedding a graph in Documenter pages.
Packages that provide environmental data implement functions under EnvironmentAPI, including EnvironmentAPI.AbstractEnvironmentBackend, EnvironmentAPI.bind_environment, EnvironmentAPI.sample, EnvironmentAPI.commit_environment!, and EnvironmentAPI.update_index!. The root-level commit_environment! is the function a model calls to store accepted environmental values after a trial calculation.
Parameter fitting and evaluation metrics are available under Evaluation: Evaluation.fit, Evaluation.RMSE, Evaluation.NRMSE, Evaluation.EF, and Evaluation.dr. PlantMeteo reducers are accessed from PlantMeteo directly rather than being re-exported by PlantSimEngine.
PlantSimEngine.AdvancedQualified compiler, cache, and low-level runtime extension APIs. These symbols are available for diagnostics, package integration, and compiler development, but are intentionally not part of the default user namespace.
sourcePlantSimEngine.Advanced contains the compiler's data structures and functions for preparing connections and refreshing cached information. Use it when integrating a package or developing the compiler and its diagnostics. For ordinary simulations, use Diagnostics.explain_* with a CompositeModel to inspect the setup.
Examples include Advanced.compile_composite_model, Advanced.refresh_bindings!, and the Advanced.CompiledCompositeModel family. These qualified APIs may evolve more quickly than the default modeling interface.
PlantSimEngine.AdvancedPlantSimEngine.AuthoringPlantSimEngine.DiagnosticsPlantSimEngine.EnvironmentAPIPlantSimEngine.EvaluationPlantSimEngine.GraphEditorPlantSimEngine.AbstractModelPlantSimEngine.AggregatePlantSimEngine.BoundManyPlantSimEngine.CallPlantSimEngine.CallTargetPlantSimEngine.CallTargetsPlantSimEngine.ClockSpecPlantSimEngine.CompositeModelPlantSimEngine.CompositeModelPlantSimEngine.CompositeModelPlantSimEngine.CompositeModelPlantSimEngine.CompositeModelTemplatePlantSimEngine.DefaultPlantSimEngine.HoldLastPlantSimEngine.InitializerPlantSimEngine.IntegratePlantSimEngine.InterpolatePlantSimEngine.ModelSpecPlantSimEngine.ObjectPlantSimEngine.ObjectIdPlantSimEngine.ObjectInstancePlantSimEngine.OutputRequestPlantSimEngine.OutputTargetsPlantSimEngine.OutputToPlantSimEngine.OverridePlantSimEngine.PreviousTimeStepPlantSimEngine.RequiredPlantSimEngine.RunContextPlantSimEngine.SelfPlantSimEngine.SimulationPlantSimEngine.StatusPlantSimEngine.UpdatesPlantSimEngine.VariableContractPlantSimEngine.EnvironmentPlantSimEngine.add_organ!PlantSimEngine.application_namePlantSimEngine.applies_toPlantSimEngine.assign_outputs!PlantSimEngine.assign_outputs!PlantSimEngine.bound_inputPlantSimEngine.call_targetsPlantSimEngine.commit_environment!PlantSimEngine.commit_environment!PlantSimEngine.continue!PlantSimEngine.depPlantSimEngine.environment_bindingsPlantSimEngine.environment_configPlantSimEngine.environment_inputsPlantSimEngine.environment_outputsPlantSimEngine.environment_windowPlantSimEngine.final_statePlantSimEngine.init_variablesPlantSimEngine.inputsPlantSimEngine.model_callsPlantSimEngine.model_objectPlantSimEngine.model_statusPlantSimEngine.object_idPlantSimEngine.object_idPlantSimEngine.object_idsPlantSimEngine.objects_from_mtgPlantSimEngine.output_policyPlantSimEngine.output_routingPlantSimEngine.output_targetsPlantSimEngine.outputsPlantSimEngine.outputs_toPlantSimEngine.register_object!PlantSimEngine.run!PlantSimEngine.run_call!PlantSimEngine.run_initializer!PlantSimEngine.runtime_modelPlantSimEngine.source_nodePlantSimEngine.step!PlantSimEngine.timespecPlantSimEngine.updatesPlantSimEngine.validate_environment_inputsPlantSimEngine.validate_environment_inputsPlantSimEngine.value_inputsPlantSimEngine.variable_contractsPlantSimEngine.variablesPlantSimEngine.variablesPlantSimEngine.@processAbstract model type. All models are subtypes of this one.
sourceReduce a producer window, using a mean by default.
sourceBoundMany <: AbstractVectorIdentity-aware, reference-backed view of a compiled Many input.
Use bound_input inside a model kernel to obtain this view. Ordinary positional indexing, iteration, broadcasting, and mutation operate on the same carrier already installed in the model status. object_ids returns the aligned, read-only object identities without copying them. Wrap an identity in ObjectId for unambiguous identity-based indexing; integer indexing remains positional.
Both aligned carriers use the one-based indexing contract of PlantSimEngine's compiled input storage.
sourceCall(selector::AbstractObjectMultiplicity)
Call(; kwargs...)Declare the target selector for a manual hard call. Applications reached only through Call are removed from the root schedule and execute when their owner invokes run_call! or call_targets.
CallTargetOne resolved executable target of a declared hard call. Obtain targets from a CallTargets collection with call_targets, then pass an individual target to run_call!(::CallTarget). A target is a runtime view owned by its compiled simulation; construct hard-call relationships with ModelSpec(...; calls=...) rather than constructing this type directly.
CallTargets <: AbstractVector{CallTarget}A cached vector-like view of the compiled targets for one declared hard call. Retrieving it does not allocate a replacement collection. Obtain it with call_targets or as the result of run_call!(::RunContext, ::Symbol).
CompositeModel(model::AbstractModel, models::AbstractModel...;
status=NamedTuple(), id=:scene, scale=:Scene, kind=nothing,
name=id, environment=nothing, timestep=nothing,
type_promotion=nothing, status_transform=nothing)Construct a concise one-object simulation. This is syntax lowering only: it creates one ordinary Object, one normal ModelSpec per model, and a regular CompositeModel. The returned model therefore uses the same compiler, scheduler, diagnostics, lifecycle, and output system as explicitly assembled composite models.
Use explicit Object, ModelSpec, and selector construction when applications need names, different cadences, explicit coupling, or different target sets. Use timestep to apply one common cadence to every supplied model.
CompositeModel(template::CompositeModelTemplate;
root, objects=(), name=nothing, overrides=NamedTuple(),
object_overrides=(), applications=(), environment=nothing,
type_promotion=nothing, status_transform=nothing)Build an executable composite model from a reusable template mounted on one concrete object subtree. root may be the owned root Object, or its id when the root is included in objects. When name is omitted, it is inferred from the root object's name or id.
This constructor is syntax lowering for an ObjectInstance passed to the regular CompositeModel constructor. Use explicit ObjectInstance values when the same composite model contains several mounted templates.
CompositeModel(root::MultiScaleTreeGraph.Node; applications=(), instances=(),
environment=nothing, type_promotion=nothing, status_transform=nothing,
id=node_id, scale=symbol, status=..., ...)Build a unified model directly from an MTG subtree. The MTG accessors are retained and reused by add_organ! when the topology grows.
CompositeModel(items...; applications=(), instances=(), environment=nothing,
type_promotion=nothing, status_transform=nothing)Create a model from Object and ObjectInstance values. Global applications and applications mounted from object instances are compiled through the same composite-model/object dependency graph.
type_promotion maps source value types to target types for one-time status materialization. status_transform(variable, value) optionally handles variable-specific cases first; the mapping is applied to its returned value. Numeric Array values are mapped element by element while arbitrary user structs are left untouched. The policy applies to statuses registered later, but not to model parameters or environment values.
CompositeModelTemplate(applications=(); kind=nothing, species=nothing, parameters=NamedTuple())Reusable model-application bundle for one kind of model object, such as a plant species. Each mounted ObjectInstance scopes unqualified ModelSpec(...; on=...) selectors to its own object subtree. Model objects are shared between instances unless an instance supplies an override.
parameters stores template-level metadata. Parameter-field merging is not implicit: use an instance model override when model parameters differ.
Default(value)Declare a status input with a genuine model-provided default. The compiler adds value to a target object's status only when the variable is neither supplied by the user nor already present.
Use the latest available producer value.
sourceInitializer(selector::One)
Initializer(; application, ...)Declare that a normally scheduled application may also initialize one newly registered object during the lifecycle event that created it. Initializers are executed with run_initializer!; unlike Call targets, their callee application remains in the root schedule and retains canonical writer ownership.
Reduce a producer window, using a sum by default.
sourceInterpolate(; mode=:linear, extrapolation=:linear)Interpolate a slower producer stream. Both mode and extrapolation accept :linear or :hold.
ModelSpec(model; name=nothing, on=nothing, inputs=NamedTuple(),
calls=NamedTuple(), outputs_to=NamedTuple(),
environment=nothing, every=nothing,
environment_bindings=NamedTuple(), environment_window=nothing,
output_routing=NamedTuple(), updates=())Configuration for one model application in a CompositeModel.
ModelSpec is the single scenario-construction form. on selects the target objects, inputs and calls declare coupling, and a calls entry may wrap its selector in Initializer for targeted newborn initialization. outputs_to declares status outputs owned by this application but stored on selected destination objects, every selects application cadence, and environment accepts an Environment configuration. Output routing and intentional duplicate-writer ordering are declared directly with output_routing and updates.
Example
ModelSpec(
model;
name=:leaf_energy,
on=Many(scale=:Leaf),
inputs=(:soil_water => One(scale=:Soil, var=:water),),
calls=(:stomata => One(within=Self(), application=:stomata),),
every=Dates.Hour(1),
environment=Environment(provider=:canopy),
output_routing=(temperature=:stream_only,),
updates=Updates(:temperature; after=:radiation),
)Object(id; scale=nothing, kind=nothing, species=nothing, name=nothing,
parent=nothing, children=ObjectId[], geometry=nothing,
status=nothing, applications=())One simulated entity, such as a canopy, plant, leaf, or soil volume. id is its stable ObjectId. Labels such as scale, kind, and name let selectors choose where models run; parent and child IDs describe topology. The scenario chooses these labels and relationships.
Supply initial model values with Status. Models can share fixed parameters while each object owns its changing state. geometry may hold the spatial information needed by an environment provider or visualization.
Pass objects to CompositeModel with their model applications. For changes during a simulation, use register_object!, remove_object!, and reparent_object! so the runtime can refresh affected connections.
ObjectId(value)Stable identity of one Object in a CompositeModel. Strings are normalized to symbols; an existing ObjectId is returned unchanged.
ObjectInstance(name, template; root, objects=(), overrides=NamedTuple(), object_overrides=())Mount a CompositeModelTemplate on one concrete object subtree.
root may be an Object owned by the instance or the id of an object supplied separately to CompositeModel. objects contains additional owned descendants. overrides maps one template application name to a replacement model implementing the same process. object_overrides contains Override entries for exceptional organs.
OutputRequest(selector, var; name=var, application=nothing, context=nothing,
policy=HoldLast(), clock=nothing)
OutputRequest(scale, var; kwargs...)Request retention and optional resampling of one model output stream.
The first form accepts the same One, OptionalOne, or Many selector used by ModelSpec selectors/bindings and object queries. Passing a scale is a convenience for Many(scale=scale).
OutputTargetsIdentity-aware, columnar destination view for one named OutputTo group. Obtain it inside a model kernel with output_targets, inspect its stable identities with object_ids, and assign identified result tables with assign_outputs!.
Destination carriers are exposed explicitly as targets.columns.<variable>.
The view is valid for the current model invocation and lifecycle generation. Do not retain it across a lifecycle barrier.
sourceOutputTo(selector; vars, coverage=:exact)Declare status outputs computed by one model application and stored on other objects selected by selector.
vars is a non-empty NamedTuple whose values are Required or Default declarations. coverage=:exact requires the application to publish every declared variable for every selected destination; no partial coverage policy is currently supported.
Example
OutputTo(
Many(scale=(:Leaf, :Internode), within=SceneScope());
vars=(
incident_par=Default(0.0),
absorbed_par=Required(Float64),
),
)Override(; object, application, model)Replace one template model application on one exceptional object. Select the template application by its application name. The replacement must implement the same process and variable contract.
sourcePreviousTimeStep(variable)A structure to flag a model input as using the value computed on the previous model timestep. This breaks same-timestep coupling cycles. The value can be initialized in the Status if needed.
sourceRequired(T)Declare a status input that must be supplied by object state or resolved from another model application.
T is an expected type, not an initialization value. It may be abstract or parametric so model declarations remain generic.
RunContextRuntime context passed as the final argument to model kernels. Use runtime_model, bound_input, output_targets, call_targets, and run_call! instead of inspecting its fields.
Self()Select the current object: the object on which the consuming model application runs. Self() means a plant only when that object is itself the plant.
SimulationResult of running a CompositeModel. Use outputs, collect_outputs, final_state, and PlantSimEngine.Diagnostics to inspect it.
Status(vars)Status type used to store the values of the variables during simulation. It is mainly used as the structure to store the variables in the TimeStepRow of a TimeStepTable (see PlantMeteo.jl docs).
Most of the code is taken from MasonProtter/MutableNamedTuples.jl, so Status is a MutableNamedTuples with a few modifications, so in essence, it is a stuct that stores a NamedTuple of the references to the values of the variables, which makes it mutable.
Examples
A leaf with one value for all variables will make a status with one time step:
julia> using PlantSimEnginejulia> st = PlantSimEngine.Status(Ra_SW_f=13.747, sky_fraction=1.0, d=0.03, aPPFD=1500.0);All these indexing methods are valid:
julia> st[:Ra_SW_f]
13.747julia> st.Ra_SW_f
13.747julia> st[1]
13.747Setting a Status variable is very easy:
julia> st[:Ra_SW_f] = 20.0
20.0julia> st.Ra_SW_f = 21.0
21.0julia> st[1] = 22.0
22.0Updates(vars...; after=nothing)Scenario-level declaration that a model updates variables which may also be computed by another model at the same scale.
after contains canonical application identifiers. It is intentionally scenario-level metadata: the model implementation stays reusable, while the simulation setup can declare ordering constraints that only exist in this coupling.
VariableContract(; unit, basis=nothing, temporal=nothing,
aggregation=nothing, extent=nothing)Scientific meaning attached to a model variable without wrapping its runtime numeric value.
unit names the physical numerator unit, for example :mol_photon.
basis names the normalization basis, for example :leaf_area, :ground_area, or :plant.
temporal names the time basis, for example :second, :day, or :step.
aggregation distinguishes values such as :instantaneous, :rate, :mean, :total, and :accumulated.
extent distinguishes :intensive and :extensive quantities when useful.
Tokens are intentionally open Symbols so packages can extend the vocabulary. The compiler compares complete contracts exactly; two connected model variables must therefore use the same tokens. Runtime payloads remain ordinary numbers or arrays.
add_organ!(parent, runtime, link, symbol, scale; index=0, id, attributes=(),
initial_status=(), use_status_adapter=true, kind=nothing,
species=nothing, name=nothing)Create an MTG node and register its corresponding model object as one operation. runtime may be a CompositeModel, RunContext, or Simulation. The model reuses the MTG accessors and status initializer supplied when it was constructed, then overlays initial_status.
Set use_status_adapter=false only when the caller guarantees that the new node's attributes and initial_status completely define its initial status. In that mode the configured MTG status accessor is not called for the new node. The node status field is always forced to the newly created node.
This is the public growth API. register_object! remains the low-level registry operation for callers that already own a fully initialized Object.
application_name(spec::ModelSpec)Optional stable name for one model application in the unified composite-model/object API.
sourceapplies_to(spec::ModelSpec)Object selector where a model application runs in the unified composite-model/object API.
sourceassign_outputs!(targets::OutputTargets, ids, columns::NamedTuple)Lower-level identified-column assignment. columns must contain every output declared by targets; its extra fields are ignored. This overload avoids a Tables.jl adapter on the stable columnar path.
assign_outputs!(targets::OutputTargets, table; id=:object_id)Assign every declared output column from an identified Tables.jl-compatible table. Destination coverage is exact: unknown, duplicate, extra, and missing IDs are rejected before any status is changed. Additional table metadata columns are ignored, while every output declared by the target group is required.
Result columns must not alias output destination storage, except for a direct self-assignment in exact destination order. Custom array types that wrap shared storage must implement Julia's Base.dataids/Base.mightalias contract so aliasing can be rejected before any status is changed.
The row permutation is cached by identity of the ID column. Reusing that column promises that its IDs and order remain unchanged; replace the ID column object when either changes.
sourcecall_targets(context::RunContext, name::Symbol; objects=nothing)Return a cached, non-executing vector-like view of the targets declared for name with ModelSpec(...; calls=...). The collection is empty for an unresolved OptionalOne, has one element for One, and contains every resolved target for Many.
When objects is provided, resolve the declared call against the current object topology and restrict the result to those objects. This explicit form is intended for models that create objects and immediately initialize selected manual-call-only applications on them. Structural refresh still occurs at the safe barrier after a pure-addition event. If the pending event also removes or reparents objects, this accessor performs a full binding/environment refresh before resolving the explicit targets. Use Initializer and run_initializer! instead when the target application must remain in the normal schedule. Each requested object may be an ObjectId, Object, an MTG node, or the Status returned by add_organ!.
Use this accessor with run_call!(::CallTarget) when targets need different sampled environments, selective execution, a controlled order, or separate trial and accepted publication.
commit_environment!(backend, handle, state, time)Commit an accepted environment state through an opaque compiled backend handle. Model kernels call commit_environment!(context, state); backend authors implement this method for their concrete mutable environment.
commit_environment!(context::RunContext, state)Commit an accepted environment state through the opaque handle compiled for the currently running model application/object. The model must declare the variables it commits with environment_outputs_.
Commits are ignored while the current model is executing as a non-publishing hard call. Trial environment states should instead be passed to run_call!(context, name; environment=state).
continue!(simulation; steps=1)Advance an existing Simulation without resetting its timeline, retained streams, temporal dependency history, or environment position.
dep(model)Return model-level default Input(...), Call(...), and Initializer(...) declarations. Models without explicit coupling requirements return an empty NamedTuple.
environment_bindings(spec::ModelSpec)Optional explicit weather aggregation bindings used by the model runtime. Each key is the target environment variable exposed to the model at execution time. Each value can be:
PlantMeteo reducer instance/type (e.g. MeanWeighted(), MaxReducer)
Function: custom reducer callable
NamedTuple: optional fields source and reducer
environment_config(spec::ModelSpec)Optional composite-model/object environment configuration declared with ModelSpec(...; environment=Environment(...)).
environment_inputs(model::AbstractModel)
environment_inputs_(model::AbstractModel)Environment variables read directly by a model.
This trait is separate from inputs_ because meteorology may be constant, table-backed, or produced by a microclimate backend. The default is empty.
environment_outputs(model::AbstractModel)
environment_outputs_(model::AbstractModel)Environment variables that a controller model is allowed to commit with commit_environment!, for example local microclimate variables computed over a canopy, voxel, or octree backend.
These declarations are environment capabilities, not status outputs. Declare diagnostic status values separately with outputs_.
environment_window(spec::ModelSpec)Optional weather window-selection strategy used by the model runtime. Defaults to nothing (runtime falls back to PlantMeteo.RollingWindow() behavior).
final_state(simulation)
final_state(simulation, object_id)
final_state(simulation, selector; context=nothing)Return a NamedTuple snapshot of the latest canonical object status. The no-selector form requires the simulation to contain exactly one object. One returns one snapshot, OptionalOne returns one snapshot or nothing, and Many returns a dictionary from object ids to snapshots.
This accessor reports final state, independently of output retention. Use collect_outputs for retained history.
init_variables(model)Return the merged genuine input defaults and initial output-state values declared by model. Inputs declared with Required(T) are omitted.
inputs(model::AbstractModel)
inputs(...)Get the inputs of one or several models.
Returns an empty tuple by default for AbstractModels (no inputs) or Missing models.
Examples
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
inputs(Process1Model(1.0))
# output
(:var1, :var2)model_calls(spec::ModelSpec)Unified composite-model/object call bindings declared with the ModelSpec(...; calls=...) keyword. Ordinary object selectors declare manual calls; Initializer wraps a selector for targeted newborn initialization while leaving the callee normally scheduled.
model_status(model::CompositeModel, source)Return the runtime Status, or nothing, owned by the model object represented by source. source accepts the same identities as object_id, including an exact MTG node. Runtime status belongs to the model registry and is not stored in MTG attributes.
object_id(model::CompositeModel, source) -> ObjectIdReturn the registered ObjectId represented by source.
source may be an ObjectId, a registered Object, a registered Status, an MTG node, or the raw value used to construct an ObjectId. For an MTG model, the id= accessor is evaluated during initial adaptation or organogenesis and the exact node-to-ObjectId association is retained; object_id does not reevaluate the accessor. Later changes to a node's attributes or raw MTG id therefore do not change its model identity. A node from a copied or foreign MTG is rejected even when its raw node id is identical.
Every result is checked against the live object registry. Removed or unknown objects therefore raise an error instead of returning a stale identity. RunContext, CallTarget, and Simulation delegate to their live model.
object_id(context::Union{RunContext,CallTarget})
model_object(context::Union{RunContext,CallTarget})
model_status(context::Union{RunContext,CallTarget})
source_node(context::Union{RunContext,CallTarget})Resolve the current execution target. model_status(context) returns the canonical registry Status, not the application-local status view passed to the kernel. source_node(context) is available for MTG-backed models and avoids requiring a topology node inside that local status view.
object_ids(values::BoundMany)Return the live, read-only ObjectId view aligned with values. The view is maintained by compiled lifecycle refresh and does not copy the identity vector.
objects_from_mtg(root; id=node_id, scale=symbol, kind=..., species=...,
name=..., geometry=..., status=...)Adapt one MTG subtree to model Object values. The MTG is traversed once; node ids and parent relations become stable model-object identities and relations. Accessors may attach labels and geometry without prescribing a plant architecture. Runtime status is not read from MTG attributes. Pass an explicit status= accessor only at a deliberate import boundary. This standalone projection retains no lifecycle identity index; construct CompositeModel(root) when later node resolution or organogenesis is required.
output_policy(model::AbstractModel)
output_policy(::Type{<:AbstractModel})Per-output scheduling policy for a model. Default is empty, meaning all outputs fallback to hold-last behaviour.
When multi-rate input bindings are inferred automatically, this trait also provides the default cross-clock policy (HoldLast, Integrate, Aggregate, or Interpolate) for each producer output.
output_routing(spec::ModelSpec)Per-output routing mode for multi-rate runs. Allowed values are:
:canonical (default): output participates in canonical status publication.
:stream_only: output is only tracked in temporal streams.
output_targets(context::RunContext, group)Return the compiled OutputTargets view for the named outputs_to group on the application currently executing. The lookup is a typed field access; selectors and destination indexes were resolved before the kernel.
outputs(model::AbstractModel)
outputs(...)Get the outputs of one or several models.
Returns an empty tuple by default for AbstractModels (no outputs) or Missing models.
Examples
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
outputs(Process1Model(1.0))
# output
(:var3,)outputs_to(spec::ModelSpec)Named distributed-output destinations declared with the ModelSpec(...; outputs_to=...) keyword.
register_object!(model, object; parent=object.parent)Register a fully initialized Object in model. Structural bindings are marked dirty and become visible to execution after the next lifecycle refresh boundary. Prefer add_organ! for MTG-backed growth.
run!(model; steps=1, constants=Constants(), outputs=:none, performance=false)Run a fresh simulation timeline while mutating object status in model. Choose outputs=:none, outputs=:all, one OutputRequest, or a vector of requests. Use continue! on the returned Simulation to advance without resetting time. Set performance=true to record coarse compiler/runtime timing and work counters for diagnostics and performance regression tests.
run_call!(context::RunContext, name::Symbol;
environment, sampled_environment, publish=false)Execute every target of the hard call declared as name and return its CallTargets collection. The return shape is always vector-like: One produces one element, OptionalOne zero or one, and Many zero or more. Initializer bindings are rejected here; execute those with run_initializer!.
When environment is supplied, every target keeps its own opaque compiled backend handle and samples that transient backend-specific state. The state is inherited by nested hard calls. Omit it to sample the committed backend state.
When sampled_environment is supplied, that already sampled model-facing value is forwarded directly to every target through the cached typed execution path. It is not resampled and is not inherited as a backend state by nested calls. environment and sampled_environment are mutually exclusive.
For finer-grained target selection, order, or direct per-target sampled environments, use call_targets and run_call!(::CallTarget). Commit an accepted mutable state with commit_environment! before publishing the accepted descendants.
run_initializer!(context::RunContext, name::Symbol, object)Run the application declared by name=Initializer(...) exactly once on one object registered during the current lifecycle event. The target application remains normally scheduled and retains canonical writer ownership. Its model mutates the newborn's canonical local status, but the targeted initializer does not publish an extra mid-step output sample or distributed/environment update. Consequently, only direct non-temporal downstream consumers may observe its newborn output in that step; downstream temporal consumers are rejected during scenario compilation.
The initializer target must use the caller's cadence and global environment, must not declare nested calls, distributed outputs, or stream-only outputs, and must be the sole potential canonical writer of each initialized output. It may use PreviousTimeStep as its only temporal input policy. The initialized object's canonical Status is returned. Calling the same initializer again for that application/object pair, or passing an existing or reparented object, is an error. The application/object pair is reserved before model code runs, so a failed attempt remains marked and cannot be retried in the same lifecycle event after an unknown partial mutation.
runtime_model(runtime)Return the live CompositeModel owned by a CompositeModel, RunContext, or Simulation. Lifecycle-capable models should call this accessor instead of reaching through runtime implementation fields.
source_node(model::CompositeModel, source) -> MultiScaleTreeGraph.NodeReturn the exact MTG node associated with a registered object identity. This is available only for a model constructed from an MTG; copied, foreign, and removed identities are rejected.
sourcetimespec(model::AbstractModel)
timespec(::Type{<:AbstractModel})Clock definition for a model. Default is single-rate behaviour (dt=1.0, phase=0.0).
updates(spec::ModelSpec)Scenario-level metadata for variables intentionally updated by this model after another producer on the same object.
sourcevalidate_environment_inputs(model_specs, environment)Validate declared environment_inputs_ against the available environment fields.
The check is intentionally field-based and independent from units/backends. When Environment source bindings remap a declared model input from another variable; the source variable is checked on the raw environment object.
sourcevalidate_environment_inputs(model::CompositeModel)
validate_environment_inputs(compiled::CompiledCompositeModel)
validate_environment_inputs(compiled::CompiledCompositeModel, environment_or_backend)Validate declared composite-model/object environment_inputs_.
The one-argument methods validate each application against its actual compiled environment backend, including application-level Environment(...) overrides. The two-argument method validates every compiled application against an explicit replacement environment backend. Diagnostics report model application ids, so several applications for the same process can be diagnosed independently.
value_inputs(spec::ModelSpec)Unified composite-model/object value-input bindings declared with the ModelSpec(...; inputs=...) keyword.
variable_contracts(model)Return the structurally validated variable-contract declaration for model. Compilation additionally checks each key against the concrete ModelSpec, including its distributed output declarations.
variables(pkg::Module)Returns a dataframe of all variables, their description and units in a package that has PlantSimEngine as a dependency (if implemented by the authors).
Note to developers
Developers of a package that depends on PlantSimEngine should put a csv file in "data/variables.csv", then this file will be returned by the function.
Examples
Here is an example with the PlantBiophysics package:
#] add PlantBiophysics
using PlantBiophysics
variables(PlantBiophysics)variables(model)
variables(model, models...)Return the values PlantSimEngine can initialize without user input: genuine input defaults declared with Default(value) and initial output-state values. Required inputs have no initialization value and are therefore omitted.
Note
Each model can (and should) have a method for this function.
using PlantSimEngine;
# Load the dummy models given as example in the package:
using PlantSimEngine.Examples;
variables(Process1Model(1.0))
variables(Process1Model(1.0), Process2Model())
# output
(var3 = -Inf, var4 = -Inf, var5 = -Inf)See also
inputs, outputs and variables_typed
@process(process::String, doc::String=""; verbose::Bool=true)This macro generates the abstract type and process identity required to simulate a process, together with its documentation. It also prints a short tutorial for implementing a model when verbose=true.
The abstract process type is then used as a supertype of all model implementations for the process, and is named "Abstract<ProcessName>Model", e.g. AbstractGrowthModel for a process called growth.
The first argument to @process is the new process name, the second is any additional documentation that should be added to the Abstract<ProcessName>Model type, and the third determines whether the short tutorial should be printed or not.
Newcomers are encouraged to use this macro because it explains what to do next. Defining the abstract type manually also requires an explicit process identity:
abstract type AbstractMy_New_ProcessModel <: AbstractModel end
PlantSimEngine.process_(::Type{AbstractMy_New_ProcessModel}) = :my_new_processExamples
@process "dummy_process" "This is a dummy process that shall not be used"PlantSimEngine.AuthoringStable model discovery, exact instance descriptions, interface comparison, and structured model/scenario validation for model authors and tooling.
sourcePlantSimEngine.DiagnosticsStructured compiler/runtime explanations and supported carrier inspection.
sourcePlantSimEngine.GraphEditorStatic graph DTOs, model discovery, graph edits, and interactive editor sessions.
sourcePlantSimEngine.EnvironmentAPIExtension protocol for global and spatial environment backends.
sourcePlantSimEngine.EvaluationGeneric model-fitting interface and simulation evaluation metrics.
source