In the previous sections we already had numeric variables (my_var) and strings (my_name). Variables are automatically generated when a value is assigned to them. Unlike many other dynamically interpreted languages, in NumeRe numeric variables cannot be converted to strings or vice versa, i.e., variables remain for the current session in the type in which they were created.
However, it is possible to convert numeric variables from pure real values to complex values and back again. To do this, it is only necessary to add an imaginary component or remove it by setting it to zero.
What is of course also possible is to convert the variable values to other types. This can be done either with the #, or by using one of the functions to_string(), to_value(), valtostr(), where their behavior differs partly strongly.
<- to_string(my_var)
-> ans = "my_var"
<- to_value("1+2+3")
-> ans = 6
<- valtostr(my_var)
-> ans = "4.712389"
Numerical variables and strings can also represent arrays, vectors, matrices and higher-order tensors. Arrays are created by assigning multiple values to a variable at once, or by subsequently adding further entries to the array using the linear index operator (VAR[i]).
<- array = {1, 2, 3}
-> ans = { 1, 2, 3}
<- str_array = "Hello"
-> ans = "Hello"
<- str_array[2] = "World"
-> ans = "World"
<- str_array
-> ans = {"Hello", "World"}
Unlike arrays, vectors, matrices and tensors contain additional dimensional information; in other words, vectors and the like are also arrays and therefore also have the linear index operator. To create vectors, matrices and higher-order tensors, you can use dedicated functions (reshape(), matfc(), matfr()). For compatibility reasons, however, an array can also be implicitly converted into a column vector.
<- my_mat = matfc({1,2,3}, {4,5,6}, {7,8,8})
/ 1, 4, 7 \
-> ans = | 2, 5, 8 |
\ 3, 6, 8 /
The variable my_mat contains the generated 3x3 matrix. The linear index operator can be used to read values along the columns. However, this does not return the dimensional information. To extract or write sub-matrices from the matrix, the multidimensional index operator (VAR(r,c,...)) can be used.
<- my_mat[2:4]
-> ans = { 2, 3, 4}
<- my_mat(2, 2)
-> ans = 5
<- my_mat(2:3, 2)
-> ans = / 5 \
\ 6 /
You can then, for example, determine the determinant and subsequently also the inverse of the matrix using the function invert():
<- det(my_mat)
-> ans = 3
<- invert(my_mat)
/ -2.666667, 3.333333, -1 \
-> ans = | 2.666667, -4.333333, 2 |
\ -1, 2, -1 /
You can then verify that it is the inverse of the matrix by performing a matrix multiplication with the operator ** (the normal multiplication operator * performs a component-wise multiplication):
<- my_mat ** invert(my_mat)
/ 1, 1.776357e-15, 0 \
-> ans = | -8.881784e-16, 1, 0 |
\ 0, 0, 1 /
As you can see, rounding errors occur here. However, these are in the order of magnitude of the relative accuracy of the underlying double data type and therefore cannot be avoided completely. You can also use the ** operator to write a matrix-vector multiplication. For example, if we wanted to rotate the vector {1,2,3} by 22.5° around the z-axis, we could write:
<- a = radian(22.5)
-> ans = 0.3926991
<- rotmat(a, 3) ** {1, 2, 3}
/ 0.1585127 \
-> ans = | 2.230442 |
\ 3 /
In addition, there are a larger number of other matrix functions, such as the calculation of eigenvalues with eigenvals() or the eigenvector system with eigenvects().
The notation of the following, more complex, data types is based on the principle: What is different, should also look different. One recognizes special data types in NumeRe accordingly directly by their call.
Tables are addressed by the expression TABLE(), i.e. with parentheses. So they have a similar syntax as functions. To create a table you use the command new or you copy an extract of an already existing table:
<- new my_table()
<- copied_table() = my_table()
Tables are used to manage multi-variant data sets, i.e. multiple data series to which one or more properties can be assigned. These can be simple numerical values, e.g. the measured current I at an applied voltage V across a resistor R. They can also be more complex data series that can contain numerical values and character strings mixed together, such as a register of persons with names, age, address and body size.
Tables represent data series in rows, so that the properties are available in columns. Each column can contain a different data type. As an example, here is a data row from the aforementioned person register:
Name Age Address Height
"Erik" 99 "Third rock from the sun, 123" 1.88
[...]
To extract a column from the dataset, my_table(:, 4) can be used. In the example above, this would then return the fourth column, i.e. the heights of all persons. Alternatively, columns can also be addressed via their name, which corresponds to my_table(:, "Height") in our example. One can use that for filling the table:
<- my_table(:, "Name") = {"Erik", "Vicky", "Max", "Thessa", ...};
<- my_table(:, "Age") = {99, 98, 97, 96, ...};
<- my_table(:, "Address") = {"Third rock from the sun, 123", ...};
<- my_table(:, "Height") = {1.88, 1.46, 1.85, 1.23, ...};
To extract a data row, you simply reverse the logic and write my_table(1, :). This will return the entries of the first row of the table (headers do not count as rows). Of course you can also restrict the other dimension in both cases, e.g. my_table(1, 1:3) to read only name, age and address of the first row.
<- my_table(:, 4)
-> ans = {1.88, 1.46, 1.85, 1.23, ...}
<- my_table(1, :)
-> ans = {"Erik", 99, "Third rock from the sun, 123", 1.88}
<- my_table(1, "Name":"Address")
-> ans = {"Erik", 99, "Third rock from the sun, 123"}
Just as you read data from the table, you can also overwrite existing with new data:
<- my_table(1, "Age":"Height") = {88, "Fourth rock from the sun, 11", 2.12}
-> ans = {88, "Fourth rock from the sun, 11", 2.12}
You can also read and write the column headers if you use # as row index:
<- my_table(#, :)
-> ans = {"Name", "Age", "Address", "Height"}
A few final pieces of information:
Tables have an autosave function. They are regularly stored in the numere.cache file and are therefore, unlike all other data types, automatically available again on restart.
To remove tables and free the memory again, you use the command remove my_table(). If you only want to delete the contents of the table, use clear my_table() (or delete my_table(:, :) if you only want to delete parts of the table). To suppress the confirmation you can append the parameter -ignore: clear my_table() -ignore
Objects are a group of data structures that, through their existing methods, can interact with the logic into which they are integrated. They can be broadly divided into two categories:
Containers, which essentially organise data into some kind of structure; these include, in particular, the Queue, the Stack, the Dictionary and the DictStruct
Functional objects, which provide a central function and manage a state for this purpose. Examples of this category include the File object, the Logger object and the Path object.
Objects are generally created by dedicated functions and can then be assigned to variables. They subsequently provide methods that can be used to access their attributes and functionalities.
<- my_dict = dict({"Hello", "My name is"}, {"World!", "Erik"});
<- my_dict.at("Hello")
-> ans = "World!"
<- my_file = file("<scriptpath>/examples/data/amplitude.dat");
<- my_file.readline
-> ans = "#Amplitudenmessung mit verschiedenen Dämpfungen"
Clusters are, as the name suggests, unstructured data. In expressions, they are referred to by using CLUSTER{}, i.e. the name with curly braces. A cluster can contain numeric values, strings and further clusters mixed together, and is therefore well suited to represent the contents of a data series of a table. In fact, the read data of a data series is internally already a cluster. To create a cluster, it is sufficient to assign one or more values to an identifier with curly braces. An explicit command to create is not needed:
<- my_cluster{} = {_pi, "Hello, World!", 42}
-> ans = {3.141593, "Hello, World!", 42}
Clusters are hierarchical and multi-dimensional, meaning the indices for reading and writing can get potentially complicated. In this example, however, a single dimension is sufficient:
<- my_cluster{2:}
-> ans = {"Hello, World!", 42}
In this example, you have output all elements in the cluster starting with element two.