Skip to main content

Overview

Constants and Lookup Tables are how you separate a model’s parameters from its logic. Instead of hardcoding values inside expressions, you name them, store them in one place, and reference them from anywhere. Change the named value once, and every expression that references it updates automatically. This matters for three reasons:
  1. Tuning: adjust model parameters without hunting through expressions. Change BASE_PROCESSING_TIME from 60 to 75 and every relevant component follows.
  2. Readability: SHIFT_EFFICIENCY_FACTOR is easier to understand than 0.87 sprinkled across expressions.
  3. Non-technical access: operators and analysts who shouldn’t modify expressions can still tune the model by changing constant values.
Both Constants and Lookup Tables are factory-scoped: they live at the factory level and can be reused across models. But each model has to opt in to the ones it uses. A constant that exists in the factory but hasn’t been added to the model is invisible to that model.
Referencing a constant or table that isn’t added to the model fails validation. The expression checker resolves names against the model’s own constants and lookup tables, so a LOOKUP against a table the factory has but the model hasn’t added produces a “Lookup table ’…’ is not defined” error (a missing constant shows up as “Unknown identifier”) — that’s the symptom you’ll actually see when the opt-in step was skipped.

Constants

A Constant is a named value with a specific type: number, text, or boolean. Each constant has:
  • Name: how expressions reference it. Names use SCREAMING_SNAKE_CASE (e.g., BASE_CYCLE_TIME)
  • Description: what it represents and what units
  • Type: number, text, or boolean
  • Value: the actual data
The type is called Text, not string. The Type dropdown offers Number, Text, and Boolean.
Names auto-uppercase as you type. The name input converts what you type to SCREAMING_SNAKE_CASE: typing base rate saves as BASE_RATE. Letters are uppercased and spaces become underscores. Reference the name as it ends up after the conversion.
Expressions reference the name. The name must therefore be a valid DSL identifier: capital letters, digits, and underscores, starting with a letter. It can’t be a reserved DSL word (such as SIM_TIME or a function name), and constants and lookup tables can’t share a name with each other. Either conflict fails validation (for example, “Constant ‘SIM_TIME’ collides with reserved identifier.”). State variables can’t reuse a constant or lookup table name either. Reference a constant by its name in any expression:

Constants vs. State Variables

Constants are static for the duration of a run: they cannot be reassigned. To mutate a value during simulation, use a State Variable instead. State variables expose a runtime assign action that constants don’t. The scope differs too: constants are factory-scoped and shared across every model that opts in, while state variables are model-scoped. A constant is the right tool for a parameter you tune between runs or share between models. A state variable is the right tool for a value the simulation itself changes: current shift, rolling WIP counter, demand signal.

When to Use a Constant

  • Any value that appears in multiple places. If you find yourself typing 0.87 in several expressions, extract it to a constant.
  • Any value you might want to change between runs. Edit the constant, run again, and compare the runs.
  • Any value a non-technical stakeholder might need to adjust. Analysts can tune a constant without knowing the expression language.
Constants are factory-scoped. Changing one changes it for every model that uses it, including copies of a model. To compare values, run with one value, change it, run again, and compare the two runs.

Managing Constants

Constants appear in the Constants section of the Modeler’s left panel, in the Overview view. Each row shows a constant the model uses and its value.
  • + Add Constant at the bottom of the section adds an existing factory constant to this model, or choose Create new… to define one and add it in one step.
  • The × on a row removes the constant from this model without deleting it. It’s disabled while the model still references the constant (“In use — remove its references first”).
  • The section’s pencil icon (View and manage all) opens the Lookups dialog on its Constants tab, which lists every constant in the factory. Add, edit, or delete constants there, and use the In Model checkbox to add or remove one for the current model. The same dialog also has Lookup Tables, State Variables, and Topics tabs.
Editing a constant’s value changes it for every model in the factory that uses it.

Lookup Tables

A Lookup Table is a named, typed-key table queryable in expressions. Each table has a value type (number, text, or boolean) and one or more keys (called “Attributes” in the UI — these are the table’s key columns, unrelated to entity attributes). Keys are themselves typed (each can be boolean, text, or number), so a multi-dimensional lookup can mix key types. Query a table with LOOKUP:

Providing a Fallback Value

LOOKUP accepts an optional default:= keyword argument that returns the supplied value when no row matches:
This is the canonical way to give a lookup a fallback. Always prefer default:= over wrapping in IF(): it’s a single expression with no double-evaluation of the same lookup.
Without default:=, missing rows return type-specific zero: 0 for a numeric table, "" (empty string) for a text table, FALSE for a boolean table. That zero is silent and easily mistaken for a real result. Use default:= whenever the difference between “no row matched” and “value is exactly zero” matters.
The keyword-argument syntax uses :=, not = (which means equality in the DSL). Despite the visual resemblance to Python’s walrus operator, := here is simply a named-argument marker, not an assignment. The same := marks the filter:= and type:= keyword args on aggregation functions and ndigits:= on ROUND.

Multi-Dimensional Lookups

A Lookup Table can be configured with more than one key column, so a single table can capture data that varies across two or more axes at once, like “processing time by (product type, station)” or “yield rate by (shift, material class).” Query it with one positional argument per key column, in the order the table defines them:
Each key column has its own type and a required list of possible values — the enumerated set of values that column accepts. That list is load-bearing, not decoration: every table entry is validated against it, and the dialog generates the table’s entries from it. Design a lookup table by enumerating each key’s domain up front. Multi-key LOOKUP calls match rows where all key columns equal the values you pass in. One argument per key column. There’s no wildcard or partial-match syntax: always pass exactly as many keys as the table defines. Composite keys via CONCAT. When a natural key is a compound string, build it in the expression rather than adding a column per fragment:

Managing Lookup Tables

Lookup tables appear in the Lookup tables section of the Overview view, right below Constants, and work the same way: + Add Lookup Table adds an existing factory table to this model (Create new… opens the dialog to define one), the × on a row removes it from this model, and the pencil icon opens the Lookups dialog on its Lookup Tables tab. In the dialog, each table has:
  • A Name (SCREAMING_SNAKE_CASE, converted as you type, like constant names)
  • A Value Type: Number, Text, or Boolean
  • One or more Attributes: the typed key columns, each with a name, a type, and its possible values
  • Entries: one row per combination of key values, each with its value
Entries are validated when you save: a row with the wrong number of keys, a key outside its column’s possible values, or a value that doesn’t match the table’s type is rejected with a Validation Error — you won’t discover a malformed row at simulation time.

When to Use a Lookup Table

  • Values that vary by a categorical attribute. Processing time by product type, yield rate by material, priority weight by customer tier, all natural lookups.
  • Routing and proportional splits. Different entities route differently; store the routing rules in a table rather than deeply nested IF expressions.
  • Data-driven configuration. When the “configuration” of a model is really a set of numbers tied to categories, a lookup table is clearer than encoding the same information across many fields.

Constants vs. Lookup Tables: Which to Use?

  • Single value used in many places → Constant
  • Value that depends on a categorical attribute → Lookup Table
  • Needs to vary between comparison runs → Constant (one value per run)
  • Large reference dataset → Lookup Table
It’s common to use both: a constant for a global factor, a lookup for per-type rates, combined in one expression.

Patterns

Processing time by product:
Conditional cost or surcharge with safe fallback:
Tunable shift dynamics:

Tips

  • DSL arithmetic is type-strict. Numbers add to numbers; text doesn’t quietly coerce. Catch type mismatches at validation rather than relying on silent conversions.
  • The value type must match the context that reads it. A boolean constant in a numeric expression, or a text lookup value feeding a numeric slot, fails validation with a type error — a common trip point after refactoring a table from number to text values.
  • Use default:= defensively. If a missing lookup row should be anything other than the type’s zero, default:= is shorter and clearer than an IF() wrapper around two LOOKUP calls.
  • Each model opts in. Creating a constant or lookup table at the factory level is the first step. Adding it to the model (from the Overview section or the dialog’s In Model checkbox) decides whether that model sees it.