Modeling Guidelines
Conventions and best practices for models that stay readable, reviewable, and correct as they grow: structure, naming, types, feedback, and rates.
A model is a design document that happens to execute. These guidelines keep Modeloop models readable by colleagues, reviewable in change requests, and well-behaved in simulation and generated code. None of them are enforced by the tool — that is precisely why they are written down.
Structure
Give every level one job. Follow the hierarchy as designed: models for architecture, tasks for scheduling, containers for organization — and no structure at all when the system is small enough to stay flat. If a container exists “because the diagram got big”, it should still have a name that means something — TorqueArbitration, not Group3.
Keep diagrams shallow and narrow. A canvas a reviewer cannot understand in one look is a canvas that hides defects. As a rule of thumb: if one level of a diagram exceeds roughly 15–20 blocks, extract cohesive groups into named containers.
Let data flow left to right. Inputs on the left, outputs on the right, feedback returning below the main path. The solver does not care about layout; humans do.
Start structural, schedule late. Model subfunctions as containers first. Promote a container to a task only when it genuinely needs its own period or trigger. Every task adds a scheduling decision someone must review.
Naming
Name signals after meaning, with units. wheel_speed_rps, coolant_temp_degc, brake_request. Signal names become simulation topics, CSV columns, and identifiers in generated code — a good name pays off four times.
Name blocks when they carry design intent. A Sum block in an obvious spot needs no name; the integrator that holds your controller’s state deserves one (speed_error_integral).
Use enums for modes. A signal whose values mean “one of a small set of things” should be a user-defined enum type, never an integer convention documented only in someone’s head.
Parameters
No magic numbers. Any value you might tune — gains, limits, thresholds, physical constants — belongs in the data context and enters the model as a $reference. Inline literals are acceptable only for structural values (input counts, table sizes).
Define shared truths once. Physical constants and shared calibration live at workspace scope. Per-model copies of the same constant will drift apart.
Types
Be explicit at boundaries. Every Model Input and Model Output should declare its exact type deliberately — this is your component’s public contract. Default float64 inside a diagram is fine; an accidental float64 on an interface destined for a fixed-point target is not.
Convert consciously. Type conversions in generated C are always explicit casts. If the model forces many conversions, that is usually a sign the types were chosen inconsistently upstream — fix the source, not the symptoms.
Feedback and memory
Close every loop through memory. Feedback must pass through a Unit Delay, Integral, or Derivative block — the solver rejects algebraic loops. Choose deliberately: Unit Delay for discrete recursion, Integral for continuous-style accumulation.
Set initial conditions on purpose. Every memory block has an initial condition, and its default of zero is a choice, not an absence of choice. A controller that wakes up with the wrong integrator state misbehaves for the first seconds — decide what the first step should output.
Avoid raw derivatives of measurements. Differentiation amplifies noise. Prefer restructuring (use the signal that is the derivative, if the plant provides one) or filter before differentiating.
Rates and scheduling
Choose periods from the physics, not from habit. Sample at least an order of magnitude faster than the dynamics you control; don’t run everything at the fastest rate “to be safe” — it wastes target CPU and hides rate-dependence bugs.
Make cross-rate boundaries visible. When a fast task feeds a slow one, the consumer sees the latest value at its rate (last-value semantics). Group cross-rate signals deliberately and document the intent; don’t scatter them.
Review the scheduling order. Tasks activated at the same tick execute in the build configuration’s explicit order. Treat a change to that order like a change to the model — it is one.
Robustness
Guard the domain of partial functions. Sqrt, Log, Asin, Acos, and division (inverted Multiply inputs) have restricted domains. If an input can leave the domain, clamp or branch in the model — don’t rely on runtime diagnostics to catch what design can prevent.
Saturate at physical limits. Actuator commands and integrators should be limited to physically meaningful ranges (Comparator + If-Then-Else, or Curve clamping). Unbounded integrators are the classic windup defect.
Treat diagnostics as a net, not a strategy. The runtime diagnostics catch NaN/Inf/overflow and substitute safe values so a run survives — but a model that triggers them has a defect. Investigate every diagnostic.
Model as text
Every diagram has an equivalent representation in MDL, Modeloop’s textual modeling language, kept bidirectionally synchronized with the canvas. Use it for review and versioning: MDL diffs read like code diffs, which makes model changes reviewable in ordinary pull-request workflows. Edit in whichever projection fits the task — the model is the single source of truth, not the text or the picture.
Review checklist
Before merging a model change:
- Every container and task has a meaningful name
- No magic numbers — tunable values are
$references - Interface signals typed deliberately, named with units
- All feedback loops pass through memory blocks with intentional initial conditions
- Partial-function inputs guarded; integrators and outputs saturated
- Task periods justified; scheduling order reviewed
- Simulation runs clean — zero diagnostics