The
param()
class, located inpizza.private.mstruct
is an essential low-level class for 🍕 Pizza³. It used as parent class for data containers used byscript
,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.
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.
param
andparamauto
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 toparamauto
, with the only difference being the instance initialization:p = paramauto()
instead ofp = param()
.
✍️ 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")
🟰 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
🧮 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 = "! ..."
.
🔢 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]"
- 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",...)
🤝 param
instances behave like lists or collections:
p[5]
→ Returns the 6th element ofp
.p[[5]]
→ Returns a substructure with the 6th element.p[1:10:2]
orp[[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.
📦 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
➡️ 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) |
💾 Save and load param
instances:
📥 Save to disk
p.write(filename)
📤 Load from disk
p = struct.read(filename).toparam()
-
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
orgetattr(p, "var")
returns the raw value of${var}
. -
To obtain a static structure, use
s = p()
ors = 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 withexp(1)
.
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.
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 fromline2
.ab
: A variable holding the literal"AB"
.line4
: A literal expression where the$
prefix preserves the literal value ofab
.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 sincex
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
The missing parameter x
can be supplied :
- by another
param
instanceparam(x=np.pi/4)
and combined with the previous instance via the operator+
(solution 1); - by using a
paramauto
instancel_auto
capable of reordering automatically fields at runtime (solution 2); - by reordering directly the variables in
l
(solution 3)
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
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
- 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
This example demonstrates basic numeric assignments:
p.a
is assigned the float10.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 convertingp.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
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 quotekey
). - 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 aspi
,e
,nan
,inf
. - NumPy functions (with the
np.
prefix) and operators such as@
and.T
. - Statistics functions:
gauss
,uniform
,randint
,choice
.
The following examples show scalar evaluations:
p.g
evaluates an expression that retrievesd[1]
and addsb
.p.h
evaluates an expression combining an element from the NumPy arrayf
and an element fromd
.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 "{}"
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 alist
.${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 ofa
defined asint
(integer)i.c
returns the value for the next indexb+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 withexp(1)
- note:
i.f
defines adict
(dictionary) containeri.g
is a key ofi.f
i.h
operates an operation*100
on the entry off
matchingg
; *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
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 referenceunits
.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
andr.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
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 ina
.t.c
calculates the maximum surface area usingpi
and the maximum value ina
.
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
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}
d.a
is directly assigned a dictionary.d.b
is assigned a string that represents a dictionary.d.c
is assigned an expression referencingb
.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 withexp(1)
- note:
d.g
sets adict
using local interpolation/evaluation (PEP 3101 syntax).d.h
same definition using a global evaluation (conventional Pythonic syntax).d.i
global representation ofh
using${h}
, no possibility of interpolationd.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
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.
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
andstart:step:stop
expands a list betweenstart
andstop
values with a stepstep
(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 shorthand1:5
u.b
expands a list similarly using variablesstart
,step
andstop
u.c
defines explicitly a list combining values anda[2]
u.d
generates a nested list including numbersexp(1)
andpi
u.f
repeats "~"f_repetitions
timesu.g
repeats["p"]
g_repetitions
timesu.h
extracts the last sublist ofd
u.i
embeds the value ofh
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
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 tonp.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]]
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
This example illustrates simple matrix operations:
m.a
is a list of numeric values.m.b
is a NumPy array created fromm.a
(as a row vector).m.c
computesm.a * 2
(element-wise multiplication for a list).m.d
computesm.b * 2
(element-wise multiplication for a NumPy array).m.e
is the transpose ofm.b
.m.f
performs matrix multiplication between the transpose ofm.b
andm.b
.- Variables
m.g
throughm.o
demonstrate various forms of expression evaluation, indexing, and string interpolation.- note:
"@{j}+1"
and"${j}+1"
do not have the same meaning.
- note:
m.p
uses a raw string (with ther
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
This example demonstrates slicing operations:
s.a
ands.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 ofn
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 vectorn
.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
This advanced example demonstrates operations such as:
- Defining a vector
V1
. - Computing
V2
asV1 + 1
. - Performing matrix multiplication between the transpose of
V1
andV2
. - 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
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.
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 ofp
.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
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
, andZ0
.
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$