HILA
|
Field is the most important Datatype offered by HILA. The Field is a container of lattice fields, and is the general object we evolve and iterate over. The Field can be comprised of either Standard types or Basic types listed above.
To see all the possible methods of a Field see the class page which lists comprehensive documentation.
By default Field variables are constructed uninitialized. Fields can also be constructed with a constant or another Field:
Assginment to Field variables is possible either from another Field or from a single element which are assignment compatible. Assignment from 0 is always possible.
Uninitialized Field does not take any storage, it is automatically allocated on first use. Fields are destructed when they run out of scope.
The principal traversal of the lattice is with site loops onsites(Parity)
, and a special location identifier X
(effectively a new keyword). Within the onsites loop X
location identifier represents the current location of a point that is currently being indexed. Note that the X
identifier is only defined within onsites loops. Access operation f[X]
can be applied only to field variables, and has the type of the field element. X
is of type X_index_type. All available methods can be seen in the class documentation. Note that the X_index_type class is only a dummy class with decelerations, yet hilapp handles defining the contents of the methods, so the source is not available.
To illustrate how looping over a Field object works we will first define a few fields:
For a field comprised of square-matrix elements, real numbers are algebraically interpreted as \( 2 = 2\cdot\mathbb{1}\), multiples of identity matrix.
We can now iterate over the fields with the onsites loop:
Above we linearly add each element at X
from g to each element at X
in f with an additional \(2\cdot\mathbb{1}\) at each site. The ALL statement is a Parity which indicates that we will iterate over the whole Field. Other options are EVEN and ODD which indicate that we only iterate over the even or odd sites.
Similarly we can write this same statement in the following short form:
Above you can also notice the simplest algebraic form, which allows for applying linear operations of the fields. The main difference is in sequencing: the first form goes through the lattice sites in one site loop, whereas the second stores the result of 2 + g to a temporary field variable which is copied to f (in this case std::moved). The site loop form is faster since it minimizes temporaries and memory accesses.
Now to demonstrate a more complicated onsites loop we will apply neighboring effects.
On the first line in the onsites loop we define a variable which we assign the e_x
neighbor to. As we can see, variables can be defined within the scope of the loop.
Non nearest neighboring indexing also works, which is illustrated on the fourth line of the onsites loop.
On line 6 of the onsites loop we can also see that if statements can be used to apply limitations, in the above case we use it to index a slice of the field.
Because f[X]
is of type field element (in this case mytype), the methods defined for the element type can be used. Within onsites loop f[X].dagger()
is ok, f.dagger()
is not. f[X]
also serves as a visual identifier for a field variable access.
External non-Field variables cannot be changed inside onsites loops (except in reductions, see below)
Reduction on a single Field variable can be done with functions:
T Field<T>::sum()
: sum of all field elements.T Field<T>::product()
: product of field elements.T Field<T>::min()
: minimum elementT Field<T>::max()
: maximumsum()
and product()
take optional arguments: sum(Parity par = ALL)
or sum(Parity par = ALL ,bool allreduce = true)
. Parity gives the parity of sites over which the reduction is done, and if allreduce = false the reduction result is only sent to rank 0. Defaults are parity=ALL and allreduce = true.
min()
and max()
have signatures
T Field<T>::min(Parity p)
: minimum over ParityT Field<T>::min(CoordinateVector &loc)
: also location of the min valueT Field<T>::min(Parity p, CoordinateVector &loc)
: location over parityNOTE: sum() can be used for any allowed Field variable, product(), min() and max() only over integer or floating point Fields (C++ arithmetic types).
Standard reduction is done by using compound operators +=
or *=
and regular variables in onsites()-loops:
By default the reduction is sent to all MPI ranks (allreduce
, implemented with MPI_Allreduce()
). If reduction only to rank 0 is needed, hila::set_allreduce(false)
can be inserted before onsites()
(uses MPI_Reduce()
). This may give higher performance.
Allreduce remains off only for the immediately following onsites()-loop, thus hila::set_allreduce(false)
has to be inserted on each reduction loop where one wishes to have allreduce off.
Sum reduction +=
can use any hila Field element type, product reduction *=
only floating point or integer variables (C++ arithmetic types).
Reductions can be accumulated using the same reduction variable:
As an implementation detail, the initial value of the reduction variable is taken from rank 0, i.e. it is zeroed on other ranks.
Individual data elements of a reduction variable can be accumulated independently:
Using Vector<T> -types for histograms (as above) is inefficient for longer vectors. Use ReductionVector instead.
Using type Reduction<T>
, where T
can be any hila Field element type, enables delayed and non-blocking reductions which are not possible with the standard reductions. These are optimisations, the results of the reductions are not affected. This type implements only the sum reduction (+=
).
The class Reduction<T>
implements the following methods (see examples below):
Option setting, called before reduction loop:
Reduction<T> & Reduction<T>::allreduce(bool on = true)
: set allreduce on/off (default: on)Reduction<T> & Reduction<T>::delayed(bool on = true)
: set delayed on/off (default: off)Reduction<T> & Reduction<T>::nonblocking(bool on = true)
: set nonblocking on/off (default: off)These return a reference to the reduction variable, so that these can be chained.
T Reduction<T>::value()
: get the final value of the reduction.void Reduction<T>::reduce()
: complete the reduction for non-blocking and/or delayed reduction. For other reductions does nothing. Is implicitly called by value()
if not called before.void Reduction<T>::start_reduce()
: start the non-blocking reduction (if non-blocking is on). Must be called by all MPI ranks.=
: initializes or resets the value.+=
: reduction operator inside onsites()
.The first call of value()
or reduce()
must be called by all ranks, otherwise the program deadlocks. Subsequent calls to value()
just return the previously reduced value.
Delayed reduction** means that the MPI reduction is delayed until the final result is desired:
Reduction across MPI ranks is done in call r.value()
. Thus, there is only one (MPI) reduction operation, whereas the standard method (using Complex<double> r
) would perform MPI reduction after each onsites()
. A The function r.value()
must be called by all ranks or program deadlocks. The result of the reduction is not affected.
Non-blocking reduction** enables overlapping MPI communication and computation (implemented using MPI_Ireduce()
). This potentially gives higher performance.
Non-blocking and delayed reduction can be combined. In this case, the reduction should be started with start_reduce()
-method:
If r.start_reduce()
is omitted, it is implicitly done at r.value()
, ensuring correct results. However, the possible benefit of overlapping communication and computation is lost.
ReductionVector enables (weighted) histogramming or general reduction to arrays. As an example, measuring "wall-to-wall" correlation function
The type implements the methods (for clarity, using placeholder variable name a)
ReductionVector<T> a(int size)
: initializes and allocates vector of size elementsa.allreduce(bool on = true)
: set allreduce on/off (default: on)a.delayed(bool on = true)
: set delayed on/off (default: off)a.is_allreduce()
, a.is_delayed()
.a[i]
where i is int : access the element number i.a.reduce()
: in delayed reduction completes the reduction. This must be called before the the result is used.a = <value>
: fills in a with the valuestd::vector<T> a.vector()
: returns std::vector of the datastd::vector<T>
:size_t a.size()
: size of aa.clear()
: zero all elements (equivalent to a = 0
)a.resize(new_size)
: resize vector aa.push_back(T val)
: insert value val, increasing size of aa.pop_back()
: remove last element, decreasing size of aT & front()
: first elementT & back()
: last elementExample of delayed ReductionVector use: make a histogram with 100 bins of N fields G[N], where we know the field values are between 0 and fmax:
NOTE: ReductionVector indexing has no protection. Using index past the ends of the array will cause trouble!
Assignment and manipulation of external variables are illustrated below:
Field::shift operations allow shifting all elements by a certain displacement vector v. Even and Odd elements cannot be shifted with Field::shift method
Access field at a single point: f[CoordinateVector]
. This can be used only outside site loops.