Model Hierarchy
Structure is optional but powerful: how to organize growing models with tasks and containers, and when a flat diagram is all you need.
A Modeloop model can be as simple as a single flat diagram: drop blocks, connect them, simulate. Nothing forces you to add structure, and small models are best left flat.
Real systems, though, grow. For those, Modeloop offers a hierarchy in which each level answers one question:
Project
└── Model ← what is this component?
└── Task ← when does it run?
└── Container ← how is it organized?
└── Blocks & signals ← what does it compute?
Every level is opt-in: you introduce it when the model needs it, not before.
Models
A model is the top-level unit of design — SensorsReading, SpeedController, ActuatorControl. Each model owns:
- a diagram — its complete internal logic;
- a public interface — the Model Input / Model Output signals it exchanges with the rest of the system.
Models are the unit of integration: they communicate only through their declared interface signals, never by reaching into each other’s internals. They are also the unit of code generation — each model becomes a self-contained module in generated Python and C.
A project can contain a single model — perfectly fine for a self-contained system — or many models composed into a larger architecture.
Use separate models for functionally cohesive components you would assign to a team, review as a unit, or deploy separately.
Tasks
A task is a scheduled unit of execution inside a model. Grouping logic into a task gives you explicit control over when it runs:
| Activation | Trigger | Typical use |
|---|---|---|
| Periodic | Fixed period (e.g. 10 ms) | Control laws, filters, signal conditioning |
| Event | Rising edge of a boolean control signal | Mode changes, fault reactions, on-demand computations |
See The Solver for scheduling semantics.
Each task also generates an independent function in code generation — a function you can integrate with an external scheduler or RTOS. This makes the task the natural unit of deployment: it maps one-to-one onto something the target system can call.
Use separate tasks when parts of a model need different rates (fast acquisition + slow control), different triggers (periodic control + event-driven mode logic), or independent integration on the target.
Containers
A container is a purely structural grouping: a named box with its own inner canvas, connected to its parent through Virtual Input / Virtual Output ports.
Containers have zero runtime cost and zero timing effect. Blocks execute in data-flow order across all container levels, as if the hierarchy were flattened. Containers exist for humans:
- they keep large diagrams readable,
- they name subfunctions (
PIDCore,Linearization,FaultDetection), - they nest to any depth,
- and they can be promoted to tasks later, when a subfunction turns out to need its own schedule.
That promotion path is the intended workflow: start structural, make it schedulable only when timing requires it.
Choosing the right level
| You need to… | Use |
|---|---|
| Build and simulate a small system | A flat diagram — no structure needed |
| Define a component with a public interface, owned by a team | Model |
| Execute logic at a specific rate or on an event | Task |
| Organize a diagram into readable, named subfunctions | Container |
| Turn a subfunction into an independently scheduled unit | Container → Task conversion |
Interfaces at every level
The hierarchy is only as strong as its boundaries. In Modeloop, data crosses a boundary exclusively through interface blocks:
- Model boundary — Model Input / Model Output: the model’s contract with the system.
- Container boundary — Virtual Input / Virtual Output: the sub-diagram’s contract with its parent.
There are no global variables and no implicit cross-references. If you can see a model’s diagram and its interface, you know everything it can possibly read or affect — which is what makes large models reviewable.
A worked structure
A typical motor control model:
Model: MotorControl
├── Inputs: target_speed_rps, measured_speed_rps, enable
├── Outputs: pwm_duty, fault_flag
│
├── Task: SpeedLoop (periodic, 1 ms)
│ ├── Container: ErrorShaping (sum, filtering)
│ ├── Container: PIDCore (gains, integral, anti-windup)
│ └── Container: OutputLimiting (saturation, rate limit)
│
└── Task: Supervision (periodic, 100 ms)
├── Container: PlausibilityChecks
└── State chart: FaultStateMachine
Fast control runs at 1 ms; supervision runs at 100 ms; each subfunction is a named container; the model’s contract is six signals. Every level answers a different question — what is this? (model), when does it run? (task), how is it organized? (container).