Core containers¶
Warning
Axom’s containers are currently the target of a refactoring effort. The following information is subject to change, as are the interfaces for the containers themselves. When changes are made to the Axom containers, the changes will be reflected here.
Axom Core contains the Array
, ArrayView
, and StackArray
classes.
Among other things, these data containers facilitate porting code that uses
std::vector
to GPUs.
Array
is a multidimensional contiguous container template. In the
1-dimensional case, this class behaves similar to std::vector
. In higher
dimensions, some vector-like functionality, such as push_back
, are not
available and multidimensional-specific operations will mirror the
ndarray
provided by numpy
when possible.
The Array
object manages all memory. Typically, the Array
object will
allocate extra space to facilitate the insertion of new elements and minimize the number of reallocations.
The actual capacity of the array (i.e., total number of elements that
the Array
can hold) can be queried via the capacity()
method.
When allocated memory is used up, inserting a new element triggers a
reallocation. At each reallocation, extra space is allocated
according to the resize_ratio parameter, which is set to 2.0
by default. To return all extra memory, an application can call
shrink().
Warning
Reallocations tend to be costly operations in terms of performance.
Use reserve()
when the number of nodes is known a priori, or
use a constructor that takes an actual size and capacity when possible.
Note
The Array destructor deallocates and returns all memory associated with it to the system.
Here’s an example showing how to use Array
instead of std::vector
.
// Here is an Array of ints with length three.
axom::Array<int> a(3);
std::cout << "Length of a = " << a.size() << std::endl;
a[0] = 2;
a[1] = 5;
a[2] = 11;
// An Array increases in size if a value is pushed back.
a.push_back(4);
std::cout << "After pushing back a value, a's length = " << a.size()
<< std::endl;
// You can also insert a value in the middle of the Array.
// Here we insert value 6 at position 2 and value 1 at position 4.
showArray(a, "a");
a.insert(2, 6);
a.insert(4, 1);
std::cout << "After inserting two values, ";
showArray(a, "a");
The output of this example is:
Length of a = 3
After appending a value, a's length = 4
Array a = [2, 5, 11, 4]
After inserting two values, Array a = [2, 5, 6, 11, 1, 4]
Applications commonly store tuples of data in a flat array or a std::vector
.
In this sense, it can be thought of as a two-dimensional array. Array
supports arbitrary dimensionalities but an alias, MCArray
(or Multi-Component Array), is provided for this two-dimensional case.
The MCArray
(i.e., Array<T, 2>
) class formalizes tuple storage, as shown in the next example.
// Here is an MCArray of ints, containing two triples.
const int numTuples = 2;
const int numComponents = 3;
axom::MCArray<int> b(numTuples, numComponents);
// Set tuple 0 to (1, 4, 2).
b(0, 0) = 1;
b(0, 1) = 4;
b(0, 2) = 2;
// Set tuple 1 to one tuple, (8, 0, -1).
// The first argument to set() is the buffer to copy into the MCArray, the
// second is the number of tuples in the buffer, and the third argument
// is the first tuple to fill from the buffer.
int ival[3] = {8, 0, -1};
b.set(ival, 3, 3);
showTupleArray(b, "b");
// Now, insert two tuples, (0, -1, 1), (1, -1, 0), into the MCArray, directly
// after tuple 0.
int jval[6] = {0, -1, 1, 1, -1, 0};
b.insert(1, numTuples * numComponents, jval);
showTupleArray(b, "b");
The output of this example is:
MCArray b with 2 3-tuples = [
[1, 4, 2]
[8, 0, -1]
]
MCArray b with 4 3-tuples = [
[1, 4, 2]
[0, -1, 1]
[1, -1, 0]
[8, 0, -1]
]
It is also often useful to wrap an external, user-supplied buffer without taking ownership
of the data. For this purpose Axom provides the ArrayView
class, which is a lightweight
wrapper over a buffer that provides one- or multi-dimensional indexing/reshaping semantics.
For example, it might be useful to reinterpret a flat (one-dimensional) array as a two-dimensional array.
This is accomplished via MCArrayView
which, similar to the MCArray
alias, is an alias for ArrayView<T, 2>
.
// The internal buffer maintained by an MCArray is accessible.
int* pa = a.data();
// An MCArray can be constructed with a pointer to an external buffer.
// Here's an Array interpreting the memory pointed to by pa as three 2-tuples.
axom::MCArrayView<int> c(pa, 3, 2);
showArray(a, "a");
showTupleArrayView(c, "c");
// Since c is an alias to a's internal memory, changes affect both Arrays.
a[0] = 1;
c(1, 1) = 9;
std::cout
<< "Array a and MCArrayView c use the same memory, a's internal buffer."
<< std::endl;
showArray(a, "a");
showTupleArrayView(c, "c");
The output of this example is:
Array a = [2, 5, 6, 11, 1, 4]
MCArrayView c with 3 2-tuples = [
[2, 5]
[6, 11]
[1, 4]
]
Array a and MCArrayView c use the same memory, a's internal buffer.
Array a = [1, 5, 6, 9, 1, 4]
MCArrayView c with 3 2-tuples = [
[1, 5]
[6, 9]
[1, 4]
]
Note
The set of permissible operations on an ArrayView
is somewhat limited,
as operations that would cause the buffer to resize are not permitted.
In the future, it will also be possible to restride an ArrayView
.
Iteration is also identical between the Array
and ArrayView
classes.
In particular:
operator()
indexes into multidimensional data. Currently, the number of indexes passed must match the dimensionality of the array.operator[]
indexes into the full buffer, i.e.,arr[i]
is equivalent toarr.data()[i]
.begin()
andend()
refer to the full buffer.
Consider the following example:
// Iteration over multidimensional arrays uses the shape() method
// to retrieve the extents in each dimension.
for(int i = 0; i < c.shape()[0]; i++)
{
for(int j = 0; j < c.shape()[1]; j++)
{
// Note that c's operator() accepts two arguments because it is two-dimensional
std::cout << "In ArrayView c, index (" << i << ", " << j << ") yields "
<< c(i, j) << std::endl;
}
}
// To iterate over the "flat" data in an Array, regardless of dimension,
// use a range-based for loop.
std::cout << "Range-based for loop over ArrayView c yields: ";
for(const int value : c)
{
std::cout << value << " ";
}
std::cout << std::endl;
// Alternatively, the "flat" data can be iterated over with operator[]
// from 0 -> size().
std::cout << "Standard for loop over ArrayView c yields: ";
for(int i = 0; i < c.size(); i++)
{
std::cout << c[i] << " ";
}
std::cout << std::endl;
The output of this example is:
In ArrayView c, index (0, 0) yields 1
In ArrayView c, index (0, 1) yields 5
In ArrayView c, index (1, 0) yields 6
In ArrayView c, index (1, 1) yields 9
In ArrayView c, index (2, 0) yields 1
In ArrayView c, index (2, 1) yields 4
Range-based for loop over ArrayView c yields: 1 5 6 9 1 4
Standard for loop over ArrayView c yields: 1 5 6 9 1 4
The StackArray
class is a work-around for a limitation in older versions
of the nvcc compiler, which do not capture arrays on the stack in device
lambdas. More details are in the API documentation and in the tests.