Skip to content

Latest commit

 

History

History
1450 lines (1058 loc) · 49.9 KB

param_demo.md

File metadata and controls

1450 lines (1058 loc) · 49.9 KB

Mathematical Notations and Capabilities in param()

The param() class, located in pizza.private.mstruct is an essential low-level class for 🍕 Pizza³. It used as parent class for data containers used by script, pipescript, dscript, scriptobject, forcefield, dforcefield. Its methods offer scripting capabilities between Pythonic and LAMMPS syntaxes.

The param class extends the struct class (located in the same module) and allows dynamic evaluation of expressions, implicit calculations, and NumPy-style operations. Instances of this class are iterable and can be managed using a Matlab-like syntax(*).

The syntax evolved between versions towards more flexibility and robustness. The capacity to perform calculations matrix/nd-operations is vital for building LAMMPS codelets/blocks with complex displacements and boundary conditions.

(*)note: Matlab inputs are also enabled as shorthands.


Manage dependencies

note: It is mandatory to import NumPy as np (internal convention) if NumPy arrays are defined in param text expressions.

The following snippet also shows how to check the current working directory (if needed)

# assuming that cwd is Pizza3/ main folder
# check it with :
'''
# control snippet
import os
current_dir = os.getcwd()
print(current_dir)
'''
import numpy as np
from pizza.private.mstruct import param, paramauto # paramauto is used in one example

def prettyprint(var, value):
    """Display the variable's name, its value, and its type after evaluation."""
    print(f"{var} = {value} (type: {type(value).__name__})")

note: If your notebook is showing an empty figure above, it is normal at at the initialization of Pizza. The code is testing the graphical capabilities.


1 | Overview

param and paramauto are sibling classes with similar features, offering MATLAB-like syntax, variable interpolation, and mathematical evaluation. Additionally, paramauto implements an internal mechanism to reorder expressions into a feasible execution sequence. This feature allows users to define expressions before their inputs or linked expressions, regardless of their order of definition. Such functionality is handy in multiscale modeling, where physical properties, discretization, and simulation parameters are interdependent. As a result, rescaling or imposing a physical property does not require rewriting the corresponding 🍕 Pizza³ template.

For conciseness, this document focuses on the param class. The same syntax applies to paramauto, with the only difference being the instance initialization: p = paramauto() instead of p = param().


1.1 | Variable Definition & Assignment

✍️ The param() class stores values and expressions as fields/attributes. Variables are referenced using the syntax ${var}. For example, if an instance is created as p = param(var=value, ...), then:

  • p.var retrieves the value of ${var}.
  • p.var = value assigns or updates the value.
  • p("var") returns the evaluated value of ${var} in the current context.

🗑️ To delete a variable, use either:

  • p.var = []
  • del(p, "var")

1.2 | param Expressions

🟰 param expressions are stored as strings. They can represent either mathematical expressions or template strings. Unlike Python’s built-in eval(), the evaluation result depends on the context—returning either a numerical value or a processed string.

📏 Matrix operations are supported using @{matrix} instead of ${matrix}.

Valid expression components:

  • Operators: +, -, *, /, **, @, .T
  • Built-in functions: sum, prod, min, max
  • Mathematical functions: sin, cos, log, pi
  • NumPy functions: Prefixed with np.
  • Some statistical functions

1.3 | Interpolation & Evaluation

🧮 Interpolation (substitution) and 🚀 evaluation occur in this order:

  • 1️⃣ Direct interpolation/substitution
    • "The content of var is ${var}"
    • note: Only fully evaluated variable can be accessed.
  • 2️⃣ Local evaluation with a text result
    • "The sum of variables ${var1 + var2}"
    • "The third value is ${var[2]}"
    • "The sum is ${sum(var)}"
  • 3️⃣ Full evaluation with a numeric result
    • "${var}", "@{vector}.T", "@{matrix1} @ @{matrix2}"
  • 4️⃣ Mixed evaluation in a list
    • ["var=${myvar}", "sum=${mylist}", "@{matrix1} @ @{matrix2}"]

🤔 If a full evaluation is not feasible, the result is stored with 4 significant digits by default (configurable).

Errors during evaluation return an error message. For example, accessing a variable that has not been evaluated will raise an error.

🚨 Enable debug messages on error evaluation with:

p = param(debug=True)

🔄 Alternative Syntax:
For simple variable substitutions, Pythonic {variable} notation is also supported alongside ${variable} for more flexible evaluation.

🔁 Nested vs. forced Evaluation:
Nested evaluation is implemented. 🌀 The best interpolation/local/global/evaluation scenario will be followed according to the context. 🔧 Full evaluation can be forced by prefixing the param expression with a ❗ like var = "! ...".


1.4 | Native vs. param Text Expressions

🔢 Variables can be stored as strings ("1.0") or numbers (1.0). Scalars and complex numbers are supported.

📐 Defining matrices & arrays using different param notations:

  • Matlab-style: $[1 2 3; 4 5 6]
  • NumPy-style: $[[1,2,3],[4,5,6]]
  • Hybrid notation: $[[1 2 3; 4 5 6],[7 8 9; 10 11 12]]
  • Range expansions: "$[1:10]" or "$[1:0.5:10]"

⚠️ Evaluation Order

  • Variables are evaluated sequentially.
  • If dependency resolution is required, use paramauto() instead.
  • Unlike Python f-strings, modifying one variable updates all dependent expressions.

🔄 Convert a dynamic param instance to a static structure with:

s = p.eval()  # or equivalently s = p()

or equivalently

s = p()

Variables can be specified

subs = p("var1","var2",...)

1.5 | Iterable Instances, Indexing & Slicing

🤝 param instances behave like lists or collections:

  • p[5] → Returns the 6th element of p.
  • p[[5]] → Returns a substructure with the 6th element.
  • p[1:10:2] or p[[0,3,8]] → Returns a substructure for the specified indices.

Auto-assigning Undefined Variables
If ${var} appears in an expression but isn’t defined, assign it automatically as:

p.var = "${var}"  # Placeholder until assigned a real value

Use p.check() to validate all variable assignments.


1.6 | Updating, Inheritance & Merging

📦 Batch updating variables within a param instance:

p.update(var1=value1, var2="expression2", ...)

or via dictionary:

p.update(**d)  # where d["var"] = value or "expression"

🔗 Merging multiple param instances using the + operator:

pmerged = poriginal + pupdate

1.7 | Conversions

➡️ Convert param instances (p) to various formats:

Conversion Method
param ➡️ Static struct (non-evaluated) s = p.tostatic()
param ➡️ Evaluated struct se = p.tostruct() or se = p()
param ➡️ Dictionary d = p.todict()
param ➡️ paramauto pa = p.paramauto() or pa = paramauto(**p)
struct ➡️ param p = s.struct2param()
struct ➡️ Dictionary d = s.struct2dict()
dict ➡️ struct s = struct(**d)
dict ➡️ param p = param(**d)
dict ➡️ paramauto pa = paramauto(**d)

1.8 | File Operations

💾 Save and load param instances:
📥 Save to disk

p.write(filename)

📤 Load from disk

p = struct.read(filename).toparam()

2 | Define variables

2.1 | Overview

  • Basic syntax: Create an instance with p = param() and then define variables as:

    p.var = value
    p.var = "value"
    p.var = "expression"
    
  • You can also initialize variables directly: p = param(var1=..., var2=...).

  • Accessing a variable: p.var or getattr(p, "var") returns the raw value of ${var}.

  • To obtain a static structure, use s = p() or s = p.eval().

  • Evaluate specific variables with p("var1", "var2", ...).

  • Prefix a string with $ to designate it as a literal (i.e., not an expression).

  • For debugging evaluation issues, use p = param(debug=True).

  • Avoid using a variable named ${e} to prevent confusion with exp(1).


2.2 | Literal expressions

For legacy support, literal expressions can be defined either by:

  • Adding the prefix $ to the expression (e.g., "$ab"), or
  • Placing the expression inside a list (e.g., ["ab"]).

note: The latest versions of param() can automatically detect literals that cannot be evaluated.

Literal Expressions Example

This example demonstrates how to use literal expressions with param():

  • line1: A raw string that escapes ${a} to prevent substitution.
  • line2: A list of strings.
  • line3: A string that interpolates a value from line2.
  • ab: A variable holding the literal "AB".
  • line4: A literal expression where the $ prefix preserves the literal value of ab.
  • line5: A plain string "ab".
  • line6: A string "sin(x)" that will not generate an error.
  • line7: An expression "${sin(x)}" which will generate an error since x is not defined.

The evaluated static structure is obtained by calling l().

l = param()
l.line1 = r"\${a}+1"        # escape `${a}` to prevent its substitution
l.line2 = ["a","b"]
l.line3 = 'The first letter in ${line2} is "${line2[0]}"'
l.ab = "AB"
l.line4 = "$ab"
l.line5 = "ab"
l.line6 = "sin(x)" # it will not generate an error
l.line7 = "${sin(x)}" # it will generate an error since x is not defined
print('The values of l:')
print(repr(l))
print('\nthe static content of l')
s = l() # equivalent to s = p.eval()
s
The values of l:
  -------------:----------------------------------------
          line1: \${a}+1
               = ${a}+1
          line2: ['a', 'b']
               = ['a', 'b']
          line3: The first letter in  [...] e2} is "${line2[0]}"
               = The first letter in ['a', 'b'] is "a"
             ab: AB
               = AB
          line4: $ab
               = ab
          line5: ab
               = ab
          line6: sin(x)
               = sin(x)
          line7: ${sin(x)}
               = <Error: Variable or function 'x' is not defined>
  -------------:----------------------------------------
parameter list (param object) with 8 definitions

the static content of l
  -------------:----------------------------------------
          line1: ${a}+1
          line2: ['a', 'b']
          line3: The first letter in ['a', 'b'] is "a"
             ab: AB
          line4: ab
          line5: ab
          line6: sin(x)
          line7: <Error: Variable or  [...]  'x' is not defined>
  -------------:----------------------------------------





structure (struct object) with 8 fields

2.3 | Inheritance of Missing Variables/Parameters, paramauto altenative and variable reordering

The missing parameter x can be supplied :

  • by another param instance param(x=np.pi/4) and combined with the previous instance via the operator + (solution 1);
  • by using a paramauto instance l_auto capable of reordering automatically fields at runtime (solution 2);
  • by reordering directly the variables in l (solution 3)

Solution 1: operator +

lfixed = param(x=np.pi/4) + l # the missing parameter must precede the definition of l (prepend)
lfixed
  -------------:----------------------------------------
              x: 0.7853981633974483
          line1: \${a}+1
               = ${a}+1
          line2: ['a', 'b']
               = ['a', 'b']
          line3: The first letter in  [...] e2} is "${line2[0]}"
               = The first letter in ['a', 'b'] is "a"
             ab: AB
               = AB
          line4: $ab
               = ab
          line5: ab
               = ab
          line6: sin(x)
               = sin(x)
          line7: ${sin(x)}
               = 0.7071067811865475
  -------------:----------------------------------------





parameter list (param object) with 9 definitions

Solution 2: Altenatively, the missing parameter can be appended to l and l can be converted to a paramauto instance. The instance paramauto shows eventually order errors, but it tries to reorder the fields at runtime to remove errors.

l.x = np.pi/4 # l is added
l_auto = l.toparamauto() ## note that the syntax **l is required to zip all variables
l_auto
WARNING: unable to interpret 3/9 expressions in "definitions"
  -------------:----------------------------------------
          line1: \${a}+1
               = ${a}+1
          line2: ['a', 'b']
          line3: The first letter in  [...] e2} is "${line2[0]}"
               = The first letter in ['a', 'b'] is "a"
             ab: AB
               = AB
          line4: $ab
               = ab
          line5: ab
               = ab
          line6: sin(x)
               = sin(x)
          line7: ${sin(x)}
               = <Error: Variable or  [...]  'x' is not defined>
              x: 0.7853981633974483
  -------------:----------------------------------------





parameter list (param object) with 9 definitions

Solution 3: Reordering fields using slicing and +.

lreordered = l[-1:] + l[:-1] # x has been appended at the previous step
lreordered
  -------------:----------------------------------------
              x: 0.7853981633974483
          line1: \${a}+1
               = ${a}+1
          line2: ['a', 'b']
               = ['a', 'b']
          line3: The first letter in  [...] e2} is "${line2[0]}"
               = The first letter in ['a', 'b'] is "a"
             ab: AB
               = AB
          line4: $ab
               = ab
          line5: ab
               = ab
          line6: sin(x)
               = sin(x)
          line7: ${sin(x)}
               = 0.7071067811865475
  -------------:----------------------------------------





parameter list (param object) with 9 definitions

note: param and struct instances can be indexed with lists [idx1,idx2,key1,key2...]:

  • with integers (0 being the first value, -1 the last one, -2 the one before the last...)
l[[-1,7]]
  -------------:----------------------------------------
              x: 0.7853981633974483
          line7: ${sin(x)}
               = 0.7071067811865475
  -------------:----------------------------------------





parameter list (param object) with 2 definitions
  • with a list of keys/names
l[["x","line7"]]
  -------------:----------------------------------------
              x: 0.7853981633974483
          line7: ${sin(x)}
               = 0.7071067811865475
  -------------:----------------------------------------





parameter list (param object) with 2 definitions
  • with a hybrid list mixing integers and key names
l[["x",-2]]
  -------------:----------------------------------------
              x: 0.7853981633974483
          line7: ${sin(x)}
               = 0.7071067811865475
  -------------:----------------------------------------





parameter list (param object) with 2 definitions

2.4 | Numeric examples

This example demonstrates basic numeric assignments:

  • p.a is assigned the float 10.0.
  • p.b is assigned the string "10", representing the number 10.
  • p.c is assigned a literal string "$10" (thus it is not interpreted as a number).
  • p.d is defined as a Python list of numbers.
  • p.f is created by converting p.d into a NumPy array (as a row vector).
p = param()                    # initialize param. Note that can use also `p = param(a=..., b=...)`
p.a = 10.0                     # number 10 as float
p.b = "10"                     # number 10 stored as a string
p.c = "$10"                    # characters "1" and "0" (not a number)
p.d  = [1.0, 0.2, 0.03, 0.004] # Python list
p.f  = np.array([p.d])         # Converts the list d into a row vector
p
  -------------:----------------------------------------
              a: 10.0
              b: 10
               = 10
              c: $10
               = 10
              d: [1.0, 0.2, 0.03, 0.004]
               = [1.0, 0.2, 0.03, 0.004]
              f: [1 0.2 0.03 0.004] (double)
  -------------:----------------------------------------





parameter list (param object) with 5 definitions

3 | Interpolation and Local Evaluation

3.1 | General Rules

This section demonstrates the interpolation and local evaluation capabilities:

  • Simple interpolation/substitution: ${var} is replaced by its content.
  • Mathematical expressions: Expressions within ${} are evaluated in place.
  • Local evaluation: Supports scalars, lists, NumPy arrays, and expressions.
  • Indexing: Use ${var[i]} or ${var[i,j]} to index into arrays or matrices.
  • Indexing: Use ${var[key]} to index dictionaries (do not quote key).
  • Escaping: Use \${...} to prevent execution of the interpolation.

Supported Operations

  • Indexing of lists, dicts, and arrays.
  • Mathematical operations using operators like +, -, *, /.**

Supported Mathematical Functions

  • Built-in functions: abs, round, min, max, sum, divmod.
  • Functions from the math module such as pi, e, nan, inf.
  • NumPy functions (with the np. prefix) and operators such as @ and .T.
  • Statistics functions: gauss, uniform, randint, choice.

3.2 | Scalar evaluations Example

The following examples show scalar evaluations:

  • p.g evaluates an expression that retrieves d[1] and adds b.
  • p.h evaluates an expression combining an element from the NumPy array f and an element from d.
  • p.i demonstrates that the notation ${d}[1] + ${b} is equivalent to a global evaluation.
  • p.j uses a similar pattern for matrix operations but mixing outer indexing is not recommended.

note: ` It is recommended not to mix global and local expressions (especially for indexing with NumPy arrays).

p.g = "${d[1]}+${b}"           # Retrieves `a[1]` = 0.2 and add 10 `0.2 + 10`
p.h = "${f[0,1]} + ${d[0]}"    # Evaluates as `0.2 + 1.0`
p.i = "${d}[1]+${b}"           # This notation also works but via global evaluation (equivalent to c for the part `${a}[1]`) `0.2 + 10`
p.j = "${f}[0,1] + ${d}[0]"    # However, it should be avoided with implicit NumPy arrays (see below)
# à la Matlab practice, type the variable to see its content
p # alternatively use repr(p)
  -------------:----------------------------------------
              a: 10.0
              b: 10
               = 10
              c: $10
               = 10
              d: [1.0, 0.2, 0.03, 0.004]
               = [1.0, 0.2, 0.03, 0.004]
              f: [1 0.2 0.03 0.004] (double)
              g: ${d[1]}+${b}
               = 10.2
              h: ${f[0,1]} + ${d[0]}
               = 1.2
              i: ${d}[1]+${b}
               = 10.2
              j: ${f}[0,1] + ${d}[0]
               = [[1.0,0.2,0.03,0.004 [...] 0.2, 0.03, 0.004][0]
  -------------:----------------------------------------





parameter list (param object) with 9 definitions

Evaluation and Display of Scalar Evaluations The following code evaluates all expressions (using p()) and displays:

  • The equivalence between inner and outer indexing for lists.
  • The differences for NumPy arrays, where only the first value (h) is numeric.
  • A warning to avoid using outer indexing outside of ${...}.
# evaluate all expressions and store them in s
s = p() # equivalent to s = p.eval() 
# c and e are equivalent
print("Inner and outer indexing are similar for list")
repr(p("g","i")) # show the evaluation of c and e
print('All values are numeric (float)')
prettyprint("g",s.g)
prettyprint("i",s.i)
# d and f are not equivalent
print("\n","Inner and outer indexing are not similar for NumPy arrays")
repr(p("h","j")) # show the evaluation of d and f
print("only the first value (h) is numeric")
prettyprint("h",s.h)
prettyprint("j",s.j)
print('avoid using outer indexing and keep it within "{}"')
Inner and outer indexing are similar for list
  -------------:----------------------------------------
              g: 10.2
              i: 10.2
  -------------:----------------------------------------
All values are numeric (float)
g = 10.2 (type: float)
i = 10.2 (type: float)

 Inner and outer indexing are not similar for NumPy arrays
  -------------:----------------------------------------
              h: 1.2
              j: [[1.0,0.2,0.03,0.004 [...] 0.2, 0.03, 0.004][0]
  -------------:----------------------------------------
only the first value (h) is numeric
h = 1.2 (type: float)
j = [[1.0,0.2,0.03,0.004]][0,1] + [1.0, 0.2, 0.03, 0.004][0] (type: str)
avoid using outer indexing and keep it within "{}"

3.3 | Nested Interpolations

Nested interpolation enables the use of a variable as an index or key:

  • ${list[${idx}]} with ${idx} a variable coding for a scalar index (for example 1 or "1") of a list.
  • ${dict["${key}"]} with ${key} a string coding for a key value of a `dict

note: Nested interpolation is not enabled for NumPy arrays defined in param text expressions. Tuples can be indexed

The following examples show the principles:

  • i.a is a list (not a vector).
  • i.b is an index of a defined as int (integer)
  • i.c returns the value for the next index b+1; the shift is done before interpolation.
  • i.d returns the same value with the shift applied after interpolation.
    • note: e is not used as a key as it can be confused with exp(1)
  • i.f defines a dict (dictionary) container
  • i.g is a key of i.f
  • i.h operates an operation *100 on the entry of f matching g; *note that quotes (here '${g}') are mandatory.
i = param()
i.a = [1,2,3]
i.b = 1
i.c = "${a[${b+1}]}"
i.d = "${a[${b}+1]}"   # as c with the increment performed outside the expression 
i.f = {"A":1, "B":2, "C":3}
i.g = "C"
i.h ="${f['${g}']*100}"
i
  -------------:----------------------------------------
              a: [1, 2, 3]
               = [1, 2, 3]
              b: 1
              c: ${a[${b+1}]}
               = 3
              d: ${a[${b}+1]}
               = 3
              f: {'A': 1, 'B': 2, 'C': 3}
              g: C
               = C
              h: ${f['${g}']*100}
               = 300
  -------------:----------------------------------------





parameter list (param object) with 7 definitions

note: Lists and dictionaries defined in param text expressions can be also indexed.

These features are exemplified by replacing Python expressions with text ones.

i.a = "[10,20,30,40]" # previous values are multiplied by  (without $, it is a list)
i.b = "2" # index +1
i.f = '{"A":100, "B":200, "C":300}'  # previous values are multiplied by 100
i
  -------------:----------------------------------------
              a: [10,20,30,40]
               = [10, 20, 30, 40]
              b: 2
               = 2
              c: ${a[${b+1}]}
               = 40
              d: ${a[${b}+1]}
               = 40
              f: {"A":100, "B":200, "C":300}
               = {'A': 100, 'B': 200, 'C': 300}
              g: C
               = C
              h: ${f['${g}']*100}
               = 30000
  -------------:----------------------------------------





parameter list (param object) with 7 definitions

3.4 | List and Nested Evaluations

This section demonstrates that variables can hold multiple values and that results are stored in lists:

note: The special character ! can be placed in front of expressions using lists as results (e.g., "!["${myvar}",${sum(myvar)+offset}]" to force recursive execution if it is not guessed internally.

param Text Example

In the following example, q.units is a literal string (with the $ prefix).

  • q.a is a list containing a literal string and another literal.
  • q.b uses interpolation to reference units.
  • q.c shows a string with interpolation and an escaped placeholder.
q = param()
q.units = "$si"                # literal string (no interpolation)
q.a = ["units","$lj"] 
q.b = ["units","${units}"]
q.c = "the ${a[0]} are ${units} as defined with \\${units}"
q
  -------------:----------------------------------------
          units: $si
               = si
              a: ['units', '$lj']
               = ['units', 'lj']
              b: ['units', '${units}']
               = ['units', 'si']
              c: the ${a[0]} are ${un [...] fined with \${units}
               = the units are si as  [...] efined with ${units}
  -------------:----------------------------------------





parameter list (param object) with 4 definitions

Text/Numeric Evaluation Example This example mixes text and numeric evaluations:

  • r.a is a numeric list.
  • r.b is provided as a string that represents a list with param expressions.
  • r.c uses the ! prefix to force recursive evaluation of expressions in lists.
  • r.d and r.e combine expressions from other list entries.
  • r.f is a list containing a mixture of expressions and literal values.
r = param()
r.a = [0,1,2]                   # this list is numeric and is already in Python
r.b = '[1,2,"test","${a[1]}"]'  # this list combines param expressions
r.c = '![1,2,"test","${a[1]}"]' # the `!` to force recursive evaluation of expressions in lists
r.d = "${b[3]}*10"              # the expressions can be combined together
r.e = "${c[3]}*10"              # the expressions can be combined together
r.f = ["${a[1]+a[2]}*3", 1,2,"test","${a[1]}", "${a[1]+a[2]}", "${1+2}", "b"]
r
  -------------:----------------------------------------
              a: [0, 1, 2]
               = [0, 1, 2]
              b: [1,2,"test","${a[1]}"]
               = [1, 2, 'test', '1']
              c: ![1,2,"test","${a[1]}"]
               = [1, 2, 'test', 1]
              d: ${b[3]}*10
               = 10
              e: ${c[3]}*10
               = 10
              f: ['${a[1]+a[2]}*3', 1 [...] 2]}', '${1+2}', 'b']
               = [9, 1, 2, 'test', 1, 3, 3, 'b']
  -------------:----------------------------------------





parameter list (param object) with 6 definitions

Additional Numeric Example

This example defines a param instance t with parameters o and p. Then:

  • t.a is constructed as a list containing ${o} and ${p}.
  • t.b computes the sum of the elements in a.
  • t.c calculates the maximum surface area using pi and the maximum value in a.
t = param(o=10,p=100)
t.a = "[${o},${p}]"
t.b = "the sum of a is ${sum(a)}"
t.c = "the maximum surface area is ${pi*max(a)**2}"
t
  -------------:----------------------------------------
              o: 10
              p: 100
              a: [${o},${p}]
               = [10, 100]
              b: the sum of a is ${sum(a)}
               = the sum of a is 110
              c: the maximum surface  [...] a is ${pi*max(a)**2}
               = the maximum surface  [...] s 31415.926535897932
  -------------:----------------------------------------





parameter list (param object) with 5 definitions

3.5 | Support of dict (dictionnaries)

By design, instances of struct (and, by extension, param()) are intended to serve as flexible containers that can hold any type of data—including class instances. However, evaluating advanced types, such as nested lists or dictionaries, is more challenging. At this stage, only minimal support is provided for these advanced types in param().

note: Dictionary fields can be defined either as native Python dictionaries or as strings representing dictionaries. One limitation is that nesting strings for key names (i.e., defining keys as strings within string representations) remains an open issue. This is because, according to PEP 3101 (the guidelines for Python's advanced string formatting), quoted keys are not accepted in formatted strings. Consequently, a compromise approach is currently implemented.

  • Local evaluation/interpolation within ${} follows PEP 3101: no quotes.
  • Global evaluation outside ${} uses conventional Pythonic syntax with quotes ' or "
  • A representation (global) of a dict d can be achieved outside local evaluation/interpolation with ${d} and {d}

Example Demonstrating Dictionary Support in param()

  • d.a is directly assigned a dictionary.
  • d.b is assigned a string that represents a dictionary.
  • d.c is assigned an expression referencing b.
  • d.d is assigned an expression that uses dictionary indexing.
  • d.f is assigned an expression that indexes a dictionary and performs an arithmetic operation.
    • note: e is not used as a key as it can be confused with exp(1)
  • d.g sets a dict using local interpolation/evaluation (PEP 3101 syntax).
  • d.h same definition using a global evaluation (conventional Pythonic syntax).
  • d.i global representation of h using ${h}, no possibility of interpolation
  • d.j idem using the Pythonic shorthand {h}, no possibility of interpolation

note: The definition of a dict within a param expression `${}' imposes fields to be used without quotes (adherence to PEP 3101)

d = param()
d.a = {'a': 'a', 'b': 2}
d.b = "{'a': 'a', 'b': 2}"
d.c = "${b}"
d.d = "${a[a]}"
d.f = "${c[b]}+1"
d.g = "${{bcopy: ${c[b]}, fcopy: ${f}}}"     # syntax within ${}, PEP 3101 holds (no quote) - local evaluation
d.h = "{'bcopy': ${c['b']}, 'fcopy': ${f}}"  # syntax outside ${} using global evaluation
d.i = "the value of h is: ${h}" # ${h} is used outside any evaluation/interpolation region
d.j = "the value of h is: {h}"  # 
d
  -------------:----------------------------------------
              a: {'a': 'a', 'b': 2}
              b: {'a': 'a', 'b': 2}
               = {'a': 'a', 'b': 2}
              c: ${b}
               = {'a': 'a', 'b': 2}
              d: ${a[a]}
               = a
              f: ${c[b]}+1
               = 3
              g: ${{bcopy: ${c[b]}, fcopy: ${f}}}
               = {bcopy: 2, fcopy: 3}
              h: {'bcopy': ${c['b']}, 'fcopy': ${f}}
               = {'bcopy': 2, 'fcopy': 3}
              i: the value of h is: ${h}
               = the value of h is: { [...] opy': 2, 'fcopy': 3}
              j: the value of h is: {h}
               = the value of h is: { [...] opy': 2, 'fcopy': 3}
  -------------:----------------------------------------





parameter list (param object) with 9 definitions

4 | Mathematical shorthands for vectors, matrices, 3D and 4D arrays

Lists cannot be directly used as vectors and must be converted into NumPy arrays before performing vectorized operations.

Note:This section separate non-mathematical syntaxes leading to list instances from mathematical ones generating NumPy instances.


4.1 | Non-Mathematical Syntaxes for Lists

Numeric lists and lists of strings are non-mathematical containers. They accept à la Matlab and Pythonic shorthands for rapid prototyping and definitions. They cannot be used directly in NumPy operations, and only their scalar values can be used.

These syntaxes are used without $[] and operates only with a minimum of interpolation. Their use is limited to simple expressions and constants (float, int, str):

  • start:stop and start:step:stop expands a list between start and stop values with a step step (default value = 1).
  • [a b; c d; e f] embeds lists row-wise [[a,b],[c,d],[e,f]], the result is not a NumPy object.
  • the operator * repeats a string or lists element-wise
  • the lists can be indexed with int, including with negative integers (e.g., -1 represents the last value, etc.)
  • the list content can be included in a string with {list} without using $

The following example illustrates rapid syntaxes applicable to lists

  • u.a defines the list [1,2,3,4,5] using a matlab shorthand 1:5
  • u.b expands a list similarly using variables start, step and stop
  • u.c defines explicitly a list combining values and a[2]
  • u.d generates a nested list including numbers exp(1) and pi
  • u.f repeats "~" f_repetitions times
  • u.g repeats ["p"] g_repetitions times
  • u.h extracts the last sublist of d
  • u.i embeds the value of h in a string
u = param()
u.a = "1:5"
u.start = "0"
u.step = "10"
u.stop = "50"
u.b = "${start}:${step}:${stop}"
u.c = "[1,30,500,${a[2]}]"
u.d = "[1 e; 3 pi; 4 5.0]"
u.f_repetitions = "20"
u.f = "'~'*${f_repetitions}"
u.g_repetitions = "3"
u.g = "['p']*${g_repetitions}"
u.h = "${d[-1]}"
u.i = "the values of h are {h}"
u
  -------------:----------------------------------------
              a: 1:5
               = [1, 2, 3, 4, 5]
          start: 0
               = 0
           step: 10
               = 10
           stop: 50
               = 50
              b: ${start}:${step}:${stop}
               = [0, 10, 20, 30, 40, 50]
              c: [1,30,500,${a[2]}]
               = [1, 30, 500, 3]
              d: [1 e; 3 pi; 4 5.0]
               = [[1, 2.7182818284590 [...] 53589793], [4, 5.0]]
  f_repetitions: 20
               = 20
              f: '~'*${f_repetitions}
               = ~~~~~~~~~~~~~~~~~~~~
  g_repetitions: 3
               = 3
              g: ['p']*${g_repetitions}
               = ['p', 'p', 'p']
              h: ${d[-1]}
               = [4, 5.0]
              i: the values of h are {h}
               = the values of h are [4, 5.0]
  -------------:----------------------------------------





parameter list (param object) with 13 definitions

4.2 | Mathematical/NumPy Syntax

The param() class offers several shorthands to seamlessly manipulate NumPy arrays using param text expressions:

  • 1D: $[1 2 3]np.atleast_2d(np.array([1,2,3]))
  • 2D: $[[1 2],[3 4]]np.array([[1,2],[3,4]])
  • 3D: $[[[1 2],[3 4]],[[5 6],[7 8]]]np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
  • 4D: $[[[[1 2]]]]np.array([[[[1,2]]]])

Variable References:

  • @{var} is equivalent to np.atleast_2d(np.array(${var})).

Transpose:

  • Use @{var}.T to transpose.

Function Application:

  • Apply a function to a vector with: np.foo(@var).

Slicing:

  • ${var[:,0]} converts a slice into a list.
  • @{var}[:,0] preserves the slice as a NumPy array.

Matlab Notations Supported:

  • Automatic row vector expansion using $[start:stop] or $[start:step:stop].
  • Spaces can be used to separate values row-wise.
  • Semicolons (;) separate rows.

Examples:

  • $[1, 2 ${var1} ; 4, 5 ${var2}] is equivalent to $[[1,2,${var1}],[4,5,${var2}]]
  • $[1;2; 3] becomes $[[1],[2],[3]]
  • [[-0.5, 0.5;-0.5, 0.5],[ -0.5, 0.5; -0.5, 0.5]] becomes $[[[-0.5,0.5],[-0.5,0.5]],[[-0.5,0.5],[-0.5,0.5]]]
  • $[[1,2;3,4],[5,6; 7,8]] becomes $[[[1,2],[3,4]],[[5,6],[7,8]]]
  • $[1, 2, 3; 4, 5, 6] becomes $[[1,2,3],[4,5,6]]

4.3 | Simple Definitions Example

This example demonstrates simple definitions using Matlab-style notations:

  • e.a uses $[1:3] to create the vector [1, 2, 3].
  • e.b uses $[1;2;3] to create a column vector [[1],[2],[3]].
  • e.c uses $[0.1:0.1:0.9] to create the vector [0.1, 0.2, ..., 0.9].
  • e.d defines a 2D matrix with $[1 2 3; 4 5 6].
e = param()
e.a = "$[1:3]"           # Becomes `[1, 2, 3]`
e.b = "$[1;2;3]"         # Becomes `[[1];[2];[3]]`
e.c = "$[0.1:0.1:0.9]"   # `[0.1, 0.2, 0.3, ..., 0.9]`
e.d = "$[1 2 3; 4 5 6]"  # `[[1,2,3], [4,5,6]]`
e
  -------------:----------------------------------------
              a: $[1:3]
               = [1 2 3] (int64)
              b: $[1;2;3]
               = [1 2 3]T (int64)
              c: $[0.1:0.1:0.9]
               = [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9] (double)
              d: $[1 2 3; 4 5 6]
               = [2×3 int64]
  -------------:----------------------------------------





parameter list (param object) with 4 definitions

4.4 | Matrix Operations Example

This example illustrates simple matrix operations:

  • m.a is a list of numeric values.
  • m.b is a NumPy array created from m.a (as a row vector).
  • m.c computes m.a * 2 (element-wise multiplication for a list).
  • m.d computes m.b * 2 (element-wise multiplication for a NumPy array).
  • m.e is the transpose of m.b.
  • m.f performs matrix multiplication between the transpose of m.b and m.b.
  • Variables m.g through m.o demonstrate various forms of expression evaluation, indexing, and string interpolation.
    • note: "@{j}+1" and "${j}+1" do not have the same meaning.
  • m.p uses a raw string (with the r prefix) to correctly escape ${a[0]}.
# Example with simple matrix operations
m=param()
m.a = [1.0, .2, .03, .004]
m.b = np.array([m.a])
m.c = m.a*2
m.d = m.b*2
m.e = m.b.T
m.f = m.b.T@m.b # Matrix multiplication for (3x1) @ (1x3)
m.g = "${a[1]}"
m.h = "${b[0,1]} + ${a[0]}"
m.i = "${f[0,1]}"
m.j = "${f[:,1]}"
m.k = "@{j}+1"  # note that "@{j}+1" and "${j}+1" do not have the same meaning
m.l = "${b.T}"  # note that b is already a NumPy array (no need to call @(b).T, which works also)
m.m = "${b.T @ b}"    # evaluate fully the matrix operation
m.n = "${b.T} @ ${b}" # concatenate two string-results separated by @
m.o ="the result is: ${b[0,1]} + ${a[0]}"
m.p = r"the value of \${a[0]} is ${a[0]}" # literal (r"  ") is used because "\$" is not a valid escape sequence, use "\\$" alternatively
m
  -------------:----------------------------------------
              a: [1.0, 0.2, 0.03, 0.004]
               = [1.0, 0.2, 0.03, 0.004]
              b: [1 0.2 0.03 0.004] (double)
              c: [1.0, 0.2, 0.03, 0.0 [...] 0, 0.2, 0.03, 0.004]
               = [1.0, 0.2, 0.03, 0.0 [...] 0, 0.2, 0.03, 0.004]
              d: [2 0.4 0.06 0.008] (double)
              e: [1 0.2 0.03 0.004]T (double)
              f: [4×4 double]
              g: ${a[1]}
               = 0.2
              h: ${b[0,1]} + ${a[0]}
               = 1.2
              i: ${f[0,1]}
               = 0.2
              j: ${f[:,1]}
               = [0.2, 0.040000000000 [...] 0001, 0.006, 0.0008]
              k: @{j}+1
               = [1.2 1.04 1.006 1.001] (double)
              l: ${b.T}
               = [[1.   ]
 [0.2  ]
 [0.03 ]
 [0.004]]
              m: ${b.T @ b}
               = [[1.0, 0.2, 0.03, 0. [...] , 0.00012, 1.6e-05]]
              n: ${b.T} @ ${b}
               = [[1.   ]
 [0.2  ]
 [ [...]  0.2   0.03  0.004]]
              o: the result is: ${b[0,1]} + ${a[0]}
               = the result is: 0.2 + 1.0
              p: the value of \${a[0]} is ${a[0]}
               = the value of ${a[0]} is 1.0
  -------------:----------------------------------------





parameter list (param object) with 16 definitions

4.5 | Slicing Example

This example demonstrates slicing operations:

  • s.a and s.b are scalar values.
  • s.c creates a NumPy vector from an operation.
  • s.n defines a vector using the $ syntax.
  • s.o1 creates a copy of n using the @{} notation.
  • s.o2 creates a NumPy vector directly.
  • s.o3 performs multiplication between two vectors.
  • s.d shows another multiplication example using a transpose.
  • s.f uses an explicit NumPy operation.
  • s.nT is the transpose of vector n.
  • s.m attempts an illegal operation (it will be kept as a string).
  • s.o shows the correct syntax for the operation.
  • s.p defines a 2D NumPy array.
  • s.q indexes the 2D array to retrieve an element.
  • s.r slices the 2D array, returning a list.
  • s.s applies an operation to the slice, preserving it as a NumPy array.
s = param(debug=True);
s.a = 1.0
s.b = "10.0"
s.c = "$[${a},2,3]*${b}" # Create a Numpy vector from an operation
s.n = "$[0,0,1]"         # another one
s.o1 = "@{n}"          # create a copy
s.o2 = "$[${a},2,3]" # create a Numpy vector
s.o3 = "@{o1} @ @{o2}.T" # multiplication between two vectots
s.d = "@{n}.T @ $[[${a},2,3]]" # another one
s.f = "($[${a},2,3]*${b}) @ ns.array([[0,0,1]]).T" # another one using explicitly NumPy
s.nT = "@{n}.T" # transpose of a vector/matrix
s.m = "${n.T}*2" # this operation is illegal and will be kept as a string
s.o = "@{n}.T*2" # this one is the correct one
s.p = "$[[1,2],[3,4]]" # Create a 2D Numpy array
s.q = "${p[1,1]}"   # index a 2D NumPy array
s.r = "${p[:,1]}"   # this is a valid syntax to get the slice as a list
s.s = "@{p}[:,1]+1" # use this syntax if you need apply an operation to the slice
s
  -------------:----------------------------------------
              a: 1.0
              b: 10.0
               = 10.0
              c: $[${a},2,3]*${b}
               = [10 20 30] (double)
              n: $[0,0,1]
               = [0 0 1] (int64)
             o1: @{n}
               = [0 0 1] (int64)
             o2: $[${a},2,3]
               = [1 2 3] (double)
             o3: @{o1} @ @{o2}.T
               = 3.0 (double)
              d: @{n}.T @ $[[${a},2,3]]
               = [3×3 double]
              f: ($[${a},2,3]*${b}) @ [...] s.array([[0,0,1]]).T
               = (np.atleast_2d(np.ar [...] s.array([[0,0,1]]).T
             nT: @{n}.T
               = [0 0 1]T (int64)
              m: ${n.T}*2
               = [[0]
 [0]
 [1]]*2
              o: @{n}.T*2
               = [0 0 2]T (int64)
              p: $[[1,2],[3,4]]
               = [2×2 int64]
              q: ${p[1,1]}
               = 4
              r: ${p[:,1]}
               = [2, 4]
              s: @{p}[:,1]+1
               = [3 5] (int64)
  -------------:----------------------------------------





parameter list (param object) with 16 definitions

4.6 | Advanced Example

This advanced example demonstrates operations such as:

  • Defining a vector V1.
  • Computing V2 as V1 + 1.
  • Performing matrix multiplication between the transpose of V1 and V2.
  • Creating a diagonal matrix from the resulting product.
  • Calculating the eigenvalues and eigenvectors of the resulting matrix.
  • Displaying the first eigenvalue in a formatted string.
a = param(debug=True)
a.V1 = "$[1.0,0.2,0.03]"
a.V2 = "@{V1}+1"
a.V3 = "@{V1}.T @ @{V2}"
a.V4 = "np.diag(@{V3})"
a.V5 = "np.linalg.eig(@{V3})"
a.out = "the first eigenvalue is: ${V5.eigenvalues[0]}"
a
  -------------:----------------------------------------
             V1: $[1.0,0.2,0.03]
               = [1 0.2 0.03] (double)
             V2: @{V1}+1
               = [2 1.2 1.03] (double)
             V3: @{V1}.T @ @{V2}
               = [3×3 double]
             V4: np.diag(@{V3})
               = [2 0.24 0.0309] (double)
             V5: np.linalg.eig(@{V3})
               = EigResult(eigenvalue [...] 332, -0.06057363]]))
            out: the first eigenvalue [...] ${V5.eigenvalues[0]}
               = the first eigenvalue [...] s: 2.270900000000001
  -------------:----------------------------------------





parameter list (param object) with 6 definitions

5 | Global evaluation

A global evaluation is attempted for all expressions after interpolation and local evaluations have been performed. If the global evaluation does not raise any error, the evaluated result is kept; otherwise, the original interpolated expression is preserved. Since several expressions involving matrix operations require global evaluation (i.e., outside of ${...} or @{...}), it is recommended to define mathematically valid expressions and, if needed, use separate variables to store results as text.

5.1 | Global Evaluation Example with NumPy

This example demonstrates global evaluation:

  • u.p creates a 2D NumPy array.
  • u.q indexes the array to retrieve an element.
  • u.r adds 1 to the second column of p.
  • u.s reshapes a slice and performs matrix multiplication in a Matlab-like manner.
  • u.t computes the eigenvalues and eigenvectors of the resulting matrix.
  • u.w calculates the sum of the first two eigenvalues.
  • u.x horizontally concatenates a literal with the sum of the eigenvalues.
u = param(debug=True)
u.p = "$[[1, 2], [3, 4]]"      # Create a 2D NumPy array
u.q = "${p[1, 1]}"             # Indexing: retrieves 4
u.r = "@{p}[:,1] + 1"          # Add 1 to the second column
u.s = "@{p}[:, 1].reshape(-1, 1) @ @{r}" # perform p(:,1)'*s in Matlab sense
u.t = "np.linalg.eig(@{s})"
u.w = "${t.eigenvalues[0]} + ${t.eigenvalues[1]}" # sum of eigen values
u.x = "$[[0,${t.eigenvalues[0]}+${t.eigenvalues[1]}]]" # horizontal concat à la Matlab
u
  -------------:----------------------------------------
              p: $[[1, 2], [3, 4]]
               = [2×2 int64]
              q: ${p[1, 1]}
               = 4
              r: @{p}[:,1] + 1
               = [3 5] (int64)
              s: @{p}[:, 1].reshape(-1, 1) @ @{r}
               = [2×2 int64]
              t: np.linalg.eig(@{s})
               = EigResult(eigenvalue [...] 576, -0.89442719]]))
              w: ${t.eigenvalues[0]}  [...]  ${t.eigenvalues[1]}
               = 26.0
              x: $[[0,${t.eigenvalues [...] {t.eigenvalues[1]}]]
               = [0 26] (double)
  -------------:----------------------------------------





parameter list (param object) with 7 definitions

5.2 | Global Evaluation Example with Various Matrix and ND- Operations

This example demonstrates various matrix operations:

  • A list l is defined.
  • Vectors and matrices (a, b, c) are created using Matlab-style notations.
  • Element-wise and matrix operations are performed.
  • More complex structures (x0, y0, z0) are defined using multiple operations.
  • Finally, the results are flattened into vectors X0, Y0, and Z0.
v = param()
v.l = [1e-3, 2e-3, 3e-3]    # l is defined as a list
v.a = "$[1 2 3]"            # a is defined with  Matlab notations
v.b = "$[1:3]"              # b is defined with  Matlab notations
v.c = "$[0.1:0.1:0.9]"      # c is defined with  Matlab notations
v.scale = "@{l}*2*@{a}"     # l is rescaled
v.x0 = "$[[[-0.5, -0.5],[-0.5, -0.5]],[[ 0.5,  0.5],[ 0.5,  0.5]]]*${scale[0,0]}*${a[0,0]}"
v.y0 = "$[[[-0.5, -0.5],[0.5, 0.5]],[[ -0.5,  -0.5],[ 0.5,  0.5]]]*${scale[0,1]}*${a[0,1]}"
v.z0 = "$[[-0.5 0.5 ;-0.5 0.5],[ -0.5,  0.5;  -0.5,  0.5]]*${l[2]}*${a[0,2]}"
v.X0 = "@{x0}.flatten()"
v.Y0 = "@{y0}.flatten()"
v.Z0 = "@{z0}.flatten()"
v
  -------------:----------------------------------------
              l: [0.001, 0.002, 0.003]
               = [0.001, 0.002, 0.003]
              a: $[1 2 3]
               = [1 2 3] (int64)
              b: $[1:3]
               = [1 2 3] (int64)
              c: $[0.1:0.1:0.9]
               = [0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9] (double)
          scale: @{l}*2*@{a}
               = [0.002 0.008 0.018] (double)
             x0: $[[[-0.5, -0.5],[-0. [...] cale[0,0]}*${a[0,0]}
               = [[2×2 matrix] [2×2 matrix]] (2×2×2 double)
             y0: $[[[-0.5, -0.5],[0.5 [...] cale[0,1]}*${a[0,1]}
               = [[2×2 matrix] [2×2 matrix]] (2×2×2 double)
             z0: $[[-0.5 0.5 ;-0.5 0. [...] ]]*${l[2]}*${a[0,2]}
               = [[2×2 matrix] [2×2 matrix]] (2×2×2 double)
             X0: @{x0}.flatten()
               = [-0.001 -0.001 -0.001 -0.001 0.001 0.001 0.001 0.001] (double)
             Y0: @{y0}.flatten()
               = [-0.008 -0.008 0.008 0.008 -0.008 -0.008 0.008 0.008] (double)
             Z0: @{z0}.flatten()
               = [-0.0045 0.0045 -0.0045 0.0045 -0.0045 0.0045 -0.0045 0.0045] (double)
  -------------:----------------------------------------





parameter list (param object) with 11 definitions

For any question, contact INRAE\olivier.vitrac@agroparistech.fr | last revision $2025-02-08$