Overview
A Model Node is a component that embeds another simulation model inside the current one. Instead of duplicating components across many models, you define a sub-model once (a production cell, an assembly line, a department) and embed it wherever you need it. Changes to the sub-model propagate everywhere it’s used. This is the architecture for complex facilities: model a single cell rigorously, compose a department from copies of that cell, compose a facility from copies of the department. Without hierarchical modeling, a complex operation means thousands of components on a single canvas. With it, you get a clean decomposition that mirrors how your operation is actually organized. If you’re new to building models, start with the Simulation overview — this page covers the composition layer that sits on top of the component basics.When to Use Model Nodes
- Repeated physical structure: your facility has five identical production cells. Model one, then embed it five times via Model Nodes.
- Reusable templates: standard sub-processes (QC inspection, packaging, kitting) that appear across many models. Define once, then reuse across every model in the factory that imports it.
- Scale: once a model passes ~50-100 components, hierarchical decomposition becomes the only way to keep it navigable.
- Team division: one person owns the line-level sub-model, another owns the facility-level composition. Model Nodes let them work in parallel.
How a Model Node Is Added
A Model Node places a separate model that already exists in the same factory. Adding one takes two steps: import the model, then drag it onto the canvas.- Import the sub-model. In the Modeler’s left panel, open the Overview view and find the Sub-models section. Click the + in the section header (or the Import Model row when nothing is imported yet) and pick one of the factory’s other models. It appears as a row in the section.
- Place it on the canvas. Open the Sub-model button on the canvas dock. It lists the models you’ve imported; drag one onto the canvas. A Model Node appears, referencing that sub-model.
Configuring a Model Node
A Model Node is a thin reference: the bulk of what it does lives in the sub-model. Select the node on the canvas to open its Properties:Mapped Connections
Input and output mappings are the wiring layer. When you draw a connection into a Model Node, ProDex asks which input in the sub-model should receive the items: you pick one of the sub-model’s Sources. When you draw a connection out of a Model Node, you pick which of the sub-model’s Sinks sends items to the downstream component. The Input Mappings and Output Mappings sections in the node’s properties show each mapping and let you change it. Validation enforces this: an input mapping must point at a Source inside the sub-model, and an output mapping at a Sink. Entities arriving through an input mapping enter the sub-model at that Source; entities reaching the mapped Sink cross back out to the parent. You can change the sub-model’s internal structure without breaking the parent: as long as the mapped Sources and Sinks still exist inside, the parent model keeps working.Shared Resources
A Resource can be shared between a parent and a sub-model with a Resource Mappings entry (Add Resource Mapping). Each entry pairs the resource the sub-model asks for with the parent resource it should draw from instead — the alias points from the sub-model’s internal resource outward to a parent resource. Unmapped resources stay local to the sub-model: if you don’t map it, the sub-model uses its own resource. The useful mental model is internal → outer, per instance: each mapping entry replaces one of the sub-model’s internal resources with a resource from the parent. Because the binding lives on the ModelNode instance (not on the sub-model), the same sub-model instantiated five times can bind each instance to a different parent resource — five cells sharing one maintenance crew, or each drawing from its own operator pool. Use shared resources for operators or equipment that physically serve multiple cells.Bridged Topics
Topics can be bridged across the parent/child boundary with Topic Mappings. Bridging is always bidirectional: there’s no per-direction configuration. An event emitted inside the sub-model on a bridged topic is received in the parent, and vice versa.In results data, a bridged emission appears as two rows in the topic-emission dataset — the originating emit plus the bridged delivery on the other side of the boundary. Don’t double-count when aggregating.
Aliased State Variables
State Variables can be aliased: a state variable declared at the parent level can be referenced from inside the sub-model under a different name with State Variable Mappings. The aliasing is bidirectional read/write: both sides see and write the same underlying value. An aliased nested variable delegates to the parent — in results datasets it emits no initial-value row of its own att=0; the parent variable is the single source of truth.
This keeps sub-models decoupled (they reference their own internal names) while still letting them participate in parent-level state.
Constants and Lookup Tables Don’t Need Mapping
Constants and lookup tables are factory-scoped. Both the parent and sub-model reference them independently: there’s no mapping for these on a Model Node. Each model adds the ones it uses in its own Constants and Lookup tables sections of the Overview, so parent and sub-model can use the same factory-level constant without coordinating.Qualified Component Paths
Components inside a Model Node are addressed by a qualified path using:: as the separator. A component with id lathe inside a ModelNode with id cell_a is referenced as cell_a::lathe anywhere outside its parent sub-model (the same form you’ll see in chart View source SQL and dataset columns).
All segments are component ids, not display names. The :: separator is reserved, so no single component id may contain it.
Component ids are namespaced per model: the same id can legitimately exist in both the parent and a sub-model (lathe at the top level and cell_a::lathe inside a cell are different components). The :: qualification is what disambiguates them.
This applies in three places:
- Chart View source queries. Component identifiers in
entity_movements,process_activity,resource_activity, and other dataframes use the qualified form — it’s what you’ll see incomponent_lookup.component_id,process_id,resource_id,variable_name, and thecomponent_idon event/hook rows. Filtering by display name as it appears in the Modeler (say,component = 'Lathe') misses every instance inside a Model Node. Usecomponent = 'cell_a::lathe', or match withLIKE. - Schedule material releases. A scheduled material release can target a Buffer inside a sub-model, but not a Source: a Source inside a sub-model receives entities through its parent Model Node, so a release aimed at it fails validation even though the Schedule Editor lists it. In the Schedule Editor’s component dropdown, components inside Model Nodes are listed after a divider, below the top-level ones.
- Cross-boundary references in DSL component-query functions: pass the qualified id when the component you’re querying lives in a sub-model.
plant_1::line_3::cell_2::lathe.
ModelNode Event System Support
Model Nodes can carry Event Listeners (topic-driven) and they work fully: listen for any topic in the parent or bridged into the sub-model, and runassign / emit / etc. like any other listener. Useful for Model Node-level coordination that isn’t tied to a single internal component. The Event Listeners section appears in the node’s properties once the model has at least one topic.
Model Nodes don’t support Event Hooks. A Model Node has no lifecycle events, so its properties show no Event Hooks section, and validation rejects any hook on a Model Node. To react to something happening inside the sub-model, put the hook on the component inside the sub-model and bridge a topic out.
Validation Recurses Across Boundaries
When you validate the parent model, validation recurses into every nested model referenced by a ModelNode. Errors inside a sub-model bubble up to the parent prefixed with the model-node path, so a broken expression incell_a’s lathe surfaces on the parent as an error attributed to the nested model rather than silently passing.
Fix the error in the sub-model; the parent re-validates automatically.
Live Reference, Not a Copy
A Model Node references its sub-model live. Any change you make to the sub-model is reflected immediately in every Model Node that references it: there’s no version pinning at the ModelNode level. If you need to keep a specific revision of a sub-model, ask Dexter to copy the sub-model before you change it.Authoring Model Nodes with Dexter
The visual editor isn’t the only entry point. Ask Dexter to compose a hierarchy for you: it can build the sub-models, place Model Nodes in the parent, and wire up their connection, resource, and topic mappings. The sub-model has to be saved in the factory before a Model Node can reference it. Dexter can also work on one subsystem at a time — slicing out a sub-model at any level of the hierarchy, changing it, and applying it back — so a facility-scale model stays editable piece by piece.Nested Models in Results Data
Hierarchy is flattened at simulation time, but the boundary crossings remain visible in the results dataframes:- Every boundary traversal logs four rows in
entity_movements: parent → nested source (synthetic), nested source → first internal component (real), last internal component → nested sink (real), and nested sink → parent (synthetic). Filterfrontier_synthetic = falsewhen you only want real hops — excluding the synthetic rows still leaves two rows where a flat model would log one. continued_to_parentlives onentity_lifecycle: on terminated rows at a nested output sink it distinguishes a real termination inside the sub-model from an entity crossing back out to the parent. FilterNOT COALESCE(continued_to_parent, FALSE)when counting real terminations. (Don’t conflate the two datasets — movement crossings are onentity_movements, lifecycle terminations onentity_lifecycle.)entity_idchanges at every Transformer, Combiner, and Separator. If a sub-model wraps a transformation, the entity that leaves is not the entity that entered — carry a stable business-identity attribute (an order number, a serial) if you need to trace a unit across the boundary.- Aliased state variables emit no
t=0initial-value row (they delegate to the parent variable), and bridged topics log two rows per emission (origin + bridged delivery).
Common Patterns
Cell template. A “production cell” sub-model is created once with canonical equipment. The facility model instantiates it five times via Model Nodes, each pointing at the same sub-model. Department composition. Build a “fabrication” model and an “assembly” model separately, then compose them in a facility model with connections between their outputs and inputs. Progressive detail. Start with a coarse facility model, a few Model Nodes, each representing a department as a black box. As you need more fidelity, add detail inside a department’s sub-model. The parent doesn’t change.Tips
- Treat sub-models as libraries. Once a sub-model is stable, avoid editing it casually: changes propagate to every instance.
- Name the sub-model’s Sources and Sinks clearly. They’re what you pick from when mapping connections; clear names prevent wiring mistakes.
- Keep the top level thin. A well-organized facility model is mostly Model Nodes with a little glue between them. If your top level has hundreds of direct components, you’re losing the benefit.
- All path segments are ids, not display names. When in doubt about an id, check the dataset columns: display names with spaces don’t appear in
::paths. - Nesting doesn’t hide provenance. Values marked Data, Stated, or Assumed in the Evidence panel keep that marking inside sub-models exactly as at the top level, and an assumptions review covers every nested model — a guess buried three levels deep still surfaces.
- Schedules reach inside. A material release can target a Buffer inside a sub-model.