Global Variables, Expressions and Units¶
Almost every number in a deck (step times, pressures, flows, temperatures, inlet and initial mole fractions, isotherm and kinetic parameters, layer and wall properties, solver tolerances) is stored as an expression, not just a value. You can type a plain number, a number with a unit such as 3.5 [bar], or a formula that refers to your own named global variables, such as P_high / 2. This page describes what an expression may contain, how units are converted and when expressions are evaluated.
Global variables in a deck¶
A deck holds a list of global (user) variables. Each one has:
| Field | Meaning |
|---|---|
| Name | The identifier you use in expressions, e.g. P_high. Names are case-sensitive and must not clash with the built-in names listed below. |
| Type | The physical quantity (Pressure, Temperature, Time, Velocity, Molar Flow Rate, …, or Dimensionless / Custom). It fixes the SI base unit the value is stored in, shown next to the value. |
| Expression | A number or a formula, optionally ending in a unit, e.g. 3.5 [bar] or 2*P_low. |
| Value | The evaluated value in SI base units. Evaluation fails (and the deck is not runnable) if the expression cannot be evaluated. |
| Description | Free text for your own notes. |
Variables are saved with the deck. From Python a variable is the parameter variable[<name>], and any other numeric parameter can be set to a formula that uses it:
sim["variable[t_feed]"] = "40 [s]"
sim["step[Feed].time"] = "t_feed" # both steps now follow t_feed
sim["step[Rinse].time"] = "t_feed"
sim["endpoint[3].pressure"] = "P_high / 2"
sim["endpoint[1].y[CarbonDioxide]"] = "y_feed" # an inlet composition
sim["endpoint[1].y[Nitrogen]"] = "1 - y_feed"
Use a variable's name in any numeric field, for example P_high in a pressure profile's final value, or t_feed in two step times so that they always stay equal.
What an expression can contain¶
Expressions are evaluated by muparser:
- numbers, including scientific notation (
1.5e-3); - the operators
+ - * / ^, parentheses and comparisons; the ternarycond ? a : bis available; - functions:
sin cos tan cot asin acos atan atan2 sinh cosh tanh coth asinh acosh atanh exp log ln log2 log10 sqrt abs sign rint min max sum avg(logandlnare both the natural logarithm; use^for powers, there is nopowfunction); - the constants
R= 8.31446261815324 J mol⁻¹ K⁻¹ andPI= 3.14159265358979; - the state variables
T,P,c,yandv(see State variables for when they mean anything); - the names of your global variables.
A global variable cannot be called T, P, c, y, v, R or PI, nor sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, exp, log, log2, log10, sqrt, abs, min, max, pow or sign. The engine does not reject all of them, so it is up to you: a variable called T would replace the state variable in every expression. Avoid the other function names too.
Units¶
Inline units¶
End an expression with a unit in square brackets to enter it in that unit:
3.5 [bar] -> 350000 Pa
25 [degC] -> 298.15 K
100 [ccm] -> 1.6667e-6 m³/s
15 [%] -> 0.15
(2 + 3) [bar] -> 500000 Pa
The rules, from core/src/math/unit_parser.cpp:
- The bracket must be the last thing in the expression.
2 [bar] + 1is an error. - The unit applies to the whole result: the formula is evaluated first and the result is converted from that unit to SI (
base = value × factor + offset, the offset being for °C and °F). - The symbol must be one the parser knows; an unknown symbol is an error. Accepted symbols include, by quantity:
- pressure
Pa kPa MPa bar atm psi Torr; temperatureK degC °C C degF °F F; lengthm cm mm um µm nm A in ft; times ms us µs min h hr d; masskg g mg t tonne lb; - energy
J kJ MJ cal kcal Btuand per moleJ/mol kJ/mol …; powerW kW MW GW mW; amountmol mmol kmol; volumem^3 m³ L l mL ml ft^3; - volumetric flow
m^3/s m^3/h L/s L/min ccm mL/min SCCM ft^3/min; mass flowkg/s g/s kg/h t/h …; molar flowmol/s mol/min mol/h mmol/s kmol/h Nm^3/h Nm^3/s SCFM …(normal and standard volumes use 0 °C and 1 atm, 22.414 L/mol); - velocity
m/s cm/s mm/s m/min km/h ft/s mph; inverse time1/s 1/min 1/h; inverse pressure1/Pa 1/kPa 1/bar 1/atm …; - density
kg/m^3 g/L g/cm^3 …; concentrationmol/m^3 mol/L mmol/L …; loadingmol/kg mmol/g mol/g …; - heat capacity
J/(kg·K) kJ/(kg·K) cal/(g·K); conductivityW/(m·K) mW/(m·K); molar masskg/mol g/mol kg/kmol; specific energyJ/kg kJ/kg MJ/kg kWh/kg kWh/tonne; diffusivitym^2/s cm^2/s mm^2/s; aream^2 cm^2 mm^2; pressure gradientPa/m kPa/m bar/m …; - fractions
- mol/mol % ppm.
Variables are already in SI. A variable's value is stored in its SI base unit, so a unit after a formula that uses variables rescales them again: with
a = 5 [bar](500 000 Pa),a/2 [kPa]gives 250 000 × 1000 = 2.5 × 10⁸ Pa. Writea/2instead.
Plain numbers are SI¶
A plain number with no unit is taken to be in the SI base unit of the field. Front ends that offer a unit selector store a value typed in another unit as number [unit], which the engine converts on evaluation. A formula is stored exactly as written, so give it an inline unit or write it in SI.
Type checking¶
If a variable's Type is a specific quantity and its expression ends in a unit of a different quantity (say Type Pressure and 300 [K]), the deck is marked not runnable with the message inline unit is Temperature but variable category is Pressure. A variable of type Dimensionless (the default for a new row) or Custom accepts any unit and keeps its type.
When expressions are evaluated¶
The engine evaluates every expression in the deck before each run (UserData::EvaluateAllExpressions). The Python package also evaluates a parameter as soon as you set it, so sim.get(path) returns the new value straight away.
Global variables first, in list order¶
Global variables are evaluated from the top of the list down, in one pass, before anything else:
- A variable may use any variable listed above it.
- A variable that uses one listed below it gets that variable's value from the previous evaluation, so it lags one edit behind. For example, with
A = 2*Blisted above a new variableB = 3 [bar],Aevaluates to 0 the first time and only becomes 600 000 Pa after the next edit. - Circular references (
A = B + 1,B = A + 1) are not detected; they produce values that change with every evaluation. - A reference to an undefined name is an error and the deck is not runnable.
So keep the list in dependency order: define base quantities first and derived ones after them. This is the engine's current behaviour (ResolveVariableExpressions in core/src/objects/user_data.cpp).
Then every other field¶
After the variables, all other expressions are evaluated once, before the run starts. Their values are fixed for the whole run.
State variables (T, P, c, y, v)¶
The names T, P, c, y and v stand for the local temperature [K], pressure [Pa], total concentration [mol/m³], component mole fraction [–] and velocity [m/s]. They are only re-evaluated with the local state in two places:
| Field | Re-evaluated | Values supplied |
|---|---|---|
| Constant mass-transfer coefficient (Adsorbent → rate model) | in every cell, at every residual evaluation | T = gas temperature of the cell, P = cell pressure, y = mole fraction of the component the coefficient belongs to; c = 0 and v = 0 |
| Arrhenius mass-transfer coefficient: pre-exponential factor and activation energy | same | same |
So 0.05*(P/1e5)^0.5 is a valid pressure-dependent constant-LDF coefficient, but c and v evaluate to zero there. (MassTransferCoefficientConstant and MassTransferCoefficientArrhenius in core/src/equations/mass_transfer_model.cpp.)
Everywhere else, including inside global variables, a state variable is evaluated once with placeholder values, T = 298.15 K, P = 10⁵ Pa, c = 40 mol/m³, y = 0 and v = 0, and the result is used as a constant for the whole run. For example a variable A = T*2 is 596.3, whatever the bed temperature. Use the dedicated models (temperature-dependent isotherms, the Arrhenius coefficient, transport correlations, inlet temperature profiles) for real state dependence.
Global variables in studies¶
The parameter registry (sim.parameters(), skarstrom params) lists global variables as parameters (variable[<name>]), and so does the engine's optimiser. Setting a parameter to a number overwrites its expression, so varying a variable moves every field that refers to it, while setting a field that refers to a variable to a number cuts that link. To change several inputs together in a parametric study or a fit, make them all refer to one variable and vary the variable. A field written as a formula is not tunable (tunable_only=True leaves it out), because setting a number would replace the formula.