HILA
Loading...
Searching...
No Matches
field.h
Go to the documentation of this file.
1/**
2 * @file field.h
3 * @brief This files containts definitions for the Field class and the classes required to define
4 * it such as field_struct.
5 */
6#ifndef FIELD_H
7#define FIELD_H
8
9#include <sstream>
10#include <iostream>
11#include <string>
12#include <cstring> //Memcpy is here...
13#include <math.h>
14#include <type_traits>
15
16#include "plumbing/defs.h"
18#include "plumbing/lattice.h"
20
21#include "plumbing/backend_vector/vector_types.h"
22
23#include "plumbing/com_mpi.h"
24
25
26// This is a marker for hilapp -- will be removed by it
27#define onsites(p) for (Parity par_dummy__(p); par_dummy__ == EVEN; par_dummy__ = ODD)
28
29template <typename T>
30class Field;
31
32template <typename T>
33void ensure_field_operators_exist();
34
35#include "plumbing/ensure_loop_functions.h"
36
37/**
38 * @class Field
39 * @brief The field class implements the standard methods for accessing Fields. Hilapp replaces the
40 * parity access patterns, Field[par] with a loop over the appropriate sites. Since the Field class
41 * is one of the more important functionalities of HILA, extensive general instructions on the Field
42 * class can be read at [HILA Functionality](@ref field_documentation) documentation.
43 *
44 * @details The Field class contains member functions used by hilapp, which are marked internal, as
45 * well as members that are useful for application developers.
46 *
47 * The Field mainly implements the interface to the Field and not the content.
48 *
49 * The Field contains a pointer to Field::field_struct, which implements MPI communication of the
50 * Field boundaries. Though generally the application developer does not need to worry about the
51 * Field::field_struct class, hence it is marked as internal and it's documentation cannot be viewed
52 * in the Web documentation.
53 *
54 * The Field::field_struct points to a field_storage, which is defined by each backend. It
55 * implements storing and accessing the Field data, including buffers for storing haloes returned
56 * from MPI communication.
57 *
58 * @tparam T Field content type
59 */
60template <typename T>
61class Field {
62
63 public:
64 enum class gather_status_t : unsigned { NOT_DONE, STARTED, DONE };
65
66 private:
67 /**
68 * @internal
69 * @class field_struct
70 * @brief Stores Field class data and communication parameters for said data
71 *
72 * Generally the user need not worry about this class.
73 * @todo field-specific boundary conditions
74 */
75 class field_struct {
76 public:
77 field_storage<T> payload; // TODO: must be maximally aligned, modifiers - never null
78 int lattice_id;
79#ifdef VECTORIZED
80 // get a direct ptr from here too, ease access
82#endif
83 unsigned assigned_to; // keeps track of first assignment to parities
84 gather_status_t gather_status_arr[3][NDIRS]; // is communication done
85
86 // neighbour pointers - because of boundary conditions, can be different for
87 // diff. fields
88 const unsigned *RESTRICT neighbours[NDIRS];
89 hila::bc boundary_condition[NDIRS];
90
91 MPI_Request receive_request[3][NDIRS];
92 MPI_Request send_request[3][NDIRS];
93#ifndef VANILLA
94 // vanilla needs no special receive buffers
95 T *receive_buffer[NDIRS];
96#endif
97 T *send_buffer[NDIRS];
98 /**
99 * @internal
100 * @brief Initialize communication
101 */
102 void initialize_communication() {
103 for (int d = 0; d < NDIRS; d++) {
104 for (int p = 0; p < 3; p++)
105 gather_status_arr[p][d] = gather_status_t::NOT_DONE;
106 send_buffer[d] = nullptr;
107#ifndef VANILLA
108 receive_buffer[d] = nullptr;
109#endif
110 }
111 }
112
113 /**
114 * @internal
115 * @brief Free communication
116 *
117 */
118 void free_communication() {
119 for (int d = 0; d < NDIRS; d++) {
120 if (send_buffer[d] != nullptr)
121 payload.free_mpi_buffer(send_buffer[d]);
122#ifndef VANILLA
123 if (receive_buffer[d] != nullptr)
124 payload.free_mpi_buffer(receive_buffer[d]);
125#endif
126 }
127 }
128
129 /**
130 * @internal
131 * @brief Allocate payload for lattice
132 */
133 void allocate_payload() {
134 payload.allocate_field(lattice);
135 }
136
137 /**
138 * @internal
139 * @brief Deallocate payload for lattice
140 */
141 void free_payload() {
142 payload.free_field();
143 }
144
145#ifndef VECTORIZED
146 /**
147 * @internal
148 * @brief Getter for individual element inside loop
149 *
150 * @param i
151 * @return auto
152 */
153 inline auto get(const unsigned i) const {
154 return payload.get(i, lattice.field_alloc_size());
155 }
156
157 template <typename A>
158 inline void set(const A &value, const unsigned i) {
159 payload.set(value, i, lattice.field_alloc_size());
160 }
161
162 /**
163 * @internal
164 * @brief Getter for an element outside a loop. Used to manipulate the field directly
165 * outside loops.
166 */
167 inline auto get_element(const unsigned i) const {
168 return payload.get_element(i, lattice);
169 }
170
171 template <typename A>
172 inline void set_element(const A &value, const unsigned i) {
173 payload.set_element(value, i, lattice);
174 }
175#else
176 template <typename vecT>
177 inline vecT get_vector(const unsigned i) const {
178 return payload.template get_vector<vecT>(i);
179 }
180 inline T get_element(const unsigned i) const {
181 return payload.get_element(i);
182 }
183
184 template <typename vecT>
185 inline void set_vector(const vecT &val, const unsigned i) {
186 return payload.set_vector(val, i);
187 }
188 inline void set_element(const T &val, const unsigned i) {
189 return payload.set_element(val, i);
190 }
191#endif
192
193 /**
194 * @internal
195 * @brief Gather boundary elements for communication
196 */
197 void gather_comm_elements(Direction d, Parity par, T *RESTRICT buffer,
198 const lattice_struct::comm_node_struct &to_node) const;
199
200 /**
201 * @internal
202 * @brief Place boundary elements from neighbour
203 */
204 void place_comm_elements(Direction d, Parity par, T *RESTRICT buffer,
205 const lattice_struct::comm_node_struct &from_node);
206
207 /**
208 * @internal
209 * @brief Place boundary elements from local lattice (used in vectorized version)
210 */
211 void set_local_boundary_elements(Direction dir, Parity par);
212
213 /**
214 * @internal
215 * @brief Gather a list of elements to a single node
216 * @param buffer
217 * @param coord_list
218 * @param root
219 */
220 void gather_elements(T *buffer, const std::vector<CoordinateVector> &coord_list,
221 int root = 0) const;
222 void scatter_elements(T *buffer, const std::vector<CoordinateVector> &coord_list,
223 int root = 0);
224
225
226 /// get the receive buffer pointer for the communication.
227 T *get_receive_buffer(Direction d, Parity par,
228 const lattice_struct::comm_node_struct &from_node);
229 };
230
231 // static_assert( std::is_pod<T>::value, "Field expects only pod-type elements
232 // (plain data): default constructor, copy and delete");
233 static_assert(std::is_trivial<T>::value && std::is_standard_layout<T>::value,
234 "Field expects only pod-type elements (plain data): default "
235 "constructor, copy and delete");
236
237 public:
238 /**
239 * @brief Field::fs holds all of the field content in Field::field_struct.
240 * @details The field_struct class is nonetheless marked as internal so the user need not worry
241 * about it.
242 */
243 field_struct *RESTRICT fs;
244
245 /**
246 * @brief Field constructor
247 * @details The following ways of constructing a Field object are the following:
248 *
249 * __Default constructor__:
250 *
251 * Assigns Field::fs to nullptr.
252 *
253 * \code {.cpp}
254 * Field<MyType> f;
255 * \endcode
256 *
257 * __Copy constructor__:
258 *
259 * Copy from already existing field of similar type if conversion exists. For example for float
260 * to double.
261 *
262 * \code {.cpp}
263 * Field<MyType> f = g;
264 * \endcode
265 *
266 * __Assignment from constant constructor__:
267 *
268 * Assign constant to field if conversion exists.
269 *
270 * \code
271 * .
272 * . Let a be a constant of type MyType1
273 * .
274 * Field<MyType2> f = a;
275 * \endcode
276 */
278
279 // put here some implementation checks for field vars
280#ifdef VECTORIZED
281 static_assert(sizeof(hila::arithmetic_type<T>) == 4 || sizeof(hila::arithmetic_type<T>) == 8,
282 "In vectorized arch (e.g. AVX2), only 4 or 8 byte (32 or 64 bit) numbers for "
283 "Field<> implemented, sorry!");
284#endif
285#if defined(CUDA) || defined(HIP)
286 static_assert(!std::is_same<hila::arithmetic_type<T>, long double>::value,
287 "Type 'long double' numbers in Field<> not supported by cuda/hip");
288#endif
289
290 fs = nullptr; // lazy allocation on 1st use
291 }
292 /**
293 * @internal
294 * @brief Copy constructor with already initialised Field
295 * @param other
296 */
297 Field(const Field &other) : Field() {
298 assert(other.is_initialized(ALL) && "Initializer Field value not set");
299
300 (*this)[ALL] = other[X];
301 }
302 /**
303 * @internal
304 * @brief Copy constructor form Field of type A to field of type F if the conversion is defined
305 * @tparam A
306 * @param other
307 */
308 template <typename A, std::enable_if_t<std::is_convertible<A, T>::value, int> = 0>
309 Field(const Field<A> &other) : Field() {
310 assert(other.is_initialized(ALL) && "Initializer Field value not set");
311
312 (*this)[ALL] = other[X];
313 }
314 /**
315 * @internal
316 * @brief Construct a new Field object with scalar (val) of type A to a field of type F type if
317 * the conversion is defined
318 * @tparam A
319 * @param val
320 */
321 template <typename A,
322 std::enable_if_t<
323 hila::is_assignable<T &, A>::value || std::is_convertible<A, T>::value, int> = 0>
324 Field(const A &val) : Field() {
325 (*this)[ALL] = val;
326 }
327 /**
328 * @internal
329 * @brief Construct a new Field object with scalar 0 with nullpointer trick
330 * @param z
331 */
332 Field(const std::nullptr_t z) : Field() {
333 (*this)[ALL] = 0;
334 }
335
336 /**
337 * @internal
338 * @brief Construct a new Field object by stealing content from previous field (rhs) which will
339 * be set to null happens in the case of std::move for example ` Field<MyType> f = g+h` where
340 * `g+h` is generated momenterally and then destroyed.
341 * @param rhs
342 */
343 Field(Field &&rhs) {
344 fs = rhs.fs;
345 rhs.fs = nullptr;
346 }
347
348 ~Field() {
349 free();
350
351#ifdef HILAPP
352 // Because destructor is instantiated for all fields,
353 // use this to put in a hook for generating call to this function
354 // in preprocessing stage
355 ensure_field_operators_exist<T>();
356#endif
357 }
358
359 /**
360 * @internal
361 * @brief Sets up memory for field content and communication.
362 */
363 void allocate() {
364 assert(fs == nullptr);
365 if (lattice.volume() == 0) {
366 hila::out0 << "Can not allocate Field variables before lattice.setup()\n";
368 }
369 fs = (field_struct *)memalloc(sizeof(field_struct));
370 fs->lattice_id = lattice.id();
371 fs->allocate_payload();
372 fs->initialize_communication();
373 mark_changed(ALL); // guarantees communications will be done
374 fs->assigned_to = 0; // and this means that it is not assigned
375
376 for (Direction d = (Direction)0; d < NDIRS; ++d) {
377
378#if !defined(CUDA) && !defined(HIP)
379 fs->neighbours[d] = lattice.neighb[d];
380#else
381 fs->payload.neighbours[d] = lattice.backend_lattice->d_neighb[d];
382#endif
383 }
384
385#ifdef SPECIAL_BOUNDARY_CONDITIONS
386 foralldir(dir) {
387 fs->boundary_condition[dir] = hila::bc::PERIODIC;
388 fs->boundary_condition[-dir] = hila::bc::PERIODIC;
389 }
390#endif
391
392#ifdef VECTORIZED
394 fs->vector_lattice = lattice.backend_lattice
395 ->get_vectorized_lattice<hila::vector_info<T>::vector_size>();
396 } else {
397 fs->vector_lattice = nullptr;
398 }
399#endif
400 }
401
402 /**
403 * @brief Destroys field data
404 * @details don't call destructors when exiting - either MPI or cuda can already be off.
405 */
406 void free() {
407 if (fs != nullptr && !hila::about_to_finish) {
408 for (Direction d = (Direction)0; d < NDIRS; ++d)
409 drop_comms(d, ALL);
410 fs->free_payload();
411 fs->free_communication();
412 std::free(fs);
413 fs = nullptr;
414 }
415 }
416
417 /**
418 * @brief Returns true if the Field data has been allocated
419 * @return bool
420 */
421 bool is_allocated() const {
422 return (fs != nullptr);
423 }
424
425 /**
426 * @brief Returns true if the Field has been assigned to.
427 * @param p Field parity
428 * @return bool
429 */
430 bool is_initialized(Parity p) const {
431 return fs != nullptr && ((fs->assigned_to & parity_bits(p)) != 0);
432 }
433
434 /**
435 * @internal
436 * @brief Returns current gather_status_t
437 * @param p Field partiy
438 * @param d Direction
439 * @return gather_status_t
440 */
441 gather_status_t gather_status(Parity p, int d) const {
442 assert(parity_bits(p) && d >= 0 && d < NDIRS);
443 return fs->gather_status_arr[(int)p - 1][d];
444 }
445 void set_gather_status(Parity p, int d, gather_status_t stat) const {
446 assert(parity_bits(p) && d >= 0 && d < NDIRS);
447 fs->gather_status_arr[(int)p - 1][d] = stat;
448 }
449
450 /**
451 * @brief Allocate Field if it is not already allocated
452 * @details Check that Field is allocated, and if not do it (if not const). Must be called
453 * before the var is actually used. "hilapp" will generate these calls as needed!
454 *
455 * If Field is const assert that the Field is allocated.
456 *
457 */
458 void check_alloc() {
459 if (!is_allocated())
460 allocate();
461 }
462
463 /**
464 * @internal
465 * @brief If Field is const assert that the Field is allocated
466 */
467 void check_alloc() const {
468 assert(is_allocated());
469 }
470
471
472 /**
473 * @internal
474 * @brief Bookkeeping for field communication
475 * @details If ALL changes, both parities invalid; if p != ALL, then p and ALL.
476 * @param p Field parity
477 */
478 void mark_changed(const Parity p) const {
479
480 for (Direction i = (Direction)0; i < NDIRS; ++i) {
481 // check if there's ongoing comms, invalidate it!
482 drop_comms(i, opp_parity(p));
483
484 set_gather_status(opp_parity(p), i, gather_status_t::NOT_DONE);
485 if (p != ALL) {
486 set_gather_status(ALL, i, gather_status_t::NOT_DONE);
487 } else {
488 set_gather_status(EVEN, i, gather_status_t::NOT_DONE);
489 set_gather_status(ODD, i, gather_status_t::NOT_DONE);
490 }
491 }
492 fs->assigned_to |= parity_bits(p);
493 }
494
495 /**
496 * @internal
497 * @brief Mark the Field already gathered, no need to communicate
498 * @details Mark the field parity gathered from Direction
499 * In case p=ALL we could mark everything gathered, but we'll be conservative here
500 * and mark only this parity, because there might be other parities on the fly and
501 * corresponding waits should be done, This should never happen in automatically
502 * generated loops. In any case start_gather, is_gathered, get_gather_parity has
503 * intelligence to figure out the right thing to do
504 *
505 * @param dir
506 * @param p
507 */
508 void mark_gathered(int dir, const Parity p) const {
509 set_gather_status(p, dir, gather_status_t::DONE);
510 }
511
512 /**
513 * @internal
514 * @brief Check if the field has been gathered since the previous communication
515 * @details par = ALL: ALL or (EVEN+ODD) are OK\n
516 * par != ALL: ALL or par are OK
517 * @param dir
518 * @param par
519 * @return true
520 * @return false
521 */
522 bool is_gathered(int dir, Parity par) const {
523 if (par != ALL) {
524 return gather_status(par, dir) == gather_status_t::DONE ||
525 gather_status(ALL, dir) == gather_status_t::DONE;
526 } else {
527 return gather_status(ALL, dir) == gather_status_t::DONE ||
528 (gather_status(EVEN, dir) == gather_status_t::DONE &&
529 gather_status(ODD, dir) == gather_status_t::DONE);
530 }
531 }
532
533 /**
534 * @internal
535 * @brief Mark communication has started
536 * @param dir
537 * @param p
538 */
539 void mark_gather_started(int dir, Parity p) const {
540 set_gather_status(p, dir, gather_status_t::STARTED);
541 }
542
543 /**
544 * @internal
545 * @brief Check if communication has started
546 * @param dir
547 * @param par
548 * @return bool
549 */
550 bool is_gather_started(int dir, Parity par) const {
551 return gather_status(par, dir) == gather_status_t::STARTED;
552 }
553
554 bool gather_not_done(int dir, Parity par) const {
555 return gather_status(par, dir) == gather_status_t::NOT_DONE;
556 }
557
558 /**
559 * @internal
560 * @brief function boundary_need_to_communicate(dir) returns false if there's special B.C. which
561 * does not need comms here, otherwise true
562 */
563 bool boundary_need_to_communicate(const Direction dir) const {
564#ifdef SPECIAL_BOUNDARY_CONDITIONS
565
566 return hila::bc_need_communication(fs->boundary_condition[dir]) ||
567 !lattice.mynode.is_on_edge(dir);
568#else
569 return true;
570#endif
571 }
572
573
574 /**
575 * @brief Set the boundary condition in a given Direction (periodic or antiperiodic)
576 * @param dir Direction of boundary condition
577 * @param bc Field boundary condition
578 */
580
581#ifdef SPECIAL_BOUNDARY_CONDITIONS
582
583
584#ifdef HILAPP
585 // this section is just to generate loop function calls for unary-. Will removed by hilapp
586 // from final code. If type T does not have -, give error
587
589 "BC possible only for types which implement unary "
590 "minus (-) -operator and are not unsigned");
591
592 ensure_unary_minus_is_loop_function<T>();
593#endif
594
595 // TODO: This works as intended only for periodic/antiperiodic b.c.
596 check_alloc();
597 fs->boundary_condition[dir] = bc;
598 fs->boundary_condition[-dir] = bc;
599#if !defined(CUDA) && !defined(HIP)
600 fs->neighbours[dir] = lattice.get_neighbour_array(dir, bc);
601 fs->neighbours[-dir] = lattice.get_neighbour_array(-dir, bc);
602#else
603 if (bc == hila::bc::PERIODIC) {
604 fs->payload.neighbours[dir] = lattice.backend_lattice->d_neighb[dir];
605 fs->payload.neighbours[-dir] = lattice.backend_lattice->d_neighb[-dir];
606 } else {
607 fs->payload.neighbours[dir] = lattice.backend_lattice->d_neighb_special[dir];
608 fs->payload.neighbours[-dir] = lattice.backend_lattice->d_neighb_special[-dir];
609 }
610#endif
611
612 // Make sure boundaries get refreshed
613 mark_changed(ALL);
614#else
615 assert(bc == hila::bc::PERIODIC &&
616 "Only periodic bondary conditions when SPECIAL_BOUNDARY_CONDITIONS is undefined");
617#endif
618 }
619
620 /**
621 * @brief Get the boundary condition of the Field
622 * @param dir Boundary condition in certain direction
623 * @return hila::bc The boundary condition of the Field
624 */
626#ifdef SPECIAL_BOUNDARY_CONDITIONS
627 return fs->boundary_condition[dir];
628#else
629 return hila::bc::PERIODIC;
630#endif
631 }
632
633 void print_boundary_condition() {
634 check_alloc();
635 hila::out0 << " ( ";
636 for (int dir = 0; dir < NDIRS; dir++) {
637 hila::out0 << (int)fs->boundary_condition[dir] << " ";
638 }
639 hila::out0 << ")\n";
640 }
641
642 /**
643 * @brief Copy the boundary condition from another field
644 *
645 * @tparam A the type of the field which we are copying from
646 * @param rhs the ohter Field
647 */
648 template <typename A>
650 foralldir(dir) {
652 }
653 }
654
655 // Overloading []
656 // declarations -- WILL BE implemented by hilapp, not written here
657 // let there be const and non-const protos
658 T operator[](const Parity p) const; // f[EVEN]
659 T operator[](const X_index_type) const; // f[X]
660 T operator[](const X_plus_direction p) const; // f[X+dir]
661 T operator[](const X_plus_offset p) const; // f[X+dir1+dir2] and others
662
663 T &operator[](const Parity p); // f[EVEN]
664 T &operator[](const X_index_type); // f[X]
665
666 T &operator[](const CoordinateVector &v); // f[CoordinateVector]
667 T &operator[](const CoordinateVector &v) const; // f[CoordinateVector]
668
669 // TEMPORARY HACK: return ptr to bare array
670 inline auto field_buffer() const {
671 return fs->payload.get_buffer();
672 }
673
674#ifndef VECTORIZED
675
676 /**
677 * @brief Get an individual element outside a loop. This is also used as a getter in the vanilla
678 * code.
679 *
680 * @param i
681 * @return auto
682 */
683 inline auto get_value_at(const unsigned i) const {
684 return fs->get_element(i);
685 }
686#else
687 inline auto get_value_at(const unsigned i) const {
688 return fs->get_element(i);
689 }
690 template <typename vecT>
691 inline auto get_vector_at(unsigned i) const {
692 return fs->template get_vector<vecT>(i);
693 }
694 inline auto get_value_at_nb_site(Direction d, unsigned i) const {
695 return fs->get_element(fs->vector_lattice->site_neighbour(d, i));
696 }
697#endif
698
699#ifndef VECTORIZED
700 /**
701 * @brief Set an individual element outside a loop. This is also used as a getter in the vanilla
702 * code.
703 *
704 * @param i
705 * @return auto
706 */
707 template <typename A>
708 inline void set_value_at(const A &value, unsigned i) {
709 fs->set_element(value, i);
710 }
711
712#else
713 template <typename vecT>
714 inline void set_vector_at(const vecT &value, unsigned i) {
715 fs->set_vector(value, i);
716 }
717
718 template <typename A>
719 inline void set_value_at(const A &value, unsigned i) {
720 fs->set_element(value, i);
721 }
722#endif
723
724 /**
725 * @name Standard arithmetic operations
726 * @brief Not all are always callable, e.g. division may not be implemented by all field types
727 * since division is not defined for all @p Field::T types.
728 * @{
729 */
730 /**
731 * @brief Assignment operator.
732 *
733 * @details Assignment can be performed in the following ways:
734 *
735 * __Assignment from field:__
736 *
737 * Assignment from field is possible if types are the same or convertible
738 *
739 * \code {.cpp}
740 * f = g;
741 * \endcode
742 *
743 * __Assignment from scalar:__
744 *
745 * Assignment from scalar is possible if scalar is convertible to Field type
746 *
747 * \code {.cpp}
748 * f = a; // a is a scalar
749 * \endcode
750 *
751 * @param rhs
752 * @return Field<T>& Assigned field
753 */
755 (*this)[ALL] = rhs[X];
756 return *this;
757 }
758
759 /**
760 * @internal
761 * @brief More general assignment operation if A can be casted into T
762 *
763 * @tparam A Type of element to be assigned
764 * @param rhs Field to assign from
765 * @return Field<T>& Assigned field
766 */
767 template <typename A,
768 std::enable_if_t<
769 hila::is_assignable<T &, A>::value || std::is_convertible<A, T>::value, int> = 0>
770 Field<T> &operator=(const Field<A> &rhs) {
771 (*this)[ALL] = rhs[X];
772 return *this;
773 }
774
775 /**
776 * @internal
777 * @brief Assginment from element
778 *
779 * @tparam A Type of element to be assigned
780 * @param d element to assign to Field
781 * @return Field<T>& Assigned Field
782 */
783 template <typename A,
784 std::enable_if_t<
785 hila::is_assignable<T &, A>::value || std::is_convertible<A, T>::value, int> = 0>
786 Field<T> &operator=(const A &d) {
787 (*this)[ALL] = d;
788 return *this;
789 }
790
791 /**
792 * @internal
793 * @brief assignment of 0 - nullptr, zero field
794 *
795 * @param z nullptr
796 * @return Field<T>& Zero Field
797 */
798 Field<T> &operator=(const std::nullptr_t &z) {
799 (*this)[ALL] = 0;
800 return *this;
801 }
802
803 /**
804 * @brief Move Assignment
805 *
806 * @param rhs
807 * @return Field<T>&
808 */
810 if (this != &rhs) {
811 free();
812 fs = rhs.fs;
813 rhs.fs = nullptr;
814 }
815 return *this;
816 }
817
818 /**
819 * @brief Addition assignment operator
820 * @details Addition assignmen operator can be called in the following ways as long as types are
821 * convertible.
822 *
823 * __Addition assignment with field:__
824 *
825 * \code{.cpp}
826 * Field<MyType> f,g;
827 * . . .
828 * f += g;
829 * \endcode
830 *
831 * __Addition assignment with scalar:__
832 *
833 * \code{.cpp}
834 * Field<MyType> f;
835 * MyType a;
836 * . . .
837 * f += a;
838 * \endcode
839 *
840 * @tparam A Type of r.h.s element
841 * @param rhs Field to sum
842 * @return Field<T>&
843 */
844 template <typename A,
845 std::enable_if_t<std::is_convertible<hila::type_plus<T, A>, T>::value, int> = 0>
847 (*this)[ALL] += rhs[X];
848 return *this;
849 }
850
851 /**
852 * @brief Subtraction assignment operator
853 * @details Subtraction assignment operator can be called in the following ways as long as types
854 * are convertible.
855 *
856 * __Subtraction assignment with field:__
857 *
858 * \code{.cpp}
859 * Field<MyType> f,g;
860 * . . .
861 * f -= g;
862 * \endcode
863 *
864 * __Subtraction assignment with scalar:__
865 *
866 * \code{.cpp}
867 * Field<MyType> f;
868 * MyType a;
869 * . . .
870 * f -= a;
871 * \endcode
872 *
873 * @tparam A Type of r.h.s element
874 * @param rhs Field to subtract
875 * @return Field<T>&
876 */
877 template <typename A,
878 std::enable_if_t<std::is_convertible<hila::type_minus<T, A>, T>::value, int> = 0>
880 (*this)[ALL] -= rhs[X];
881 return *this;
882 }
883
884 /**
885 * @brief Product assignment operator
886 * @details Product assignment operator can be called in the following ways as long as types
887 * are convertible.
888 *
889 * __Product assignment with field:__
890 *
891 * \code{.cpp}
892 * Field<MyType> f,g;
893 * . . .
894 * f *= g;
895 * \endcode
896 *
897 * __Product assignment with scalar:__
898 *
899 * \code{.cpp}
900 * Field<MyType> f;
901 * MyType a;
902 * . . .
903 * f *= a;
904 * \endcode
905 *
906 * @tparam A Type of r.h.s element
907 * @param rhs Field to compute product with
908 * @return Field<T>&
909 */
910 template <typename A,
911 std::enable_if_t<std::is_convertible<hila::type_mul<T, A>, T>::value, int> = 0>
913 (*this)[ALL] *= rhs[X];
914 return *this;
915 }
916
917 /**
918 * @brief Division assignment operator
919 * @details Division assignment operator can be called in the following ways as long as types
920 * are convertible.
921 *
922 * __Division assignment with field:__
923 *
924 * \code{.cpp}
925 * Field<MyType> f,g;
926 * . . .
927 * f /= g;
928 * \endcode
929 *
930 * __Division assignment with scalar:__
931 *
932 * \code{.cpp}
933 * Field<MyType> f;
934 * MyType a;
935 * . . .
936 * f /= a;
937 * \endcode
938 *
939 * @tparam A Type of r.h.s element
940 * @param rhs Field to divide with
941 * @return Field<T>&
942 */
943 template <typename A,
944 std::enable_if_t<std::is_convertible<hila::type_div<T, A>, T>::value, int> = 0>
946 (*this)[ALL] /= rhs[X];
947 return *this;
948 }
949
950 /**
951 * @internal
952 * @brief += Operator between element and field if type of rhs and Field type T are compatible
953 *
954 * @tparam A Type of r.h.s element
955 * @param rhs Element to sum
956 * @return Field<T>&
957
958 */
959 template <typename A,
960 std::enable_if_t<std::is_convertible<hila::type_plus<T, A>, T>::value, int> = 0>
961 Field<T> &operator+=(const A &rhs) {
962 (*this)[ALL] += rhs;
963 return *this;
964 }
965
966 /**
967 * @internal
968 * @brief -= Operator between scalar and field if type of rhs and Field type T are compatible
969 *
970 * @tparam A Type of r.h.s element
971 * @param rhs Element to subtract
972 * @return Field<T>&
973 */
974 template <typename A,
975 std::enable_if_t<std::is_convertible<hila::type_minus<T, A>, T>::value, int> = 0>
976 Field<T> &operator-=(const A &rhs) {
977 (*this)[ALL] -= rhs;
978 return *this;
979 }
980
981 /**
982 * @internal
983 * @brief *= Operator between element and field if type of rhs and Field type T are compatible
984 *
985 * @tparam A Type of r.h.s element
986 * @param rhs Element to multiply with
987 * @return Field<T>&
988 */
989 template <typename A,
990 std::enable_if_t<std::is_convertible<hila::type_mul<T, A>, T>::value, int> = 0>
991 Field<T> &operator*=(const A &rhs) {
992 (*this)[ALL] *= rhs;
993 return *this;
994 }
995
996 /**
997 * @internal
998 * @brief /= Operator between element and field if type of rhs and Field type T are compatible
999 *
1000 * @tparam A Type of r.h.s element
1001 * @param rhs Element to divide with
1002 * @return Field<T>&
1003 */
1004 template <typename A,
1005 std::enable_if_t<std::is_convertible<hila::type_div<T, A>, T>::value, int> = 0>
1006 Field<T> &operator/=(const A &rhs) {
1007 (*this)[ALL] /= rhs;
1008 return *this;
1009 }
1010
1011 // Unary + and -
1012
1013 /**
1014 * @brief Unary + operator, acts as Identity
1015 *
1016 * @return Field<T> Returns itself
1017 */
1019 return *this;
1020 }
1021
1022 /**
1023 * @brief Unary - operator, acts as negation to all field elements
1024 *
1025 * @return Field<T> Returns negated version of itself
1026 */
1028 Field<T> f;
1029 f[ALL] = -(*this)[X];
1030 return f;
1031 }
1032
1033 /**
1034 * @brief Field comparison operator.
1035 * @details Fields are equal if their content is element-by-element equal
1036 *
1037 * @param rhs Field to compare Field with
1038 * @return true
1039 * @return false
1040 */
1041 template <typename S>
1042 bool operator==(const Field<S> &rhs) const {
1043 double s = 0;
1044 onsites(ALL) s += !((*this)[X] == rhs[X]);
1045 return (s == 0);
1046 }
1047
1048 template <typename S>
1049 bool operator!=(const Field<S> &rhs) const {
1050 return !(*this == rhs);
1051 }
1052
1053
1054 /**
1055 * @brief Squarenorm
1056 * @details Squarenorm of Field \f$f\f$ is dependent on how it is defined for Field type T.
1057 *
1058 * \f$|f|^2 = \sum_{\forall x \in f} |x|^2\f$
1059 *
1060 * If squarenorm is defined for type T then the definition can be seen on the types
1061 * documentation page.
1062 * @return double
1063 */
1064 double squarenorm() const {
1065 double n = 0;
1066 onsites(ALL) {
1067 n += ::squarenorm((*this)[X]);
1068 }
1069 return n;
1070 }
1071
1072 /**
1073 * @brief Norm
1074 * @details Norm of Field depending on how it is defined for Field type T
1075 *
1076 * \f$|f| = \sqrt{\sum_{\forall x \in f} |x|^2}\f$
1077 *
1078 * If norm is defined for type T then the definition can be seen on the types
1079 * documentation page.
1080 * @return double
1081 */
1082 double norm() {
1083 return sqrt(squarenorm());
1084 }
1085
1086 /**
1087 * @brief Returns field with all elements conjugated depending how conjugate is defined for type
1088 *
1089 * @return Field<T>
1090 */
1091 Field<T> conj() const {
1092 Field<T> f;
1093 f[ALL] = ::conj((*this)[X]);
1094 return f;
1095 }
1096
1097 /**
1098 * @brief Returns dagger or Hermitian conjugate of Field depending on how it is
1099 * defined for Field type T
1100 *
1101 * @return Field<T>
1102 */
1103 template <typename R = T, typename A = decltype(::dagger(std::declval<R>()))>
1105 Field<A> f;
1106 f[ALL] = ::dagger((*this)[X]);
1107 return f;
1108 }
1109
1110 /**
1111 * @brief Returns real part of Field
1112 *
1113 * @tparam R Field current type
1114 * @tparam A Field real part type
1115 * @return Field<A>
1116 */
1117 template <typename R = T, typename A = decltype(::real(std::declval<R>()))>
1118 Field<A> real() const {
1119 Field<A> f;
1120 f[ALL] = ::real((*this)[X]);
1121 return f;
1122 }
1123
1124 /**
1125 * @brief Returns imaginary part of Field
1126 *
1127 * @tparam R Field current type
1128 * @tparam A Field imaginary part type
1129 * @return Field<A>
1130 */
1131 template <typename R = T, typename A = decltype(::imag(std::declval<R>()))>
1132 Field<A> imag() const {
1133 Field<A> f;
1134 f[ALL] = ::imag((*this)[X]);
1135 return f;
1136 }
1137 /** @} */
1138
1139 // Communication routines. These are all internal.
1141 void wait_gather(Direction d, Parity p) const;
1142 void gather(Direction d, Parity p = ALL) const;
1143 void drop_comms(Direction d, Parity p) const;
1144 void cancel_comm(Direction d, Parity p) const;
1145
1146 /**
1147 * @brief Create a periodically shifted copy of the field
1148 * @details this is currently OK only for short moves, very inefficient for longer moves
1149 *
1150 * @code{.cpp}
1151 * .
1152 * . //Field<MyType> f is defined
1153 * .
1154 * CoordinateVector v = {1,1,0}
1155 * f.shift(v) // Shift all elements of field once in the x direction and once in the y direction
1156 * f.shift(v,EVEN) // Shift all EVEN elements of field once in the x direction and once in the y
1157 * direction
1158 * @endcode
1159 * @param v
1160 * @param par
1161 * @param r
1162 * @return Field<T>& returns a reference to res
1163 */
1164 Field<T> &shift(const CoordinateVector &v, Field<T> &r, Parity par) const;
1165
1166 Field<T> &shift(const CoordinateVector &v, Field<T> &r) const {
1167 return shift(v, r, ALL);
1168 }
1169
1170 Field<T> shift(const CoordinateVector &v) const;
1171
1172 // General getters and setters
1173
1174 /// Set a single element. Assuming that each node calls this with the same value, it
1175 /// is sufficient to set the element locally
1176
1177 /**
1178 * @brief Get singular element which will be broadcast to all nodes
1179 * @details Works as long as `value` type A and field type T match
1180 * @tparam A type
1181 * @param coord Coordinate of element to be set
1182 * @param value Value to be set
1183 * @return const T
1184 */
1185 template <typename A, std::enable_if_t<std::is_assignable<T &, A>::value, int> = 0>
1186 const T set_element(const CoordinateVector &coord, const A &value) {
1187 T element;
1188 element = value;
1189 assert(is_initialized(ALL) && "Field not initialized yet");
1190 if (lattice.is_on_mynode(coord)) {
1191 set_value_at(element, lattice.site_index(coord));
1192 }
1193 mark_changed(coord.parity());
1194 return element;
1195 }
1196
1197 /**
1198 * @brief Get singular element which will be broadcast to all nodes
1199 *
1200 * @param coord coordinate of fetched element
1201 * @return const T
1202 */
1203 const T get_element(const CoordinateVector &coord) const {
1204 T element;
1205
1206 assert(is_initialized(ALL) && "Field not initialized yet");
1207 int owner = lattice.node_rank(coord);
1208
1209 if (hila::myrank() == owner) {
1210 element = get_value_at(lattice.site_index(coord));
1211 }
1212
1213 hila::broadcast(element, owner);
1214
1215 return element;
1216 }
1217
1218 /**
1219 * @internal
1220 * @brief Set an array of elements in the field
1221 * @remark Assuming that each node calls this with the same value,
1222 * it is sufficient to set the elements locally
1223 *
1224 * @param elements @typedef vector<T> of elements to set
1225 * @param coord_list @typedef vector<CoordinateVector> of coordinates to set
1226 */
1227 void set_elements(const std::vector<T> &elements,
1228 const std::vector<CoordinateVector> &coord_list);
1229 /**
1230 * @internal
1231 * @brief Retrieves list of elements to all nodes.
1232 * @param coord_list vector of coordinates which will be fetched
1233 * @param broadcast if true then elements retrieved to root node will be broadcast to all nodes
1234 * @return std::vector<T> list of all elements
1235 */
1236 std::vector<T> get_elements(const std::vector<CoordinateVector> &coord_list,
1237 bool broadcast = false) const;
1238
1239 std::vector<T> get_subvolume(const CoordinateVector &cmin, const CoordinateVector &cmax,
1240 bool broadcast = false) const;
1241
1242 std::vector<T> get_slice(const CoordinateVector &c, bool broadcast = false) const;
1243
1244 void copy_local_data(std::vector<T> &buffer) const;
1245 void set_local_data(const std::vector<T> &buffer);
1246
1247 void copy_local_data_with_halo(std::vector<T> &buffer) const;
1248
1249
1250 // inline void set_element_at(const CoordinateVector &coord, const A &elem) {
1251 // T e;
1252 // e = elem;
1253 // set_element(e, coord);
1254 // }
1255
1256 // inline void set_element_at(const CoordinateVector &coord, std::nullptr_t elem) {
1257 // T e;
1258 // e = 0;
1259 // set_element(e, coord);
1260 // }
1261
1262 /// @internal
1263 /// Compound element ops - used in field element operations like F[cv] += smth;
1264 /// These are optimized so that do NOT return a value, thus cannot be chained. This avoids MPI.
1265 template <typename A,
1266 std::enable_if_t<std::is_assignable<T &, hila::type_plus<T, A>>::value, int> = 0>
1267 inline void compound_add_element(const CoordinateVector &coord, const A &av) {
1268 assert(is_initialized(ALL));
1269 if (lattice.is_on_mynode(coord)) {
1270 auto i = lattice.site_index(coord);
1271 auto v = get_value_at(i);
1272 v += av;
1273 set_value_at(v, i);
1274 }
1275 mark_changed(coord.parity());
1276 }
1277
1278 template <typename A,
1279 std::enable_if_t<std::is_assignable<T &, hila::type_minus<T, A>>::value, int> = 0>
1280 inline void compound_sub_element(const CoordinateVector &coord, const A &av) {
1281 assert(is_initialized(ALL));
1282 if (lattice.is_on_mynode(coord)) {
1283 auto i = lattice.site_index(coord);
1284 auto v = get_value_at(i);
1285 v -= av;
1286 set_value_at(v, i);
1287 }
1288 mark_changed(coord.parity());
1289 }
1290
1291 template <typename A,
1292 std::enable_if_t<std::is_assignable<T &, hila::type_mul<T, A>>::value, int> = 0>
1293 inline void compound_mul_element(const CoordinateVector &coord, const A &av) {
1294 assert(is_initialized(ALL));
1295 if (lattice.is_on_mynode(coord)) {
1296 auto i = lattice.site_index(coord);
1297 auto v = get_value_at(i);
1298 v *= av;
1299 set_value_at(v, i);
1300 }
1301 mark_changed(coord.parity());
1302 }
1303
1304 template <typename A,
1305 std::enable_if_t<std::is_assignable<T &, hila::type_div<T, A>>::value, int> = 0>
1306 inline void compound_div_element(const CoordinateVector &coord, const A &av) {
1307 assert(is_initialized(ALL));
1308 if (lattice.is_on_mynode(coord)) {
1309 auto i = lattice.site_index(coord);
1310 auto v = get_value_at(i);
1311 v /= av;
1312 set_value_at(v, i);
1313 }
1314 mark_changed(coord.parity());
1315 }
1316
1317
1318 // pre- and postfix ++ -- return value
1319 ///@internal
1320 inline const T increment_postfix_element(const CoordinateVector &coord) {
1321 T r, v;
1322 v = get_element(coord);
1323 r = v;
1324 v++;
1325 set_element(coord, v);
1326 return r;
1327 }
1328
1329 ///@internal
1330 inline const T increment_prefix_element(const CoordinateVector &coord) {
1331 T v = get_element(coord);
1332 ++v;
1333 set_element(coord, v);
1334 return v;
1335 }
1336
1337 ///@internal
1338 inline const T decrement_postfix_element(const CoordinateVector &coord) {
1339 T r, v;
1340 v = get_element(coord);
1341 r = v;
1342 v--;
1343 set_element(coord, v);
1344 return r;
1345 }
1346
1347 ///@internal
1348 inline const T decrement_prefix_element(const CoordinateVector &coord) {
1349 T v = get_element(coord);
1350 --v;
1351 set_element(coord, v);
1352 return v;
1353 }
1354
1355
1356 // Fourier transform declaration
1357 Field<T> FFT(fft_direction fdir = fft_direction::forward) const;
1358 Field<T> FFT(const CoordinateVector &dirs, fft_direction fdir = fft_direction::forward) const;
1359
1361 FFT_real_to_complex(fft_direction fdir = fft_direction::forward) const;
1363 FFT_complex_to_real(fft_direction fdir = fft_direction::forward) const;
1364
1365
1366 // Reflect the field along all or 1 coordinate
1367 Field<T> reflect() const;
1368 Field<T> reflect(Direction dir) const;
1369 Field<T> reflect(const CoordinateVector &dirs) const;
1370
1371 // Writes the Field to disk
1372 void write(std::ofstream &outputfile, bool binary = true, int precision = 8) const;
1373 void write(const std::string &filename, bool binary = true, int precision = 8) const;
1374
1375 void read(std::ifstream &inputfile);
1376 void read(const std::string &filename);
1377
1378 void write_subvolume(std::ofstream &outputfile, const CoordinateVector &cmin,
1379 const CoordinateVector &cmax, int precision = 6) const;
1380 void write_subvolume(const std::string &filenname, const CoordinateVector &cmin,
1381 const CoordinateVector &cmax, int precision = 6) const;
1382
1383 template <typename Out>
1384 void write_slice(Out &outputfile, const CoordinateVector &slice, int precision = 6) const;
1385
1386 /**
1387 * @brief Sum reduction of Field
1388 * @details The sum in the reduction is defined by the Field type T
1389 * @param par Parity
1390 * @param allreduce Switch to turn on or off MPI allreduce
1391 * @return T Field element type reduced to one element
1392 */
1393 T sum(Parity par = Parity::all, bool allreduce = true) const;
1394
1395 /**
1396 * @brief Product reduction of Field
1397 * @details The product in the reduction is defined by the Field type T.
1398 * @param par Parity
1399 * @param allreduce Switch to turn on or off MPI allreduce
1400 * @return T Field element type reduced to one element
1401 */
1402 T product(Parity par = Parity::all, bool allreduce = true) const;
1403
1404 /**
1405 * @brief Declare gpu_reduce here, defined only for GPU targets
1406 * @internal
1407 * @param min_or_max
1408 * @param par
1409 * @param loc
1410 * @return T
1411 */
1412 T gpu_minmax(bool min_or_max, Parity par, CoordinateVector &loc) const;
1413
1414 /**
1415 * @brief Minimum value of Field. If CoordinateVector is passed to function
1416 * then location of minimum value will be stored in said CoordinateVector
1417 * @name Min functions
1418 * @param par ::Parity
1419 * @return T Minimum value of Field
1420 */
1421 /** @{ */
1422 T min(Parity par = ALL) const;
1423 T min(CoordinateVector &loc) const;
1424 T min(Parity par, CoordinateVector &loc) const;
1425 /** @} */
1426
1427 /**
1428 * @brief Maximum value of Field. If CoordinateVector is passed to function
1429 * then location of maximum value will be stored in said CoordinateVector
1430 * @name Max functions
1431 * @param par ::Parity
1432 * @return T Minimum value of Field
1433 */
1434 /** @{ */
1435 T max(Parity par = ALL) const;
1436 T max(CoordinateVector &loc) const;
1437 T max(Parity par, CoordinateVector &loc) const;
1438 /** @} */
1439
1440 /**
1441 * @brief Function to perform min or max operations
1442 * @internal
1443 *
1444 * @param is_min if true we compute min
1445 * @param par Parity
1446 * @param loc Location of min or max
1447 * @return T
1448 */
1449 T minmax(bool is_min, Parity par, CoordinateVector &loc) const;
1450
1451 void random();
1452 void gaussian_random(double width = 1.0);
1453
1454
1455}; // End of class Field<>
1456
1457///////////////////////////////
1458// operators +-*/
1459// these operators rely on SFINAE, OK if field_hila::type_plus<A,B> exists i.e. A+B is
1460// OK
1461// There are several versions of the operators, depending if one of the arguments is
1462// Field or scalar, and if the return type is the same as the Field argument. This can
1463// enable the compiler to avoid extra copies of the args
1464
1465///////////////////////////////
1466/// operator + (Field + Field) -generic
1467template <typename A, typename B,
1468 std::enable_if_t<!std::is_same<hila::type_plus<A, B>, A>::value &&
1469 !std::is_same<hila::type_plus<A, B>, B>::value,
1470 int> = 0>
1471auto operator+(const Field<A> &lhs, const Field<B> &rhs) -> Field<hila::type_plus<A, B>> {
1473 tmp[ALL] = lhs[X] + rhs[X];
1474 return tmp;
1475}
1476
1477// (Possibly) optimzed version where the 1st argument can be reused
1478template <typename A, typename B,
1479 std::enable_if_t<std::is_same<hila::type_plus<A, B>, A>::value, int> = 0>
1480auto operator+(Field<A> lhs, const Field<B> &rhs) {
1481 lhs[ALL] += rhs[X];
1482 return lhs;
1483}
1484
1485// Optimzed version where the 2nd argument can be reused
1486template <typename A, typename B,
1487 std::enable_if_t<!std::is_same<hila::type_plus<A, B>, A>::value &&
1488 std::is_same<hila::type_plus<A, B>, B>::value,
1489 int> = 0>
1490auto operator+(const Field<A> &lhs, Field<B> rhs) {
1491 rhs[ALL] += lhs[X];
1492 return rhs;
1493}
1494
1495//////////////////////////////
1496/// operator + (Field + scalar)
1497
1498template <typename A, typename B,
1499 std::enable_if_t<!std::is_same<hila::type_plus<A, B>, A>::value, int> = 0>
1500auto operator+(const Field<A> &lhs, const B &rhs) -> Field<hila::type_plus<A, B>> {
1502 tmp[ALL] = lhs[X] + rhs;
1503 return tmp;
1504}
1505
1506template <typename A, typename B,
1507 std::enable_if_t<std::is_same<hila::type_plus<A, B>, A>::value, int> = 0>
1508Field<A> operator+(Field<A> lhs, const B &rhs) {
1509 lhs[ALL] += rhs;
1510 return lhs;
1511}
1512
1513
1514//////////////////////////////
1515/// operator + (scalar + Field)
1516
1517template <typename A, typename B,
1518 std::enable_if_t<!std::is_same<hila::type_plus<A, B>, B>::value, int> = 0>
1519auto operator+(const A &lhs, const Field<B> &rhs) -> Field<hila::type_plus<A, B>> {
1520 return rhs + lhs;
1521}
1522
1523template <typename A, typename B,
1524 std::enable_if_t<std::is_same<hila::type_plus<A, B>, B>::value, int> = 0>
1525Field<B> operator+(const A &lhs, Field<B> rhs) {
1526 return rhs + lhs;
1527}
1528
1529
1530//////////////////////////////
1531/// operator - Field - Field -generic
1532template <typename A, typename B,
1533 std::enable_if_t<!std::is_same<hila::type_minus<A, B>, A>::value &&
1534 !std::is_same<hila::type_minus<A, B>, B>::value,
1535 int> = 0>
1538 tmp[ALL] = lhs[X] - rhs[X];
1539 return tmp;
1540}
1541
1542// Optimzed version where the 1st argument can be reused
1543template <typename A, typename B,
1544 std::enable_if_t<std::is_same<hila::type_minus<A, B>, A>::value, int> = 0>
1545auto operator-(Field<A> lhs, const Field<B> &rhs) {
1546 lhs[ALL] -= rhs[X];
1547 return lhs;
1548}
1549
1550// Optimzed version where the 2nd argument can be reused
1551template <typename A, typename B,
1552 std::enable_if_t<!std::is_same<hila::type_minus<A, B>, A>::value &&
1553 std::is_same<hila::type_minus<A, B>, B>::value,
1554 int> = 0>
1555auto operator-(const Field<A> &lhs, Field<B> rhs) {
1556 rhs[ALL] = lhs[X] - rhs[X];
1557 return rhs;
1558}
1559
1560//////////////////////////////
1561/// operator - (Field - scalar)
1562
1563template <typename A, typename B,
1564 std::enable_if_t<!std::is_same<hila::type_minus<A, B>, A>::value, int> = 0>
1565auto operator-(const Field<A> &lhs, const B &rhs) -> Field<hila::type_minus<A, B>> {
1567 tmp[ALL] = lhs[X] - rhs;
1568 return tmp;
1569}
1570
1571template <typename A, typename B,
1572 std::enable_if_t<std::is_same<hila::type_minus<A, B>, A>::value, int> = 0>
1573Field<A> operator-(Field<A> lhs, const B &rhs) {
1574 lhs[ALL] -= rhs;
1575 return lhs;
1576}
1577
1578//////////////////////////////
1579/// operator - (scalar - Field)
1580
1581template <typename A, typename B,
1582 std::enable_if_t<!std::is_same<hila::type_minus<A, B>, B>::value, int> = 0>
1583auto operator-(const A &lhs, const Field<B> &rhs) -> Field<hila::type_minus<A, B>> {
1585 tmp[ALL] = lhs - rhs[X];
1586 return tmp;
1587}
1588
1589template <typename A, typename B,
1590 std::enable_if_t<std::is_same<hila::type_minus<A, B>, B>::value, int> = 0>
1591Field<B> operator-(const A &lhs, Field<B> rhs) {
1592 rhs[ALL] = lhs - rhs[X];
1593 return rhs;
1594}
1595
1596///////////////////////////////
1597/// operator * (Field * Field)
1598/// generic
1599template <typename A, typename B,
1600 std::enable_if_t<!std::is_same<hila::type_mul<A, B>, A>::value &&
1601 !std::is_same<hila::type_mul<A, B>, B>::value,
1602 int> = 0>
1603auto operator*(const Field<A> &lhs, const Field<B> &rhs) -> Field<hila::type_mul<A, B>> {
1605 tmp[ALL] = lhs[X] * rhs[X];
1606 return tmp;
1607}
1608
1609/// reuse 1st
1610template <typename A, typename B,
1611 std::enable_if_t<std::is_same<hila::type_mul<A, B>, A>::value, int> = 0>
1613 lhs[ALL] = lhs[X] * rhs[X];
1614 return lhs;
1615}
1616
1617/// reuse 2nd
1618template <typename A, typename B,
1619 std::enable_if_t<!std::is_same<hila::type_mul<A, B>, A>::value &&
1620 std::is_same<hila::type_mul<A, B>, B>::value,
1621 int> = 0>
1623 rhs[ALL] = lhs[X] * rhs[X];
1624 return rhs;
1625}
1626
1627/////////////////////////////////
1628/// operator * (scalar * field)
1629
1630template <typename A, typename B,
1631 std::enable_if_t<!std::is_same<hila::type_mul<A, B>, B>::value, int> = 0>
1632auto operator*(const A &lhs, const Field<B> &rhs) -> Field<hila::type_mul<A, B>> {
1634 tmp[ALL] = lhs * rhs[X];
1635 return tmp;
1636}
1637
1638template <typename A, typename B,
1639 std::enable_if_t<std::is_same<hila::type_mul<A, B>, B>::value, int> = 0>
1640Field<B> operator*(const A &lhs, Field<B> rhs) {
1641 rhs[ALL] = lhs * rhs[X];
1642 return rhs;
1643}
1644
1645/////////////////////////////////
1646/// operator * (field * scalar)
1647
1648template <typename A, typename B,
1649 std::enable_if_t<!std::is_same<hila::type_mul<A, B>, A>::value, int> = 0>
1650auto operator*(const Field<A> &lhs, const B &rhs) -> Field<hila::type_mul<A, B>> {
1652 tmp[ALL] = lhs[X] * rhs;
1653 return tmp;
1654}
1655
1656template <typename A, typename B,
1657 std::enable_if_t<std::is_same<hila::type_mul<A, B>, A>::value, int> = 0>
1658Field<A> operator*(Field<A> lhs, const B &rhs) {
1659 lhs[ALL] = lhs[X] * rhs;
1660 return lhs;
1661}
1662
1663///////////////////////////////
1664/// operator / (Field / Field)
1665/// generic
1666template <typename A, typename B,
1667 std::enable_if_t<!std::is_same<hila::type_div<A, B>, A>::value &&
1668 !std::is_same<hila::type_div<A, B>, B>::value,
1669 int> = 0>
1672 tmp[ALL] = l[X] / r[X];
1673 return tmp;
1674}
1675
1676/// reuse 1st
1677template <typename A, typename B,
1678 std::enable_if_t<std::is_same<hila::type_div<A, B>, A>::value, int> = 0>
1680 l[ALL] = l[X] / r[X];
1681 return l;
1682}
1683
1684/// reuse 2nd
1685template <typename A, typename B,
1686 std::enable_if_t<!std::is_same<hila::type_div<A, B>, A>::value &&
1687 std::is_same<hila::type_div<A, B>, B>::value,
1688 int> = 0>
1690 r[ALL] = l[X] / r[X];
1691 return r;
1692}
1693
1694//////////////////////////////////
1695/// operator / (scalar/Field)
1696template <typename A, typename B,
1697 std::enable_if_t<!std::is_same<hila::type_div<A, B>, B>::value, int> = 0>
1698auto operator/(const A &lhs, const Field<B> &rhs) -> Field<hila::type_div<A, B>> {
1700 tmp[ALL] = lhs / rhs[X];
1701 return tmp;
1702}
1703
1704template <typename A, typename B,
1705 std::enable_if_t<std::is_same<hila::type_div<A, B>, B>::value, int> = 0>
1706Field<B> operator/(const A &lhs, Field<B> rhs) {
1707 rhs[ALL] = lhs / rhs[X];
1708 return rhs;
1709}
1710
1711//////////////////////////////////
1712/// operator / (Field/scalar)
1713template <typename A, typename B,
1714 std::enable_if_t<!std::is_same<hila::type_div<A, B>, A>::value, int> = 0>
1715auto operator/(const Field<A> &lhs, const B &rhs) -> Field<hila::type_div<A, B>> {
1717 tmp[ALL] = lhs[X] / rhs;
1718 return tmp;
1719}
1720
1721template <typename A, typename B,
1722 std::enable_if_t<std::is_same<hila::type_div<A, B>, A>::value, int> = 0>
1723auto operator/(Field<A> lhs, const B &rhs) {
1724 lhs[ALL] = lhs[X] / rhs;
1725 return lhs;
1726}
1727
1728namespace hila {
1729
1730/**
1731 * @brief Implement hila::swap() for Fields too, equivalent to std::swap()
1732 */
1733template <typename T>
1734void swap(Field<T> &A, Field<T> &B) {
1735 std::swap(A.fs, B.fs);
1736}
1737} // namespace hila
1738
1739
1740///////////////////////////////////////////////////////////////////////
1741// Allow some arithmetic functions if implemented
1742
1743template <typename T, typename R = decltype(exp(std::declval<T>()))>
1744Field<R> exp(const Field<T> &arg) {
1745 Field<R> res;
1746 onsites(ALL) {
1747 res[X] = exp(arg[X]);
1748 }
1749 return res;
1750}
1751
1752template <typename T, typename R = decltype(log(std::declval<T>()))>
1753Field<R> log(const Field<T> &arg) {
1754 Field<R> res;
1755 onsites(ALL) {
1756 res[X] = log(arg[X]);
1757 }
1758 return res;
1759}
1760
1761template <typename T, typename R = decltype(sin(std::declval<T>()))>
1762Field<R> sin(const Field<T> &arg) {
1763 Field<R> res;
1764 onsites(ALL) {
1765 res[X] = sin(arg[X]);
1766 }
1767 return res;
1768}
1769
1770template <typename T, typename R = decltype(cos(std::declval<T>()))>
1771Field<R> cos(const Field<T> &arg) {
1772 Field<R> res;
1773 onsites(ALL) {
1774 res[X] = cos(arg[X]);
1775 }
1776 return res;
1777}
1778
1779template <typename T, typename R = decltype(tan(std::declval<T>()))>
1780Field<R> tan(const Field<T> &arg) {
1781 Field<R> res;
1782 onsites(ALL) {
1783 res[X] = tan(arg[X]);
1784 }
1785 return res;
1786}
1787
1788template <typename T, typename R = decltype(asin(std::declval<T>()))>
1789Field<R> asin(const Field<T> &arg) {
1790 Field<R> res;
1791 onsites(ALL) {
1792 res[X] = asin(arg[X]);
1793 }
1794 return res;
1795}
1796
1797template <typename T, typename R = decltype(acos(std::declval<T>()))>
1798Field<R> acos(const Field<T> &arg) {
1799 Field<R> res;
1800 onsites(ALL) {
1801 res[X] = acos(arg[X]);
1802 }
1803 return res;
1804}
1805
1806template <typename T, typename R = decltype(atan(std::declval<T>()))>
1807Field<R> atan(const Field<T> &arg) {
1808 Field<R> res;
1809 onsites(ALL) {
1810 res[X] = atan(arg[X]);
1811 }
1812 return res;
1813}
1814
1815template <typename T, typename R = decltype(abs(std::declval<T>()))>
1816Field<R> abs(const Field<T> &arg) {
1817 Field<R> res;
1818 onsites(ALL) {
1819 res[X] = abs(arg[X]);
1820 }
1821 return res;
1822}
1823
1824template <typename T, typename P, typename R = decltype(pow(std::declval<T>()), std::declval<P>())>
1825Field<R> pow(const Field<T> &arg, const P p) {
1826 Field<R> res;
1827 onsites(ALL) {
1828 res[X] = pow(arg[X], p);
1829 }
1830 return res;
1831}
1832
1833template <typename T>
1834double squarenorm(const Field<T> &arg) {
1835 double r = 0;
1836 onsites(ALL) {
1837 r += squarenorm(arg[X]);
1838 }
1839 return r;
1840}
1841
1842template <typename T>
1843double norm(const Field<T> &arg) {
1844 return sqrt(squarenorm(arg));
1845}
1846
1847template <typename T>
1848Field<T> conj(const Field<T> &arg) {
1849 return arg.conj();
1850}
1851
1852template <typename T, typename A = decltype(::dagger(std::declval<T>()))>
1853Field<A> dagger(const Field<T> &arg) {
1854 return arg.dagger();
1855}
1856
1857template <typename T, typename A = decltype(::real(std::declval<T>()))>
1858Field<A> real(const Field<T> &arg) {
1859 return arg.real();
1860}
1861
1862template <typename T, typename A = decltype(::imag(std::declval<T>()))>
1863Field<A> imag(const Field<T> &arg) {
1864 return arg.imag();
1865}
1866
1867
1868template <typename A, typename B, typename R = decltype(std::declval<A>() - std::declval<B>())>
1869double squarenorm_relative(const Field<A> &a, const Field<B> &b) {
1870 double res = 0;
1871 onsites(ALL) {
1872 res += squarenorm(a[X] - b[X]);
1873 }
1874 return res;
1875}
1876
1877
1878template <typename T>
1880 Field<T> res;
1881 shift(v, res, ALL);
1882 return res;
1883}
1884
1885
1886/// @internal
1887/// drop_comms(): if field is changed or deleted,
1888/// cancel ongoing communications. This should happen very seldom,
1889/// only if there are "by-hand" start_gather operations and these are not needed
1890template <typename T>
1891void Field<T>::drop_comms(Direction d, Parity p) const {
1892
1893 if (is_comm_initialized()) {
1894 if (is_gather_started(d, ALL))
1895 cancel_comm(d, ALL);
1896 if (p != ALL) {
1897 if (is_gather_started(d, p))
1898 cancel_comm(d, p);
1899 } else {
1900 if (is_gather_started(d, EVEN))
1901 cancel_comm(d, EVEN);
1902 if (is_gather_started(d, ODD))
1903 cancel_comm(d, ODD);
1904 }
1905 }
1906}
1907
1908/// @internal cancel ongoing send and receive
1909template <typename T>
1910void Field<T>::cancel_comm(Direction d, Parity p) const {
1911 if (lattice.nn_comminfo[d].from_node.rank != hila::myrank()) {
1912 cancel_receive_timer.start();
1913 MPI_Cancel(&fs->receive_request[(int)p - 1][d]);
1914 cancel_receive_timer.stop();
1915 }
1916 if (lattice.nn_comminfo[d].to_node.rank != hila::myrank()) {
1917 cancel_send_timer.start();
1918 MPI_Cancel(&fs->send_request[(int)p - 1][d]);
1919 cancel_send_timer.stop();
1920 }
1921}
1922
1923
1924/// @internal And a convenience combi function
1925template <typename T>
1926void Field<T>::gather(Direction d, Parity p) const {
1927 start_gather(d, p);
1928 wait_gather(d, p);
1929}
1930
1931
1932// Read in the "technical" communication bits
1933
1934#include "field_comm.h"
1935
1936
1937template <typename T>
1938void Field<T>::random() {
1939
1940#if defined(CUDA) || defined(HIP)
1941
1942 if (!hila::is_device_rng_on()) {
1943
1944 std::vector<T> rng_buffer(lattice.mynode.volume());
1945 for (auto &element : rng_buffer)
1946 hila::random(element);
1947 (*this).set_local_data(rng_buffer);
1948
1949 } else {
1950 onsites(ALL) {
1951 hila::random((*this)[X]);
1952 }
1953 }
1954#else
1955
1956 onsites(ALL) {
1957 hila::random((*this)[X]);
1958 }
1959
1960#endif
1961}
1962
1963template <typename T>
1964void Field<T>::gaussian_random(double width) {
1965
1966#if defined(CUDA) || defined(HIP)
1967
1968 if (!hila::is_device_rng_on()) {
1969
1970 std::vector<T> rng_buffer(lattice.mynode.volume());
1971 for (auto &element : rng_buffer)
1972 hila::gaussian_random(element, width);
1973 (*this).set_local_data(rng_buffer);
1974
1975 } else {
1976 onsites(ALL) {
1977 hila::gaussian_random((*this)[X], width);
1978 }
1979 }
1980#else
1981
1982 onsites(ALL) {
1983 hila::gaussian_random((*this)[X], width);
1984 }
1985
1986#endif
1987}
1988
1989
1990#ifdef HILAPP
1991
1992////////////////////////////////////////////////////////////////////////////////
1993// A couple of placeholder functions, not included in produced code.
1994// These are here in order for hilapp to generate explicitly
1995// some Direction and CoordinateVector operations, which may not exist in
1996// original code as such. It is easiest to let the general hilapp
1997// code generation to do it using this hack, instead of hard-coding these to
1998// hilapp.
1999//
2000// These are needed because hilapp changes X+d-d -> +d-d, which may involve
2001// an operator not met before
2002
2003inline void dummy_X_f() {
2004 Direction d1 = e_x;
2005 CoordinateVector v1(0);
2006 onsites(ALL) {
2007 Direction d;
2008 d = +d1;
2009 d = -d1; // unaryops
2010 CoordinateVector vec;
2011 vec = +v1;
2012 vec = -v1;
2013
2014 // Direction + vector combos
2015 vec = d + d1;
2016 vec = d - d1;
2017 vec = v1 + d1;
2018 vec = v1 - d1;
2019 vec = d1 + v1;
2020 vec = d1 - v1;
2021 vec = vec + v1;
2022 vec = vec - v1;
2023
2024 // and Direction index func
2025 vec[e_x] = vec[0] + vec[e_y] + vec.e(e_y);
2026 }
2027}
2028
2029/**
2030 * @internal
2031 * @brief Dummy function including Field<T> functions and methods which
2032 * need to be explicitly seen by hilapp during 1st pass in order to
2033 * generater necessary functions. Add here ops as needed
2034 */
2035template <typename T>
2036inline void ensure_field_operators_exist() {
2037
2038 ensure_unary_minus_is_loop_function<T>();
2039 ensure_assign_zero_is_loop_function<T>();
2040
2041 // make shift also explicit
2042 CoordinateVector v = 0;
2043 Field<T> f;
2044 f = f.shift(v);
2045}
2046
2047#endif
2048
2049#endif // FIELD_H
Array< n, m, T > conj(const Array< n, m, T > &arg)
Return conjugate Array.
Definition array.h:674
Array< n, m, T > asin(Array< n, m, T > a)
Inverse Sine.
Definition array.h:1109
Array< n, m, T > exp(Array< n, m, T > a)
Exponential.
Definition array.h:1059
Array< n, m, hila::arithmetic_type< T > > imag(const Array< n, m, T > &arg)
Return imaginary part of Array.
Definition array.h:702
hila::arithmetic_type< T > squarenorm(const Array< n, m, T > &rhs)
Return square norm of Array.
Definition array.h:1018
Array< n, m, T > tan(Array< n, m, T > a)
Tangent.
Definition array.h:1099
Array< n, m, T > cos(Array< n, m, T > a)
Cosine.
Definition array.h:1089
Array< n, m, T > acos(Array< n, m, T > a)
Inverse Cosine.
Definition array.h:1119
Array< n, m, T > sqrt(Array< n, m, T > a)
Square root.
Definition array.h:1039
Array< n, m, T > atan(Array< n, m, T > a)
Inverse Tangent.
Definition array.h:1129
Array< n, m, T > pow(Array< n, m, T > a, int b)
Power.
Definition array.h:1204
Array< n, m, T > sin(Array< n, m, T > a)
Sine.
Definition array.h:1079
Array< n, m, hila::arithmetic_type< T > > real(const Array< n, m, T > &arg)
Return real part of Array.
Definition array.h:688
The field class implements the standard methods for accessing Fields. Hilapp replaces the parity acce...
Definition field.h:61
Field< T > & operator=(const Field< T > &rhs)
Assignment operator.
Definition field.h:754
Field()
Field constructor.
Definition field.h:277
Field< A > real() const
Returns real part of Field.
Definition field.h:1118
std::vector< T > get_subvolume(const CoordinateVector &cmin, const CoordinateVector &cmax, bool broadcast=false) const
get a subvolume of the field elements to all nodes
Definition field_comm.h:680
Field< T > & shift(const CoordinateVector &v, Field< T > &r, Parity par) const
Create a periodically shifted copy of the field.
Definition field_comm.h:162
void set_boundary_condition(Direction dir, hila::bc bc)
Set the boundary condition in a given Direction (periodic or antiperiodic)
Definition field.h:579
void write_subvolume(std::ofstream &outputfile, const CoordinateVector &cmin, const CoordinateVector &cmax, int precision=6) const
Definition field_io.h:215
const T get_element(const CoordinateVector &coord) const
Get singular element which will be broadcast to all nodes.
Definition field.h:1203
hila::bc get_boundary_condition(Direction dir) const
Get the boundary condition of the Field.
Definition field.h:625
Field< A > imag() const
Returns imaginary part of Field.
Definition field.h:1132
T gpu_minmax(bool min_or_max, Parity par, CoordinateVector &loc) const
Declare gpu_reduce here, defined only for GPU targets.
Field< A > dagger() const
Returns dagger or Hermitian conjugate of Field depending on how it is defined for Field type T.
Definition field.h:1104
const T set_element(const CoordinateVector &coord, const A &value)
Get singular element which will be broadcast to all nodes.
Definition field.h:1186
T max(Parity par=ALL) const
Find maximum value from Field.
Definition reduction.h:424
double norm()
Norm.
Definition field.h:1082
bool is_initialized(Parity p) const
Returns true if the Field has been assigned to.
Definition field.h:430
auto get_value_at(const unsigned i) const
Get an individual element outside a loop. This is also used as a getter in the vanilla code.
Definition field.h:683
field_struct *__restrict__ fs
Field::fs holds all of the field content in Field::field_struct.
Definition field.h:243
void set_value_at(const A &value, unsigned i)
Set an individual element outside a loop. This is also used as a getter in the vanilla code.
Definition field.h:708
void free()
Destroys field data.
Definition field.h:406
Field< T > operator+() const
Unary + operator, acts as Identity.
Definition field.h:1018
Field< Complex< hila::arithmetic_type< T > > > FFT_real_to_complex(fft_direction fdir=fft_direction::forward) const
Definition fft.h:467
Field< T > & operator=(Field< T > &&rhs)
Move Assignment.
Definition field.h:809
Field< T > & operator*=(const Field< A > &rhs)
Product assignment operator.
Definition field.h:912
bool is_allocated() const
Returns true if the Field data has been allocated.
Definition field.h:421
void copy_boundary_condition(const Field< A > &rhs)
Copy the boundary condition from another field.
Definition field.h:649
Field< T > conj() const
Returns field with all elements conjugated depending how conjugate is defined for type.
Definition field.h:1091
T product(Parity par=Parity::all, bool allreduce=true) const
Product reduction of Field.
Definition reduction.h:306
void check_alloc()
Allocate Field if it is not already allocated.
Definition field.h:458
bool operator==(const Field< S > &rhs) const
Field comparison operator.
Definition field.h:1042
double squarenorm() const
Squarenorm.
Definition field.h:1064
dir_mask_t start_gather(Direction d, Parity p=ALL) const
Definition field_comm.h:262
Field< T > & operator-=(const Field< A > &rhs)
Subtraction assignment operator.
Definition field.h:879
Field< T > operator-() const
Unary - operator, acts as negation to all field elements.
Definition field.h:1027
void write(std::ofstream &outputfile, bool binary=true, int precision=8) const
Write the field to a file stream.
Definition field_io.h:79
std::vector< T > get_elements(const std::vector< CoordinateVector > &coord_list, bool broadcast=false) const
Get a list of elements and store them into a vector.
Definition field_comm.h:663
T minmax(bool is_min, Parity par, CoordinateVector &loc) const
Function to perform min or max operations.
Definition reduction.h:329
T min(Parity par=ALL) const
Find minimum value from Field.
Definition reduction.h:404
T sum(Parity par=Parity::all, bool allreduce=true) const
Sum reduction of Field.
Definition reduction.h:296
void read(std::ifstream &inputfile)
Read the Field from a stream.
Definition field_io.h:147
std::vector< T > get_slice(const CoordinateVector &c, bool broadcast=false) const
and get a slice (subvolume)
Definition field_comm.h:701
Field< T > & operator/=(const Field< A > &rhs)
Division assignment operator.
Definition field.h:945
Field< T > & operator+=(const Field< A > &rhs)
Addition assignment operator.
Definition field.h:846
T e(const int i, const int j) const
Standard array indexing operation for matrices and vectors.
Definition matrix.h:286
X-coordinate type - "dummy" class.
The field_storage struct contains minimal information for using the field in a loop....
auto get_element(const unsigned i, const lattice_struct &lattice) const
Conditionally reture bool type false if type T does't have unary - operator.
bool is_on_mynode(const CoordinateVector &c) const
Is the coordinate on THIS node.
Definition lattice.cpp:118
unsigned *__restrict__ neighb[NDIRS]
Main neighbour index array.
Definition lattice.h:203
std::array< nn_comminfo_struct, NDIRS > nn_comminfo
nearest neighbour comminfo struct
Definition lattice.h:200
unsigned site_index(const CoordinateVector &c) const
Definition lattice.cpp:136
Complex< T > dagger(const Complex< T > &val)
Return dagger of Complex number.
Definition cmplx.h:1223
T abs(const Complex< T > &a)
Return absolute value of Complex number.
Definition cmplx.h:1187
T arg(const Complex< T > &a)
Return argument of Complex number.
Definition cmplx.h:1199
This header file defines:
Parity
Parity enum with values EVEN, ODD, ALL; refers to parity of the site. Parity of site (x,...
constexpr Parity EVEN
bit pattern: 001
#define foralldir(d)
Macro to loop over (all) Direction(s)
Definition coordinates.h:78
constexpr unsigned NDIRS
Number of directions.
Definition coordinates.h:57
constexpr Parity ODD
bit pattern: 010
unsigned char dir_mask_t
Direction
Enumerator for direction that assigns integer to direction to be interpreted as unit vector.
Definition coordinates.h:34
constexpr Parity ALL
bit pattern: 011
This file defines all includes for HILA.
fft_direction
define a class for FFT direction
Definition defs.h:150
#define RESTRICT
Definition defs.h:51
auto operator/(const Field< A > &l, const Field< B > &r) -> Field< hila::type_div< A, B > >
Definition field.h:1670
auto operator+(const Field< A > &lhs, const Field< B > &rhs) -> Field< hila::type_plus< A, B > >
operator + (Field + Field) -generic
Definition field.h:1471
auto operator-(const Field< A > &lhs, const Field< B > &rhs) -> Field< hila::type_minus< A, B > >
operator - Field - Field -generic
Definition field.h:1536
auto operator*(const Field< A > &lhs, const Field< B > &rhs) -> Field< hila::type_mul< A, B > >
Definition field.h:1603
Implement hila::swap for gauge fields.
Definition array.h:981
T gaussian_random()
Template function T hila::gaussian_random<T>(),generates gaussian random value of type T,...
Definition random.h:183
logger_class log
Now declare the logger.
int myrank()
rank of this node
Definition com_mpi.cpp:235
bool is_device_rng_on()
Check if the RNG on GPU is allocated and ready to use.
Definition hila_gpu.cpp:55
double random()
Real valued uniform random number generator.
Definition hila_gpu.cpp:118
std::ostream out0
This writes output only from main process (node 0)
bc
list of field boundary conditions - used only if SPECIAL_BOUNDARY_CONDITIONS defined
Definition lattice.h:31
T broadcast(T &var, int rank=0)
Broadcast the value of var to all MPI ranks from rank (default=0).
Definition com_mpi.h:153
bool bc_need_communication(hila::bc bc)
False if we have b.c. which does not require communication.
Definition lattice.h:34
void terminate(int status)
T gaussian_random(out_only T &val, double w=1.0)
Template function const T & hila::gaussian_random(T & variable,double width=1)
Definition random.h:158
X + coordinate offset, used in f[X+CoordinateVector] or f[X+dir1+dir2] etc.
is_vectorizable_type<T>::value is always false if the target is not vectorizable
Definition vector_types.h:8
Information necessary to communicate with a node.
Definition lattice.h:134
bool is_on_edge(Direction d) const
true if this node is on the edge of the lattice to dir d
Definition lattice.h:106