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

Generated by: LCOV version 1.14