// Copyright (c) 2009-2016 The Regents of the University of Michigan
// This file is part of the HOOMD-blue project, released under the BSD 3-Clause License.


// Maintainer: joaander
#include "ParticleGroup.h"

#ifdef ENABLE_CUDA
#include "ParticleGroup.cuh"
#include "CachedAllocator.h"
#endif

#include <boost/python.hpp>
#include <boost/bind.hpp>
using namespace boost::python;
using namespace boost;

#include <algorithm>
#include <iostream>
using namespace std;

/*! \file ParticleGroup.cc
    \brief Defines the ParticleGroup and related classes
*/

//////////////////////////////////////////////////////////////////////////////
// ParticleSelector

/*! \param sysdef System the particles are to be selected from
*/
ParticleSelector::ParticleSelector(boost::shared_ptr<SystemDefinition> sysdef)
    : m_sysdef(sysdef), m_pdata(sysdef->getParticleData())
    {
    assert(m_sysdef);
    assert(m_pdata);
    m_exec_conf = m_pdata->getExecConf();
    }

/*! \param tag Tag of the particle to check
    \returns true if the particle is selected
    \returns false if it is not
*/
bool ParticleSelector::isSelected(unsigned int tag) const
    {
    // base class doesn't do anything useful
    return false;
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorAll

/*! \param sysdef System the particles are to be selected from
*/
ParticleSelectorAll::ParticleSelectorAll(boost::shared_ptr<SystemDefinition> sysdef)
    : ParticleSelector(sysdef)
    { }

/*! \param tag All of the particle to check
    \returns true if particle is local
*/
bool ParticleSelectorAll::isSelected(unsigned int tag) const
    {
    assert(tag <= m_pdata->getMaximumTag());

    return m_pdata->isParticleLocal(tag);
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorTag

/*! \param sysdef System the particles are to be selected from
    \param tag_min Minimum tag to select (inclusive)
    \param tag_max Maximum tag to select (inclusive)
*/
ParticleSelectorTag::ParticleSelectorTag(boost::shared_ptr<SystemDefinition> sysdef,
                                         unsigned int tag_min,
                                         unsigned int tag_max)
    : ParticleSelector(sysdef), m_tag_min(tag_min), m_tag_max(tag_max)
    {
    // make a quick check on the sanity of the input data
    if (m_tag_max < m_tag_min)
        m_exec_conf->msg->warning() << "group: max < min specified when selecting particle tags" << endl;

    if (!m_pdata->getNGlobal() || m_tag_max > m_pdata->getMaximumTag())
        {
        m_exec_conf->msg->error() << "Cannot select particles with tags larger than the number of particles "
             << endl;
        throw runtime_error("Error selecting particles");
        }
    }

/*! \param tag Tag of the particle to check
    \returns true if \a m_tag_min <= \a tag <= \a m_tag_max
*/
bool ParticleSelectorTag::isSelected(unsigned int tag) const
    {
    assert(tag <= m_pdata->getMaximumTag());
    return (m_tag_min <= tag && tag <= m_tag_max);
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorType

/*! \param sysdef System the particles are to be selected from
    \param typ_min Minimum type id to select (inclusive)
    \param typ_max Maximum type id to select (inclusive)
*/
ParticleSelectorType::ParticleSelectorType(boost::shared_ptr<SystemDefinition> sysdef,
                                           unsigned int typ_min,
                                           unsigned int typ_max)
    : ParticleSelector(sysdef), m_typ_min(typ_min), m_typ_max(typ_max)
    {
    // make a quick check on the sanity of the input data
    if (m_typ_max < m_typ_min)
        m_exec_conf->msg->warning() << "group: max < min specified when selecting particle types" << endl;

    if (m_typ_max >= m_pdata->getNTypes())
        m_exec_conf->msg->warning() << "group: Requesting the selection of a non-existant particle type" << endl;
    }

/*! \param tag Tag of the particle to check
    \returns true if the type of particle \a tag is in the inclusive range [ \a m_typ_min, \a m_typ_max ]
*/
bool ParticleSelectorType::isSelected(unsigned int tag) const
    {
    assert(tag <= m_pdata->getMaximumTag());

    // access array directly instead of going through the getType() interface
    ArrayHandle<Scalar4> h_postype(m_pdata->getPositions(), access_location::host, access_mode::read);
    ArrayHandle<unsigned int> h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read);

    unsigned int idx = h_rtag.data[tag];

    if (idx >= m_pdata->getN())
        {
        // particle is not local
        return false;
        }

    unsigned int typ = __scalar_as_int(h_postype.data[idx].w);

    // see if it matches the criteria
    bool result = (m_typ_min <= typ && typ <= m_typ_max);

    return result;
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorRigid

/*! \param sysdef System the particles are to be selected from
    \param rigid true selects particles that are in rigid bodies, false selects particles that are not part of a body
*/
ParticleSelectorRigid::ParticleSelectorRigid(boost::shared_ptr<SystemDefinition> sysdef,
                                             bool rigid)
    : ParticleSelector(sysdef), m_rigid(rigid)
    {
    }

/*! \param tag Tag of the particle to check
    \returns true if the type of particle \a tag meets the rigid critera selected
*/
bool ParticleSelectorRigid::isSelected(unsigned int tag) const
    {
    assert(tag <=  m_pdata->getMaximumTag());

    // get body id of current particle tag
    unsigned int body = m_pdata->getBody(tag);

    // see if it matches the criteria
    bool result = false;
    if (m_rigid && body != NO_BODY)
        result = true;
    if (!m_rigid && body == NO_BODY)
        result = true;

    return result;
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorRigidCenter

ParticleSelectorRigidCenter::ParticleSelectorRigidCenter(boost::shared_ptr<SystemDefinition> sysdef)
    :ParticleSelector(sysdef)
    {
    }

/*! \param tag Tag of the particle to check
    \returns true if the type of particle \a tag is a center particle of a rigid body
*/
bool ParticleSelectorRigidCenter::isSelected(unsigned int tag) const
    {
    assert(tag <= m_pdata->getMaximumTag());

    // access array directly instead of going through the getBody() interface
    ArrayHandle<unsigned int> h_body(m_pdata->getBodies(), access_location::host, access_mode::read);
    ArrayHandle<unsigned int> h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read);

    unsigned int idx = h_rtag.data[tag];

    if (idx >= m_pdata->getN())
        {
        // particle is not local
        return false;
        }

    // get position of particle
    unsigned int body = h_body.data[idx];

    // see if it matches the criteria
    return (body == tag);
    }


//////////////////////////////////////////////////////////////////////////////
// ParticleSelectorCuboid

ParticleSelectorCuboid::ParticleSelectorCuboid(boost::shared_ptr<SystemDefinition> sysdef, Scalar3 min, Scalar3 max)
    :ParticleSelector(sysdef), m_min(min), m_max(max)
    {
    // make a quick check on the sanity of the input data
    if (m_min.x >= m_max.x || m_min.y >= m_max.y || m_min.z >= m_max.z)
        m_exec_conf->msg->warning() << "group: max < min specified when selecting particle in a cuboid" << endl;
    }

/*! \param tag Tag of the particle to check
    \returns true if the type of particle \a tag is in the cuboid

    Evaluation is performed by \a m_min.x <= x < \a m_max.x so that multiple cuboids stacked next to each other
    do not have overlapping sets of particles.
*/
bool ParticleSelectorCuboid::isSelected(unsigned int tag) const
    {
    assert(tag <= m_pdata->getMaximumTag());

    // access array directly instead of going through the getPosition() interface
    ArrayHandle<Scalar4> h_postype(m_pdata->getPositions(), access_location::host, access_mode::read);
    ArrayHandle<unsigned int> h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read);

    unsigned int idx = h_rtag.data[tag];

    if (idx >= m_pdata->getN())
        {
        // particle is not local
        return false;
        }

    // get position of particle
    Scalar4 postype = h_postype.data[idx];

    // see if it matches the criteria
    bool result = (m_min.x <= postype.x && postype.x < m_max.x &&
                   m_min.y <= postype.y && postype.y < m_max.y &&
                   m_min.z <= postype.z && postype.z < m_max.z);

    return result;
    }

//////////////////////////////////////////////////////////////////////////////
// ParticleGroup

/*! \param sysdef System definition to build the group from
    \param selector ParticleSelector used to choose the group members
    \param update_tags If true, update tags whenever global particle number changes

    Particles where criteria falls within the range [min,max] (inclusive) are added to the group.
*/
ParticleGroup::ParticleGroup(boost::shared_ptr<SystemDefinition> sysdef,
    boost::shared_ptr<ParticleSelector> selector,
    bool update_tags)
    : m_sysdef(sysdef),
      m_pdata(sysdef->getParticleData()),
      m_exec_conf(m_pdata->getExecConf()),
      m_num_local_members(0),
      m_particles_sorted(true),
      m_reallocated(false),
      m_global_ptl_num_change(false),
      m_selector(selector),
      m_update_tags(update_tags),
      m_warning_printed(false)
    {
    #ifdef ENABLE_CUDA
    if (m_pdata->getExecConf()->isCUDAEnabled())
        {
        // create a ModernGPU context
        m_mgpu_context = mgpu::CreateCudaDeviceAttachStream(0);
        }
    #endif

    // update member tag arrays
    updateMemberTags(true);

    // connect to the particle sort signal
    m_sort_connection = m_pdata->connectParticleSort(bind(&ParticleGroup::slotParticleSort, this));

    // connect reallocate() method to maximum particle number change signal
    m_max_particle_num_change_connection = m_pdata->connectMaxParticleNumberChange(bind(&ParticleGroup::slotReallocate, this));

    // connect updateMemberTags() method to maximum particle number change signal
    m_global_particle_num_change_connection = m_pdata->connectGlobalParticleNumberChange(bind(&ParticleGroup::slotGlobalParticleNumChange, this));
    }

/*! \param sysdef System definition to build the group from
    \param member_tags List of particle tags that belong to the group

    All particles specified in \a member_tags will be added to the group.
*/
ParticleGroup::ParticleGroup(boost::shared_ptr<SystemDefinition> sysdef, const std::vector<unsigned int>& member_tags)
    : m_sysdef(sysdef),
      m_pdata(sysdef->getParticleData()),
      m_exec_conf(m_pdata->getExecConf()),
      m_num_local_members(0),
      m_particles_sorted(true),
      m_reallocated(false),
      m_global_ptl_num_change(false),
      m_update_tags(false),
      m_warning_printed(false)
    {
    // check input
    unsigned int max_tag = m_pdata->getMaximumTag();
    for (std::vector<unsigned int>::const_iterator it = member_tags.begin(); it != member_tags.end(); ++it)
        {
        if (*it > max_tag)
            {
            m_exec_conf->msg->error() << "group.*: Member " << *it << " does not exist in particle data." << std::endl;
            throw std::runtime_error("Error creating ParticleGroup\n");
            }
        }

    // let's make absolutely sure that the tag order given from outside is sorted
    std::vector<unsigned int> sorted_member_tags =  member_tags;
    sort(sorted_member_tags.begin(), sorted_member_tags.end());

    // store member tags
    GPUArray<unsigned int> member_tags_array(member_tags.size(), m_exec_conf);
    m_member_tags.swap(member_tags_array);

        {
        ArrayHandle<unsigned int> h_member_tags(m_member_tags, access_location::host, access_mode::overwrite);
        std::copy(sorted_member_tags.begin(), sorted_member_tags.end(), h_member_tags.data);
        }

    // one byte per particle to indicate membership in the group, initialize with current number of local particles
    GPUArray<unsigned char> is_member(m_pdata->getMaxN(), m_pdata->getExecConf());
    m_is_member.swap(is_member);

    GPUArray<unsigned char> is_member_tag(m_pdata->getRTags().size(), m_pdata->getExecConf());
    m_is_member_tag.swap(is_member_tag);

    // build the reverse lookup table for tags
    buildTagHash();

    GPUArray<unsigned int> member_idx(member_tags.size(), m_pdata->getExecConf());
    m_member_idx.swap(member_idx);

    #ifdef ENABLE_CUDA
    if (m_pdata->getExecConf()->isCUDAEnabled())
        {
        // create a ModernGPU context
        m_mgpu_context = mgpu::CreateCudaDeviceAttachStream(0);
        }
    #endif

    // now that the tag list is completely set up and all memory is allocated, rebuild the index list
    rebuildIndexList();

    // connect to the particle sort signal
    m_sort_connection = m_pdata->connectParticleSort(bind(&ParticleGroup::slotParticleSort, this));

    // connect reallocate() method to maximum particle number change signal
    m_max_particle_num_change_connection = m_pdata->connectMaxParticleNumberChange(bind(&ParticleGroup::slotReallocate, this));

    // connect updateMemberTags() method to maximum particle number change signal
    m_global_particle_num_change_connection = m_pdata->connectGlobalParticleNumberChange(bind(&ParticleGroup::slotGlobalParticleNumChange, this));
    }

ParticleGroup::~ParticleGroup()
    {
    // disconnect the sort connection, but only if there was a particle data to connect it to in the first place
    if (m_pdata)
        {
        m_sort_connection.disconnect();
        m_max_particle_num_change_connection.disconnect();
        m_global_particle_num_change_connection.disconnect();
        }
    }

/*! \param force_update If true, always update member tags
 */
void ParticleGroup::updateMemberTags(bool force_update) const
    {
    if (m_selector && !(m_update_tags || force_update) && ! m_warning_printed)
        {
        m_pdata->getExecConf()->msg->warning()
            << "Particle number change but group is static. Create group with update=True if it should be updated."
            << std::endl << "This warning is printed only once." << std::endl;
        m_warning_printed = true;
        }

    if (m_selector && (m_update_tags || force_update))
        {
        // notice message
        m_pdata->getExecConf()->msg->notice(7) << "ParticleGroup: rebuilding tags" << std::endl;

        // assign all of the particles that belong to the group
        // for each particle in the (global) data
        vector<unsigned int> member_tags;

            {
            // loop through local particles and select those that match selection criterium
            ArrayHandle<unsigned int> h_tag(m_pdata->getTags(), access_location::host, access_mode::read);
            for (unsigned int idx = 0; idx < m_pdata->getN(); ++idx)
                {
                unsigned int tag = h_tag.data[idx];
                if (m_selector->isSelected(tag))
                    member_tags.push_back(tag);
                }
            }

        #ifdef ENABLE_MPI
        if (m_pdata->getDomainDecomposition())
            {
            // combine lists from all processors
            std::vector< std::vector<unsigned int> > member_tags_proc(m_exec_conf->getNRanks());
            all_gather_v(member_tags, member_tags_proc, m_exec_conf->getMPICommunicator());

            assert(member_tags_proc.size() == m_exec_conf->getNRanks());

            // combine all tags into an ordered set
            unsigned int n_ranks = m_exec_conf->getNRanks();
            std::set<unsigned int> tag_set;
            for (unsigned int irank = 0; irank < n_ranks; ++irank)
                {
                tag_set.insert(member_tags_proc[irank].begin(), member_tags_proc[irank].end());
                }

            // construct list
            member_tags.clear();
            member_tags.insert(member_tags.begin(), tag_set.begin(), tag_set.end());
            }
        #endif

        // store member tags in GPUArray
        GPUArray<unsigned int> member_tags_array(member_tags.size(), m_pdata->getExecConf());
        m_member_tags.swap(member_tags_array);

        // sort member tags
        std::sort(member_tags.begin(), member_tags.end());

            {
            ArrayHandle<unsigned int> h_member_tags(m_member_tags, access_location::host, access_mode::overwrite);
            std::copy(member_tags.begin(), member_tags.end(), h_member_tags.data);
            }

        GPUArray<unsigned int> member_idx(member_tags.size(), m_pdata->getExecConf());
        m_member_idx.swap(member_idx);
        }

    // one byte per particle to indicate membership in the group, initialize with current number of local particles
    GPUArray<unsigned char> is_member(m_pdata->getMaxN(), m_pdata->getExecConf());
    m_is_member.swap(is_member);

    GPUArray<unsigned char> is_member_tag(m_pdata->getRTags().size(), m_pdata->getExecConf());
    m_is_member_tag.swap(is_member_tag);

    // build the reverse lookup table for tags
    buildTagHash();

    // now that the tag list is completely set up and all memory is allocated, rebuild the index list
    rebuildIndexList();
    }

void ParticleGroup::reallocate() const
    {
    m_is_member.resize(m_pdata->getMaxN());

    if (m_is_member_tag.getNumElements() != m_pdata->getRTags().size())
        {
        // reallocate if necessary
        GPUArray<unsigned char> is_member_tag(m_pdata->getRTags().size(), m_exec_conf);
        m_is_member_tag.swap(is_member_tag);

        buildTagHash();
        }
    }

/*! \returns Total mass of all particles in the group
    \note This method aquires the ParticleData internally
*/
Scalar ParticleGroup::getTotalMass() const
    {
    // grab the particle data
    ArrayHandle< Scalar4 > h_vel(m_pdata->getVelocities(), access_location::host, access_mode::read);

    // loop  through all indices in the group and total the mass
    Scalar total_mass = 0.0;
    for (unsigned int i = 0; i < getNumMembers(); i++)
        {
        unsigned int idx = getMemberIndex(i);
        total_mass += h_vel.data[idx].w;
        }
    return total_mass;
    }

/*! \returns The center of mass of the group, in unwrapped coordinates
    \note This method aquires the ParticleData internally
*/
Scalar3 ParticleGroup::getCenterOfMass() const
    {
    // grab the particle data
    ArrayHandle< Scalar4 > h_vel(m_pdata->getVelocities(), access_location::host, access_mode::read);
    ArrayHandle< Scalar4 > h_pos(m_pdata->getPositions(), access_location::host, access_mode::read);
    ArrayHandle< int3 > h_image(m_pdata->getImages(), access_location::host, access_mode::read);

    // grab the box dimensions
    BoxDim box = m_pdata->getBox();

    // loop  through all indices in the group and compute the weighted average of the positions
    Scalar total_mass = 0.0;
    Scalar3 center_of_mass = make_scalar3(Scalar(0.0), Scalar(0.0), Scalar(0.0));
    for (unsigned int i = 0; i < getNumMembers(); i++)
        {
        unsigned int idx = getMemberIndex(i);
        Scalar mass = h_vel.data[idx].w;
        total_mass += mass;
        Scalar3 pos = make_scalar3(h_pos.data[idx].x, h_pos.data[idx].y, h_pos.data[idx].z);
        Scalar3 unwrapped = box.shift(pos, h_image.data[idx]);
        center_of_mass.x += mass * unwrapped.x;
        center_of_mass.y += mass * unwrapped.y;
        center_of_mass.z += mass * unwrapped.z;
        }
    center_of_mass.x /= total_mass;
    center_of_mass.y /= total_mass;
    center_of_mass.z /= total_mass;

    return center_of_mass;
    }

/*! \param a First particle group
    \param b Second particle group

    \returns A shared pointer to a newly created particle group that contains all the elements present in \a a and
    \a b
*/
boost::shared_ptr<ParticleGroup> ParticleGroup::groupUnion(boost::shared_ptr<ParticleGroup> a,
                                                           boost::shared_ptr<ParticleGroup> b)
    {
    // vector to store the new list of tags
    vector<unsigned int> member_tags;

    if (a != b)
        {
        unsigned int n_a = a->getNumMembersGlobal();
        unsigned int n_b = b->getNumMembersGlobal();

        // make the union
        ArrayHandle<unsigned int> h_members_a(a->m_member_tags, access_location::host, access_mode::read);
        ArrayHandle<unsigned int> h_members_b(b->m_member_tags, access_location::host, access_mode::read);

        insert_iterator< vector<unsigned int> > ii(member_tags, member_tags.begin());
        set_union(h_members_a.data,
                  h_members_a.data + n_a,
                  h_members_b.data,
                  h_members_b.data + n_b,
                  ii);
        }
    else
        {
        unsigned int n_a = a->getNumMembersGlobal();

        // If the two arguments are the same, just return a copy of the whole group (we cannot
        // acquire the member_tags array twice)
        ArrayHandle<unsigned int> h_members_a(a->m_member_tags, access_location::host, access_mode::read);

        insert_iterator< vector<unsigned int> > ii(member_tags, member_tags.begin());
        std::copy(h_members_a.data,
                  h_members_a.data + n_a,
                  ii);
        }



    // create the new particle group
    boost::shared_ptr<ParticleGroup> new_group(new ParticleGroup(a->m_sysdef, member_tags));

    // return the newly created group
    return new_group;
    }

/*! \param a First particle group
    \param b Second particle group

    \returns A shared pointer to a newly created particle group that contains only the elements present in both \a a and
    \a b
*/
boost::shared_ptr<ParticleGroup> ParticleGroup::groupIntersection(boost::shared_ptr<ParticleGroup> a,
                                                                  boost::shared_ptr<ParticleGroup> b)
    {
    // vector to store the new list of tags
    vector<unsigned int> member_tags;

    if (a != b)
        {
        unsigned int n_a = a->getNumMembersGlobal();
        unsigned int n_b = b->getNumMembersGlobal();

        // make the intersection
        ArrayHandle<unsigned int> h_members_a(a->m_member_tags, access_location::host, access_mode::read);
        ArrayHandle<unsigned int> h_members_b(b->m_member_tags, access_location::host, access_mode::read);

        insert_iterator< vector<unsigned int> > ii(member_tags, member_tags.begin());
        set_intersection(h_members_a.data,
                         h_members_a.data + n_a,
                         h_members_b.data,
                         h_members_b.data + n_b,
                         ii);
        }
    else
        {
        unsigned int n_a = a->getNumMembersGlobal();
        // If the two arguments are the same, just return a copy of the whole group (we cannot
        // acquire the member_tags array twice)
        ArrayHandle<unsigned int> h_members_a(a->m_member_tags, access_location::host, access_mode::read);

        insert_iterator< vector<unsigned int> > ii(member_tags, member_tags.begin());
        std::copy(h_members_a.data,
                  h_members_a.data + n_a,
                  ii);
        }

    // create the new particle group
    boost::shared_ptr<ParticleGroup> new_group(new ParticleGroup(a->m_sysdef, member_tags));

    // return the newly created group
    return new_group;
    }

/*! \param a First particle group
    \param b Second particle group

    \returns A shared pointer to a newly created particle group that contains only the elements present in \a a, and
    not any present in \a b
*/
boost::shared_ptr<ParticleGroup> ParticleGroup::groupDifference(boost::shared_ptr<ParticleGroup> a,
                                                                boost::shared_ptr<ParticleGroup> b)
    {
    // vector to store the new list of tags
    vector<unsigned int> member_tags;

    if (a != b)
        {
        unsigned int n_a = a->getNumMembersGlobal();
        unsigned int n_b = b->getNumMembersGlobal();
        // make the difference
        ArrayHandle<unsigned int> h_members_a(a->m_member_tags, access_location::host, access_mode::read);
        ArrayHandle<unsigned int> h_members_b(b->m_member_tags, access_location::host, access_mode::read);

        insert_iterator< vector<unsigned int> > ii(member_tags, member_tags.begin());
        set_difference(h_members_a.data,
                  h_members_a.data + n_a,
                  h_members_b.data,
                  h_members_b.data + n_b,
                  ii);
        }
    else
        {
        // If the two arguments are the same, just return an empty group (we cannot
        // acquire the member_tags array twice)
        }


    // create the new particle group
    boost::shared_ptr<ParticleGroup> new_group(new ParticleGroup(a->m_sysdef, member_tags));

    // return the newly created group
    return new_group;
    }

/*! Builds the by-tag-lookup table for group membership
 */
void ParticleGroup::buildTagHash() const
    {
    ArrayHandle<unsigned char> h_is_member_tag(m_is_member_tag, access_location::host, access_mode::overwrite);
    ArrayHandle<unsigned int> h_member_tags(m_member_tags, access_location::host, access_mode::read);

    // reset member ship flags
    memset(h_is_member_tag.data, 0, sizeof(unsigned char)*(m_pdata->getRTags().size()));

    unsigned int num_members = m_member_tags.getNumElements();
    for (unsigned int member = 0; member < num_members; member++)
        {
        h_is_member_tag.data[h_member_tags.data[member]] = 1;
        }
    }

/*! \pre m_member_tags has been filled out, listing all particle tags in the group
    \pre memory has been allocated for m_is_member and m_member_idx
    \post m_is_member is updated so that it reflects the current indices of the particles in the group
    \post m_member_idx is updated listing all particle indices belonging to the group, in index order
*/
void ParticleGroup::rebuildIndexList() const
    {
    // notice message
    m_pdata->getExecConf()->msg->notice(10) << "ParticleGroup: rebuilding index" << std::endl;

    #ifdef ENABLE_CUDA
    if (m_pdata->getExecConf()->isCUDAEnabled() )
        {
        rebuildIndexListGPU();
        }
    else
    #endif
        {

        // rebuild the membership flags for the  indices in the group and construct member list
        ArrayHandle<unsigned char> h_is_member(m_is_member, access_location::host, access_mode::readwrite);
        ArrayHandle<unsigned char> h_is_member_tag(m_is_member_tag, access_location::host, access_mode::read);
        ArrayHandle<unsigned int> h_tag(m_pdata->getTags(), access_location::host, access_mode::read);
        ArrayHandle<unsigned int> h_member_idx(m_member_idx, access_location::host, access_mode::readwrite);
        unsigned int nparticles = m_pdata->getN();
        unsigned int cur_member = 0;
        for (unsigned int idx = 0; idx < nparticles; idx ++)
            {
            assert(h_tag.data[idx] <= m_pdata->getMaximumTag());
            unsigned char is_member = h_is_member_tag.data[h_tag.data[idx]];
            h_is_member.data[idx] =  is_member;
            if (is_member)
                {
                h_member_idx.data[cur_member] = idx;
                cur_member++;
                }
            }

        m_num_local_members = cur_member;
        assert(m_num_local_members <= m_member_tags.getNumElements());
        }

    // index has been rebuilt
    m_particles_sorted = false;
    }

#ifdef ENABLE_CUDA
//! rebuild index list on the GPU
void ParticleGroup::rebuildIndexListGPU() const
    {
    ArrayHandle<unsigned char> d_is_member(m_is_member, access_location::device, access_mode::overwrite);
    ArrayHandle<unsigned char> d_is_member_tag(m_is_member_tag, access_location::device, access_mode::read);
    ArrayHandle<unsigned int> d_member_idx(m_member_idx, access_location::device, access_mode::overwrite);
    ArrayHandle<unsigned int> d_tag(m_pdata->getTags(), access_location::device, access_mode::read);

    // get temporary buffer
    ScopedAllocation<unsigned int> d_tmp(m_pdata->getExecConf()->getCachedAllocator(), m_pdata->getN());

    // reset membership properties
    if (m_member_tags.getNumElements() > 0)
        {
        gpu_rebuild_index_list(m_pdata->getN(),
                           d_is_member_tag.data,
                           d_is_member.data,
                           d_member_idx.data,
                           d_tag.data,
                           m_num_local_members,
                           d_tmp.data,
                           m_mgpu_context);
        if (m_exec_conf->isCUDAErrorCheckingEnabled())
            CHECK_CUDA_ERROR();
        }
    else
        m_num_local_members = 0;
    }
#endif

void export_ParticleGroup()
    {
    class_<ParticleGroup, boost::shared_ptr<ParticleGroup>, boost::noncopyable>
            ("ParticleGroup", init< boost::shared_ptr<SystemDefinition>, boost::shared_ptr<ParticleSelector>, bool >())
            .def(init<boost::shared_ptr<SystemDefinition>, boost::shared_ptr<ParticleSelector> >())
            .def(init<boost::shared_ptr<SystemDefinition>, const std::vector<unsigned int>& >())
            .def(init<>())
            .def("getNumMembersGlobal", &ParticleGroup::getNumMembersGlobal)
            .def("getMemberTag", &ParticleGroup::getMemberTag)
            .def("getTotalMass", &ParticleGroup::getTotalMass)
            .def("getCenterOfMass", &ParticleGroup::getCenterOfMass)
            .def("groupUnion", &ParticleGroup::groupUnion)
            .def("groupIntersection", &ParticleGroup::groupIntersection)
            .def("groupDifference", &ParticleGroup::groupDifference)
            .def("updateMemberTags", &ParticleGroup::updateMemberTags)
            ;

    class_<ParticleSelector, boost::shared_ptr<ParticleSelector>, boost::noncopyable>
            ("ParticleSelector", init< boost::shared_ptr<SystemDefinition> >())
            .def("isSelected", &ParticleSelector::isSelected)
            ;

    class_<ParticleSelectorAll, boost::shared_ptr<ParticleSelectorAll>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorAll", init< boost::shared_ptr<SystemDefinition> >())
        ;

    class_<ParticleSelectorTag, boost::shared_ptr<ParticleSelectorTag>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorTag", init< boost::shared_ptr<SystemDefinition>, unsigned int, unsigned int >())
        ;

    class_<ParticleSelectorType, boost::shared_ptr<ParticleSelectorType>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorType", init< boost::shared_ptr<SystemDefinition>, unsigned int, unsigned int >())
        ;

    class_<ParticleSelectorRigid, boost::shared_ptr<ParticleSelectorRigid>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorRigid", init< boost::shared_ptr<SystemDefinition>, bool >())
        ;

    class_<ParticleSelectorCuboid, boost::shared_ptr<ParticleSelectorCuboid>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorCuboid", init< boost::shared_ptr<SystemDefinition>, Scalar3, Scalar3 >())
        ;

    class_<ParticleSelectorRigidCenter, boost::shared_ptr<ParticleSelectorRigidCenter>, bases<ParticleSelector>, boost::noncopyable>
        ("ParticleSelectorRigidCenter", init< boost::shared_ptr<SystemDefinition> >())
        ;
    }
