8. LvArray::tensorOps
¶
LvArray::tensorOps
is a collection of free template functions which perform common linear algebra operations on compile time sized matrices and vectors. All of the functions work on device.
8.1. Object Representation¶
The LvArray::tensorOps
methods currently operate on four different types of object; scalars, vectors, matrices, and symmetric matrices.
Vectors are represented by either a one dimensional c-array or a one dimensional LvArray::Array
object. Examples of acceptable vector types are
double[ 4 ]
int[ 55 ]
LvArray::Array< float, 1, camp::idx_seq< 0 >, std::ptrdiff_t, LvArray::MallocBuffer >
LvArray::ArrayView< long, 1, 0, std::ptrdiff_t, LvArray::MallocBuffer >
LvArray::ArraySlice< double, 1, 0, std::ptrdiff_t >
LvArray::ArraySlice< double, 1, -1, std::ptrdiff_t >
Matrices are represented by either a two dimensional c-array or a two dimensional LvArray::Array
object. Examples of acceptable matrix types
double[ 3 ][ 3 ]
int[ 5 ][ 2 ]
LvArray::Array< float, 2, camp::idx_seq< 0, 1 >, std::ptrdiff_t, LvArray::MallocBuffer >
LvArray::ArrayView< long, 2, 0, std::ptrdiff_t, LvArray::MallocBuffer >
:LvArray::ArraySlice< double, 2, 1, std::ptrdiff_t >
LvArray::ArraySlice< double, 2, 0, std::ptrdiff_t >
LvArray::ArraySlice< double, 2, -1, std::ptrdiff_t >
Symmetric matrices are represented in Voigt notation as a vector. This means that a symmetric matrix is represented as vector of length three as and that a symmetric matrix is represented as vector of length six as . One consequence of this is that you can use the vector methods to for example add one symmetric matrix to another.
8.2. Common operations¶
Variables | |
---|---|
: A scalar. | |
: A vector in . | |
: A vector in . | |
: A vector in . | |
: A matrix in . | |
: A matrix in . | |
Operation | Function |
tensorOps::copy< m >( x, y ) |
|
tensorOps::copy< m, n >( A, B ) |
|
tensorOps::scale< m >( x, alpha ) |
|
tensorOps::scale< m, n >( A, alpha ) |
|
tensorOps::scaledCopy< m >( x, y, alpha ) |
|
tensorOps::scaledCopy< m, n >( A, B, alpha ) |
|
tensorOps::add< m >( x, y ) |
|
tensorOps::add< m, n >( A, B, alpha ) |
|
tensorOps::subtract< m >( x, y ) |
|
tensorOps::scaledAdd< m >( x, y, alpha ) |
|
tensorOps::hadamardProduct< m >( x, y, z ) |
There is also a function fill
that will set all the entries of the object to a specific value. For a vector it is called as tensorOps::fill< n >( x )
and for a matrix as tensorOps::fill< m, n >( A )
.
8.3. Vector operations¶
Variables | |
---|---|
: A scalar. | |
: A vector in . | |
: A vector in . | |
: A vector in . | |
Operation | Function |
tensorOps::maxAbsoluteEntry< m >( x ) |
|
tensorOps::l2NormSquared< m >( x ) |
|
tensorOps::l2Norm< m >( x ) |
|
tensorOps::normalize< m >( x ) |
|
tensorOps::AiBi( x, y ) |
If , and are all in then you can perform the operation as tensorOps::crossProduct( x, y, z )
.
8.4. Matrix vector operations¶
Variables | |
---|---|
: A vector in . | |
: A vector in . | |
: A matrix in . | |
Operation | Function |
tensorOps::Rij_eq_AiBj< m, n >( A, x, y ) |
|
tensorOps::Rij_add_AiBj< m, n >( A, x, y ) |
|
tensorOps::Ri_eq_AijBj< m, n >( x, A, y ) |
|
tensorOps::Ri_add_AijBj< m, n >( x, A, y ) |
|
tensorOps::Ri_eq_AjiBj< n, m >( y, A, x ) |
|
tensorOps::Ri_add_AjiBj< n, m >( y, A, x ) |
8.5. Matrix operations¶
Variables | |
---|---|
: A matrix in . | |
: A matrix in . | |
: A matrix in . | |
: A matrix in . | |
: A matrix in . | |
Operation Function | |
tensorOps::transpose< m, n >( A, D ) |
|
tensorOps::Rij_eq_AikBkj< m, n, p >( A, B, C ) |
|
tensorOps::Rij_add_AikBkj< m, n, p >( A, B, C ) |
|
tensorOps::Rij_eq_AikBjk< m, p, n >( B, A, C ) |
|
tensorOps::Rij_add_AikBjk< m, p, n >( B, A, C ) |
|
tensorOps::Rij_add_AikAjk< m, n >( E, A ) |
|
tensorOps::Rij_eq_AkiBkj< p, n, m >( C, B, A ) |
|
tensorOps::Rij_add_AkiBkj< p, n, m >( C, B, A ) |
8.6. Square matrix operations¶
Variables | |
---|---|
: A scalar. | |
: A matrix in . | |
: A matrix in . | |
Operation | Function |
tensorOps::transpose< m >( A ) |
|
tensorOps::tensorOps::addIdentity< m >( A, alpha ) |
|
tensorOps::trace< m >( A ) |
|
tensorOps::determinant< m >( A ) |
|
tensorOps::invert< m >( A, B ) |
|
tensorOps::invert< m >( A ) |
Note
Apart from tensorOps::determinant
and tensorOps::invert
are only implemented for matrices of size and .
8.7. Symmetric matrix operations¶
Variables | |
---|---|
: A scalar. | |
: A vector in . | |
: A vector in . | |
: A matrix in . | |
: A matrix in . | |
: A symmetric matrix in . | |
: A symmetric matrix in . | |
Operation | Function |
tensorOps::symAddIdentity< m >( S, alpha ) |
|
tensorOps::symTrace< m >( S ) |
|
tensorOps::Ri_eq_symAijBj< m >( x, S ,y ) |
|
tensorOps::Ri_add_symAijBj< m >( x, S ,y ) |
|
tensorOps::Rij_eq_symAikBjk< m >( A, S, B ) |
|
tensorOps::Rij_eq_AikSymBklAjl< m >( S, A, Q ) |
|
tensorOps::symDeterminant< m >( S ) |
|
tensorOps::symInvert< m >( S, Q ) |
|
tensorOps::symInvert< m >( S ) |
|
tensorOps::symEigenvalues< M >( x, S ) |
|
tensorOps::symEigenvectors< M >( x, S ) |
There are also two function tensorOps::denseToSymmetric
and tensorOps::symmetricToDense
which convert between dense and symmetric matrix representation.
Note
Apart from tensorOps::symAddIdentity
and tensorOps::symTrace
the symmetric matrix operations are only implemented for matrices of size and .
8.8. Examples¶
TEST( tensorOps, AiBi )
{
double const x[ 3 ] = { 0, 1, 2 };
double const y[ 3 ] = { 1, 2, 3 };
// Works with c-arrays
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( x, y ), 8 );
LvArray::Array< double,
1,
camp::idx_seq< 0 >,
std::ptrdiff_t,
LvArray::MallocBuffer > xArray( 3 );
xArray( 0 ) = 0;
xArray( 1 ) = 1;
xArray( 2 ) = 2;
// Works with LvArray::Array.
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( xArray, y ), 8 );
// Works with LvArray::ArrayView.
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( xArray.toView(), y ), 8 );
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( xArray.toViewConst(), y ), 8 );
// Create a 2D array with fortran layout.
LvArray::Array< double,
2,
camp::idx_seq< 1, 0 >,
std::ptrdiff_t,
LvArray::MallocBuffer > yArray( 1, 3 );
yArray( 0, 0 ) = 1;
yArray( 0, 1 ) = 2;
yArray( 0, 2 ) = 3;
// Works with non contiguous slices.
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( x, yArray[ 0 ] ), 8 );
EXPECT_EQ( LvArray::tensorOps::AiBi< 3 >( xArray, yArray[ 0 ] ), 8 );
}
[Source: examples/exampleTensorOps.cpp]
You can mix and match the data types of the objects and also call the tensorOps
methods on device.
CUDA_TEST( tensorOps, device )
{
// Create an array of 5 3x3 symmetric matrices.
LvArray::Array< int,
2,
camp::idx_seq< 1, 0 >,
std::ptrdiff_t,
LvArray::ChaiBuffer > symmetricMatrices( 5, 6 );
int offset = 0;
for( int & value : symmetricMatrices )
{
value = offset++;
}
LvArray::ArrayView< int const,
2,
0,
std::ptrdiff_t,
LvArray::ChaiBuffer > symmetricMatricesView = symmetricMatrices.toViewConst();
// The tensorOps methods work on device.
RAJA::forall< RAJA::cuda_exec< 32 > >(
RAJA::TypedRangeSegment< std::ptrdiff_t >( 0, symmetricMatricesView.size( 0 ) ),
[symmetricMatricesView] __device__ ( std::ptrdiff_t const i )
{
double result[ 3 ];
float x[ 3 ] = { 1.3f, 2.2f, 5.3f };
// You can mix value types.
LvArray::tensorOps::Ri_eq_symAijBj< 3 >( result, symmetricMatricesView[ i ], x );
LVARRAY_ERROR_IF_NE( result[ 0 ], x[ 0 ] * symmetricMatricesView( i, 0 ) +
x[ 1 ] * symmetricMatricesView( i, 5 ) +
x[ 2 ] * symmetricMatricesView( i, 4 ) );
}
);
}
[Source: examples/exampleTensorOps.cpp]
8.9. Bounds checking¶
Whatever the argument type the number of dimensions is checked at compile time. For example if you pass a double[ 3 ][ 3 ]
or a three dimensional LvArray::ArraySlice
to LvArray::tensorOps::crossProduct
you will get a compilation error since that function is only implemented for vectors. When passing a c-array as an argument the size of the array is checked at compile time. For example if you pass int[ 2 ][ 3 ]
to LvArray::tensorOps::addIdentity
you will get a compilation error because that function only operates on square matrices. However when passing an LvArray::Array*
object the size is only checked at runtime if LVARRAY_BOUNDS_CHECK
is defined.
TEST( tensorOps, boundsCheck )
{
double x[ 3 ] = { 0, 1, 2 };
LvArray::tensorOps::normalize< 3 >( x );
// This would fail to compile since x is not length 4.
// LvArray::tensorOps::normalize< 4 >( x );
LvArray::Array< double,
1,
camp::idx_seq< 0 >,
std::ptrdiff_t,
LvArray::MallocBuffer > xArray( 8 );
xArray( 0 ) = 10;
#if defined(LVARRAY_BOUNDS_CHECK)
// This will fail at runtime.
EXPECT_DEATH_IF_SUPPORTED( LvArray::tensorOps::normalize< 3 >( xArray ), "" );
#endif
int matrix[ 2 ][ 2 ] = { { 1, 0 }, { 0, 1 } };
LvArray::tensorOps::normalize< 2 >( matrix[ 0 ] );
// This would fail to compile since normalize expects a vector
// LvArray::tensorOps::normalize< 4 >( matrix );
LvArray::Array< double,
2,
camp::idx_seq< 0, 1 >,
std::ptrdiff_t,
LvArray::MallocBuffer > matrixArray( 2, 2 );
matrixArray( 0, 0 ) = 1;
matrixArray( 0, 1 ) = 1;
LvArray::tensorOps::normalize< 2 >( matrixArray[ 0 ] );
// This will also fail to compile
// LvArray::tensorOps::normalize< 2 >( matrixArray );
}
[Source: examples/exampleTensorOps.cpp]