Skip to content
Draft
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
21 changes: 16 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ API.
- `@process` defines the abstract process type.
- `process(model)` identifies the process.
- `inputs_(model)` and `outputs_(model)` declare status variables.
- In `outputs_`, `Distributed(Default(value))` or `Distributed(Required(T))`
declares an output on selected destination objects. Ordinary values stay local.
- `environment_inputs_(model)` and `environment_outputs_(model)` declare environment
variables.
- `dep(model)` optionally returns model-author defaults using `Input(...)` and
Expand All @@ -27,8 +29,9 @@ Read the current model's parameters directly from `model`. `context` is a

- `CompositeModel` owns a `ObjectRegistry`, model applications, instances, and an
environment.
- `Object` is one runtime entity with stable `ObjectId`, labels, parent,
geometry, and `Status`.
- `Object` is one runtime entity with a unique, stable `ObjectId`, optional
display `name`, group labels, parent, geometry, and `Status`. Display names
need not be unique and never select objects; the UI shows the ID by default.
- Plant architecture is not prescribed. Users choose scales and topology.
- `CompositeModelTemplate` and `ObjectInstance` reuse the same model definitions across
several plants or objects.
Expand All @@ -47,6 +50,10 @@ ModelSpec(model; name=:application, on=selector, inputs=(...), calls=(...), ever

- `on` selects where the model runs.
- `inputs` declares value dependencies.
- `outputs_to` is a tuple of anonymous `OutputTo(selector; vars=...)` entries.
One entry can omit `vars` to bind all distributed outputs; several entries
must explicitly partition their names. Kernels request columns with
`output_targets(context, (:variable,))`.
- `calls` declares manually executable hard dependencies.
- `Updates(:x; after=:producer)` orders intentional duplicate writers.
- `output_routing=(x=:stream_only,)` excludes an output from canonical
Expand All @@ -67,11 +74,15 @@ Scope and topology:
- `Subtree()`: the current object and its descendants
- `SelfPlant()`: the current plant instance/root
- `Ancestor(...)`
- `Scope(name)`
- `Scope(:instance_name)`: an instance's root and descendants
- `Scope(ObjectId(root_id))`: a root chosen directly by object ID and its descendants
- `Relation(...)`

Use keyword criteria for object labels: `kind=:plant`, `species=:oil_palm`,
`scale=:Leaf`, and `name=:leaf_1`.
Use `id=:leaf_1` to select a particular object. Preserve the ID's value type,
including numeric MTG IDs. Use keyword criteria for group labels:
`kind=:plant`, `species=:oil_palm`, and `scale=:Leaf`.
`ModelSpec.name` and `ObjectInstance.name` remain identifiers for applications
and instances; do not treat them as object display names.

`Self()` never means the model, species, or plant unless the current object is
itself that plant.
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

- Declare outputs written to selected objects inside `outputs_` with
`Distributed(Default(value))` or `Distributed(Required(T))`. These variables
are now visible when inspecting a model before building a scenario.
- `outputs_to` now takes an anonymous tuple of `OutputTo` entries. One entry
can omit `vars` to bind all distributed outputs; multiple entries must
explicitly partition variable names. Defaults and types belong to the model.
- Request destination columns with `output_targets(context, (:x, :y))`.
Missing, duplicate, and incompatible destination declarations fail before
initialization. Named output groups and schemas in `OutputTo.vars` were removed.

## v0.15.0

### Summary
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ ModelSpec(
This is the package API for multiscale, multi-plant, soil, microclimate, and
model-scale simulations.

An object needs a unique ID, for example `Object(:leaf_1)`. Select it with
`One(id=:leaf_1)`, or add `scale=:Leaf` to select it as part of
`Many(scale=:Leaf)`. An optional `name="Sunlit leaf"` changes its display
label; it does not change the object's identity or connections. Model
application names identify the configured uses of models.

## Installation

The examples on this development branch use `CompositeModel`. Registered
Expand Down
42 changes: 21 additions & 21 deletions benchmark/test-distributed-output-benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ struct DistributedOutputBenchmarkBoundInputModel <:
AbstractDistributed_Output_Benchmark_Bound_InputModel end
struct DistributedOutputBenchmarkStatusInputModel <:
AbstractDistributed_Output_Benchmark_Status_InputModel end
struct DistributedOutputBenchmarkSceneWriterModel <:
AbstractDistributed_Output_Benchmark_Scene_WriterModel end
struct DistributedOutputBenchmarkSceneWriterModel{D} <:
AbstractDistributed_Output_Benchmark_Scene_WriterModel
declarations::D
end
DistributedOutputBenchmarkSceneWriterModel() =
DistributedOutputBenchmarkSceneWriterModel(NamedTuple())
struct DistributedOutputBenchmarkAssignmentModel{T,I,C,M} <:
AbstractDistributed_Output_Benchmark_AssignmentModel
table::T
Expand Down Expand Up @@ -49,9 +53,12 @@ PlantSimEngine.outputs_(::DistributedOutputBenchmarkStatusInputModel) = (
total=0.0,
)
PlantSimEngine.inputs_(::DistributedOutputBenchmarkSceneWriterModel) = NamedTuple()
PlantSimEngine.outputs_(::DistributedOutputBenchmarkSceneWriterModel) = NamedTuple()
PlantSimEngine.outputs_(model::DistributedOutputBenchmarkSceneWriterModel) = model.declarations
PlantSimEngine.inputs_(::DistributedOutputBenchmarkAssignmentModel) = NamedTuple()
PlantSimEngine.outputs_(::DistributedOutputBenchmarkAssignmentModel) = NamedTuple()
PlantSimEngine.outputs_(model::DistributedOutputBenchmarkAssignmentModel) =
NamedTuple{keys(model.columns)}(map(values(model.columns)) do column
Distributed(Default(zero(eltype(column))))
end)
function PlantSimEngine.run!(
::DistributedOutputBenchmarkSceneWriterModel,
status,
Expand All @@ -69,7 +76,7 @@ function PlantSimEngine.run!(
constants,
context,
)
targets = PlantSimEngine.output_targets(context, :leaves)
targets = PlantSimEngine.output_targets(context, keys(model.columns))
benchmark_assign_outputs_api!(model.mode, targets, model)
return nothing
end
Expand Down Expand Up @@ -448,9 +455,6 @@ function setup_distributed_output_public_assignment_benchmark(
data.column_values.rank :
reverse(data.column_values.rank)
table = ncolumns == 1 ? base_table : (; base_table..., rank)
output_variables = ncolumns == 1 ?
(incident_par=Default(0.0),) :
(incident_par=Default(0.0), rank=Default(0))
objects = Object[Object(:scene; scale=:Scene)]
sizehint!(objects, nobjects + 1)
for (index, object_id) in enumerate(data.object_ids)
Expand Down Expand Up @@ -481,9 +485,8 @@ function setup_distributed_output_public_assignment_benchmark(
name=:scene_assignment,
on=One(scale=:Scene),
outputs_to=(
leaves=OutputTo(
Many(scale=:Leaf, within=SceneScope());
vars=output_variables,
OutputTo(
Many(scale=:Leaf, within=SceneScope()),
),
),
),
Expand All @@ -503,9 +506,6 @@ function setup_distributed_output_wide_assignment_benchmark(
columns = NamedTuple{names}(
ntuple(index -> fill(Float64(index), nobjects), ncolumns),
)
output_variables = NamedTuple{names}(
ntuple(_ -> Default(0.0), ncolumns),
)
objects = Object[Object(:scene; scale=:Scene)]
sizehint!(objects, nobjects + 1)
for object_id in data.object_ids
Expand All @@ -532,9 +532,8 @@ function setup_distributed_output_wide_assignment_benchmark(
name=:scene_wide_assignment,
on=One(scale=:Scene),
outputs_to=(
leaves=OutputTo(
Many(scale=:Leaf, within=SceneScope());
vars=output_variables,
OutputTo(
Many(scale=:Leaf, within=SceneScope()),
),
),
),
Expand Down Expand Up @@ -625,13 +624,14 @@ function setup_distributed_output_compilation_benchmark(
end
application = if distributed
ModelSpec(
DistributedOutputBenchmarkSceneWriterModel();
DistributedOutputBenchmarkSceneWriterModel((
incident_par=Distributed(Default(0.0)),
));
name=:scene_writer,
on=One(scale=:Scene),
outputs_to=(
leaves=OutputTo(
Many(scale=:Leaf, within=SceneScope());
vars=(incident_par=Default(0.0),),
OutputTo(
Many(scale=:Leaf, within=SceneScope()),
),
),
)
Expand Down
29 changes: 12 additions & 17 deletions benchmark/test-hard-call-path-benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ function setup_hard_call_path_benchmark(;
PlantSimEngine.Object(
Symbol(:leaf_, index);
scale=:Leaf,
name=Symbol(:leaf_, index),
parent=:scene,
)
for index in 1:nobjects
Expand All @@ -179,7 +178,7 @@ function setup_hard_call_path_benchmark(;
else
caller_selector =
usage == :sparse ?
One(name=:leaf_1) :
One(id=:leaf_1) :
Many(scale=:Leaf)
caller = ModelSpec(
BenchmarkCallControllerModel();
Expand Down Expand Up @@ -277,26 +276,25 @@ function setup_compiled_hard_call_benchmark(;
end
if kind == :sampled_environment
model = CompositeModel(
PlantSimEngine.Object(:scene; scale=:Scene, name=:scene),
PlantSimEngine.Object(:scene; scale=:Scene),
PlantSimEngine.Object(
:leaf_1;
scale=:Leaf,
name=:leaf_1,
parent=:scene,
);
applications=(
ModelSpec(
BenchmarkSampledEnvironmentSourceModel();
name=:source,
on=One(name=:leaf_1),
on=One(id=:leaf_1),
),
ModelSpec(
BenchmarkSampledEnvironmentControllerModel((T=30.0,));
name=:controller,
on=One(name=:scene),
on=One(id=:scene),
calls=(
:source => One(
name=:leaf_1,
id=:leaf_1,
application=:source,
),
),
Expand All @@ -307,14 +305,13 @@ function setup_compiled_hard_call_benchmark(;
return model, Int(steps)
end
leaf_count = kind == :many ? target_count : 1
objects = Any[PlantSimEngine.Object(:scene; scale=:Scene, name=:scene)]
objects = Any[PlantSimEngine.Object(:scene; scale=:Scene)]
if kind == :nested
push!(
objects,
PlantSimEngine.Object(
:middle;
scale=:Plant,
name=:middle,
parent=:scene,
),
)
Expand All @@ -325,7 +322,6 @@ function setup_compiled_hard_call_benchmark(;
PlantSimEngine.Object(
Symbol(:leaf_, index);
scale=:Leaf,
name=Symbol(:leaf_, index),
parent=kind == :nested ? :middle : :scene,
)
for index in 1:leaf_count
Expand All @@ -341,10 +337,10 @@ function setup_compiled_hard_call_benchmark(;
middle = ModelSpec(
BenchmarkBulkCallControllerModel(1, false, false);
name=:middle,
on=One(name=:middle),
on=One(id=:middle),
calls=(
:source => One(
name=:leaf_1,
id=:leaf_1,
within=Subtree(),
application=:source,
),
Expand All @@ -353,10 +349,10 @@ function setup_compiled_hard_call_benchmark(;
root = ModelSpec(
BenchmarkBulkCallControllerModel(1, false, true);
name=:root,
on=One(name=:scene),
on=One(id=:scene),
calls=(
:source => One(
name=:middle,
id=:middle,
within=Subtree(),
application=:middle,
),
Expand All @@ -366,7 +362,7 @@ function setup_compiled_hard_call_benchmark(;
else
selector = kind == :many ?
Many(scale=:Leaf, application=:source) :
One(name=:leaf_1, application=:source)
One(id=:leaf_1, application=:source)
effective_repeats = kind == :repeated ? repeats : 1
effective_publish = kind == :published ? true : publish
controller = ModelSpec(
Expand All @@ -376,7 +372,7 @@ function setup_compiled_hard_call_benchmark(;
true,
);
name=:controller,
on=One(name=:scene),
on=One(id=:scene),
calls=(:source => selector,),
)
applications = (source, controller)
Expand Down Expand Up @@ -505,7 +501,6 @@ function benchmark_lifecycle_event(simulation, new_index)
PlantSimEngine.Object(
new_id;
scale=:Leaf,
name=new_id,
parent=:scene,
),
)
Expand Down
9 changes: 4 additions & 5 deletions benchmark/test-immutable-scenario-benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,19 @@ function setup_many_cadence_schedule_benchmark(;
throw(ArgumentError("`napplications` must be positive."))
nsteps > 0 || throw(ArgumentError("`nsteps` must be positive."))
cadences = (1, 2, 3, 4, 6, 8, 12, 24)
object_names = Tuple(Symbol(:schedule_slot_, index) for index in 1:napplications)
object_ids = Tuple(Symbol(:schedule_slot_, index) for index in 1:napplications)
objects = Object[
Object(
object_name;
name=object_name,
object_id;
scale=:ScheduleSlot,
)
for object_name in object_names
for object_id in object_ids
]
applications = ModelSpec[
ModelSpec(
ImmutableScenarioBenchmarkSource();
name=Symbol(:scheduled_application_, index),
on=One(scale=:ScheduleSlot, name=object_names[index]),
on=One(scale=:ScheduleSlot, id=object_ids[index]),
every=Hour(cadences[mod1(index, length(cadences))]),
)
for index in 1:napplications
Expand Down
Loading
Loading