Aboria

PrevUpHomeNext

Particle Container

Creating Particles
Multidimensional Data Types
Working with particles within the container
Internal Data for Variables
Particle's value_type versus reference
Important differences from STL containers
Conversion to VTK formats

The main particles data-structure, or container, is called Aboria::Particles. It is templated using a tuple of variable types, explained below. For example, the following creates a set of particles which each have (along with the standard variables such as position, id etc) a data package consisting of one double variable type named scalar.

using namespace Aboria;

ABORIA_VARIABLE(scalar, double, "my scalar")
typedef Particles<std::tuple<scalar>> MyParticles;
MyParticles particles;

You can set the dimension of the container by using an optional unsigned integer template argument (defaults to 3). For example, if you wanted a container of particles in 2D space, you would use

typedef Particles<std::tuple<scalar>, 2> MyParticles2;

If you wanted each particle to have a potential variable held as a double, as well as a velocity variable held as a Aboria::vdouble3 vector class, then you would write the following

ABORIA_VARIABLE(potential, double, "potential energy")
ABORIA_VARIABLE(velocity, vdouble3, "velocity")
typedef Particles<std::tuple<potential, velocity>> MyParticles3;

Note that there is a special case for boolean variables, which must be represented by an integer, rather than a boolean. This is due to the STL specialisation of a boolean STL vector, which conflicts with the internal design of Aboria. For example, here we can use an 8-bit unsigned integer to stand in for the boolean flag variable.

ABORIA_VARIABLE(flag, uint8_t, "my flag variable")

You can give the MyParticles constructor a single int argument to initialise the container with n particles:

const int n = 100;
MyParticles particles2(n);

To create new particles simply use the value_type of the container type. For example, to create a new particle you could write

MyParticles::value_type p;

Each value_type is a tuple of values, of the types specified by each variable. You can retrieve or set these value using the Aboria::get function, which is templated on the variable type. For example, say you wanted to set the scalar variable for particle p:

get<scalar>(p) = 1.0;

You can print the value back out, again using the Aboria::get function

std::cout << "the scalar variable equals " << get<scalar>(p) << std::endl;

The value_type of the Particles container also has, a position, a unique id and a boolean flag indicating if this particle is alive or not. The position type is dependent on the dimension, so the best way is to get the type from the container type, i.e.

typedef MyParticles::position position;
get<position>(p) = vdouble3(0, 0, 0);

Getting the id or alive flag from a value_type is much simpler

std::cout << "the particle id is " << get<id>(p) << std::endl;
std::cout << "the particle alive flag is " << get<alive>(p) << std::endl;

Once you are happy with your particle, you can add it to the container using the Aboria::Particles::push_back member function

particles.push_back(p);

Aboria provides an internal vector type Aboria::Vector for types representing a vector of dimension d. Aboria::Vector is templated on the type of each element and the number of dimensions:

Vector<double, 3> dim3vector;

There are a number of predefined double, int, and bool vector types, up to dimension 7, and typedefed by the pattern v<type><dim>. E.g. Aboria::vdouble3, Aboria::vdouble6, Aboria::vint2, Aboria::vbool5...

You can use the indexing operator Aboria::Particles::operator[] to simply loop through the container

for (size_t i = 0; i < particles.size(); i++) {
  std::cout << "Accessing particle with id = " << get<id>(particles[i])
            << "\n";
}

Note that the index operator Aboria::Particles::operator[] returns a Aboria::Particles::reference, which is defined as a tuple containing references to each of the variables. This is different from a reference to Aboria::Particles::value_type.

Or you can use the normal STL Aboria::Particles::begin() and Aboria::Particles::end() functions that return random access iterators to the beginning and end of the container.

for (auto i = particles.begin(); i != particles.end(); i++) {
  std::cout << "Accessing particle with id = " << get<id>(*i) << "\n";
}

Or

for (auto i : particles) {
  std::cout << "Accessing particle with id = " << get<id>(i) << "\n";
}

Or you can use the STL algorithm for_each. If you are using a GCC compiler, you can turn on the parallel mode to enable this loop to be run in parallel

std::for_each(particles.begin(), particles.end(), [](auto i) {
  std::cout << "Accessing particle with id = " << get<id>(i) << "\n";
});

Each variable is held internally by a STL vector std::vector. If you wish to directly access this vector, then you can use the normal Aboria::get functions to get it.

std::vector<size_t> &ids = get<id>(particles);
std::vector<double> &scalars = get<scalar>(particles);

When you index an individual particle using the bracket operator Aboria::Particles::operator[], it returns a getter_type, which is essentially a tuple of references to the variables for that particle. This getter_type is typedef-ed to Aboria::Particles::reference, and acts as the reference type for the container. Similarly, the value_type for the continer is also a Aboria::getter_type, but instead holds a tuple of values instead of references.

Reading the above paragraph, you will note the fundamental difference from normal STL containers, in that value_type& is not the same as reference. This can be relevant when writing functors for STL algorithms, where you will need to be sure if you need a value_type& or a reference.

For example, the std::sort algorithm internally stores a value_type of an element which is used in the comparison, so the functor needs to be equivalent to the following

bool cmp(const value_type& a, const value_type& b)

However, the std::transform algorithm can use a unaryop functor equivalent to

Ret fun(const reference a)

Which is more efficient than value_type&, since dereferencing the iterator will result in a reference.

[Note] Note

Fortunatelly, c++14 makes all this a lot easier, since you can just use the auto keyword and let the compiler deduce the correct type!

The Aboria::Particles data structure acts fairly typically like a normal STL random-access container, with a few important differences. It has methods like push_back, clear, size, erase. It provides subtypes like value_type, reference, const_reference, iterator, const_iterator. All of the normal algorithms in the standard library should work with this container, if you find any that don't please let us know and we will try to fix this.

The main differences between Aboria::Particles and normal STL containers are:

1. The difference between value_type& and reference mentioned described earlier.

2. Additional member functions are available to suit the specific purpose of this container, for example the push_back function can take a vector data-type for the particle position, and the get_query function for neighbour searching.

3. When using the neighbourhood searching capabilities of the container, the order of the particles in the particle container might change due to internal sorting for neighbourhood searching efficiency. So do not assume that the particle ordering is fixed. For example, the push_back member function can reorder the particles if neighbourhood searching is turned on.

It is possible to convert the Aboria::Particles data structure to a VTK unstructured grid class using the Aboria::Particles::get_grid function. This function will write out each particle as a 3D point in the unstructured grid. By default all the particle's variables are converted into VTK data arrays and added to the grid, except for those with names starting with the character "_".

In order to write out the resultant grid to a file using the VTK data format, Aboria provides a useful helper function Aboria::vtkWriteGrid, which can write out the grid along with any constant fields (e.g. a timestamp) that you may need. For example, the following code writes out the entire contents of the the particle set to the file doc00001.vtu, along with a constant field named "time" containing the value 1.0.

vtkWriteGrid("doc", 0, particles.get_grid(true), {{"time", 1.0}});

PrevUpHomeNext