Aboria

PrevUpHomeNext

Symbolic Expressions

Setup
Constant Expressions
Univariate Expressions
Bivariate Expressions

To start using symbolic expressions, you first need to define a set of symbols to represent your variables, as well as labels to represent your particle set(s).

A symbol representing each particle's position is defined as

Symbol<position> p;

A symbol representing the variable alive is defined as

Symbol<alive> _alive;

A label representing the particle set particles with type MyParticles is defined as

Label<0, Particles_t> a(particles);
Label<1, Particles_t> b(particles);

The first template argument is the id of the label, and the second is the type of the particle set label refers to. Note that the type of each label must be unique, as this type is used to determine which particle you mean when using the label within expressions. You can use the id template argument to ensure that each label type is unique.

Labels refer to a specific particle set. For example, given a bivariate neighbour expression involving two particles from particles, the label a defined above would refer to the first particle, and b would refer to the second. Note that the result values of a bivariate expression will form a matrix of values, with particle a corresponding to the row of the matrix, and particle b corresponding to the column.

Now we have defined our labels and symbols, we can create a simple expression to set the position of all particles to double3(0,0,1)

p[a] = vdouble3(0, 0, 1);

If we want to add 1 to every particle position, we can use an increment expression

p[a] += 1;

A special case involves the alive variable flag. If we use our _alive symbol to set all the alive flag's to false like so

_alive[a] = false;

Then after the expression is complete Aboria will call the delete_particles member function to delete all the particles with get<alive>(particle) == false (i.e. all of them). After this expression the particle container will be empty.

Single-particle dependent expressions can easily be built by using a single label on the RHS of the expression. For example we can also add a constant value 1 to each particle position like so

p[a] = p[a] + 1;

Or we can use a flag variable flag, along with its symbol f, so define two sets of particles within the same container and only change the particle position if get<flag>(particle) == true. Recall that we can't use boolean flags so we can instead use a uint8_t datatype. For example

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

// create particle container and set flags and positions here
Symbol<flag> f;
p[a] = if_else(f[a], p[a] + 1, p[a]);

We can even selectively delete particle using our f and _alive symbols.

_alive[a] = if_else(f[a], true, false);

So far we have used constant or univariate expressions on the RHS of an assignment operator. This makes sense because we can only assign an expression to a single particle if that expression depends on a constant or that particle's variables. However, we might also want our expression to depend on the other particles in the container, or another container. In this case we wish to use a bivariate expression. For example, the following equation defined a sum over all other particles (with label b), using the function w, which depends on the separation of particle a and b.

$$ p_a = \sum_b w(p_b-p_a) $$

Normally for a bivariate expression we wish to accumulate the contribution from other particles in order to arrive at a result. This is most often done with a summation. In Aboria, we can define an accumulator object, which takes a single template argument which is the function or functor to accumulate with. For example, the following defines a summation accumulator using std::plus

Accumulate<std::plus<vdouble3>> sum;
Once we have this summation accumulator, we can write an expression similar
to the equation above like so, using $w(p_b) = (p_b - p_a)$.Note that the
first argument to `sum` is set to `true`, indicating the summation is over
  • all* particles in label b.
p[a] = sum(b, p[b] - p[a]);

We might also want to sum the inverse distance between particle pairs, like so

p[a] = sum(b, 1.0 / (p[b] - p[a]));

Unfortunately if a and b refer to the same particle container this will result a divide-by-zero at runtime, when a and b are labels for the same particle. Therefore we can restrict the evaluation of the sum by setting the second argument to ensure that the id of particles a and b are non-identical. Recall that id is a built-in variable that contains a unique id for each particle in the container.

Symbol<id> _id;
p[a] = sum(b, if_else(_id[a] != _id[b], 1.0, 0.0) / (p[b] - p[a]));

So the first argument to sum is the label to sum over, the second is the conditional expression that must evaluate to true to be included in the summation, and the third is an expression that provides the value to be included in the summation.

There is a special case for the conditional expression, when you want to sum over all particles within a certain radius. This can be expressed using the Aboria::AccumulateWithinDistance summation

AccumulateWithinDistance<std::plus<vdouble3>> sum_within_two(2);
auto dx = create_dx(a, b);
p[a] = sum_within_two(b, dx / pow(norm(dx), 2));

where dx is a Dx symbol representing the shortest vector from b to a. Note that this might be different from p[a]-p[b] for periodic domains. The symbolic function [functionref Aboria::norm norm] returns the 2-norm, or magnitude, of the vector dx, and the symbolic function [functionref Aboria::pow pow] returns the first argument to the power of the second (in much the same way as std::pow, but lazily evaluated).

In this case Aboria will perform a summation over neighbours closer than a radius of 2, and will use the neighbourhood searching facility described in aboria.neighbourhood_searching to find these neighbouring particles. Note that you need to call Aboria::Particles::init_neighbour_search before any bivariate expressions using neighbourhood searching.

As another, more complete example, we can use the

neighbourhood-searching expressions to count the number of particles within a distance of 2 of each individual particle, storing the result in a variable called count.

ABORIA_VARIABLE(count, int, "number of surrounding particles")
typedef Particles<std::tuple<count>> MyParticles;
typedef MyParticles::position position_t;

// add some particles
MyParticles particles2(N);

std::uniform_real_distribution<double> uni(0, 1);
for (size_t i = 0; i < N; ++i) {
  auto &gen = get<generator>(particles2)[i];
  get<position_t>(particles2)[i] = vdouble3(uni(gen), uni(gen), uni(gen));
}

// initialise neighbour searching
particles2.init_neighbour_search(
    vdouble3::Constant(0), vdouble3::Constant(1), vbool3::Constant(false));

// define symbols and labels, and sum
Symbol<count> c;
Label<0, MyParticles> i(particles2);
Label<1, MyParticles> j(particles2);
AccumulateWithinDistance<std::plus<int>> neighbour_sum(2);
auto dx_ij = create_dx(i, j);

// count neighbouring particles within a distance of 2
c[i] = neighbour_sum(j, 1);

PrevUpHomeNext