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 inFUNCTION(args) form, arithmetic operators, comparison operators, and logical operators.
Operator Precedence
Expressions follow standard operator precedence (highest to lowest):NOT, unary-- Multiplication, division —
*,/ - Addition, subtraction —
+,- - Comparison —
<,<=,>,>=,=,==,!= ANDOR
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
Single-Entity Context
Runs with one specific entity in scope. Everything from no-entity context, plus:- Entity attributes by bare name —
priority,product_class,weight ENTITY_TYPE,ENTITY_AGE
Multi-Entity Context
Runs with multiple entities in scope — Combiner output assignments (where several input entities are being merged) andon_batch_created on a Separator (where multiple outputs are being produced from one input). Everything from no-entity context, plus:
- Aggregation functions over the set of entities (see Aggregation Functions)
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 timeSIM_DURATION— configured simulation durationENTITY_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 idBUFFER_CAPACITY(component_id, default:=0)— configured capacity of the bufferRESOURCE_AVAILABLE(resource_id, default:=0)— currently available units of the named resourceRESOURCE_CAPACITY(resource_id, default:=0)— configured capacity of the resourceSTATION_WIP(component_id, entity_type_slug?, default:=0)— work in progress at the named station; the optionalentity_type_slugfilters to a specific entity typeSELF— 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.
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: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 writeNORMAL(60, 10) as an expression. Distributions are JSON objects specified in the model schema:
mean, std, min, max, and so on — can themselves be expressions:
Common Functions
Control Flow
IF(condition, then, else)— branch between two valuesAND,OR,NOT— logical combinations (infix/prefix operators, not functions)- Comparison operators:
==,!=,<,<=,>,>=,=
Math
ABS(x)— absolute valuePOW(x, y)—xraised to theyMOD(x, y)— remainder ofxdivided byy- 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 optionaldefault:=keyword argument is returned when no row matches. Without a default,LOOKUPreturns a type-specific zero (0for numbers,""for strings,FALSEfor booleans) — never null.
Aggregation Functions
Valid only in multi-entity contexts (Combiner output assignments and conditions; Separatoron_batch_created hooks). Aggregations operate over the set of entities in scope:
SUM(expr)— sum of the expression evaluated on each entityMEAN(expr)— averageCOUNT(expr)— count of entities where the expression is non-nullMAX(expr)— maximum valueMIN(expr)— minimum valueANY(expr)— true if the expression is true for any entityALL(expr)— true if the expression is true for every entity
Common Patterns
Conditional routing — send each entity down the right path:mean adapts to each entity:
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
LOOKUPand component-query functions (BUFFER_LEVEL, etc.) acceptdefault:=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
prioritynot available here,” the expression is running in a context that doesn’t have an entity in scope. See Expression Contexts. SELFvs. bare attribute names.SELFrefers 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 idwip_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.

