Configurable Array Implementation
By default, Yardl uses the xtensor library to implement multi-dimensional arrays in C++.
Yardl also supports user-defined multi-dimensional array implementations.
If, for example, the xtensor types are incompatible with your target software environment, you can tell Yardl where to find your own implementation at model generation time.
Defining a Custom Array Implementation
Yardl requires an implementation for each of its three types of multi-dimensional arrays:
FixedNDArray<T, Dims...>: All dimension sizes are known at compile timeNDArray<T, N>: Number of dimensions is known at compile timeDynamicNDArray<T>: Number of dimensions configured at runtime
See Arrays for more on the Yardl array types.
These definitions must be accessible from a single "override" header.
The Override Header
The array override header has three responsibilities:
- Include your custom array implementation, which may be defined in other C++ headers/source files.
- Define the Yardl array types.
- Define the free functions required for compatibility with Yardl.
For example, an override header may look like the following:
/** Include multi-dimensional array implementation(s) **/
#include <external-ndarray-implementation>
#include <custom-dynamic-array>
#include <xtensor/xfixed.hpp>
namespace yardl {
/** Define the three array types **/
// Alias xtensor's fixed array type
template <typename T, size_t... Dims>
using FixedNDArray = xt::xtensor_fixed<T, xt::xshape<Dims...>, xt::layout_type::row_major, false>;
// Alias my custom dynamic array implementation
template <typename T>
using DynamicNDArray = custom::dynamic_array<T>;
// Extend my external Array implementation to implement NDArray
template <typename T, size_t N>
class NDArray : public external::Array<T, N>
{
// ... Wrapper implementation
}
/** API functions required for Yardl compatibility **/
template <typename T, size_t... Dims>
size_t size(FixedNDArray<T, Dims...> const& arr) {
return arr.size();
}
template <typename T>
size_t size(DynamicNDArray<T> const& arr) {
return arr.get_size();
}
template <typename T, size_t N>
size_t size(NDArray<T, N> const& arr) {
return arr.get_number_of_elements();
}
/** More API functions continued... **/
}General Requirements
- Your header must define the three array types, which have the following type signatures
template <typename T, size_t... Dims> class FixedNDArraytemplate <typename T> class DynamicNDArraytemplate <typename T, size_t N> class NDArray
- Each type must have:
- Default constructor (can be constructed with no arguments)
- Copy/Move constructors
- Copy/Move assignment operators
- Equality/Inequality operators
begin()andend()member functions to support iteration (constand non-const)
- The
FixedNDArraytype must be Trivally Copyable. For this reason, usingxtensorfor fixed arrays is recommended. - The array types must be defined within the
yardlnamespace - Your header must also define a collection of API functions, described below, also within the
yardlnamespace
Array API Requirements
The following functions must be defined for each array type A<T>:
size_t size(A const& a)Returns the total number of elements.
size_t dimension(A const& a)Returns the number of dimensions.
size_t shape(A const& a, size_t dimension)Returns the length of the given dimension.
T* dataptr(A<T>& a)Returns a pointer to the first element.
T const* dataptr(A<T> const& a)Returns a
constpointer to the first element.template <typename T, class... Args> T const& at(A<T> const& a, Args... indices)Returns a
constreference to the element at the given indices.
The following functions must be defined in addition to those above:
std::array<size_, sizeof...(Dims)> shape(FixedNDArray<T, Dims...> const& a)Returns the shape (dimensions) of the fixed array.
std::vector<size_t> shape(DynamicNDArray<T> const& a)Returns the shape (dimensions) of the dynamic array.
std::array<size_t> shape(NDArray<T, N> const& a)Returns the shape (dimensions) of the ndarray.
void resize(DynamicNDArray<T>& a, std::vector<size_t> const& shape)Changes the array's shape (dimensions) without preserving data.
void resize(NDArray<T, N>& a, std::array<size_t> const& shape)Changes the array's shape (dimensions) without preserving data.
Using a Custom Array Implementation
To configure Yardl to use your custom array implementation in your model, add the overrideArrayHeader option to the cpp section of your model's _package.yml.
For example:
namespace: MyNamespace
cpp:
sourcesOutputDir: ../cpp/generated
generateCMakeLists: true
overrideArrayHeader: external/my-array-impl.h // [!code ++]The generated C++ code will then #include "external/my-array-impl.h" instead of the default Yardl multi-dimensional array implementation.
Ensure that your header is on the include path before compiling your generated code.
Examples
Yardl provides two examples for configuring multi-dimensional array implementations.
Yardl's Default Implementation
Yardl's default xtensor implementation can be found in yardl/detail/ndarray/impl.h after generating C++ code for your model.
This header:
- Includes the
xtensorheaders - Aliases the
xtensortypes to define each of the three Yardl array types - Implements all of the API functions described above
Example Custom Implementation
This example can be found in the Yardl code in the cpp/test/external directory. This directory contains two files:
hoNDArray.hThis file defines a base
NDArray<T>class and derivedhoNDArray<T>class, both in theexternalnamespace. These are loosely based on the multi-dimensional array implementation found in the Gadgetron open source image reconstruction framework.ndarray_impl.hThis file:
- Includes
hoNDArray.h - Defines a collection of helpers in the
yardl::detailnamespace- These are used to implement syntactic convenience and may not be needed in your code
- Uses
xtensorto implementyardl::FixedNDArray(identical to Yardl's default implementation) - Defines two new classes
yardl::NDArrayandyardl::DynamicNDArray, both of which derive from theexternal::hoNDArrayimplementation. - Defines the array API free functions
- Since both
NDArrayandDynamicNDArrayextend the samehoNDArrayclass, many of the free functions are only implemented once, e.g. the following function implements theyardl::sizeoperator for both those types:cpptemplate <typename T> size_t size(external::hoNDArray<T> const& arr);
- Since both
- Includes