Skip to main content

Overview

The Expression Language (or DSL) is how you make ProDex models dynamic. Wherever you’d expect to enter a fixed number — routing conditions, queue priorities, resource requirements, entity attribute values, event triggers, and the parameters of processing-time distributions — you can enter an expression instead. Expressions reference simulation state, entity attributes, constants, and lookup tables, so your model responds to the data flowing through it rather than behaving identically for every entity. A user who only enters fixed numbers is using a small fraction of what the Modeler can do. Learning the expression language is what turns a static flow diagram into a living model of your operations.

Where Expressions Appear

Most numeric and logical fields in a component configuration accept either a literal value or an expression. Common places:
  • Arrival rate on a Source — can depend on simulation time or a schedule
  • Routing conditions on a Router — branches based on entity attributes, resource availability, or queue state
  • Resource requirements on a Process — how many units of a resource are needed, conditional on the entity
  • Priority on a queue — sorts waiting entities by a computed score
  • Event conditions on Event Hooks — when to fire an action
  • Entity attribute assignments on a Source, Transformer, Combiner, or Separator output — compute initial or updated attribute values
  • Distribution parameters — the mean, std, min, max, etc. inside a distribution specification can themselves be expressions. See Distributions vs. Expressions below.

Syntax Basics

Expressions follow an Excel-like syntax: numeric literals, string literals in double quotes, function calls in FUNCTION(args) form, arithmetic operators, comparison operators, and logical operators.
5
"TypeA"
FUNCTION(arg1, arg2)
a + b * c
IF(condition, then_value, else_value)
priority > 5 AND qc_passed

Operator Precedence

Expressions follow standard operator precedence (highest to lowest):
  1. NOT, unary -
  2. Multiplication, division*, /
  3. Addition, subtraction+, -
  4. Comparison<, <=, >, >=, =, ==, !=
  5. AND
  6. OR
AND, OR, and NOT are infix and prefix operators, not functions — write a AND b, a OR b, NOT a, never AND(a, b). Both = and == are accepted as equality operators. Use parentheses for explicit grouping when you want to override precedence.

Expression Contexts

The context an expression runs in determines which identifiers are available to reference. Getting context wrong produces validation errors like “identifier not available here” that are confusing without understanding the model. There are three contexts.

No-Entity Context

Runs without a specific entity in scope. Available identifiers:
  • Built-in variables like SIM_TIME, SIM_DURATION, BUFFER_LEVEL(...), RESOURCE_AVAILABLE(...)
  • Constants by name
  • State variables by name
  • SELF — the current component (when the expression is attached to one, e.g., inside an Event Hook)
  • Entity attributes are not available
Used in: Source arrival rates (before any entity exists), Event Hook conditions that don’t involve an entity, scheduled actions, and global state checks.

Single-Entity Context

Runs with one specific entity in scope. Everything from no-entity context, plus:
  • Entity attributes by bare namepriority, product_class, weight
  • ENTITY_TYPE, ENTITY_AGE
Used in: Process processing time, Router conditions, Transformer attribute assignments, per-entity Event Hooks, and similar places where one entity is clearly in scope.

Multi-Entity Context

Runs with multiple entities in scope — Combiner output assignments (where several input entities are being merged) and on_batch_created on a Separator (where multiple outputs are being produced from one input). Everything from no-entity context, plus: A common mistake: writing priority > 5 in a Combiner’s output assignment. With multiple entities in scope, which entity’s priority? The engine rejects this. You need an aggregation like MAX(priority) > 5 or ANY(priority > 5).

Built-in Variables

These reference live simulation state:
  • SIM_TIME — current simulation time
  • SIM_DURATION — configured simulation duration
  • ENTITY_TYPE — type slug of the current entity (single-entity context only)
  • ENTITY_AGE — time the current entity has been in the system (single-entity context only)
  • BUFFER_LEVEL(component_id, default:=0) — current number of entities in the buffer with that id
  • BUFFER_CAPACITY(component_id, default:=0) — configured capacity of the buffer
  • RESOURCE_AVAILABLE(resource_id, default:=0) — currently available units of the named resource
  • RESOURCE_CAPACITY(resource_id, default:=0) — configured capacity of the resource
  • STATION_WIP(component_id, entity_type_slug?, default:=0) — work in progress at the named station; the optional entity_type_slug filters to a specific entity type
  • SELF — the current component (on Event Hooks and similar expressions attached to a specific component). Refers to the component, not the entity in scope — distinct from entity attribute access.
All component-query functions take the component’s id (the slug used internally by the platform), not its human-readable display name. They also all accept a default:= keyword argument used when the referenced component doesn’t exist or the value is unavailable — without it, they return a type-specific zero.

Entity Attributes

Entities carry typed attributes through the flow — booleans, numbers, text, text lists, number lists. Reference the current entity’s attributes by their bare name in any expression that runs in a single-entity context:
priority
product_class
weight
No self. prefix — the entity in scope is implicit. Expressions reference attributes directly by the name defined on the entity type. In multi-entity contexts, bare attribute names refer to the set of entities and must be wrapped in an aggregation function like SUM(weight) or MAX(priority). See Aggregation Functions. Entity attributes are set on a Source, can be modified by a Transformer, and can be read from any expression downstream. They persist with the entity through the entire flow. See Entities for the full attribute type system and assignment strategies.

Distributions vs. Expressions

A common confusion: distributions are not DSL functions. You can’t write NORMAL(60, 10) as an expression. Distributions are JSON objects specified in the model schema:
{"type": "normal", "mean": 60, "std": 10}
The parameters inside a distribution — mean, std, min, max, and so on — can themselves be expressions:
{"type": "normal", "mean": "BASE_TIME + weight * 0.5", "std": 10}
So the split is: the distribution type and its shape are static configuration; the parameter values are dynamic expressions. See Distributions for the full list of supported distribution types.

Common Functions

Control Flow

  • IF(condition, then, else) — branch between two values
  • AND, OR, NOT — logical combinations (infix/prefix operators, not functions)
  • Comparison operators: ==, !=, <, <=, >, >=, =

Math

  • ABS(x) — absolute value
  • POW(x, y)x raised to the y
  • MOD(x, y) — remainder of x divided by y
  • Standard arithmetic: +, -, *, /

Strings

  • CONTAINS(text, substring) — whether a string contains a substring (case-sensitive)
  • LEN(text) — length of a string, as a number

Lookups

  • LOOKUP(table_name, key1, key2, ..., default:=value) — look up a value from a Lookup Table. Supports one key per dimension for multi-dimensional tables. The optional default:= keyword argument is returned when no row matches. Without a default, LOOKUP returns a type-specific zero (0 for numbers, "" for strings, FALSE for booleans) — never null.

Aggregation Functions

Valid only in multi-entity contexts (Combiner output assignments and conditions; Separator on_batch_created hooks). Aggregations operate over the set of entities in scope:
  • SUM(expr) — sum of the expression evaluated on each entity
  • MEAN(expr) — average
  • COUNT(expr) — count of entities where the expression is non-null
  • MAX(expr) — maximum value
  • MIN(expr) — minimum value
  • ANY(expr) — true if the expression is true for any entity
  • ALL(expr) — true if the expression is true for every entity
Example — a Combiner producing an assembly, setting output attributes from its inputs:
# Output attribute: total_weight
SUM(weight)

# Output attribute: priority
MAX(priority)

# Output condition: emit only if every input passed QC
ALL(qc_passed)

Common Patterns

Conditional routing — send each entity down the right path:
IF(product_class = "rush", "express_line", "standard_line")
Attribute-driven distribution parameter — in a distribution spec, the mean adapts to each entity:
{"type": "normal",
 "mean": "LOOKUP(cycle_times_by_product, ENTITY_TYPE) * complexity_factor",
 "std": 10}
Backpressure — slow arrivals when downstream is congested, via an expression on the Source arrival rate:
IF(BUFFER_LEVEL("wip_buffer") > 100, 0, 1)
Priority scoring — sort waiting entities by computed importance:
due_priority * 0.7 + customer_tier * 0.3
Assembly output — combine several inputs into one (Combiner, multi-entity context):
# Assembled entity's weight is the sum of its inputs
SUM(weight)

# Emit only if all inputs passed QC
ALL(qc_passed)

Tips and Gotchas

  • Expressions are evaluated on every entity, every time. Keep them simple where possible — complex expressions in high-throughput paths add up.
  • Default handling on lookups and component queries. Both LOOKUP and component-query functions (BUFFER_LEVEL, etc.) accept default:= to specify a fallback. Without one they return a type-specific zero, not null.
  • Entity attributes are not State Variables. Attributes belong to an entity and move with it. State Variables belong to the model and persist across entities.
  • Context matters. If you hit a validation error like “identifier priority not available here,” the expression is running in a context that doesn’t have an entity in scope. See Expression Contexts.
  • SELF vs. bare attribute names. SELF refers to the component the expression is attached to (on Event Hooks). Bare names refer to entity attributes. They don’t overlap.
  • Component-query functions take the component id, not the display name. BUFFER_LEVEL("wip_buffer") looks up the component with id wip_buffer — the same id that appears in simulation event data.
  • Prefer Constants and Lookup Tables over hardcoded values. Change a constant in one place; you can’t easily find-and-replace across dozens of expressions.