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 | |
---|---|
Fortunatelly, c++14 makes all this a lot easier, since you can just use
the |
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}});