ParticleGroup

Most UAMMD modules can be instructed to act only on a subset of particles, this is handled by creating a ParticleGroup. A group can contain all particles, no particles or anything in between.

class ParticleGroup

Keeps track of a subset of particles which properties reside in ParticleData.

std::shared_ptr<ParticleData> getParticleData()

Get the instance of ParticleData the ParticleGroup works on.

void clear();

Remove all particles from the group.

void addParticlesById(access::location loc, const int *ids, int N);

Add particles to the group via an array (with memory residing according to loc) of particle ids.

void addParticlesByCurrentIndex(access::location loc, const int *indices, int N);

Add particles to the group via an array with the current indices of the particles in ParticleData (this is faster than addParticlesById()).

const int *getIndicesRawPtr(access::location loc);

Get a raw memory pointer to a list with the indices of the particles in this group. Returns a null pointer if all or none particles are in the group.

IndexIterator getIndexIterator(access::location loc);

Get an iterator with the indices of particles in this group.

const int *getGroupIndexMaskRawPtr(access::location loc);

Get an int pointer of size pd->getNumParticles() returning 1 if the particle in the particle currently in that index index is part of the group and 0 otherwise. loc specifies the memory location of the underlying array. Returns a null pointer if all or none particles are in the group.

MaskIterator getGroupIndexMask(access::location loc);

Same as getGroupIndexMask but will also work with groups containing all particles, returning a constant iterator with a value of 1.

template<class Iterator>
accessIterator<Iterator> getPropertyIterator(Iterator property, access::location loc);

Returns an iterator that will have size pg->getNumberParticles() and will iterate over the particles in the group.For example, If a group contains only the particle with id=10, passing pd->getPos(...).begin() to this function will return an iterator so that iterator[0] = pos[10]; and it will take into account any possible reordering of the pos array. The location does not have to be specified if the property argument is a property_ptr provided by ParticleData.

int getNumberParticles();

Returns the number of particles currently in the group.

std::string getName();

Returns the given name of the group.

Hint

ParticleGroup does not always create an actual list of particles. The iterator returned by getPropertyIterator takes advantage of this

Creation

ParticleGroup exposes several constructors:

ParticleGroup::ParticleGroup(std::shared_ptr<ParticleData> pd, std::string name = std::string("noName"));

This constructor creates a ParticleGroup containing all particles in the provided ParticleData instance.

ParticleGroup::ParticleGroup(ParticleSelector selector, std::shared_ptr<ParticleData> pd, std::string name = std::string("noName"));

Fills the group with the particles according to the provided ParticleSelector.

template<class InputIterator>
ParticleGroup::ParticleGroup(InputIterator begin, InputIterator end, std::shared_ptr<ParticleData> pd, std::string name = std::string("noName"));

Fills the group with the particles ids provided in the iterator range begin:end.

Example

//By default a ParticleGroup will contain all particles
auto allParticlesGroup = make_shared<ParticleGroup>(pd, sys, "AGroupWithAllParticles");

//Different selectors offer different criteria
//In this case, it will result in a group with particles whose ID lies between 4 and 8
particle_selector::IDRange selector(4,8);
auto aGroupWithSomeIDs = make_shared<ParticleGroup>(selector, pd, sys, "SomeName");

//Equivalently a list of particle IDs can be provided directly
auto idrange = std::vector<int>(4); std::iota(idrange.begin(), idrange.end(), 4);
auto anEquivalentGroup = make_shared<ParticleGroup>(idrange.begin(), idrange.end(), pd, sys, "SomeOtherName");

//A group containing all particles of a certain type (or types) (type being the value of pos.w)
auto groupOfParticlesWithType0 = make_shared<ParticleGroup>(particle_selector::Type(0), pd, sys, "Type 0 particles");
auto groupOfParticlesWithType0And3 = make_shared<ParticleGroup>(particle_selector::Type({0,3}), pd, sys, "Type 0 and type 3 particles");

//A group of 10 random particles
std::vector<int> randomlyOrderedIds(numberParticles);
std::iota(randomlyOrderedIds.begin(), randomlyOrderedIds.end(), 0);
std::shuffle(randomlyOrderedIds.begin(), randomlyOrderedIds.end(), std::mt19937{std::random_device{}()});
randomlyOrderedIds.resize(10);
auto groupOf10RandomParticles = make_shared<ParticleGroup>(randomlyOrderedIds.begin(), randomlyOrderedIds.end(), pd, sys, "10 Random Particles");

Instructions on how to create a selector are located in ParticleGroup.cuh but the easiest way to create a group with a custom criteria is to just pass a list of particle ids as in the examples.

Particle selectors

Selectors are small functors providing a member that checks if a given particle should be in a group or not.

class ParticleSelector

This is a concept, not a virtual class that must be inherited. Any class defining a member with the signature below will act as a valid selector for ParticleGroup

bool isSelected(int particleIndex, std::shared_ptr<ParticleData> pd);

This function should use the provided ParticleData instance to decide if the particle with index particleIndex should be included in the group or not.

Important

Selectors are only used for particle inclusion into a group when the group is created. ParticleGroup will not track the changes in the inclusion conditions.

Example

A selector that returns true for every particle.

class All{
 public:
   All(){}
   bool isSelected(int particleIndex, std::shared_ptr<ParticleData> pd){
     return true;
   }
 };

Available particle selector

Creating a group by providing the ids of the relevant particles can be in many cases the most acceptable way of creating a group. However, several selectors are available for convenience under the particle_selector namespace.

class particle_selector::All;

Selects all the particles.

class particle_selector::None;

Results in an empty group.

class particle_selector::IDRange

Select particles with ID in a certain range

IDRange::IDRange(int first, int last);
class particle_selector::Domain

Select particles inside a certain rectangular region of the simulation box.

Domain::Domain(real3 origin, Box domain, Box simulationBox);

This selector will first fold the particles into simulationBox and then choose any particle that lies inside a region given by domain with origin origin.

class particle_selector::Type

Select particles by type (using the fourth element of the positions, pos.w)

Type::Type(std::vector<int> typesToSelect)

A list of types that should go into the group.

Usage with UAMMD modules

When it makes sense, UAMMD modules will have an optional ParticleGroup argument at creation. See for example Pair Forces.

General usage

ParticleGroup will keep track of its particles and will always provide their up to date global indices.

//You can request an iterator with the current indices of the particles in a group with:
auto indicesOfParticlesInGroup = pg->getIndexIterator(access::location::gpu);

//Or get a plain array with the indices directly, if it exists.
auto rawMemoryPtrOfIndices = pg->getIndicesRawPtr(access::location::gpu); //or cpu, it will be nullptr if all (or none) particles are in the group

//You can also request an iterator that will read a ParticleData array using the group indices directly.
//This allows to write generic code that will work both with a group or with a ParticleData array.
auto allPositions = pd->getPos(access::location::gpu, access::mode::read);
auto IteratorWithPositionsInGroup = pg->getPropertyIterator(allPositions);
...
//In device code
real4 positionOfFirstParticleInGroup = IteratorWithPositionsInGroup[0];

Hint

As a general rule, when writing UAMMD code, it is wise to access particle properties using ParticleGroups instead of ParticleData directly.

Note

A default group contains all particles, it is a special case and incurs no overhead (besides maybe a couple of registers) when created or used.