SpECTRE Documentation Coverage Report
Current view: top level - __w/spectre/spectre/docs/DevGuide - ImplementingVectors.md Hit Total Coverage
Commit: 9b01d30df5d2e946e7e38cc43c008be18ae9b1d2 Lines: 0 1 0.0 %
Date: 2024-04-23 04:54:49
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : \cond NEVER
       2             : Distributed under the MIT License.
       3             : See LICENSE.txt for details.
       4             : \endcond
       5             : 
       6             : # Implementing SpECTRE vectors {#implementing_vectors}
       7             : 
       8             : \tableofcontents
       9             : 
      10             : # Overview of SpECTRE Vectors {#general_structure}
      11             : 
      12             : In SpECTRE, sets of contiguous or related data are stored in specializations of
      13             : vector data types. The canonical implementation of this is the `DataVector`,
      14             : which is used for storage of a contiguous sequence of doubles which support a
      15             : wide variety of mathematical operations and represent data on a grid used during
      16             : an evolution or elliptic solve. However, we support the ability to easily
      17             : generate similar vector types which can hold data of a different type
      18             : (e.g. `std::complex<double>`), or support a different set of mathematical
      19             : operations. SpECTRE vector classes are derived from the class template
      20             : `VectorImpl`. The remainder of this brief guide gives a description of the tools
      21             : for defining additional vector types.
      22             : 
      23             : For reference, all functions described here can also be found in brief in the
      24             : Doxygen documentation for VectorImpl.hpp, and a simple reference implementation
      25             : can be found in DataVector.hpp and DataVector.cpp.
      26             : 
      27             : # The class definition {#class_definition}
      28             : 
      29             : SpECTRE vector types inherit from vector types implemented in the
      30             : high-performance arithmetic library
      31             : [Blaze](https://bitbucket.org/blaze-lib/blaze). Using inheritance, SpECTRE
      32             : vectors gracefully make use of the math functions defined for the Blaze types,
      33             : but can be customized for the specific needs in SpECTRE computations.
      34             : 
      35             : The trio of template parameters for `VectorImpl` are the type of the stored data
      36             : (e.g. `double` for `DataVector`), the result type for mathematical operations,
      37             : and the static size. The result type is used by Blaze to ensure that only
      38             : compatible vector types are used together in mathematical expressions. For
      39             : example, a vector representing `double` data on a grid (`DataVector`) cannot be
      40             : added to a vector representing spectral coefficients (`ModalVector`). This
      41             : avoids subtle bugs that arise when vector types are unintentionally mixed.
      42             : In nearly all cases the result type will be the vector type that is being
      43             : defined, so, for instance, `DataVector` is a derived class of
      44             : `VectorImpl<double, DataVector, 5>`. This template pattern is known as the
      45             : ["Curiously Recurring Template Pattern"](https://en.wikipedia.org/wiki/
      46             : Curiously_recurring_template_pattern) (CRTP).
      47             : The static size is used as an optimization for small vector sizes. If your
      48             : vector is small, rather than doing heap allocations, it will use stack
      49             : allocations in order to be efficient. The default static size is set by a global
      50             : constexpr bool `default_vector_impl_static_size`.
      51             : 
      52             : For the Blaze system to use the CRTP inheritance appropriately, it requires the
      53             : specification of separate type traits in the `blaze` namespace.
      54             : These traits can usually be declared in a standard form, so are abstracted in a
      55             : macro. For any new vector `MyNewVector`, the pattern that must appear at the
      56             : beginning of the file (i.e. before the class definition) is:
      57             : ```
      58             : /// \cond
      59             : class MyNewVector;
      60             : /// \endcond
      61             : namespace blaze {
      62             : DECLARE_GENERAL_VECTOR_BLAZE_TRAITS(MyNewVector);
      63             : }
      64             : ```
      65             : The class template `VectorImpl` defines various constructors, assignment
      66             : operators, and iterator generation members. Most of these are inherited from
      67             : Blaze types, but in addition, the methods `set_data_ref`, and `pup` are defined
      68             : for use in SpECTRE. All except for the assignment operators and constructors
      69             : will be implicitly inherited from `VectorImpl`. The assignment and constructors
      70             : may be inherited calling the following alias code in the vector class
      71             : definition:
      72             : ```
      73             : using VectorImpl<T,VectorType,StaticSize>::operator=;
      74             : using VectorImpl<T,VectorType,StaticSize>::VectorImpl;
      75             : ```
      76             : 
      77             : Only the mathematical operations supported on the base Blaze types are supported
      78             : by default. Those operations are determined by the storage type `T` and by the
      79             : Blaze library. See [blaze-wiki/Vector_Operations](https://bitbucket.org/
      80             : blaze-lib/blaze/wiki/Vector%20Operations).
      81             : 
      82             : # Allowed operator specification {#blaze_definitions}
      83             : 
      84             : Blaze keeps track of the return type of unary and binary operations using "type
      85             : trait" structs. These specializations for vector types should be placed in the
      86             : header file associated with the `VectorImpl` specialization. For `DataVector`,
      87             : the specializations are defined in `DataStructures/DataVector.hpp`. The presence
      88             : or absence of template specializations of these structs also determines the set
      89             : of allowed operations between the vector type and other types. For example, if
      90             : adding a `double` to a `DataVector` should be allowed and the result should be
      91             : treated as a `DataVector` for subsequent operations, then the struct
      92             : `blaze::AddTrait<DataVector, double>` needs to be defined as follows:
      93             : 
      94             : ```
      95             : namespace blaze {
      96             : // the `template <>` head tells the compiler that
      97             : // `AddTrait<DataVector, double>` is a class template specialization
      98             : template <>
      99             : struct AddTrait<DataVector, double> {
     100             :     // the `Type` alias tells blaze that the result should be treated like a
     101             :     // `DataVector` for any further operations
     102             :     using Type = DataVector;
     103             : };
     104             : }  // namespace blaze
     105             : ```
     106             : 
     107             : Note that this only adds support for `DataVector + double`, not `double +
     108             : DataVector`. To get the latter the following AddTrait specialization must be
     109             : defined
     110             : 
     111             : ```
     112             : namespace blaze {
     113             : // the `template <>` head tells the compiler that
     114             : // `AddTrait<double, DataVector>` is a class template specialization
     115             : template <>
     116             : struct AddTrait<double, DataVector> {
     117             :     // the `Type` alias tells blaze that the result should be treated like a
     118             :     // `DataVector` for any further operations
     119             :     using Type = DataVector;
     120             : };
     121             : }  // namespace blaze
     122             : ```
     123             : 
     124             : Four helper macros are defined to assist with generating the many
     125             : specializations that binary operations may require. Both of these macros must be
     126             : put inside the blaze namespace for them to work correctly.
     127             : 
     128             : The first of these helper macros is
     129             : `BLAZE_TRAIT_SPECIALIZE_BINARY_TRAIT(VECTOR_TYPE, BLAZE_MATH_TRAIT)`, which will
     130             : define all of the pairwise operations (`BLAZE_MATH_TRAIT`) for the vector type
     131             : (`VECTOR_TYPE`) with itself and for the vector type with its `value_type`. This
     132             : reduces the three specializations similar to the above code blocks to a single
     133             : line call,
     134             : 
     135             : ```
     136             : namespace blaze {
     137             : BLAZE_TRAIT_SPECIALIZE_BINARY_TRAIT(DataVector, AddTrait)
     138             : }  // namespace blaze
     139             : ```
     140             : 
     141             : The second helper macro is provided to easily define all of the arithmetic
     142             : operations that will typically be supported for a vector type with its value
     143             : type. The macro is
     144             : `VECTOR_BLAZE_TRAIT_SPECIALIZE_ARITHMETIC_TRAITS(VECTOR_TYPE)`, and defines all
     145             : of:
     146             : - `IsVector<VECTOR_TYPE>` to `std::true_type`
     147             : - `TransposeFlag<VECTOR_TYPE>`, which informs Blaze of the interpretation of the
     148             :   data as a "column" or "row" vector
     149             : - `AddTrait` for the `VECTOR_TYPE` and its value type (3 Blaze struct
     150             :   specializations)
     151             : - `SubTrait` for the `VECTOR_TYPE` and its value type (3 Blaze struct
     152             :   specializations)
     153             : - `MultTrait` for the `VECTOR_TYPE` and its value type (3 Blaze struct
     154             :   specializations)
     155             : - `DivTrait` for the `VECTOR_TYPE` and its value type (3 Blaze struct
     156             :   specializations)
     157             : 
     158             : This macro is similarly intended to be used in the `blaze` namespace and can
     159             : substantially simplify these specializations for new vector types. For instance,
     160             : the call for `DataVector` is:
     161             : 
     162             : ```
     163             : namespace blaze {
     164             : VECTOR_BLAZE_TRAIT_SPECIALIZE_ARITHMETIC_TRAITS(DataVector)
     165             : }  // namespace blaze
     166             : ```
     167             : 
     168             : The third helper macro is provided to define a combination of Blaze traits for
     169             : symmetric operations of a vector type with a second type (which may or may not
     170             : be a vector type). The macro is
     171             : `BLAZE_TRAIT_SPECIALIZE_COMPATIBLE_BINARY_TRAIT(VECTOR, COMPATIBLE, TRAIT)`, and
     172             : defines the appropriate trait for the two combinations `<VECTOR, COMPATIBLE>`
     173             : and `<COMPATIBLE, VECTOR>`, and defines the result type to be `VECTOR`. For
     174             : instance, to support the multiplication of a `ComplexDataVector` with a
     175             : `DataVector` and have the result be a `ComplexDataVector`, the following macro
     176             : call should be included in the `blaze` namespace:
     177             : 
     178             : ```
     179             : namespace blaze {
     180             : BLAZE_TRAIT_SPECIALIZE_COMPATIBLE_BINARY_TRAIT(ComplexDataVector, DataVector,
     181             :                                                MultTrait);
     182             : }  // namespace blaze
     183             : ```
     184             : 
     185             : Finally, the fourth helper macro is provided to define all of the blaze traits
     186             : which are considered either unary or binary maps. This comprises most named
     187             : unary functions (like `sin()` or `sqrt()`) and named binary functions (like
     188             : `hypot()` and `atan2()`). The macro
     189             : `VECTOR_BLAZE_TRAIT_SPECIALIZE_ALL_MAP_TRAITS(VECTOR_TYPE)` broadly specializes
     190             : all blaze-defined maps in which the given `VECTOR_TYPE` as the sole argument
     191             : (for unary maps) or both arguments (for binary maps). This macro is also
     192             : intended to be used in the blaze namespace. The call for `DataVector` is:
     193             : 
     194             : ```
     195             : namespace blaze {
     196             : VECTOR_BLAZE_TRAIT_SPECIALIZE_ALL_MAP_TRAITS(DataVector)
     197             : }  // namespace blaze
     198             : ```
     199             : 
     200             : # Supporting operations for `std::array`s of vectors {#array_vector_definitions}
     201             : 
     202             : In addition to operations between SpECTRE vectors, it is useful to gracefully
     203             : handle operations between `std::arrays` of vectors element-wise. There are
     204             : general macros defined for handling operations between array specializations:
     205             : `DEFINE_STD_ARRAY_BINOP` and `DEFINE_STD_ARRAY_INPLACE_BINOP` from
     206             : `Utilities/StdArrayHelpers.hpp`.
     207             : 
     208             : In addition, there is a macro for rapidly generating addition and subtraction
     209             : between arrays of vectors and arrays of their data types. The macro
     210             : `MAKE_STD_ARRAY_VECTOR_BINOPS(VECTOR_TYPE)` will define:
     211             : - the element-wise `+` and `-` with `std::array<VECTOR_TYPE, N>` and
     212             :   `std::array<VECTOR_TYPE, N>`
     213             : - the element-wise `+` and `-` of either ordering of
     214             :   `std::array<VECTOR_TYPE, N>` with `std::array<VECTOR_TYPE::value_type, N>`
     215             : - the `+=` and `-=` of `std::array<VECTOR_TYPE, N>` with a
     216             :   `std::array<VECTOR_TYPE, N>`
     217             : - the `+=` and `-=` of `std::array<VECTOR_TYPE, N>` with a
     218             :   `std::array<VECTOR_TYPE::value_type, N>`.
     219             : 
     220             : # Equivalence operators {#Vector_type_equivalence}
     221             : 
     222             : Equivalence operators are supported by the Blaze type inheritance. The
     223             : equivalence operator `==` evaluates to true on a pair of vectors if they are the
     224             : same size and contain the same values, regardless of ownership.
     225             : 
     226             : # MakeWithValueImpl {#Vector_MakeWithValueImpl}
     227             : 
     228             : SpECTRE offers the convenience function `make_with_value` for various types. The
     229             : typical behavior for a SpECTRE vector type is to create a new vector type of the
     230             : same type and length initialized with the value provided as the second argument
     231             : in all entries. This behavior may be created by placing the macro
     232             : `MAKE_WITH_VALUE_IMPL_DEFINITION_FOR(VECTOR_TYPE)` in the .hpp file. Any other
     233             : specializations of `MakeWithValueImpl` will need to be written manually.
     234             : 
     235             : # Interoperability with other data types {#Vector_tensor_and_variables}
     236             : 
     237             : When additional vector types are added, small changes are necessary if they are
     238             : to be used as the base container type either for `Tensor`s or for `Variables` (a
     239             : `Variables` contains `Tensor`s), which contain some vector type.
     240             : 
     241             : In `Tensor.hpp`, there is a `static_assert` which white-lists the possible types
     242             : that can be used as the storage type in `Tensor`s. Any new vectors must be added
     243             : to that white-list if they are to be used within `Tensor`s.
     244             : 
     245             : `Variables` is templated on the storage type of the stored `Tensor`s. However,
     246             : any new data type should be appropriately tested. New vector types should be
     247             : tested by invoking new versions of existing testing functions templated on the
     248             : new vector type, rather than `DataVector`.
     249             : 
     250             : # Writing tests {#Vector_tests}
     251             : 
     252             : In addition to the utilities for generating new vector types, there are a number
     253             : of convenience functions and utilities for easily generating the tests necessary
     254             : to verify that the vectors function appropriately. These utilities are in
     255             : `VectorImplTestHelper.hpp`, and documented individually in the
     256             : TestingFrameworkGroup. Presented here are the salient details for rapidly
     257             : assembling basic tests for vectors.
     258             : 
     259             : ## Utility check functions
     260             : Each of these functions is intended to encapsulate a single frequently used unit
     261             : test and is templated (in order) on the vector type and the value type to be
     262             : generated. The default behavior is to uniformly sample values between -100 and
     263             : 100, but alternative bounds may be passed in via the function arguments.
     264             : 
     265             : ### `TestHelpers::VectorImpl::vector_test_construct_and_assign()`
     266             :  This function tests a battery of construction and assignment operators for the
     267             :  vector type.
     268             : 
     269             : ### `TestHelpers::VectorImpl::vector_test_serialize()`
     270             : This function tests that vector types can be serialized and deserialized,
     271             : retaining their data.
     272             : 
     273             : ### `TestHelpers::VectorImpl::vector_test_ref()`
     274             : This function tests the `set_data_ref` method of sharing data between vectors,
     275             : and that the appropriate owning flags and move operations are handled correctly.
     276             : 
     277             : ### `TestHelpers::VectorImpl::vector_test_math_after_move()`
     278             : Tests several combinations of math operations and ownership before and after use
     279             : of `std::move`.
     280             : 
     281             : ### `TestHelpers::VectorImpl::vector_ref_test_size_error()`
     282             : This function intentionally generates an error when assigning values from one
     283             : vector to a differently sized, non-owning vector (made non-owning by use of
     284             : `set_data_ref`). The assertion test which calls this function should search for
     285             : the string "Must copy/move/assign into same size". Three forms of the test are
     286             : provided, which are switched between using a value from the enum
     287             : `RefSizeErrorTestKind` in the first function argument:
     288             : - `RefSizeErrorTestKind::Copy`: tests that the size error is appropriately
     289             :   generated when copying to a non-owning vector of the wrong size. This has
     290             :   "copy" in the message.
     291             : - `RefSizeErrorTestKind::ExpressionAssign`: tests that the size error is
     292             :   appropriately generated when assigning the result of a mathematical expression
     293             :   to a non-owning vector of the wrong size. This has "assign" in the message.
     294             : - `RefSizeErrorTestKind::Move`: tests that the size error is appropriately
     295             :   generated when a vector is `std::move`d into a non-owning vector of the wrong
     296             :   size. This has "move" in the message.
     297             : 
     298             : ## `TestHelpers::VectorImpl::test_functions_with_vector_arguments()`
     299             : 
     300             : This is a general function for testing the mathematical operation of vector
     301             : types with other vector types and/or their base types, with or without various
     302             : reference wrappers. This may be used to efficiently test the full set of
     303             : permitted math operations on a vector. See the documentation of
     304             : `test_functions_with_vector_arguments()` for full usage details.
     305             : 
     306             : An example simple use case for the math test utility:
     307             : \snippet Test_DataVector.cpp test_functions_with_vector_arguments_example
     308             : 
     309             : More use cases of this functionality can be found in `Test_DataVector.cpp`.
     310             : 
     311             : # Vector storage nuts and bolts {#Vector_storage}
     312             : 
     313             : Internally, all vector classes inherit from the templated `VectorImpl`, which
     314             : inherits from a `blaze::CustomVector`. Most of the mathematical operations are
     315             : supported through the Blaze inheritance, which ensures that the math operations
     316             : execute the optimized forms in Blaze.
     317             : 
     318             : Blaze also offers the possibility of restricting operations via `groups` in the
     319             : `blaze::CustomVector` template arguments.
     320             : Currently, we do not use the `blaze::GroupTag` functionality to determine
     321             : available operations for vectors, but in principle this feature could allow us
     322             : to further simplify our operator choice logic in the SpECTRE vector code.
     323             : 
     324             : SpECTRE vectors can be either "owning" or "non-owning". If a vector is owning,
     325             : it allocates and controls the data it has access to, and is responsible for
     326             : eventually freeing that data when the vector goes out of scope. If the vector is
     327             : non-owning, it acts as a (possibly complete) "view" of otherwise allocated
     328             : memory. Non-owning vectors do not manage memory, nor can they change size. The
     329             : two cases of data ownership cause the underlying data to be handled fairly
     330             : differently, so we will discuss each in turn.
     331             : 
     332             : When a SpECTRE vector is constructed as owning, or becomes owning, its memory
     333             : is allocated in one of two ways.
     334             : 
     335             : 1. The size of the vector is larger than the `StaticSize` template parameter to
     336             :    `VectorImpl`. In that case, it allocates its own block of memory of
     337             :    appropriate size, and stores a pointer to that memory in a `std::unique_ptr`
     338             :    named `owned_data_`. The `std::unique_ptr` ensures that the SpECTRE vector
     339             :    needs to perform no further direct memory management, and that the memory
     340             :    will be appropriately managed whenever the `std::unique_ptr owned_data_`
     341             :    member is deleted or moved.
     342             : 2. The size of the vector is less than or equal to the `StaticSize` template. In
     343             :    this case, the data is stored on the stack in a `std::array<T, StaticSize>`
     344             :    member variable called `static_owned_data_`. Since it is on the stack, this
     345             :    doesn't require any memory management by the user.
     346             : 
     347             : In either case, the base `blaze::CustomVector` must also be told about the
     348             : pointer, which is always accomplished by calling the protected function
     349             : `VectorImpl.reset_pointer_vector(const size_t set_size)`, which sets the
     350             : `blaze::CustomVector` internal pointer to either the pointer obtained by
     351             : `std::unique_pointer.get()` or the pointer obtained by `std::array.data()`
     352             : depending on the size of the vector.
     353             : 
     354             : When a SpECTRE vector is constructed as non-owning by the `VectorImpl(ValueType*
     355             : start, size_t set_size)` constructor, or becomes non-owning by the
     356             : `set_data_ref` function, neither the internal `std::unique_ptr` named
     357             : `owned_data_` nor the internal `std::array` named `static_owned_data_`
     358             : points to the data represented by the vector and both can be thought of as
     359             : "inactive" for the purposes of computation and memory management. This behavior
     360             : is desirable, because otherwise the `std::unique_ptr` would attempt to free
     361             : memory that is presumed to be also used elsewhere, causing difficult to diagnose
     362             : memory errors. And we needn't worry about the `std::array` because it's
     363             : allocated on the stack. The non-owning SpECTRE vector updates the base
     364             : `blaze::CustomVector` pointer directly by calling `blaze::CustomVector.reset`
     365             : from the derived class (on itself).

Generated by: LCOV version 1.14