TRIQS/nda 1.3.0
Multi-dimensional array library for C++
|
In this example, we show how use, spot and define symmetries in nda arrays.
All the following code snippets are part of the same main
function:
Here, we have defined some basic quantities/types:
idx_t
defines a multi-dimensional index type (in our case it is 2-dimensional).sym_t
is a tuple type consisting of a multi-dimensional index and an nda::operation.sym_func_t
is a callable type that takes a multi-dimensional index and returns a sym_t
object. The index in the sym_t
object refers to an element which is related to the element at the original index by the nda::operation.Let's take a look at a simple example.
We start by defining a hermitian symmetry for our matrix. A symmetry is simply an object of type sym_func_t
that specifies how elements in an array are related to one another.
As you can see, h_symmetry
is a callable object that takes an idx_t
and returns a sym_t
object. The two lines have the following meaning:
(i,j)
are related to the element (j,i)
by a complex conjugation.Once all of the desired symmetries have been defined, we can construct an nda::sym_grp object:
The constructor of the symmetry group takes an array and a list of symmetries as input. It uses the shape of the array and the symmetries to find all symmetry classes.
For our \( 3 \times 3 \) hermitian matrix, there should be 6 symmetry classes corresponding to the 6 independent elements of the matrix:
Output:
A symmetry class contains all the elements that are related to each other by some symmetry. It stores the flat index of the element as well as the nda::operation specifying how it is related to a special representative element of the class. In practice, the representative element is simply the first element of the class.
Let us print all the symmetry classes:
Output:
Keep in mind that elements of our matrix have the following flat indices:
Output:
Now we can see why the elements 0, 4 and 8 are in their own symmetry class and elements 1 and 3, 2 and 6 and elements 5 and 7 are related to each other via a complex conjugation.
One of the features of symmetry groups is that they can be used to initialize or assign to an existing array with an initializer function satisfying the nda::NdaInitFunc concept:
An nda::NdaInitFunc object should take a multi-dimensional index as input and return a value for the given index. Since we are working with complex matrices, the return type in our case is std::complex<double>
.
count_eval
simply counts the number of times init_func
has been called.
Let's initialize our array:
Output:
As expected the array is hermitian.
The advantage of using symmetry groups to initialize an array becomes clear when we check how often our init_func
object has been called:
Output:
It has been called exactly once for each symmetry class.
In our simple example, this does not really make a difference but for large arrays where the number of symmetry classes is much smaller than the size of the array and where evaluating init_func
is expensive, this can give a considerable speedup.
We have already learned above about representative elements of symmetry classes. The symmetry group object gives us access to those elements:
Output:
The representative elements in our case simply belong to the upper triangular part of the matrix.
Representative data can also be used to initialize or assign to an array. Let's try this out:
Output:
Here, we first multiplied the original representative data by 2 and then initialized a new array with it.
The nda::sym_grp class provides a method that let's us symmetrize an existing array and simultaneously obtain the maximum symmetry violation.
For each symmetry class, it first calculates the representative element by looping over all elements of the class, applying the stored nda::operation to each element (for a sign flip and a complex conjugation this is equal to applying the inverse operation) and summing the resulting values. The sum divided by the number of elements in the symmetry class gives us the representative element.
It then compares each element in the class to the representative element, notes their absolute difference to obtain the maximum symmetry violation and sets the element to the value of the representative element (modulo the nda::operation).
Symmetrizing one of our already hermitian matrices shouldn't change the matrix and the maximum symmetry violation should be zero:
Output:
Let's change one of the off-diagonal elements:
Output:
The matrix is not hermitian anymore and therefore violates our initial h_symmetry
.
If we symmetrize again:
Output:
We can see that the symmetrized matrix is now different from the original and that the maximum symmetry violation is not equal to zero.