Arithmetic Parsing

Arithmetic Parsing

The specification supports Python expressions in all YAML values (not keys), which can be parsed. Expressions are parsed in the order in which they appear in input files, with the exception of the variables key, which is parsed before the others. The following variables file contains some examples of expressions that may be used in YAML.

variables:
  scalar: 123
  array:  [1, 2, 3]
  dict:   {key1: 1, key2: 2}
  string: "string" # Quotes are required for strings

  # Any Python expression can be used. A limited set of functions are available.
  expression: scalar + 1 # Any Python expression can be used
  complex_expression: scalar + array[0] + dict["key1"] + pow(len(string), 4)

  # If an expression WOULD otherwise be wrapped in quotes, we can force it
  # to be treated as an expression by wrapping it in parentheses
  expresssion_with_strings: ("string1" + "string2")
  multiline_expression: |
    def func(x):
      return x + 1
  expression_calling_function: func(1)

What is Parsed

To decide whether an expression is parsed, the parser uses the current context. If a string is expected in the context & a string is provided, then the expression is treated as a string (e.g., "name: 123" will treat "123" as a string). If a string is not expected in the context, it will be parsed. To force an expression to be treated as a string, you may quote-wrap the expression. To force it to be treated as Python code, you may parentheses-wrap it.

After an expression is parsed, it is type-casted if there is a required type, and any further accesses to that expression will yield the type-casted object. If type casting fails, an error is raised.

Parsing traverses all YAML objects that are also type-casted, and it recurses into Timeloop Front-End known objects (e.g., recurse into components and actions, do not recurse into a users's dict-in-a-dict).

Available Python Functions

The parser includes the following functions from the Python math library: ceil, comb, copysign, fabs, factorial, floor, fmod, frexp, fsum, gcd, isclose, isfinite, isinf, isnan, isqrt, ldexp, modf, perm, prod, remainder, trunc, exp, expm1, log, log1p, log2, log10, pow, sqrt, acos, asin, atan, atan2, cos, dist, hypot, sin, tan, degrees, radians, acosh, asinh, atanh, cosh, sinh, tanh, erf, erfc, gamma, lgamma, pi, e, tau, inf, nan

The parser includes the following Python keywords: abs, round, pow, sum, range, len, min, max, float, int, str, bool, list, tuple, enumerate, getcwd, map

Scope Rules

When parsing Python expressions, the scope of available variables follows the following rules. Precedence is given to earlier rules (i.e., an earlier rule may overwrite a later rule if they both use the same key).

  1. When parsing a dictionary, previously-parsed keys are available in the local scope as variables. For example:

    dict_to_parse:
    a: 1
    b: a + 5
  2. Previously-parsed keys from outer scope(s) are available. For example:

    dict_to_parse:
    outer_a: 1
    inner_dict:
      inner_a: outer_a + 5
  3. Top-level variables are available. For example:

    
    variables:
    global_a: 5
    
    # Somewhere else
    expression: global_a
  4. The top-level specification is globally available as spec. For example:

    dict_to_parse:
    a: spec.variables["global_a"]
    num_threads: spec.mapper.num_threads

Additionally, when parsing the architecture, architecture nodes can be referenced by name, and nodes inherit attributes from parent containers. Note that while forward references ARE allowed in this manner, they should be used with caution as forward-referenced objects will not have parsed arithmetic. For example:

  - !Container # Top-level system
    name: system
    attributes:
      system_val: 123

  - !Component
    name: component1
    subclass: component_name_here
    attributes:
      value1: TOP_LEVEL_DEFINE       # Reference top-level define
      scalar: TOP_LEVEL_DEFINE * 123 # Overwrite component default
      from_system: system_val        # Directly reference parent containers
    spatial: {meshX: value1} # Can reference attributes of this component

  - !Component
    name: component2
    subclass: component_name_here
    attributes:
      value1: component1.attributes["value1"] # Reference component1 attribute

Including Extra Functions

Additional functions may be defined in Python scripts and included through the top-level globals.expression_custom_functions key, which takes in a path to the additional function .py file. All globally-visible functions will be added from the .py. See the globals top-key page for more details.

Parsing Cheatsheet

REFERENCE THE EXERCISES REPO