MKX Language
This chapter documents the core language used inside MKX input files. It is organized from simple values up to larger composition features such as conditional blocks and local bindings.
Overview
MKX language features appear in places such as:
letbindings- component attributes
- reaction model arguments
- simulation settings
- output settings
The language is easiest to understand in four layers:
- scalar values
- collection values
- expressions
- structural composition features
1. Scalar Values
Numbers
1
1.0
1e-8
120e3
Strings
"results"
"key"
Identifiers
Identifiers are used for names and symbolic values.
thermal
arr_base
cvode_bdf
Boolean Values
Boolean values are written as identifiers.
true
false
2. Collection Values
Lists
Lists use square brackets.
[tabdelim, hdf5]
["A*", "B*", "*"]
[{T=600}, {T=650}, {T=700}]
Objects
Objects use braces and store keyed values.
{phase=gas, init=1.0, role=reactant}
{Vf=1e13, Vb=1e13}
{method=cvode_bdf, atol=1e-12, rtol=1e-10, t_end=1e6}
Both = and : are accepted in object-style assignments.
{Vf=1e13, Vb=1e13}
{Vf:1e13, Vb:1e13}
3. Expressions
Expressions are used inside object fields, model arguments, solver settings,
conditions, and let bindings.
Supported Expression Forms
MKX currently supports:
- numeric literals
- string literals
- identifiers
- boolean identifiers
- unary expressions
- binary arithmetic expressions
- parenthesized expressions
- function calls
- list literals
- object literals
Arithmetic Expressions
Supported binary arithmetic operators:
+-*/^
Examples:
50e3 + shift
2 * x
Eaf / 2
x ^ 2
Unary Expressions
Supported unary operators:
- unary
+ - unary
-
Examples:
+1e3
-5e3
Parenthesized Expressions
(50e3 + shift) / 2
Function Calls
Function-call syntax is supported for a small set of built-in numeric functions.
pow(10, 3)
max(2, 5, 4)
sqrt(1e-8)
Supported built-in functions during normalization:
exp(x)log(x)sqrt(x)abs(x)pow(x, y)min(x1, x2, ...)max(x1, x2, ...)
Operator Precedence
Expressions are parsed with the following precedence, from highest to lowest:
- parenthesized expressions
- unary
+,- - power
^ - multiplication and division
- addition and subtraction
Parentheses are encouraged whenever an expression becomes hard to read.
4. Structural Composition
MKX also supports features that help build larger files cleanly.
let Bindings
Use let to define reusable values.
let asite = 1e-20
let arr = {Vf=1e13, Vb=1e13}
let solver_cfg = {method=cvode_bdf, atol=1e-12, rtol=1e-10, t_end=1e6}
Spread Syntax
Object values can be expanded into argument lists using spread syntax.
let arr = {Vf=1e13, Vb=1e13}
{A*}+{B*}=>{AB*}+{*} @ ArrheniusDefault(*arr, Eaf=120e3, Eab=80e3)
The same spread form may also be used inside object-style field lists, such as component attributes:
let s1 = {site="S1", site_density=1.0}
components {
*S1 {phase=surface, init=1.0, tags=[emptysite], *s1},
A*S1 {phase=surface, init=0.0, *s1}
}
As with model arguments, later explicit fields override earlier spread values.
where Bindings
Local bindings may be attached to individual entries using where (...).
{A*}+{B*}=>{C*}+{*} @ ArrheniusDefault(*arr, Eaf=50e3 + shift, Eab=55e3)
where (shift=-5e3)
This is useful when a value is local to a single entry and should not become a
top-level let.
Block Conditionals
MKX supports structural conditionals inside components { ... } and
reactions { ... } blocks.
The syntax is:
if(enabled) {
...
} else {
...
}
The condition must resolve to a boolean value, typically from a top-level
let.
Reaction Example
let enabled = true
reactions {
if(enabled) {
{A}+{*}=>{B}+{*} @ ArrheniusDefault(Vf=1e6, Vb=0.0, Eaf=0.0, Eab=1e5)
} else {
{A}+{*}=>{B}+{*} @ ArrheniusDefault(Vf=1e6, Vb=1e7, Eaf=30e3, Eab=1e5)
}
}
This lets you switch entire elementary steps on or off, or choose between two alternative parameterizations, without duplicating the surrounding file.
Component Example
let use_adsorbate = false
components {
A {phase=gas, init=1.0, role=reactant},
* {phase=surface, init=1.0, tags=[emptysite]},
if(use_adsorbate) {
A* {phase=surface, init=0.0}
} else {
B* {phase=surface, init=0.0}
}
}
Worked Example
The example below combines let, spread syntax, arithmetic, and a
block-conditional reaction choice.
let include_conversion_step = true
let arr_fast = {Vf=1e6, Vb=0.0}
let arr_slow = {Vf=1e6, Vb=1e7}
reactions {
if(include_conversion_step) {
{A}+{*}=>{B}+{*} @ ArrheniusDefault(*arr_fast, Eaf=0.0, Eab=1e5)
} else {
{A}+{*}=>{B}+{*} @ ArrheniusDefault(*arr_slow, Eaf=30e3, Eab=1e5)
}
}
Read from top to bottom, this does the following:
- defines a boolean switch
- defines two reusable parameter objects
- chooses one full reaction entry or the other inside
reactions { ... }
Supported And Unsupported Features
It is important to distinguish between syntax accepted by the parser and features intended for normal use.
Supported
The following are supported and intended for normal use:
- numeric arithmetic with
+,-,*,/,^ - grouping with parentheses
- lists and objects
- reusable values through
let - spread syntax
- local
wherebindings - block conditionals inside
componentsandreactions - the built-in functions listed above
Not Supported
The following are not currently supported:
- comparison operators such as
==,!=,<,<=,>,>= - logical operators such as
&&,||,! - string concatenation
- indexing into lists or objects
- member access syntax
- arbitrary user-defined functions
- function names other than the built-in functions listed above
- nested block conditionals
In particular, generic call syntax such as foo(1, 2) may parse, but it will
fail during normalization unless foo is one of the supported built-in
functions.
Likewise, a conditional such as if(T > 600) { ... } else { ... } is not yet
supported because comparison operators are not yet part of the language.
Comments
Line comments start with #.
# This is a comment