From 26d43f8641578723bbf7f3d42a360ed0790d8dcc Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 10 Jun 2020 11:51:28 -0500 Subject: [PATCH 01/45] Implement CPU dynamic bond forming algorithm first working bond finding algorithm between two groups, looks up neighbours from neighbour list does not create duplicate bonds, does not segfault --- azplugins/CMakeLists.txt | 1 + azplugins/DynamicBondUpdater.cc | 316 ++++++++++++++++++++++++++++++++ azplugins/DynamicBondUpdater.h | 99 ++++++++++ azplugins/module.cc | 2 + azplugins/update.py | 48 +++++ 5 files changed, 466 insertions(+) create mode 100644 azplugins/DynamicBondUpdater.cc create mode 100644 azplugins/DynamicBondUpdater.h diff --git a/azplugins/CMakeLists.txt b/azplugins/CMakeLists.txt index a2cbcf90..30b31483 100644 --- a/azplugins/CMakeLists.txt +++ b/azplugins/CMakeLists.txt @@ -35,6 +35,7 @@ endif(NOT HOOMD_EXTERNAL_BUILD) set(_${COMPONENT_NAME}_sources module.cc BounceBackGeometry.cc + DynamicBondUpdater.cc ImplicitEvaporator.cc ImplicitDropletEvaporator.cc ImplicitPlaneEvaporator.cc diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc new file mode 100644 index 00000000..384f14bc --- /dev/null +++ b/azplugins/DynamicBondUpdater.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: mphoward + +/*! + * \file DynamicBondUpdater.cc + * \brief Definition of DynamicBondUpdater + */ + +#include "DynamicBondUpdater.h" + +namespace azplugins +{ + + +/*! + * \param sysdef System definition + * \param inside_type Type id of particles inside region + * \param outside_type Type id of particles outside region + * \param z_lo Lower bound of region in z + * \param z_hi Upper bound of region in z + */ +DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cutsq, + unsigned int bond_type, + unsigned int bond_reservoir_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2) + : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cutsq(r_cutsq), + m_bond_type(bond_type),m_bond_reservoir_type(bond_reservoir_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2) + { + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + + assert(m_nlist); + + m_bond_data = m_sysdef->getBondData(); + // allocate memory for the number of current bonds array + GPUArray counts((int)m_pdata->getN(), m_exec_conf); + m_curr_num_bonds.swap(counts); + + // allocate a max size for all possible pairs - is there a better way to do this? + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds_group_1; + GPUArray possible_bonds(size, m_exec_conf); + m_possible_bonds.swap(possible_bonds); + + calculateCurrentBonds(); + checkSystemSetup(); + } + +DynamicBondUpdater::~DynamicBondUpdater() + { + m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdater" << std::endl; + + } + +void DynamicBondUpdater::calculateCurrentBonds() +{ + + ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); + + m_reservoir_size=0; + + // this should make the simulation also restartable, already existing bonds of m_bond_type will be registered + const unsigned int size = (unsigned int)m_bond_data->getN(); + for (unsigned int i = 0; i < size; i++) + { + //lookup current bond type + unsigned int type = m_bond_data->getTypeByIndex(i); + + if(type==m_bond_reservoir_type) + { + ++m_reservoir_size; + } + if (type==m_bond_type) + { + // lookup the tag of each of the particles participating in the bond + const BondData::members_t bond = m_bond_data->getMembersByIndex(i); + + unsigned int tag_i = bond.tag[0]; + unsigned int tag_j = bond.tag[1]; + + // add this bond to the book keeping arrays and the map of all exisitng bonds + ++h_curr_num_bonds.data[tag_i]; + ++h_curr_num_bonds.data[tag_j]; + // saving exisitng bonds in both directions. This should make the bond finding algorithm safe regardless of + // the storage mode of the neighbour list. + m_all_existing_bonds[{tag_i,tag_j}] = 1; + m_all_existing_bonds[{tag_i,tag_j}] = 1; + } + } +} + +void DynamicBondUpdater::checkSystemSetup() +{ + + if (m_bond_type >= m_bond_data -> getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type + << " is not a valid bond type." << std::endl; + throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); + } + + + if (m_bond_reservoir_type >= m_bond_data -> getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_reservoir_type + << " is not a valid bond type." << std::endl; + throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); + } + + //ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); + + if (m_reservoir_size==0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Bond reservoir size is zero." << std::endl; + throw std::runtime_error("DynamicBondUpdater: Bond reservoir size must be larger than zero."); + } + +} + +/*! + * \param timestep Timestep update is called + */ +void DynamicBondUpdater::update(unsigned int timestep) + { + + //calculateCurrentBonds(); + findPotentialBondPairs(timestep); + + formBondPairs(timestep); + + } + +/*! + * \param timestep Timestep update is called + */ +void DynamicBondUpdater::findPotentialBondPairs(unsigned int timestep) + { + + // start by updating the neighborlist + //m_nlist->addExclusionsFromBonds(); + m_nlist->compute(timestep); + + ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle< unsigned int > h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + + // neighbour list information + ArrayHandle h_n_neigh(m_nlist->getNNeighArray(), access_location::host, access_mode::read); + ArrayHandle h_nlist(m_nlist->getNListArray(), access_location::host, access_mode::read); + ArrayHandle h_head_list(m_nlist->getHeadList(), access_location::host, access_mode::read); + // box + const BoxDim& box = m_pdata->getGlobalBox(); + + ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::read); + + // temp vector storage of possible bonds found + std::vector possible_bonds; + + // for each particle in group_1 - parallelize the outer loop on the GPU? + for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) + { + // get particle index + const unsigned int idx_i = m_group_1->getMemberIndex(i); + const unsigned int tag_i = h_tag.data[idx_i]; + //const unsigned int idx_i = h_group_1.data[i]; + + // loop over all of the neighbors of this particle + const unsigned int myHead = h_head_list.data[idx_i]; + const unsigned int size = (unsigned int)h_n_neigh.data[idx_i]; + unsigned int num_possible_bonds_i = 0; + + // this way of finding neighbours introduces some artifacts if the particle index and spatial positions are + // correlated, because the neighbor list returns neighbours ordered by index, so the particles with lower index + // get bonded first if the number of possible bonds exceedst the bond limit. if there is spatial correlation + // this could lead to artifacts in the spatial configuration as well. Is it worth it to shuffle the order? + for (unsigned int k = 0; k < size; k++) + { + // access the index of this neighbor + const unsigned int idx_j = h_nlist.data[myHead + k]; + const unsigned int tag_j = h_tag.data[idx_j]; + + bool is_in_group_2 = m_group_2->isMember(idx_j); // needs to be replaced with something else on the GPU + + const unsigned int current_bonds_on_j = h_curr_num_bonds.data[tag_j]; + const unsigned int current_bonds_on_i = h_curr_num_bonds.data[tag_i]; + + // check that this bond doesn't already exists, second particle is in second group, and max number of bonds is not reached for both + if (is_in_group_2 + && m_all_existing_bonds.count({tag_i, tag_j}) ==0 + && m_all_existing_bonds.count({tag_j, tag_i}) ==0 + && current_bonds_on_j < m_max_bonds_group_2 + && num_possible_bonds_i < m_max_bonds_group_1-current_bonds_on_i ) + { + // caclulate distance squared + const Scalar3 pi = make_scalar3(h_pos.data[idx_i].x, h_pos.data[idx_i].y, h_pos.data[idx_i].z); + const Scalar3 pj = make_scalar3(h_pos.data[idx_j].x, h_pos.data[idx_j].y, h_pos.data[idx_j].z); + Scalar3 dx = pi - pj; + dx = box.minImage(dx); + const Scalar rsq = dot(dx, dx); + + if (rsq < m_r_cutsq) + { + possible_bonds.push_back(make_scalar2(tag_i,tag_j)); + //std::cout<< "added bond to possible list "<< idx_i << " "<< idx_j << "tag "<< tag_i << " "<< tag_j<< std::endl; + ++num_possible_bonds_i; + } + + } + } + + + } + + // reset possible bond list + ArrayHandle h_possible_bonds(m_possible_bonds, access_location::host, access_mode::overwrite); + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds_group_1; + memset((void*)h_possible_bonds.data,-1.0,sizeof(Scalar2)*size); + + // Before we copy the possible_bonds vector content to the h_possible_bonds array, we need count number of bonds + // formed towards particles in group_2 (second entry in possible bonds) because there could be too many. + // The group_1 bonds should be okay because we are able to check in the for loop above. + + // a temp map which holds count of each encountered particle tag in group_2 + std::unordered_map count_group_2_possible_bonds; + + // iterate over all possible bonds and use the unordered_map to count occurences, if occurences is larger than max_bonds_group_2 + // then don't copy that entry into the h_possible_bonds Array + unsigned int current = 0; + for (auto i = possible_bonds.begin(); i != possible_bonds.end(); ++i) + { + unsigned int tag_j = i->y; + ++count_group_2_possible_bonds[tag_j]; + const unsigned int current_bonds_on_j = h_curr_num_bonds.data[tag_j]; + if(count_group_2_possible_bonds[tag_j] + current_bonds_on_j < m_max_bonds_group_2) + { + h_possible_bonds.data[current]=make_scalar2(i->x,i->y); + ++current; + } + } + + m_curr_bonds_to_form = current; + + } + + +void DynamicBondUpdater::formBondPairs(unsigned int timestep) + { + + + ArrayHandle h_possible_bonds(m_possible_bonds, access_location::host, access_mode::read); + ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); + + ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::readwrite); + ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::readwrite); +// ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + + // only do something if there are bonds to form and there are blank bonds left + if (m_curr_bonds_to_form>0 && m_reservoir_size>0) + { + + const unsigned int size = (unsigned int)m_bond_data->getN(); + unsigned int current = 0; + for (unsigned int i = 0; i < size; i++) + { + + unsigned int type = h_typeval.data[i].type; + + if (type == m_bond_reservoir_type && current < m_curr_bonds_to_form) + { + h_typeval.data[i].type = m_bond_type; + unsigned int tag_i = h_possible_bonds.data[current].x; + unsigned int tag_j = h_possible_bonds.data[current].y; + + h_bonds.data[i].tag[0] = tag_i; + h_bonds.data[i].tag[1] = tag_j; + + //add new bond to the book keeping arrays and the map + ++h_curr_num_bonds.data[tag_i]; + ++h_curr_num_bonds.data[tag_j]; + m_all_existing_bonds[{tag_i,tag_j}]=1; + m_all_existing_bonds[{tag_i,tag_j}]=1; + + ++current; + --m_reservoir_size; + } + } + } + m_curr_bonds_to_form=0; + + } + + +namespace detail +{ +/*! + * \param m Python module to export to + */ +void export_DynamicBondUpdater(pybind11::module& m) + { + namespace py = pybind11; + py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) + .def(py::init, std::shared_ptr, std::shared_ptr, + std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); + + //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) + //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) + //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) + //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); + } +} + +} // end namespace azplugins diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h new file mode 100644 index 00000000..28c6f32a --- /dev/null +++ b/azplugins/DynamicBondUpdater.h @@ -0,0 +1,99 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: mphoward + +/*! + * \file DynamicBondUpdater.h + * \brief Declaration of DynamicBondUpdater + */ + +#ifndef AZPLUGINS_DYNAMIC_BOND_UPDATER_H_ +#define AZPLUGINS_DYNAMIC_BOND_UPDATER_H_ + +#ifdef NVCC +#error This header cannot be compiled by nvcc +#endif + +#include "hoomd/md/NeighborList.h" +#include "hoomd/Updater.h" +#include "hoomd/ParticleGroup.h" + +#include "hoomd/extern/pybind/include/pybind11/pybind11.h" + +namespace azplugins +{ + +//! Particle type updater +/*! + * Flips particle types based on their z height. Particles are classified as + * either inside or outside of the region, and can be flipped between these two + * types. Particles that are of neither the inside nor outside type are ignored. + * + * The region is defined by a slab along z. This could be easily extended to + * accommodate a generic region criteria, but for now, the planar slab in z is + * all that is necessary. + */ +class PYBIND11_EXPORT DynamicBondUpdater : public Updater + { + public: + + //! Constructor with parameters + DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cutsq, + unsigned int bond_type, + unsigned int bond_reservoir_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2); + + //! Destructor + virtual ~DynamicBondUpdater(); + + //! Evaporate particles + virtual void update(unsigned int timestep); + + + protected: + std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding + std::shared_ptr m_bond_data; //!< Bond data + + std::shared_ptr m_group_1; //!< First particle group to form bonds with + std::shared_ptr m_group_2; //!< Second particle group to form bonds with + + const Scalar m_r_cutsq; //!< cutoff squared for the bond forming criterion + + unsigned int m_bond_type; //!< Type id of the bond to form + unsigned int m_bond_reservoir_type; //!< Type id of the bond reservoir + + unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group + unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group + + GPUArray m_curr_num_bonds; //!< current number of bonds for each particle + std::map, int> m_all_existing_bonds; //!< map of all current existing bonds of bond_type + GPUArray m_possible_bonds; //!< list of possible bonds, size: size(group_1)*max_bonds_1 + + unsigned int m_curr_bonds_to_form; //!< number of bonds to form in the current timestep + unsigned int m_reservoir_size; + + //! Changes the particle types according to an update rule + virtual void findPotentialBondPairs(unsigned int timestep); + + virtual void formBondPairs(unsigned int timestep); + + private: + void calculateCurrentBonds(); + void checkSystemSetup(); + }; + +namespace detail +{ +//! Export the Evaporator to python +void export_DynamicBondUpdater(pybind11::module& m); +} // end namespace detail + +} // end namespace azplugins + +#endif // AZPLUGINS_TYPE_UPDATER_H_ diff --git a/azplugins/module.cc b/azplugins/module.cc index a5d4a8b1..9fd098eb 100644 --- a/azplugins/module.cc +++ b/azplugins/module.cc @@ -20,6 +20,7 @@ namespace py = pybind11; #include "WallPotentials.h" /* Updaters */ +#include "DynamicBondUpdater.h" #include "ReversePerturbationFlow.h" #include "TypeUpdater.h" #include "ParticleEvaporator.h" @@ -189,6 +190,7 @@ PYBIND11_MODULE(_azplugins, m) azplugins::detail::export_special_pair_potential(m,"SpecialPairPotentialLJ96"); /* Updaters */ + azplugins::detail::export_DynamicBondUpdater(m); azplugins::detail::export_ReversePerturbationFlow(m); azplugins::detail::export_TypeUpdater(m); azplugins::detail::export_ParticleEvaporator(m); // this must follow TypeUpdater because TypeUpdater is the python base class diff --git a/azplugins/update.py b/azplugins/update.py index 510664fe..c76b622d 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -4,6 +4,9 @@ # Maintainer: mphoward / Everyone is free to add additional updaters import hoomd +from hoomd import _hoomd +from hoomd.md import _md + from . import _azplugins @@ -130,3 +133,48 @@ def set_params(self, inside=None, outside=None, lo=None, hi=None): if self.lo >= self.hi: hoomd.context.msg.error('update.type: lower z bound ' + str(self.lo) + ' >= upper z bound ' + str(self.hi) + '.\n') raise ValueError('update.type: upper and lower bounds are inverted') + + +class dynamic_bond(hoomd.update._updater): + def __init__(self, nlist, r_cut,bond_type, bond_reservoir_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): + + hoomd.util.print_status_line() + + hoomd.update._updater.__init__(self) + self.nlist = nlist + + if not hoomd.context.exec_conf.isCUDAEnabled(): + cpp_class = _azplugins.DynamicBondUpdater + self.nlist.cpp_nlist.setStorageMode(_md.NeighborList.storageMode.half) + else: + hoomd.context.msg.error('update.dynamic_bond not implemented on the GPU \n') + raise ValueError('update.dynamic_bond not implemented on the GPU ') + #cpp_class = _azplugins.TypeUpdaterGPU + + # look up the bond ids based on the given names - this will throw an error if the bond types do not exist + bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) + bond_reservoir_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_reservoir_type) + + self.rcutsq = r_cut**2.0 + + # we need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different + new_cpp_group = _hoomd.ParticleGroup.groupIntersection(group_1.cpp_group, group_2.cpp_group) + if new_cpp_group.getNumMembersGlobal()>0 and max_bonds_1 != max_bonds_2: + hoomd.context.msg.error('update.dynamic_bond: groups are overlapping with ' + str(new_cpp_group.getNumMembersGlobal()) + + ' common members, but maximum bonds formed by each is different ' + str(max_bonds_1) + + ' != '+ str(max_bonds_2)+ '.\n') + raise ValueError('update.dynamic_bond: groups are overlapping with different number of maximum bonds') + + #it doesn't really make sense to allow partially overlapping groups? + + self.cpp_updater = cpp_class(hoomd.context.current.system_definition, + self.nlist.cpp_nlist,group_1.cpp_group,group_2.cpp_group,self.rcutsq,bond_type_id,bond_reservoir_type_id,max_bonds_1,max_bonds_2) + self.setupUpdater(period, phase) + + # how to do handling of exclusions in the neighborlist correctly? + # neighbor list is ordered by particle id, does this create artifacts? (CPU) + # what happens if bond reservoir is empty? should we throw a warning? + + def set_params(self, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): + # todo - cpp class right now doesn't have any set/get functions + hoomd.util.print_status_line() From c0236f9b7cdb6ef5cf887fd88c245ef66518878a Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 24 Jun 2020 11:07:43 -0500 Subject: [PATCH 02/45] Own neighbour tree for dynamic bonds --- azplugins/DynamicBondUpdater.cc | 274 +++++++++++++++++++++++++++----- azplugins/DynamicBondUpdater.h | 12 +- 2 files changed, 249 insertions(+), 37 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 384f14bc..0c14fb18 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -10,6 +10,7 @@ #include "DynamicBondUpdater.h" + namespace azplugins { @@ -42,13 +43,19 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, GPUArray counts((int)m_pdata->getN(), m_exec_conf); m_curr_num_bonds.swap(counts); + m_max_bonds=20; // allocate a max size for all possible pairs - is there a better way to do this? - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds_group_1; - GPUArray possible_bonds(size, m_exec_conf); - m_possible_bonds.swap(possible_bonds); + // could model the neighbor list m_conditions + const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + GPUArray all_possible_bonds(size, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + + m_aabbs.resize(m_group_1->getNumMembers()); - calculateCurrentBonds(); checkSystemSetup(); + updateImageVectors(); + calculateCurrentBonds(); + } DynamicBondUpdater::~DynamicBondUpdater() @@ -57,42 +64,221 @@ DynamicBondUpdater::~DynamicBondUpdater() } +bool SortBonds(Scalar3 i, Scalar3 j) + { + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + return r_sq_1 < r_sq_2; + } + + // Function for binary_predicate + bool CompareBonds(Scalar3 i, Scalar3 j) + { + + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_21 = __scalar_as_int(j.x); + const unsigned int tag_22 = __scalar_as_int(j.y); + + if (tag_11==tag_21 && tag_12==tag_22) + { + return true; + }else{ + return false; + } + } + + + void DynamicBondUpdater::calculateCurrentBonds() { - ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); + // std::cout<< " in DynamicBondUpdater::calculateCurrentBonds() "< h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); - m_reservoir_size=0; + const BoxDim& box = m_pdata->getBox(); - // this should make the simulation also restartable, already existing bonds of m_bond_type will be registered - const unsigned int size = (unsigned int)m_bond_data->getN(); - for (unsigned int i = 0; i < size; i++) + unsigned int group_size_1 = m_group_1->getNumMembers(); + for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) { - //lookup current bond type - unsigned int type = m_bond_data->getTypeByIndex(i); + unsigned int i = m_group_1->getMemberIndex(group_idx); - if(type==m_bond_reservoir_type) - { - ++m_reservoir_size; - } - if (type==m_bond_type) + // make a point particle AABB + vec3 my_pos(h_postype.data[i]); + // std::cout<< "particle "<< i << " in group 1 "<< group_idx< h_body(m_pdata->getBodies(), access_location::host, access_mode::read); + //ArrayHandle h_diameter(m_pdata->getDiameters(), access_location::host, access_mode::read); + + //ArrayHandle h_r_cut(m_r_cut, access_location::host, access_mode::read); + + // reset possible bond list + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + + Scalar r_cut = 3.0; + Scalar m_r_buff = 0.4; + bool m_filter_body = false; + // neighborlist data + // ArrayHandle h_head_list(m_head_list, access_location::host, access_mode::read); + // ArrayHandle h_Nmax(m_Nmax, access_location::host, access_mode::read); + // ArrayHandle h_conditions(m_conditions, access_location::host, access_mode::readwrite); + // ArrayHandle h_nlist(m_nlist, access_location::host, access_mode::overwrite); + // ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + + // Loop over all particles in group 2 + //std::cout<< " inDynamicBondUpdater::calculateCurrentBonds() before particle loop"<getNumMembers(); + for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) + { + unsigned int i = m_group_2->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + + const Scalar4 postype_i = h_postype.data[i]; + const vec3 pos_i = vec3(postype_i); + const unsigned int type_i = __scalar_as_int(postype_i.w); + const unsigned int body_i = h_body.data[i]; + + unsigned int n_curr_bond = 0; + + for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector + { + // make an AABB for the image of this particle + vec3 pos_i_image = pos_i + m_image_list[cur_image]; + hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, r_cut); + hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; + // stackless traversal of the tree + for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) + { + if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) + { + if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) + { + for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) + { + // neighbor j + unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); + + // skip self-interaction always + bool excluded = (i == j); + + if (!excluded) + { + // compute distance + Scalar4 postype_j = h_postype.data[j]; + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) + - vec_to_scalar3(pos_i_image); + Scalar dr_sq = dot(drij,drij); + Scalar r_cutsq = r_cut*r_cut; + if (dr_sq <= r_cutsq) + { + if (n_curr_bond < m_max_bonds) + { + const unsigned int tag_j = h_tag.data[j]; + Scalar3 d ; + if( tag_i < tag_j ) + { + d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + }else{ + d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); + } + h_all_possible_bonds.data[group_idx + n_curr_bond] = d; + ++n_curr_bond; + } + + } + } + } + } + } + else + { + // skip ahead + cur_node_idx += cur_aabb_tree->getNodeSkip(cur_node_idx); + } + } // end stackless search + } // end loop over images + //} // end loop over pair types + } // end loop over group 2 +//thrust::copy_if(input.begin(), input.end(), output.begin(), is_non_zero()); +//Now we call the sort function + +//std::copy_if(h_all_possible_bonds.data,is_set()) +} + + +/*! + * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 27 translation vectors + * when the simulation box is 3D periodic. In 2D, there are at most 9 translation vectors. In MPI runs, a ghost layer + * of particles is added from adjacent ranks, so there is no need to perform any translations in this direction. + * The translation vectors are determined by linear combination of the lattice vectors, and must be recomputed any + * time that the box resizes. + */ +void DynamicBondUpdater::updateImageVectors() + { + const BoxDim& box = m_pdata->getBox(); + uchar3 periodic = box.getPeriodic(); + unsigned char sys3d = (this->m_sysdef->getNDimensions() == 3); + + // now compute the image vectors + // each dimension increases by one power of 3 + unsigned int n_dim_periodic = (unsigned int)(periodic.x + periodic.y + sys3d*periodic.z); + m_n_images = 1; + for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) + { + m_n_images *= 3; + } + + // reallocate memory if necessary + if (m_n_images > m_image_list.size()) + { + m_image_list.resize(m_n_images); + } + + vec3 latt_a = vec3(box.getLatticeVector(0)); + vec3 latt_b = vec3(box.getLatticeVector(1)); + vec3 latt_c = vec3(box.getLatticeVector(2)); + + // there is always at least 1 image, which we put as our first thing to look at + m_image_list[0] = vec3(0.0, 0.0, 0.0); + + // iterate over all other combinations of images, skipping those that are + unsigned int n_images = 1; + for (int i=-1; i <= 1 && n_images < m_n_images; ++i) + { + for (int j=-1; j <= 1 && n_images < m_n_images; ++j) { - // lookup the tag of each of the particles participating in the bond - const BondData::members_t bond = m_bond_data->getMembersByIndex(i); - - unsigned int tag_i = bond.tag[0]; - unsigned int tag_j = bond.tag[1]; - - // add this bond to the book keeping arrays and the map of all exisitng bonds - ++h_curr_num_bonds.data[tag_i]; - ++h_curr_num_bonds.data[tag_j]; - // saving exisitng bonds in both directions. This should make the bond finding algorithm safe regardless of - // the storage mode of the neighbour list. - m_all_existing_bonds[{tag_i,tag_j}] = 1; - m_all_existing_bonds[{tag_i,tag_j}] = 1; + for (int k=-1; k <= 1 && n_images < m_n_images; ++k) + { + if (!(i == 0 && j == 0 && k == 0)) + { + // skip any periodic images if we don't have periodicity + if (i != 0 && !periodic.x) continue; + if (j != 0 && !periodic.y) continue; + if (k != 0 && (!sys3d || !periodic.z)) continue; + + m_image_list[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; + ++n_images; + } + } } } -} + } + void DynamicBondUpdater::checkSystemSetup() { @@ -128,10 +314,10 @@ void DynamicBondUpdater::checkSystemSetup() void DynamicBondUpdater::update(unsigned int timestep) { - //calculateCurrentBonds(); + calculateCurrentBonds(); findPotentialBondPairs(timestep); - formBondPairs(timestep); + // formBondPairs(timestep); } @@ -141,6 +327,22 @@ void DynamicBondUpdater::update(unsigned int timestep) void DynamicBondUpdater::findPotentialBondPairs(unsigned int timestep) { + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); + const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size,SortBonds); + Scalar3 *last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size,CompareBonds); + std::cout<<" last "<< last->x<<" "<< last->y << " "<z <addExclusionsFromBonds(); m_nlist->compute(timestep); @@ -216,7 +418,7 @@ void DynamicBondUpdater::findPotentialBondPairs(unsigned int timestep) } // reset possible bond list - ArrayHandle h_possible_bonds(m_possible_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); const unsigned int size = m_group_1->getNumMembers()*m_max_bonds_group_1; memset((void*)h_possible_bonds.data,-1.0,sizeof(Scalar2)*size); @@ -237,13 +439,13 @@ void DynamicBondUpdater::findPotentialBondPairs(unsigned int timestep) const unsigned int current_bonds_on_j = h_curr_num_bonds.data[tag_j]; if(count_group_2_possible_bonds[tag_j] + current_bonds_on_j < m_max_bonds_group_2) { - h_possible_bonds.data[current]=make_scalar2(i->x,i->y); + // h_possible_bonds.data[current]=make_scalar2(i->x,i->y); ++current; } } m_curr_bonds_to_form = current; - + */ } @@ -251,7 +453,7 @@ void DynamicBondUpdater::formBondPairs(unsigned int timestep) { - ArrayHandle h_possible_bonds(m_possible_bonds, access_location::host, access_mode::read); + ArrayHandle h_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::readwrite); diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 28c6f32a..5e17eeba 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -15,6 +15,7 @@ #error This header cannot be compiled by nvcc #endif +#include "hoomd/AABBTree.h" #include "hoomd/md/NeighborList.h" #include "hoomd/Updater.h" #include "hoomd/ParticleGroup.h" @@ -73,11 +74,19 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_curr_num_bonds; //!< current number of bonds for each particle std::map, int> m_all_existing_bonds; //!< map of all current existing bonds of bond_type - GPUArray m_possible_bonds; //!< list of possible bonds, size: size(group_1)*max_bonds_1 + + unsigned int m_max_bonds; //!< maximum number of bonds which can be formed by the first group + GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*max_bonds_1 unsigned int m_curr_bonds_to_form; //!< number of bonds to form in the current timestep unsigned int m_reservoir_size; + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group 1 + GPUVector m_aabbs; //!< Flat array of AABBs of all types + + std::vector< vec3 > m_image_list; //!< List of translation vectors + unsigned int m_n_images; //!< The number of image vectors to check + //! Changes the particle types according to an update rule virtual void findPotentialBondPairs(unsigned int timestep); @@ -85,6 +94,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater private: void calculateCurrentBonds(); + void updateImageVectors(); void checkSystemSetup(); }; From 0c71990ec1f06a9bed71dacd554675d2435f8b93 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 2 Jul 2020 17:08:59 -0500 Subject: [PATCH 03/45] first draft CPU version working --- azplugins/DynamicBondUpdater.cc | 557 ++++++++++++++++---------------- azplugins/DynamicBondUpdater.h | 48 ++- azplugins/update.py | 13 +- 3 files changed, 324 insertions(+), 294 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 0c14fb18..365f406e 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -1,7 +1,7 @@ // Copyright (c) 2018-2020, Michael P. Howard // This file is part of the azplugins project, released under the Modified BSD License. -// Maintainer: mphoward +// Maintainer: astatt /*! * \file DynamicBondUpdater.cc @@ -14,47 +14,49 @@ namespace azplugins { - -/*! - * \param sysdef System definition - * \param inside_type Type id of particles inside region - * \param outside_type Type id of particles outside region - * \param z_lo Lower bound of region in z - * \param z_hi Upper bound of region in z - */ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, - const Scalar r_cutsq, + const Scalar r_cut, unsigned int bond_type, - unsigned int bond_reservoir_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cutsq(r_cutsq), - m_bond_type(bond_type),m_bond_reservoir_type(bond_reservoir_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2) + : Updater(sysdef), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut), + m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), + m_box_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - assert(m_nlist); + m_pdata->getBoxChangeSignal().connect(this); m_bond_data = m_sysdef->getBondData(); - // allocate memory for the number of current bonds array - GPUArray counts((int)m_pdata->getN(), m_exec_conf); - m_curr_num_bonds.swap(counts); - - m_max_bonds=20; - // allocate a max size for all possible pairs - is there a better way to do this? - // could model the neighbor list m_conditions - const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - GPUArray all_possible_bonds(size, m_exec_conf); + + // allocate initial Memory - is 4 a good number? + m_max_bonds = 4; + m_max_bonds_overflow = 0; + GPUArray all_possible_bonds(m_group_2->getNumMembers()*m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); + //todo: reset all aabb componentes if group sizes change m_aabbs.resize(m_group_1->getNumMembers()); + //todo: need to register total particle number change and reallocate this + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + + + GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + + memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + checkSystemSetup(); - updateImageVectors(); - calculateCurrentBonds(); + calculateExistingBonds(); } @@ -64,6 +66,39 @@ DynamicBondUpdater::~DynamicBondUpdater() } + +/*! + * \param timestep Timestep update is called + */ +void DynamicBondUpdater::update(unsigned int timestep) +{ + // update properties that depend on the box + if (m_box_changed) + { + updateImageVectors(); + m_box_changed = false; + } + + // rebuild the list of possible bonds until there is no overflow + bool overflowed = false; + do + { + calculatePossibleBonds(); + overflowed = m_max_bonds < m_max_bonds_overflow; + // if we overflowed, need to reallocate memory and re-calculate + if (overflowed) + { + resizePossibleBondlists(); + } + } while (overflowed); + + filterPossibleBonds(); + makeBonds(); + + +} + +//todo: should go into helper class? bool SortBonds(Scalar3 i, Scalar3 j) { const Scalar r_sq_1 = i.z; @@ -71,7 +106,7 @@ bool SortBonds(Scalar3 i, Scalar3 j) return r_sq_1 < r_sq_2; } - // Function for binary_predicate +//todo: should go into helper class? bool CompareBonds(Scalar3 i, Scalar3 j) { @@ -80,78 +115,167 @@ bool SortBonds(Scalar3 i, Scalar3 j) const unsigned int tag_21 = __scalar_as_int(j.x); const unsigned int tag_22 = __scalar_as_int(j.y); - if (tag_11==tag_21 && tag_12==tag_22) + if (tag_11==tag_21 && tag_12==tag_22) // (i,j)==(i,j) + { + return true; + }else if (tag_11==tag_22 && tag_12==tag_21) // (i,j)==(j,i) { return true; - }else{ + } + else{ return false; } } +//todo: find a better descriptive name for this function +bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) +{ + const unsigned int tag_1 = __scalar_as_int(i.x); + const unsigned int tag_2 = __scalar_as_int(i.y); + + if (tag_1==0 && tag_2==0 ){ + return true; + }else{ + return isExistingBond(tag_1,tag_2); + } + +} + +void DynamicBondUpdater::calculateExistingBonds() +{ + // reset exisitng bond list + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()*m_existing_bonds_list_indexer.getH()); + + ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); + ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::read); + + // for each of the bonds + const unsigned int size = (unsigned int)m_bond_data->getN(); + for (unsigned int i = 0; i < size; i++) + { + // lookup the tag of each of the particles participating in the bond + const typename BondData::members_t& bond = h_bonds.data[i]; + unsigned int tag1 = bond.tag[0]; + unsigned int tag2 = bond.tag[1]; + unsigned int type = h_typeval.data[i].type; + + //only keep track of the bonds we are forming - does this make sense? + // if (type == m_bond_type) + // { + AddtoExistingBonds(tag1,tag2); + // } + } +} + +/*! \param tag1 First particle tag in the pair + \param tag2 Second particle tag in the pair + \return true if the particles \a tag1 and \a tag2 are bonded +*/ +bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) +{ + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); + unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; + + for (unsigned int i = 0; i < n_existing_bonds; i++) + { + if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,i)] == tag2) + return true; + } + return false; + } -void DynamicBondUpdater::calculateCurrentBonds() +void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) { - // std::cout<< " in DynamicBondUpdater::calculateCurrentBonds() "<getMaximumTag()); + assert(tag2 <= m_pdata->getMaximumTag()); + + // don't add a bond twice - should not happen anyway + if (isExistingBond(tag1, tag2)) return; + + + bool overflowed = false; + + // access arrays + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + + // resize the list if necessary + if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + + if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + + + if (overflowed) resizeExistingBondList(); + + // access arrays + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + + // add tag2 to tag1's exclusion list + unsigned int pos1 = h_n_existing_bonds.data[tag1]; + assert(pos1 < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos1)] = tag2; + h_n_existing_bonds.data[tag1]++; + + // add tag1 to tag2's exclusion list + unsigned int pos2 = h_n_existing_bonds.data[tag2]; + assert(pos2 < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos2)] = tag1; + h_n_existing_bonds.data[tag2]++; + +} + +void DynamicBondUpdater::resizeExistingBondList() + { + unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; + + m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); + // update the indexer + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; + } + +void DynamicBondUpdater::calculatePossibleBonds() + { + //todo: is it worth it to seperate the tree building out and check if update is necessary? //make tree for group 1 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); - const BoxDim& box = m_pdata->getBox(); - unsigned int group_size_1 = m_group_1->getNumMembers(); for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) { unsigned int i = m_group_1->getMemberIndex(group_idx); - // make a point particle AABB vec3 my_pos(h_postype.data[i]); - // std::cout<< "particle "<< i << " in group 1 "<< group_idx< h_body(m_pdata->getBodies(), access_location::host, access_mode::read); - //ArrayHandle h_diameter(m_pdata->getDiameters(), access_location::host, access_mode::read); - - //ArrayHandle h_r_cut(m_r_cut, access_location::host, access_mode::read); + m_aabb_tree.buildTree(&(h_aabbs.data[0]) , group_size_1); - // reset possible bond list + // reset content of possible bond list ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - Scalar r_cut = 3.0; - Scalar m_r_buff = 0.4; - bool m_filter_body = false; - // neighborlist data - // ArrayHandle h_head_list(m_head_list, access_location::host, access_mode::read); - // ArrayHandle h_Nmax(m_Nmax, access_location::host, access_mode::read); - // ArrayHandle h_conditions(m_conditions, access_location::host, access_mode::readwrite); - // ArrayHandle h_nlist(m_nlist, access_location::host, access_mode::overwrite); - // ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - + //traverse the tree // Loop over all particles in group 2 - //std::cout<< " inDynamicBondUpdater::calculateCurrentBonds() before particle loop"<getNumMembers(); for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) { unsigned int i = m_group_2->getMemberIndex(group_idx); const unsigned int tag_i = h_tag.data[i]; - const Scalar4 postype_i = h_postype.data[i]; const vec3 pos_i = vec3(postype_i); - const unsigned int type_i = __scalar_as_int(postype_i.w); - const unsigned int body_i = h_body.data[i]; unsigned int n_curr_bond = 0; @@ -159,7 +283,7 @@ void DynamicBondUpdater::calculateCurrentBonds() { // make an AABB for the image of this particle vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, r_cut); + hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, m_r_cut); hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; // stackless traversal of the tree for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) @@ -183,22 +307,21 @@ void DynamicBondUpdater::calculateCurrentBonds() Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - vec_to_scalar3(pos_i_image); Scalar dr_sq = dot(drij,drij); - Scalar r_cutsq = r_cut*r_cut; + const Scalar r_cutsq = m_r_cut*m_r_cut; if (dr_sq <= r_cutsq) { if (n_curr_bond < m_max_bonds) - { - const unsigned int tag_j = h_tag.data[j]; - Scalar3 d ; - if( tag_i < tag_j ) - { - d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); - }else{ - d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); - } - h_all_possible_bonds.data[group_idx + n_curr_bond] = d; - ++n_curr_bond; - } + { + const unsigned int tag_j = h_tag.data[j]; + Scalar3 d ; + d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); + h_all_possible_bonds.data[group_idx + n_curr_bond] = d; + } + else // trigger resize current possible bonds > m_max_bonds + { + m_max_bonds_overflow = n_curr_bond; + } + ++n_curr_bond; } } @@ -212,12 +335,33 @@ void DynamicBondUpdater::calculateCurrentBonds() } } // end stackless search } // end loop over images - //} // end loop over pair types } // end loop over group 2 -//thrust::copy_if(input.begin(), input.end(), output.begin(), is_non_zero()); -//Now we call the sort function -//std::copy_if(h_all_possible_bonds.data,is_set()) + } + +void DynamicBondUpdater::filterPossibleBonds() +{ + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + +// first sort whole array by distance between particles in that particular possible bond +std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds); + +// now make sure each possible bond is in the array only once by comparing tags +auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size, CompareBonds); +m_all_possible_bonds_end = std::distance(h_all_possible_bonds.data,last); + +// then remove a possible bond if it already exists. It also removes zeros, e.g. +// (0,0,0), which fill the unused spots in the array. +auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + m_all_possible_bonds_end, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + +m_all_possible_bonds_end = std::distance(h_all_possible_bonds.data,last2); + +// at this point, the sub-array: h_all_possible_bonds[0,m_all_possible_bonds_end] +// should contain only unique entries of possible bonds which are not yet formed. } @@ -283,217 +427,86 @@ void DynamicBondUpdater::updateImageVectors() void DynamicBondUpdater::checkSystemSetup() { - if (m_bond_type >= m_bond_data -> getNTypes()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type - << " is not a valid bond type." << std::endl; - throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); - } - - - if (m_bond_reservoir_type >= m_bond_data -> getNTypes()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_reservoir_type - << " is not a valid bond type." << std::endl; - throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); - } - - //ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); - - if (m_reservoir_size==0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Bond reservoir size is zero." << std::endl; - throw std::runtime_error("DynamicBondUpdater: Bond reservoir size must be larger than zero."); - } +if (m_bond_type >= m_bond_data -> getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type + << " is not a valid bond type." << std::endl; + throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); + } } -/*! - * \param timestep Timestep update is called - */ -void DynamicBondUpdater::update(unsigned int timestep) - { - calculateCurrentBonds(); - findPotentialBondPairs(timestep); - - // formBondPairs(timestep); +//todo: should the list be grown more than 1 at a time for efficiency? +void DynamicBondUpdater::resizePossibleBondlists() +{ + m_max_bonds=m_max_bonds_overflow; + m_max_bonds_overflow=0; + unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + m_all_possible_bonds.resize(size); - } + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; +} /*! * \param timestep Timestep update is called */ -void DynamicBondUpdater::findPotentialBondPairs(unsigned int timestep) - { - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); - const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - - std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size,SortBonds); - Scalar3 *last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size,CompareBonds); - std::cout<<" last "<< last->x<<" "<< last->y << " "<z <addExclusionsFromBonds(); - m_nlist->compute(timestep); - - ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle< unsigned int > h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - - // neighbour list information - ArrayHandle h_n_neigh(m_nlist->getNNeighArray(), access_location::host, access_mode::read); - ArrayHandle h_nlist(m_nlist->getNListArray(), access_location::host, access_mode::read); - ArrayHandle h_head_list(m_nlist->getHeadList(), access_location::host, access_mode::read); - // box - const BoxDim& box = m_pdata->getGlobalBox(); - - ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::read); - - // temp vector storage of possible bonds found - std::vector possible_bonds; - - // for each particle in group_1 - parallelize the outer loop on the GPU? - for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) - { - // get particle index - const unsigned int idx_i = m_group_1->getMemberIndex(i); - const unsigned int tag_i = h_tag.data[idx_i]; - //const unsigned int idx_i = h_group_1.data[i]; - - // loop over all of the neighbors of this particle - const unsigned int myHead = h_head_list.data[idx_i]; - const unsigned int size = (unsigned int)h_n_neigh.data[idx_i]; - unsigned int num_possible_bonds_i = 0; - - // this way of finding neighbours introduces some artifacts if the particle index and spatial positions are - // correlated, because the neighbor list returns neighbours ordered by index, so the particles with lower index - // get bonded first if the number of possible bonds exceedst the bond limit. if there is spatial correlation - // this could lead to artifacts in the spatial configuration as well. Is it worth it to shuffle the order? - for (unsigned int k = 0; k < size; k++) - { - // access the index of this neighbor - const unsigned int idx_j = h_nlist.data[myHead + k]; - const unsigned int tag_j = h_tag.data[idx_j]; - - bool is_in_group_2 = m_group_2->isMember(idx_j); // needs to be replaced with something else on the GPU - - const unsigned int current_bonds_on_j = h_curr_num_bonds.data[tag_j]; - const unsigned int current_bonds_on_i = h_curr_num_bonds.data[tag_i]; - - // check that this bond doesn't already exists, second particle is in second group, and max number of bonds is not reached for both - if (is_in_group_2 - && m_all_existing_bonds.count({tag_i, tag_j}) ==0 - && m_all_existing_bonds.count({tag_j, tag_i}) ==0 - && current_bonds_on_j < m_max_bonds_group_2 - && num_possible_bonds_i < m_max_bonds_group_1-current_bonds_on_i ) - { - // caclulate distance squared - const Scalar3 pi = make_scalar3(h_pos.data[idx_i].x, h_pos.data[idx_i].y, h_pos.data[idx_i].z); - const Scalar3 pj = make_scalar3(h_pos.data[idx_j].x, h_pos.data[idx_j].y, h_pos.data[idx_j].z); - Scalar3 dx = pi - pj; - dx = box.minImage(dx); - const Scalar rsq = dot(dx, dx); - - if (rsq < m_r_cutsq) - { - possible_bonds.push_back(make_scalar2(tag_i,tag_j)); - //std::cout<< "added bond to possible list "<< idx_i << " "<< idx_j << "tag "<< tag_i << " "<< tag_j<< std::endl; - ++num_possible_bonds_i; - } - - } - } +void DynamicBondUpdater::makeBonds() +{ + // we need to count how many bonds are in the h_all_possible_bonds array for a given tag + // so that we don't end up forming too many bonds in one step + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - } + GPUArray total_counts_tag(m_pdata->getRTags().size(), m_exec_conf); + ArrayHandle h_total_counts_tag(total_counts_tag, access_location::host, access_mode::readwrite); + memset((void*)h_total_counts_tag.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); - // reset possible bond list - ArrayHandle h_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds_group_1; - memset((void*)h_possible_bonds.data,-1.0,sizeof(Scalar2)*size); + // Loop over all possible bonds and count how many times a tag is in h_all_possible_bonds array + for (unsigned int i = 0; i < m_all_possible_bonds_end; i++) + { + Scalar3 d = h_all_possible_bonds.data[i]; + const int tag_i = __scalar_as_int(d.x); + const int tag_j = __scalar_as_int(d.y); + h_total_counts_tag.data[tag_i]=m_max_bonds_group_1 - h_n_existing_bonds.data[tag_i]; + h_total_counts_tag.data[tag_j]=m_max_bonds_group_2 - h_n_existing_bonds.data[tag_j]; + } - // Before we copy the possible_bonds vector content to the h_possible_bonds array, we need count number of bonds - // formed towards particles in group_2 (second entry in possible bonds) because there could be too many. - // The group_1 bonds should be okay because we are able to check in the for loop above. + GPUArray current_counts_tag(m_pdata->getRTags().size(), m_exec_conf); + ArrayHandle h_current_counts_tag(current_counts_tag, access_location::host, access_mode::readwrite); + memset((void*)h_current_counts_tag.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); - // a temp map which holds count of each encountered particle tag in group_2 - std::unordered_map count_group_2_possible_bonds; + //todo: can this for loop be simplified/paralleized? + bool added_bonds = false; + for (unsigned int i = 0; i < m_all_possible_bonds_end; i++) + { + Scalar3 d = h_all_possible_bonds.data[i]; + unsigned int tag_i = __scalar_as_int(d.x); + unsigned int tag_j = __scalar_as_int(d.y); - // iterate over all possible bonds and use the unordered_map to count occurences, if occurences is larger than max_bonds_group_2 - // then don't copy that entry into the h_possible_bonds Array - unsigned int current = 0; - for (auto i = possible_bonds.begin(); i != possible_bonds.end(); ++i) + //todo: put in other external criteria here, e.g. probability of bond formation etc + if (h_current_counts_tag.data[tag_i]y; - ++count_group_2_possible_bonds[tag_j]; - const unsigned int current_bonds_on_j = h_curr_num_bonds.data[tag_j]; - if(count_group_2_possible_bonds[tag_j] + current_bonds_on_j < m_max_bonds_group_2) - { - // h_possible_bonds.data[current]=make_scalar2(i->x,i->y); - ++current; - } + m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); + AddtoExistingBonds(tag_i,tag_j); + h_current_counts_tag.data[tag_i]++; + h_current_counts_tag.data[tag_j]++; + added_bonds = true; } + } - m_curr_bonds_to_form = current; - */ - } - - -void DynamicBondUpdater::formBondPairs(unsigned int timestep) - { - - - ArrayHandle h_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); - ArrayHandle h_curr_num_bonds(m_curr_num_bonds, access_location::host, access_mode::readwrite); - - ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::readwrite); - ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::readwrite); -// ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - - // only do something if there are bonds to form and there are blank bonds left - if (m_curr_bonds_to_form>0 && m_reservoir_size>0) - { - - const unsigned int size = (unsigned int)m_bond_data->getN(); - unsigned int current = 0; - for (unsigned int i = 0; i < size; i++) - { - - unsigned int type = h_typeval.data[i].type; - - if (type == m_bond_reservoir_type && current < m_curr_bonds_to_form) - { - h_typeval.data[i].type = m_bond_type; - unsigned int tag_i = h_possible_bonds.data[current].x; - unsigned int tag_j = h_possible_bonds.data[current].y; + if (added_bonds) m_pdata->notifyParticleSort(); + //todo: how to add this? m_nlist as parameter? + //notify neighbor lists +// if (m_exclude_from_nlist) +// m_nlist->addExclusion(p_from_idx,p_to_idx); - h_bonds.data[i].tag[0] = tag_i; - h_bonds.data[i].tag[1] = tag_j; +} - //add new bond to the book keeping arrays and the map - ++h_curr_num_bonds.data[tag_i]; - ++h_curr_num_bonds.data[tag_j]; - m_all_existing_bonds[{tag_i,tag_j}]=1; - m_all_existing_bonds[{tag_i,tag_j}]=1; - ++current; - --m_reservoir_size; - } - } - } - m_curr_bonds_to_form=0; - - } namespace detail @@ -505,8 +518,8 @@ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); + .def(py::init, std::shared_ptr, + std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 5e17eeba..b0846693 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -41,12 +41,10 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, - const Scalar r_cutsq, + const Scalar r_cut, unsigned int bond_type, - unsigned int bond_reservoir_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2); @@ -64,38 +62,58 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr m_group_1; //!< First particle group to form bonds with std::shared_ptr m_group_2; //!< Second particle group to form bonds with - const Scalar m_r_cutsq; //!< cutoff squared for the bond forming criterion + const Scalar m_r_cut; //!< cutoff for the bond forming criterion unsigned int m_bond_type; //!< Type id of the bond to form - unsigned int m_bond_reservoir_type; //!< Type id of the bond reservoir unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - GPUArray m_curr_num_bonds; //!< current number of bonds for each particle - std::map, int> m_all_existing_bonds; //!< map of all current existing bonds of bond_type + unsigned int m_max_bonds; //!< maximum number of possible bonds found + unsigned int m_max_bonds_overflow; //!< maximum number of possible bonds found if there is an overflow - unsigned int m_max_bonds; //!< maximum number of bonds which can be formed by the first group - GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*max_bonds_1 + + GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*n_max_bonds(=20) + unsigned int m_all_possible_bonds_end; unsigned int m_curr_bonds_to_form; //!< number of bonds to form in the current timestep unsigned int m_reservoir_size; - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group 1 - GPUVector m_aabbs; //!< Flat array of AABBs of all types + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group 1 + GPUVector m_aabbs; //!< Flat array of AABBs of all types std::vector< vec3 > m_image_list; //!< List of translation vectors unsigned int m_n_images; //!< The number of image vectors to check - //! Changes the particle types according to an update rule - virtual void findPotentialBondPairs(unsigned int timestep); - virtual void formBondPairs(unsigned int timestep); + GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag + GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag + unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles + Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list private: - void calculateCurrentBonds(); + //bool SortBonds(Scalar3 i, Scalar3 j); //todo: should go into helper class. or not be member of this class anyway + //bool CompareBonds(Scalar3 i, Scalar3 j); //todo: should go into helper class. or not be member of this class anyway + bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag todo: rename to something sensible + void calculateExistingBonds(); + void calculatePossibleBonds(); + void filterPossibleBonds(); + void makeBonds(); + void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); + bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag void updateImageVectors(); void checkSystemSetup(); + void resizePossibleBondlists(); + void resizeExistingBondList(); + + //! Notification of a box size change + void slotBoxChanged() + { + m_box_changed = true; + } + + bool m_box_changed; //!< Flag if box changed + }; namespace detail diff --git a/azplugins/update.py b/azplugins/update.py index c76b622d..6c011cfe 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -136,26 +136,24 @@ def set_params(self, inside=None, outside=None, lo=None, hi=None): class dynamic_bond(hoomd.update._updater): - def __init__(self, nlist, r_cut,bond_type, bond_reservoir_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): + def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): hoomd.util.print_status_line() hoomd.update._updater.__init__(self) - self.nlist = nlist + if not hoomd.context.exec_conf.isCUDAEnabled(): cpp_class = _azplugins.DynamicBondUpdater - self.nlist.cpp_nlist.setStorageMode(_md.NeighborList.storageMode.half) else: hoomd.context.msg.error('update.dynamic_bond not implemented on the GPU \n') raise ValueError('update.dynamic_bond not implemented on the GPU ') #cpp_class = _azplugins.TypeUpdaterGPU - # look up the bond ids based on the given names - this will throw an error if the bond types do not exist + # look up the bond id based on the given name - this will throw an error if the bond types do not exist bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) - bond_reservoir_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_reservoir_type) - self.rcutsq = r_cut**2.0 + self.r_cut = r_cut # we need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different new_cpp_group = _hoomd.ParticleGroup.groupIntersection(group_1.cpp_group, group_2.cpp_group) @@ -168,7 +166,8 @@ def __init__(self, nlist, r_cut,bond_type, bond_reservoir_type,group_1, group_2, #it doesn't really make sense to allow partially overlapping groups? self.cpp_updater = cpp_class(hoomd.context.current.system_definition, - self.nlist.cpp_nlist,group_1.cpp_group,group_2.cpp_group,self.rcutsq,bond_type_id,bond_reservoir_type_id,max_bonds_1,max_bonds_2) + group_1.cpp_group,group_2.cpp_group,self.r_cut, + bond_type_id,max_bonds_1,max_bonds_2) self.setupUpdater(period, phase) # how to do handling of exclusions in the neighborlist correctly? From df11255012f0881dedc7158dd80749e1b8f6099f Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 3 Jul 2020 16:00:44 -0500 Subject: [PATCH 04/45] Add Nparticle and box size change reallocation --- azplugins/DynamicBondUpdater.cc | 151 +++++++++++++++++--------------- azplugins/DynamicBondUpdater.h | 66 ++++++-------- azplugins/update.py | 17 ++-- 3 files changed, 118 insertions(+), 116 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 365f406e..8aa5e01c 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -23,40 +23,28 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, unsigned int max_bonds_group_2) : Updater(sysdef), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut), m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), - m_box_changed(true) + m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect(this); + m_pdata->getGlobalParticleNumberChangeSignal().connect(this); + // m_pdata->getNumTypesChangeSignal().connect(this); + m_bond_data = m_sysdef->getBondData(); - // allocate initial Memory - is 4 a good number? - m_max_bonds = 4; + // allocate initial Memory - grows if necessary + m_max_bonds = (max_bonds_group_1 < max_bonds_group_2) ? max_bonds_group_2 : max_bonds_group_1; m_max_bonds_overflow = 0; GPUArray all_possible_bonds(m_group_2->getNumMembers()*m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); - //todo: reset all aabb componentes if group sizes change + //todo: reset all aabb componentes if group sizes changes? + // if groups change this updater might just not work properly - groups don't have a change signal m_aabbs.resize(m_group_1->getNumMembers()); - //todo: need to register total particle number change and reallocate this - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); - - - GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); - - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - - memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); - memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - checkSystemSetup(); - calculateExistingBonds(); } @@ -79,6 +67,13 @@ void DynamicBondUpdater::update(unsigned int timestep) m_box_changed = false; } + // update properties that depend on the number of particles + if (m_max_N_changed) + { + allocateParticleArrays(); + m_max_N_changed = false; + } + // rebuild the list of possible bonds until there is no overflow bool overflowed = false; do @@ -93,6 +88,7 @@ void DynamicBondUpdater::update(unsigned int timestep) } while (overflowed); filterPossibleBonds(); + // this function is not easily implemented on the GPU, uses addBondedGroup() makeBonds(); @@ -106,7 +102,7 @@ bool SortBonds(Scalar3 i, Scalar3 j) return r_sq_1 < r_sq_2; } -//todo: should go into helper class? +//todo: should go into helper class? - also, faster way without branching? bool CompareBonds(Scalar3 i, Scalar3 j) { @@ -115,14 +111,13 @@ bool SortBonds(Scalar3 i, Scalar3 j) const unsigned int tag_21 = __scalar_as_int(j.x); const unsigned int tag_22 = __scalar_as_int(j.y); - if (tag_11==tag_21 && tag_12==tag_22) // (i,j)==(i,j) - { - return true; - }else if (tag_11==tag_22 && tag_12==tag_21) // (i,j)==(j,i) + if ((tag_11==tag_21 && tag_12==tag_22) || // (i,j)==(i,j) + (tag_11==tag_22 && tag_12==tag_21)) // (i,j)==(j,i) { return true; } - else{ + else + { return false; } } @@ -151,7 +146,7 @@ void DynamicBondUpdater::calculateExistingBonds() memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()*m_existing_bonds_list_indexer.getH()); ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); - ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::read); +// ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::read); // for each of the bonds const unsigned int size = (unsigned int)m_bond_data->getN(); @@ -161,9 +156,9 @@ void DynamicBondUpdater::calculateExistingBonds() const typename BondData::members_t& bond = h_bonds.data[i]; unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; - unsigned int type = h_typeval.data[i].type; + // unsigned int type = h_typeval.data[i].type; - //only keep track of the bonds we are forming - does this make sense? + //only keep track of the bond type we are forming - does this make sense? // if (type == m_bond_type) // { AddtoExistingBonds(tag1,tag2); @@ -186,7 +181,6 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,i)] == tag2) return true; } - return false; } @@ -199,10 +193,8 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) // don't add a bond twice - should not happen anyway if (isExistingBond(tag1, tag2)) return; - bool overflowed = false; - // access arrays ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); // resize the list if necessary @@ -215,8 +207,6 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) if (overflowed) resizeExistingBondList(); - // access arrays - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); // add tag2 to tag1's exclusion list @@ -233,6 +223,7 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) } +//todo: should the list be grown more than 1 at a time for efficiency? void DynamicBondUpdater::resizeExistingBondList() { unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; @@ -243,10 +234,41 @@ void DynamicBondUpdater::resizeExistingBondList() m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; } +//todo: should the list be grown more than 1 at a time for efficiency? +void DynamicBondUpdater::resizePossibleBondlists() + { + m_max_bonds=m_max_bonds_overflow; + m_max_bonds_overflow=0; + unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + m_all_possible_bonds.resize(size); + + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + } + + +void DynamicBondUpdater::allocateParticleArrays() + { + + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + + GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + + memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + + calculateExistingBonds(); + } + void DynamicBondUpdater::calculatePossibleBonds() { - //todo: is it worth it to seperate the tree building out and check if update is necessary? - //make tree for group 1 + //todo: is it worth it to seperate the tree building out and check if update is necessary similar to neighbor list? + // make tree for group 1 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); @@ -267,7 +289,7 @@ void DynamicBondUpdater::calculatePossibleBonds() const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - //traverse the tree + // traverse the tree // Loop over all particles in group 2 unsigned int group_size_2 = m_group_2->getNumMembers(); for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) @@ -350,17 +372,17 @@ std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds // now make sure each possible bond is in the array only once by comparing tags auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size, CompareBonds); -m_all_possible_bonds_end = std::distance(h_all_possible_bonds.data,last); +m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); // then remove a possible bond if it already exists. It also removes zeros, e.g. // (0,0,0), which fill the unused spots in the array. auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + m_all_possible_bonds_end, + h_all_possible_bonds.data + m_num_all_possible_bonds, [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); -m_all_possible_bonds_end = std::distance(h_all_possible_bonds.data,last2); +m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); -// at this point, the sub-array: h_all_possible_bonds[0,m_all_possible_bonds_end] +// at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. } @@ -436,21 +458,6 @@ if (m_bond_type >= m_bond_data -> getNTypes()) } - -//todo: should the list be grown more than 1 at a time for efficiency? -void DynamicBondUpdater::resizePossibleBondlists() -{ - m_max_bonds=m_max_bonds_overflow; - m_max_bonds_overflow=0; - unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - m_all_possible_bonds.resize(size); - - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; -} - -/*! - * \param timestep Timestep update is called - */ void DynamicBondUpdater::makeBonds() { @@ -460,46 +467,48 @@ void DynamicBondUpdater::makeBonds() ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - GPUArray total_counts_tag(m_pdata->getRTags().size(), m_exec_conf); - ArrayHandle h_total_counts_tag(total_counts_tag, access_location::host, access_mode::readwrite); - memset((void*)h_total_counts_tag.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + GPUArray total_counts(m_pdata->getRTags().size(), m_exec_conf); + ArrayHandle h_total_counts(total_counts, access_location::host, access_mode::readwrite); + memset((void*)h_total_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); // Loop over all possible bonds and count how many times a tag is in h_all_possible_bonds array - for (unsigned int i = 0; i < m_all_possible_bonds_end; i++) + // save how many bonds to form (max bonds - existing bonds) in h_total_counts + for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) { Scalar3 d = h_all_possible_bonds.data[i]; const int tag_i = __scalar_as_int(d.x); const int tag_j = __scalar_as_int(d.y); - h_total_counts_tag.data[tag_i]=m_max_bonds_group_1 - h_n_existing_bonds.data[tag_i]; - h_total_counts_tag.data[tag_j]=m_max_bonds_group_2 - h_n_existing_bonds.data[tag_j]; + h_total_counts.data[tag_i]=m_max_bonds_group_1 - h_n_existing_bonds.data[tag_i]; + h_total_counts.data[tag_j]=m_max_bonds_group_2 - h_n_existing_bonds.data[tag_j]; } - GPUArray current_counts_tag(m_pdata->getRTags().size(), m_exec_conf); - ArrayHandle h_current_counts_tag(current_counts_tag, access_location::host, access_mode::readwrite); - memset((void*)h_current_counts_tag.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + GPUArray current_counts(m_pdata->getRTags().size(), m_exec_conf); + ArrayHandle h_current_counts(current_counts, access_location::host, access_mode::readwrite); + memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); //todo: can this for loop be simplified/paralleized? bool added_bonds = false; - for (unsigned int i = 0; i < m_all_possible_bonds_end; i++) + for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) { Scalar3 d = h_all_possible_bonds.data[i]; unsigned int tag_i = __scalar_as_int(d.x); unsigned int tag_j = __scalar_as_int(d.y); //todo: put in other external criteria here, e.g. probability of bond formation etc - if (h_current_counts_tag.data[tag_i]addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); - h_current_counts_tag.data[tag_i]++; - h_current_counts_tag.data[tag_j]++; + h_current_counts.data[tag_i]++; + h_current_counts.data[tag_j]++; added_bonds = true; } } if (added_bonds) m_pdata->notifyParticleSort(); - //todo: how to add this? m_nlist as parameter? + + //todo: how to add this? m_nlist as parameter? also needs to know if exclusions are set in nlist //notify neighbor lists // if (m_exclude_from_nlist) // m_nlist->addExclusion(p_from_idx,p_to_idx); diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index b0846693..90152bb2 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -25,16 +25,6 @@ namespace azplugins { -//! Particle type updater -/*! - * Flips particle types based on their z height. Particles are classified as - * either inside or outside of the region, and can be flipped between these two - * types. Particles that are of neither the inside nor outside type are ignored. - * - * The region is defined by a slab along z. This could be easily extended to - * accommodate a generic region criteria, but for now, the planar slab in z is - * all that is necessary. - */ class PYBIND11_EXPORT DynamicBondUpdater : public Updater { public: @@ -51,60 +41,51 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Destructor virtual ~DynamicBondUpdater(); - //! Evaporate particles + //! find and make new bonds virtual void update(unsigned int timestep); protected: - std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding + //std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding std::shared_ptr m_bond_data; //!< Bond data std::shared_ptr m_group_1; //!< First particle group to form bonds with std::shared_ptr m_group_2; //!< Second particle group to form bonds with - const Scalar m_r_cut; //!< cutoff for the bond forming criterion - - unsigned int m_bond_type; //!< Type id of the bond to form - - unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group - unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - - unsigned int m_max_bonds; //!< maximum number of possible bonds found - unsigned int m_max_bonds_overflow; //!< maximum number of possible bonds found if there is an overflow - + const Scalar m_r_cut; //!< cutoff for the bond forming criterion + unsigned int m_bond_type; //!< Type id of the bond to form + unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group + unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*n_max_bonds(=20) - unsigned int m_all_possible_bonds_end; + unsigned int m_max_bonds; //!< maximum number of possible bonds which can be found + unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds - unsigned int m_curr_bonds_to_form; //!< number of bonds to form in the current timestep - unsigned int m_reservoir_size; - - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group 1 - GPUVector m_aabbs; //!< Flat array of AABBs of all types - - std::vector< vec3 > m_image_list; //!< List of translation vectors - unsigned int m_n_images; //!< The number of image vectors to check + GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*n_max_bonds + unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 + GPUVector m_aabbs; //!< Flat array of AABBs of all types + std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal + unsigned int m_n_images; //!< The number of image vectors to check GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag - GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag - unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles + GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag + unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list private: - //bool SortBonds(Scalar3 i, Scalar3 j); //todo: should go into helper class. or not be member of this class anyway - //bool CompareBonds(Scalar3 i, Scalar3 j); //todo: should go into helper class. or not be member of this class anyway - bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag todo: rename to something sensible + bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); void calculatePossibleBonds(); void filterPossibleBonds(); void makeBonds(); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); - bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag + bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag void updateImageVectors(); void checkSystemSetup(); void resizePossibleBondlists(); void resizeExistingBondList(); + void allocateParticleArrays(); //! Notification of a box size change void slotBoxChanged() @@ -112,7 +93,14 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater m_box_changed = true; } - bool m_box_changed; //!< Flag if box changed + //! Notification of total particle number change + void slotNumParticlesChanged() + { + m_max_N_changed = true; + } + + bool m_box_changed; //!< Flag if box dimensions changed + bool m_max_N_changed; //!< Flag if total number of particles changed }; diff --git a/azplugins/update.py b/azplugins/update.py index 6c011cfe..fcb988fe 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -139,7 +139,6 @@ class dynamic_bond(hoomd.update._updater): def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): hoomd.util.print_status_line() - hoomd.update._updater.__init__(self) @@ -155,7 +154,9 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,peri self.r_cut = r_cut - # we need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different + # it doesn't really make sense to allow partially overlapping groups? + # Maybe it should be excluded. + # We need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different: new_cpp_group = _hoomd.ParticleGroup.groupIntersection(group_1.cpp_group, group_2.cpp_group) if new_cpp_group.getNumMembersGlobal()>0 and max_bonds_1 != max_bonds_2: hoomd.context.msg.error('update.dynamic_bond: groups are overlapping with ' + str(new_cpp_group.getNumMembersGlobal()) @@ -163,16 +164,20 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,peri + ' != '+ str(max_bonds_2)+ '.\n') raise ValueError('update.dynamic_bond: groups are overlapping with different number of maximum bonds') - #it doesn't really make sense to allow partially overlapping groups? + # preliminary testing indicates that it is faster on the CPU to have group_1 to be the bigger one + # swap such that group 1 is the bigger of the two + if group_2.cpp_group.getNumMembersGlobal()> group_1.cpp_group.getNumMembersGlobal(): + temp_group = group_1 + group_1 = group_2 + group_2 = temp_group self.cpp_updater = cpp_class(hoomd.context.current.system_definition, group_1.cpp_group,group_2.cpp_group,self.r_cut, bond_type_id,max_bonds_1,max_bonds_2) self.setupUpdater(period, phase) - # how to do handling of exclusions in the neighborlist correctly? - # neighbor list is ordered by particle id, does this create artifacts? (CPU) - # what happens if bond reservoir is empty? should we throw a warning? + # how to do handling of exclusions in the neighbor list correctly? + def set_params(self, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): # todo - cpp class right now doesn't have any set/get functions From 313ba5453901e25ee4888663e77fd22f3a64c285 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 6 Jul 2020 09:59:36 -0500 Subject: [PATCH 05/45] Add empty files for GPU implementation --- azplugins/CMakeLists.txt | 2 + azplugins/DynamicBondUpdater.cc | 2 + azplugins/DynamicBondUpdaterGPU.cc | 52 ++++++++++++++++++++++++ azplugins/DynamicBondUpdaterGPU.cu | 21 ++++++++++ azplugins/DynamicBondUpdaterGPU.cuh | 25 ++++++++++++ azplugins/DynamicBondUpdaterGPU.h | 61 +++++++++++++++++++++++++++++ azplugins/module.cc | 2 + azplugins/update.py | 7 ++-- 8 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 azplugins/DynamicBondUpdaterGPU.cc create mode 100644 azplugins/DynamicBondUpdaterGPU.cu create mode 100644 azplugins/DynamicBondUpdaterGPU.cuh create mode 100644 azplugins/DynamicBondUpdaterGPU.h diff --git a/azplugins/CMakeLists.txt b/azplugins/CMakeLists.txt index 30b31483..9aa1d645 100644 --- a/azplugins/CMakeLists.txt +++ b/azplugins/CMakeLists.txt @@ -52,6 +52,7 @@ set(_${COMPONENT_NAME}_sources # cuda-enabled c++ source files if(ENABLE_CUDA) list(APPEND _${COMPONENT_NAME}_sources + DynamicBondUpdaterGPU.cc ImplicitEvaporatorGPU.cc ImplicitDropletEvaporatorGPU.cc ImplicitPlaneEvaporatorGPU.cc @@ -71,6 +72,7 @@ set(_${COMPONENT_NAME}_cu_sources BondPotentials.cu BounceBackNVEGPU.cu DPDPotentialGeneralWeight.cu + DynamicBondUpdaterGPU.cu ImplicitDropletEvaporatorGPU.cu ImplicitPlaneEvaporatorGPU.cu OrientationRestraintComputeGPU.cu diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 8aa5e01c..7aafe92d 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -52,6 +52,8 @@ DynamicBondUpdater::~DynamicBondUpdater() { m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdater" << std::endl; + m_pdata->getBoxChangeSignal().disconnect(this); + m_pdata->getGlobalParticleNumberChangeSignal().disconnect(this); } diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc new file mode 100644 index 00000000..6612a8dd --- /dev/null +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: astatt + +/*! + * \file DynamicBondUpdaterGPU.cc + * \brief Definition of DynamicBondUpdaterGPU + */ + +#include "DynamicBondUpdaterGPU.h" +#include "DynamicBondUpdaterGPU.cuh" + +namespace azplugins +{ + + DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2) + : DynamicBondUpdater(sysdef, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2) + { + m_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater", m_exec_conf)); + } + +DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() + { + } + +namespace detail +{ +/*! + * \param m Python module to export to + */ + void export_DynamicBondUpdaterGPU(pybind11::module& m) + { + namespace py = pybind11; + py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) + .def(py::init, std::shared_ptr, + std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); + + //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) + //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) + //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) + //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); + } +} // end namespace detail + +} // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu new file mode 100644 index 00000000..cf2e351a --- /dev/null +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -0,0 +1,21 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: astatt + +/*! + * \file DynamicBondUpdaterGPU.cu + * \brief Definition of kernel drivers and kernels for DynamicBondUpdaterGPU + */ + +#include "DynamicBondUpdaterGPU.cuh" + +namespace azplugins +{ +namespace gpu +{ +namespace kernel +{ +} //end namespace kernel +} // end namespace gpu +} // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh new file mode 100644 index 00000000..a7115b4a --- /dev/null +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -0,0 +1,25 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: astatt + +/*! + * \file DynamicBondUpdaterGPU.cuh + * \brief Declaration of kernel drivers for DynamicBondUpdaterGPU + */ + +#ifndef AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ +#define AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ + +#include +#include "hoomd/HOOMDMath.h" + +namespace azplugins +{ +namespace gpu +{ + +} +} + +#endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h new file mode 100644 index 00000000..2d9ba862 --- /dev/null +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -0,0 +1,61 @@ +// Copyright (c) 2018-2020, Michael P. Howard +// This file is part of the azplugins project, released under the Modified BSD License. + +// Maintainer: astatt + +/*! + * \file DynamicBondUpdaterGPU.h + * \brief Declaration of DynamicBondUpdaterGPU.h + */ + +#ifndef AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_H_ +#define AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_H_ + +#ifdef NVCC +#error This header cannot be compiled by nvcc +#endif + +#include "DynamicBondUpdater.h" +#include "hoomd/Autotuner.h" + +namespace azplugins +{ + +//! Particle type updater on the GPU +/*! + * See DynamicBondUpdater for details. This class inherits and minimally implements + * the CPU methods from DynamicBondUpdater on the GPU. + */ +class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater + { + public: + //! Constructor with parameters + DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2); + + //! Destructor + virtual ~DynamicBondUpdaterGPU(); + + //! find and make new bonds + // virtual void update(unsigned int timestep); + + protected: + + private: + std::unique_ptr m_tuner; //!< Tuner + }; + +namespace detail +{ +//! Export DynamicBondUpdaterGPU to python +void export_DynamicBondUpdaterGPU(pybind11::module& m); +} // end namespace detail + +} // end namespace azplugins + +#endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_H_ diff --git a/azplugins/module.cc b/azplugins/module.cc index 9fd098eb..31198a5d 100644 --- a/azplugins/module.cc +++ b/azplugins/module.cc @@ -25,6 +25,7 @@ namespace py = pybind11; #include "TypeUpdater.h" #include "ParticleEvaporator.h" #ifdef ENABLE_CUDA +#include "DynamicBondUpdaterGPU.h" #include "ReversePerturbationFlowGPU.h" #include "TypeUpdaterGPU.h" #include "ParticleEvaporatorGPU.h" @@ -195,6 +196,7 @@ PYBIND11_MODULE(_azplugins, m) azplugins::detail::export_TypeUpdater(m); azplugins::detail::export_ParticleEvaporator(m); // this must follow TypeUpdater because TypeUpdater is the python base class #ifdef ENABLE_CUDA + azplugins::detail::export_DynamicBondUpdaterGPU(m); azplugins::detail::export_ReversePerturbationFlowGPU(m); azplugins::detail::export_TypeUpdaterGPU(m); azplugins::detail::export_ParticleEvaporatorGPU(m); diff --git a/azplugins/update.py b/azplugins/update.py index fcb988fe..64b477c6 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -145,9 +145,10 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,peri if not hoomd.context.exec_conf.isCUDAEnabled(): cpp_class = _azplugins.DynamicBondUpdater else: - hoomd.context.msg.error('update.dynamic_bond not implemented on the GPU \n') - raise ValueError('update.dynamic_bond not implemented on the GPU ') - #cpp_class = _azplugins.TypeUpdaterGPU + cpp_class = _azplugins.DynamicBondUpdaterGPU + # hoomd.context.msg.error('update.dynamic_bond not implemented on the GPU \n') + # raise ValueError('update.dynamic_bond not implemented on the GPU ') + # look up the bond id based on the given name - this will throw an error if the bond types do not exist bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) From e641a2afaa90286762776a1624b8ce3579d35d44 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 16 Jul 2020 11:02:21 -0500 Subject: [PATCH 06/45] Bond sorting/selecting -> GPU --- azplugins/DynamicBondUpdater.cc | 42 +++-- azplugins/DynamicBondUpdater.h | 14 +- azplugins/DynamicBondUpdaterGPU.cc | 57 ++++++- azplugins/DynamicBondUpdaterGPU.cu | 251 ++++++++++++++++++++++++++++ azplugins/DynamicBondUpdaterGPU.cuh | 18 ++ azplugins/DynamicBondUpdaterGPU.h | 7 +- 6 files changed, 364 insertions(+), 25 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 7aafe92d..f9070df6 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -41,7 +41,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_all_possible_bonds.swap(all_possible_bonds); //todo: reset all aabb componentes if group sizes changes? - // if groups change this updater might just not work properly - groups don't have a change signal + // if groups change during the simulation this updater might just not work properly - groups don't have a change signal? m_aabbs.resize(m_group_1->getNumMembers()); checkSystemSetup(); @@ -62,6 +62,12 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(unsigned int timestep) { + // don't do anything if either one of the groups is empty + const unsigned int group_size_1 = m_group_1->getNumMembers(); + const unsigned int group_size_2 = m_group_2->getNumMembers(); + if(group_size_1 == 0 || group_size_2 == 0) + return; + // update properties that depend on the box if (m_box_changed) { @@ -80,7 +86,7 @@ void DynamicBondUpdater::update(unsigned int timestep) bool overflowed = false; do { - calculatePossibleBonds(); + findAllPossibleBonds(); overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-calculate if (overflowed) @@ -96,7 +102,7 @@ void DynamicBondUpdater::update(unsigned int timestep) } -//todo: should go into helper class? +//todo: should go into helper class/separate file bool SortBonds(Scalar3 i, Scalar3 j) { const Scalar r_sq_1 = i.z; @@ -104,7 +110,7 @@ bool SortBonds(Scalar3 i, Scalar3 j) return r_sq_1 < r_sq_2; } -//todo: should go into helper class? - also, faster way without branching? +//todo: migrate to separate file/class. faster way without branching? bool CompareBonds(Scalar3 i, Scalar3 j) { @@ -192,7 +198,7 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) assert(tag1 <= m_pdata->getMaximumTag()); assert(tag2 <= m_pdata->getMaximumTag()); - // don't add a bond twice - should not happen anyway + // don't add a bond twice - should not happen anyway - todo: might be able to avoid this check if (isExistingBond(tag1, tag2)) return; bool overflowed = false; @@ -211,13 +217,13 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - // add tag2 to tag1's exclusion list + // add tag2 to tag1's existing bonds list unsigned int pos1 = h_n_existing_bonds.data[tag1]; assert(pos1 < m_existing_bonds_list_indexer.getH()); h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos1)] = tag2; h_n_existing_bonds.data[tag1]++; - // add tag1 to tag2's exclusion list + // add tag1 to tag2's existing bonds list unsigned int pos2 = h_n_existing_bonds.data[tag2]; assert(pos2 < m_existing_bonds_list_indexer.getH()); h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos2)] = tag1; @@ -267,8 +273,9 @@ void DynamicBondUpdater::allocateParticleArrays() calculateExistingBonds(); } -void DynamicBondUpdater::calculatePossibleBonds() +void DynamicBondUpdater::findAllPossibleBonds() { + //std::cout<<" in DynamicBondUpdater::findAllPossibleBonds"< h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); @@ -458,11 +465,20 @@ if (m_bond_type >= m_bond_data -> getNTypes()) throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); } + if(m_group_1->getNumMembers()<=0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: group 1 appears to be empty. Bonds cannot be formed. " << std::endl; + } + + if(m_group_2->getNumMembers()<=0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: group 2 appears to be empty. Bonds cannot be formed. " << std::endl; + } + } void DynamicBondUpdater::makeBonds() { - // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); @@ -488,7 +504,7 @@ void DynamicBondUpdater::makeBonds() ArrayHandle h_current_counts(current_counts, access_location::host, access_mode::readwrite); memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); - //todo: can this for loop be simplified/paralleized? + //todo: can this for loop be simplified/parallelized? bool added_bonds = false; for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) { @@ -496,7 +512,8 @@ void DynamicBondUpdater::makeBonds() unsigned int tag_i = __scalar_as_int(d.x); unsigned int tag_j = __scalar_as_int(d.y); - //todo: put in other external criteria here, e.g. probability of bond formation etc + //todo: put in other external criteria here, e.g. probability of bond formation etc. + //todo: randomize which bonds are formed or keep them ordered by their distances? if (h_current_counts.data[tag_i]notifyParticleSort(); - //todo: how to add this? m_nlist as parameter? also needs to know if exclusions are set in nlist + //todo: how to add this? m_nlist as parameter? also needs to know if exclusions are set in nlist. //notify neighbor lists // if (m_exclude_from_nlist) // m_nlist->addExclusion(p_from_idx,p_to_idx); @@ -532,6 +549,7 @@ void export_DynamicBondUpdater(pybind11::module& m) .def(py::init, std::shared_ptr, std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); + //todo: implement needed getter/setter functions //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 90152bb2..a38ec47e 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -19,7 +19,6 @@ #include "hoomd/md/NeighborList.h" #include "hoomd/Updater.h" #include "hoomd/ParticleGroup.h" - #include "hoomd/extern/pybind/include/pybind11/pybind11.h" namespace azplugins @@ -41,7 +40,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Destructor virtual ~DynamicBondUpdater(); - //! find and make new bonds + //! update - find and make new bonds virtual void update(unsigned int timestep); @@ -63,7 +62,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*n_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 GPUVector m_aabbs; //!< Flat array of AABBs of all types std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal unsigned int m_n_images; //!< The number of image vectors to check @@ -72,12 +71,11 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list - - private: + virtual void filterPossibleBonds(); + bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); - void calculatePossibleBonds(); - void filterPossibleBonds(); + void findAllPossibleBonds(); void makeBonds(); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag @@ -99,7 +97,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater m_max_N_changed = true; } - bool m_box_changed; //!< Flag if box dimensions changed + bool m_box_changed; //!< Flag if box dimensions changed bool m_max_N_changed; //!< Flag if total number of particles changed }; diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 6612a8dd..2c51b765 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -21,15 +21,66 @@ namespace azplugins unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : DynamicBondUpdater(sysdef, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2) + : DynamicBondUpdater(sysdef, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2), + m_num_nonzero_bonds(m_exec_conf) { - m_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater", m_exec_conf)); + m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); + } DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { } +/*void DynamicBondUpdaterGPU::findAllPossibleBonds() + { + + } +*/ + +void DynamicBondUpdaterGPU::filterPossibleBonds() +{ + + //todo: figure out in which order the thrust calls are the fastest. + // suspect: sort - remove zeros - unique - filter -remove zeros ? + + const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + + // sort and remove all existing zeros + ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); + ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); + + gpu::sort_and_remove_zeros_possible_bond_array(d_all_possible_bonds.data, + size, + m_num_nonzero_bonds.getDeviceFlags()); + + + m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); + + //filter out the existing bonds + m_tuner_filter_bonds->begin(); + gpu::filter_existing_bonds(d_all_possible_bonds.data, + d_n_existing_bonds.data, + d_existing_bonds_list.data, + m_existing_bonds_list_indexer, + m_num_all_possible_bonds, + m_tuner_filter_bonds->getParam()); + m_tuner_filter_bonds->end(); + + + // filtering existing bonds out introduced some zeros back into the array, remove them + gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, + m_num_all_possible_bonds, + m_num_nonzero_bonds.getDeviceFlags()); + + + m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); + +// at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] +// should contain only unique entries of possible bonds which are not yet formed. +} + namespace detail { /*! @@ -48,5 +99,5 @@ namespace detail //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); } } // end namespace detail - + } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index cf2e351a..5db58b24 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -9,13 +9,264 @@ */ #include "DynamicBondUpdaterGPU.cuh" +#include +#include + + +#include namespace azplugins { + +//todo: migrate to separate file/class. +struct SortBondsGPUDistance{ + __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) + { + const Scalar r_sq_1 = in0.z; + const Scalar r_sq_2 = in1.z; + const unsigned int tag_0 = __scalar_as_int(in0.x); + const unsigned int tag_1 = __scalar_as_int(in1.x); + if (r_sq_1 ==r_sq_2) + return tag_0 < tag_1; + return r_sq_1 < r_sq_2; + } +}; + +struct SortBondsGPUFirstTag{ + __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) + { + const unsigned int tag_0 = __scalar_as_int(in0.x); + const unsigned int tag_1 = __scalar_as_int(in1.x); + return tag_0 < tag_1; + } +}; + +struct SortBondsGPUSecondTag{ + __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) + { + const unsigned int tag_0 = __scalar_as_int(in0.y); + const unsigned int tag_1 = __scalar_as_int(in1.y); + return tag_0 < tag_1; + } +}; + +struct isZeroBondGPU{ + __host__ __device__ bool operator()(const Scalar3 &in0) + { + const unsigned int tag_0 = __scalar_as_int(in0.x); + const unsigned int tag_1 = __scalar_as_int(in0.y); + if ( tag_0==0 && tag_1 ==0) + { + return true; + } + else + { + return false; + } + } +}; + +struct CompareBondsGPU{ + __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) + { + const unsigned int tag_11 = __scalar_as_int(in0.x); + const unsigned int tag_12 = __scalar_as_int(in0.y); + const unsigned int tag_21 = __scalar_as_int(in1.x); + const unsigned int tag_22 = __scalar_as_int(in1.y); + + if ((tag_11==tag_21 && tag_12==tag_22) || // (i,j)==(i,j) + (tag_11==tag_22 && tag_12==tag_21)) // (i,j)==(j,i) + { + return true; + } + else + { + return false; + } + } + }; + namespace gpu { + +//! Number of elements of the exisitng bond list to process in each batch +const unsigned int FILTER_BATCH_SIZE = 4; + namespace kernel { + +/*! \param d_n_neigh Number of neighbors for each particle (read/write) + \param d_nlist Neighbor list for each particle (read/write) + \param nli Indexer for indexing into d_nlist + \param d_n_ex Number of exclusions for each particle + \param d_ex_list List of exclusions for each particle + \param exli Indexer for indexing into d_ex_list + \param N Number of particles + \param ex_start Start filtering the nlist from exclusion number \a ex_start + + gpu_nlist_filter_kernel() processes the neighbor list \a d_nlist and removes any entries that are excluded. To allow + for an arbitrary large number of exclusions, these are processed in batch sizes of FILTER_BATCH_SIZE. The kernel + must be called multiple times in order to fully remove all exclusions from the nlist. + + \note The driver gpu_nlist_filter properly makes as many calls as are necessary, it only needs to be called once. + + \b Implementation + + One thread is run for each particle. Exclusions \a ex_start, \a ex_start + 1, ... are loaded in for that particle + (or the thread returns if there are no exclusions past that point). The thread then loops over the neighbor list, + comparing each entry to the list of exclusions. If the entry is not excluded, it is written back out. \a d_n_neigh + is updated to reflect the current number of particles in the list at the end of the kernel call. +*/ +__global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, + const unsigned int *d_n_existing_bonds, + const unsigned int *d_existing_bonds_list, + const Index2D exli, + const unsigned int size, + const unsigned int ex_start) + { + // compute the bond index this thread operates on + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + + // quit now if this thread is processing past the end of the list of all possible bonds + if (idx >= size) + return; + + Scalar3 current_bond = d_all_possible_bonds[idx]; + unsigned int tag_1 = __scalar_as_int(current_bond.x); + unsigned int tag_2 = __scalar_as_int(current_bond.y); + + if(tag_1==0 && tag_2==0) + return; + + //const unsigned int n_neigh = d_n_neigh[idx]; + const unsigned int n_ex = d_n_existing_bonds[tag_1]; + unsigned int new_n_neigh = 0; + + // quit now if the ex_start flag is past the end of n_ex + if (ex_start >= n_ex) + return; + +// printf("in filter_existing_bonds idx %d tag_i %d tag_j %d dist %f \n",idx,tag_1,tag_2,current_bond.z); + + // count the number of existing bonds to process in this thread + const unsigned int n_ex_process = n_ex - ex_start; + + // load the existing bond list into "local" memory - fully unrolled loops should dump this into registers + unsigned int l_existing_bonds_list[FILTER_BATCH_SIZE]; + #pragma unroll + for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) + { + // printf("in filter_existing_bonds cur_ex_idx %d \n",cur_ex_idx); + if (cur_ex_idx < n_ex_process) + l_existing_bonds_list[cur_ex_idx] = d_existing_bonds_list[exli(tag_1, cur_ex_idx + ex_start)]; + else + l_existing_bonds_list[cur_ex_idx] = 0xffffffff; + // printf("in filter_existing_bonds idx %d tag_i %d tag_j %d dist %f cur_ex_idx %d l_existing_bonds_list %d \n",idx,tag_1,tag_2,current_bond.z,cur_ex_idx,l_existing_bonds_list[cur_ex_idx]); + } + + + + // test if excluded + bool excluded = false; + #pragma unroll + for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) + { + if (tag_2 == l_existing_bonds_list[cur_ex_idx]) + excluded = true; + } + + // add this entry back to the list if it is not excluded + if (excluded) + { + d_all_possible_bonds[idx] = make_scalar3(0,0,0.0); + } + + } + } //end namespace kernel + +cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds) + { + if (size == 0) return cudaSuccess; + // wrapper for pointer needed for thrust + thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + + // first remove all zeros + isZeroBondGPU zero; + thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, zero); + unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); + + // sort remainder by distance, should make all identical bonds consequtive + SortBondsGPUDistance sort; + thrust::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); + CompareBondsGPU comp; + + // thrust::unique only removes identical consequtive elements. + thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); + unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); + + *d_max_non_zero_bonds=l1; + + return cudaSuccess; + } + +cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds) + { + if (size == 0) return cudaSuccess; + // wrapper for pointer needed for thrust + thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + // remove all zeros + isZeroBondGPU zero; + thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, zero); + unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); + *d_max_non_zero_bonds=l0; + + return cudaSuccess; + } + + +cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, + unsigned int *d_n_existing_bonds, + const unsigned int *d_existing_bonds_list, + const Index2D& exli, + const unsigned int size, + const unsigned int block_size) + { + static unsigned int max_block_size = UINT_MAX; + if (max_block_size == UINT_MAX) + { + cudaFuncAttributes attr; + cudaFuncGetAttributes(&attr, (const void *)kernel::filter_existing_bonds); + max_block_size = attr.maxThreadsPerBlock; + } + + unsigned int run_block_size = min(block_size, max_block_size); + + // determine parameters for kernel launch + int n_blocks = size/run_block_size + 1; + + // split the processing of the full exclusion list up into a number of batches + unsigned int n_batches = (unsigned int)ceil(double(exli.getH())/double(FILTER_BATCH_SIZE)); + unsigned int ex_start = 0; + for (unsigned int batch = 0; batch < n_batches; batch++) + { + kernel::filter_existing_bonds<<>>(d_all_possible_bonds, + d_n_existing_bonds, + d_existing_bonds_list, + exli, + size, + ex_start); + + ex_start += FILTER_BATCH_SIZE; + } + + return cudaSuccess; + } + + } // end namespace gpu } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index a7115b4a..30d9f65a 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -13,11 +13,29 @@ #include #include "hoomd/HOOMDMath.h" +#include "hoomd/Index1D.h" namespace azplugins { namespace gpu { +cudaError_t sort_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size); + +cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, + unsigned int *d_n_existing_bonds, + const unsigned int *d_existing_bonds_list, + const Index2D& exli, + const unsigned int size, + const unsigned int block_size); + +cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds); + +cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds); } } diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 2d9ba862..4fc2c00e 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -45,9 +45,12 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater // virtual void update(unsigned int timestep); protected: - + + virtual void filterPossibleBonds(); private: - std::unique_ptr m_tuner; //!< Tuner + std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter + GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles + }; namespace detail From a7cea56900da96cc2450008e152190b69dbb9fc4 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 27 Jul 2020 18:49:04 -0500 Subject: [PATCH 07/45] Copied tree neighbor list, CUDA error --- azplugins/DynamicBondUpdater.cc | 21 +- azplugins/DynamicBondUpdater.h | 8 +- azplugins/DynamicBondUpdaterGPU.cc | 187 +++++++++++- azplugins/DynamicBondUpdaterGPU.cu | 105 +++++-- azplugins/DynamicBondUpdaterGPU.cuh | 454 +++++++++++++++++++++++++++- azplugins/DynamicBondUpdaterGPU.h | 40 ++- azplugins/update.py | 2 + 7 files changed, 766 insertions(+), 51 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index f9070df6..fea842da 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -39,6 +39,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_bonds_overflow = 0; GPUArray all_possible_bonds(m_group_2->getNumMembers()*m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); + m_num_all_possible_bonds=0; //todo: reset all aabb componentes if group sizes changes? // if groups change during the simulation this updater might just not work properly - groups don't have a change signal? @@ -67,7 +68,7 @@ void DynamicBondUpdater::update(unsigned int timestep) const unsigned int group_size_2 = m_group_2->getNumMembers(); if(group_size_1 == 0 || group_size_2 == 0) return; - + std::cout<<"in DynamicBondUpdater::update "<getNumMembers()*m_max_bonds; m_all_possible_bonds.resize(size); - + m_num_all_possible_bonds=0; m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; } @@ -273,10 +277,11 @@ void DynamicBondUpdater::allocateParticleArrays() calculateExistingBonds(); } +// this is based on the NeighborListTree c++ implementation void DynamicBondUpdater::findAllPossibleBonds() { - //std::cout<<" in DynamicBondUpdater::findAllPossibleBonds"< h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); @@ -330,7 +335,9 @@ void DynamicBondUpdater::findAllPossibleBonds() // skip self-interaction always bool excluded = (i == j); - + //todo: bonds which already exist should be not put in the array in the first place. + // that could save us from needing to filter out the exclusions later? but why is the + // neighbor list not doing that? to take advantage of the same structure for all the neighbor lists? if (!excluded) { // compute distance @@ -372,7 +379,7 @@ void DynamicBondUpdater::findAllPossibleBonds() void DynamicBondUpdater::filterPossibleBonds() { - + std::cout<< "in DynamicBondUpdater::filterPossibleBonds()"<< std::endl; ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index a38ec47e..aeed0054 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -72,14 +72,16 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list virtual void filterPossibleBonds(); - + bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); - void findAllPossibleBonds(); + + virtual void findAllPossibleBonds(); + void makeBonds(); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag - void updateImageVectors(); + virtual void updateImageVectors(); void checkSystemSetup(); void resizePossibleBondlists(); void resizeExistingBondList(); diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 2c51b765..7f943821 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -22,9 +22,11 @@ namespace azplugins unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) : DynamicBondUpdater(sysdef, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2), - m_num_nonzero_bonds(m_exec_conf) + m_num_nonzero_bonds(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { + m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); + m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_tree_copy", m_exec_conf)); } @@ -32,17 +34,123 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { } -/*void DynamicBondUpdaterGPU::findAllPossibleBonds() +// this is based on the LBVH NeighborListGPUTree implementation +void DynamicBondUpdaterGPU::findAllPossibleBonds() { + std::cout<< "in DynamicBondUpdaterGPU::findAllPossibleBonds"<getBox(); + + ArrayHandle d_postype(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_group_1_indexes(m_group_1->getIndexArray(), access_location::device, access_mode::read); + + unsigned int group_size_1 = m_group_1->getNumMembers(); + const BoxDim lbvh_box = getLBVHBox(); + + // build a lbvh for group_1 + m_lbvh.build(gpu::PointMapInsertOp(d_postype.data, d_group_1_indexes.data, group_size_1), + lbvh_box.getLo(), + lbvh_box.getHi()); + + //todo: need to use the neighbor list data structure, unclear how to restructure the internal cast into uint4 otherwise? + GPUArray n_bonds(m_group_2->getNumMembers(), m_exec_conf); + ArrayHandle h_n_bonds(n_bonds, access_location::host, access_mode::overwrite); + memset((void*)h_n_bonds.data, 0, sizeof(unsigned int)*m_group_2->getNumMembers()); + + ArrayHandle d_n_bonds(n_bonds, access_location::device, access_mode::readwrite); + + unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + GPUArray all_possible_bonds_int(size, m_exec_conf); + ArrayHandle h_all_possible_bonds_int(all_possible_bonds_int, access_location::host, access_mode::overwrite); + memset((void*)h_all_possible_bonds_int.data, 0, sizeof(unsigned int)*size); + + ArrayHandle d_all_possible_bonds_int(all_possible_bonds_int, access_location::device, access_mode::readwrite); + + GPUArray head_list(m_group_2->getNumMembers(), m_exec_conf); + ArrayHandle h_head_list(head_list, access_location::host, access_mode::overwrite); + + unsigned int headAddress = 0; + for (unsigned int i=0; i < m_group_2->getNumMembers(); ++i) + { + h_head_list.data[i] = headAddress; + headAddress+=m_max_bonds; + } + + ArrayHandle d_head_list(head_list, access_location::device, access_mode::readwrite); + // neighbor list write op + + gpu::NeighborListOp nlist_op(d_all_possible_bonds_int.data, d_n_bonds.data,&m_max_bonds_overflow, d_head_list.data, m_max_bonds); + + /* NeighborListOp(unsigned int* neigh_list_, + unsigned int* nneigh_, + unsigned int* new_max_neigh_, + const unsigned int* first_neigh_, + unsigned int max_neigh_) + */ + + ArrayHandle d_group_2_indexes(m_group_2->getIndexArray(), access_location::device, access_mode::read); + + gpu::ParticleQueryOp query_op(d_postype.data, + NULL, + NULL, + d_group_2_indexes.data, + m_group_2->getNumMembers(), + m_group_1->getNumMembers(), + m_r_cut, + m_r_cut+0.4, + box); + + std::cout<< "m_max_bonds_overflow before "<< m_max_bonds_overflow << " m_max_bonds "<< m_max_bonds <getNumMembers()<getNumMembers()<getN()< h_group_2_indexes(m_group_2->getIndexArray(), access_location::host, access_mode::read); + std::cout<< "group 2 index "; + for (unsigned int i=0; i < m_group_2->getNumMembers(); ++i) + { + std::cout<< " "<< h_group_2_indexes.data[i] ; + } + std::cout<< std::endl; + m_traverser.traverse(nlist_op, query_op, m_lbvh, m_image_list); + + std::cout<< "m_max_bonds_overflow after "<< m_max_bonds_overflow << " m_max_bonds "<< m_max_bonds < h_all_possible_bonds_int(all_possible_bonds_int, access_location::host, access_mode::read); + ArrayHandle h_n_bonds(n_bonds, access_location::host, access_mode::read); + + for (unsigned int i = 0; i < m_group_2->getNumMembers(); i++) + { + std::cout<<" d_n_bonds "<< i << " "<< h_n_bonds.data[i] << " all_possible_bonds " ; + for (unsigned int j = 0; j < m_max_bonds; j++) + { + std::cout<< " "<< h_all_possible_bonds_int.data[i*m_max_bonds+j] << " "; + } + std::cout <getNumMembers()*m_max_bonds<getNumMembers()*m_max_bonds; + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + m_num_all_possible_bonds=0; } -*/ -void DynamicBondUpdaterGPU::filterPossibleBonds() -{ - //todo: figure out in which order the thrust calls are the fastest. - // suspect: sort - remove zeros - unique - filter -remove zeros ? +void DynamicBondUpdaterGPU::filterPossibleBonds() + { + std::cout<< "in DynamicBondUpdaterGPU::filterPossibleBonds()"<< std::endl; + // todo: figure out in which order the thrust calls are the fastest. + // is using build in thrust functions the best solution? + // suspect: sort - remove zeros - unique - filter (which introduces zeros) - remove zeros ? const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; @@ -58,7 +166,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - //filter out the existing bonds + //filter out the existing bonds - based on neighbor list exclusion handeling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, d_n_existing_bonds.data, @@ -79,7 +187,66 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. -} + } + +/*! + * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 26 translation vectors + * when the simulation box is 3D periodic (the self-image is excluded). In 2D, there are at most 8 translation vectors. + * In MPI runs, a ghost layer of particles is added from adjacent ranks, so there is no need to perform any translations + * in this direction. The translation vectors are determined by linear combination of the lattice vectors, and must be + * recomputed any time that the box resizes. + */ +void DynamicBondUpdaterGPU::updateImageVectors() + { + std::cout<< "in DynamicBondUpdaterGPU::updateImageVectors()"<getBox(); + uchar3 periodic = box.getPeriodic(); + unsigned char sys3d = (m_sysdef->getNDimensions() == 3); + + // now compute the image vectors + // each dimension increases by one power of 3 + unsigned int n_dim_periodic = (periodic.x + periodic.y + sys3d*periodic.z); + m_n_images = 1; + for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) + { + m_n_images *= 3; + } + m_n_images -= 1; // remove the self image + + // reallocate memory if necessary + if (m_n_images > m_image_list.getNumElements()) + { + GlobalVector image_list(m_n_images, m_exec_conf); + m_image_list.swap(image_list); + } + + ArrayHandle h_image_list(m_image_list, access_location::host, access_mode::overwrite); + Scalar3 latt_a = box.getLatticeVector(0); + Scalar3 latt_b = box.getLatticeVector(1); + Scalar3 latt_c = box.getLatticeVector(2); + + // iterate over all other combinations of images, skipping those that are + unsigned int n_images = 0; + for (int i=-1; i <= 1 && n_images < m_n_images; ++i) + { + for (int j=-1; j <= 1 && n_images < m_n_images; ++j) + { + for (int k=-1; k <= 1 && n_images < m_n_images; ++k) + { + if (!(i == 0 && j == 0 && k == 0)) + { + // skip any periodic images if we don't have periodicity + if (i != 0 && !periodic.x) continue; + if (j != 0 && !periodic.y) continue; + if (k != 0 && (!sys3d || !periodic.z)) continue; + + h_image_list.data[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; + ++n_images; + } + } + } + } + } namespace detail { @@ -98,6 +265,6 @@ namespace detail //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); } -} // end namespace detail +} // end namespace detail } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 5db58b24..02e0cbee 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -8,10 +8,14 @@ * \brief Definition of kernel drivers and kernels for DynamicBondUpdaterGPU */ +#include "hoomd/HOOMDMath.h" #include "DynamicBondUpdaterGPU.cuh" #include #include +// todo: should azplugins have its own "extern"? +#include "hoomd/extern/neighbor/neighbor/LBVH.cuh" +#include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" #include @@ -26,7 +30,10 @@ struct SortBondsGPUDistance{ const Scalar r_sq_2 = in1.z; const unsigned int tag_0 = __scalar_as_int(in0.x); const unsigned int tag_1 = __scalar_as_int(in1.x); - if (r_sq_1 ==r_sq_2) + // todo: is this necessary for the thrust::unique to filter out all dublicates or + // would r_sq_1 < r_sq_2 be enough? What happens if two different potential + // bonds have EXACTLY same length? + if (r_sq_1 == r_sq_2) return tag_0 < tag_1; return r_sq_1 < r_sq_2; } @@ -95,27 +102,8 @@ const unsigned int FILTER_BATCH_SIZE = 4; namespace kernel { -/*! \param d_n_neigh Number of neighbors for each particle (read/write) - \param d_nlist Neighbor list for each particle (read/write) - \param nli Indexer for indexing into d_nlist - \param d_n_ex Number of exclusions for each particle - \param d_ex_list List of exclusions for each particle - \param exli Indexer for indexing into d_ex_list - \param N Number of particles - \param ex_start Start filtering the nlist from exclusion number \a ex_start - - gpu_nlist_filter_kernel() processes the neighbor list \a d_nlist and removes any entries that are excluded. To allow - for an arbitrary large number of exclusions, these are processed in batch sizes of FILTER_BATCH_SIZE. The kernel - must be called multiple times in order to fully remove all exclusions from the nlist. - - \note The driver gpu_nlist_filter properly makes as many calls as are necessary, it only needs to be called once. - - \b Implementation - - One thread is run for each particle. Exclusions \a ex_start, \a ex_start + 1, ... are loaded in for that particle - (or the thread returns if there are no exclusions past that point). The thread then loops over the neighbor list, - comparing each entry to the list of exclusions. If the entry is not excluded, it is written back out. \a d_n_neigh - is updated to reflect the current number of particles in the list at the end of the kernel call. +/*! + This kernel is modeled after the neighbor list exclusions filtering mechanism. */ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, const unsigned int *d_n_existing_bonds, @@ -140,7 +128,6 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, //const unsigned int n_neigh = d_n_neigh[idx]; const unsigned int n_ex = d_n_existing_bonds[tag_1]; - unsigned int new_n_neigh = 0; // quit now if the ex_start flag is past the end of n_ex if (ex_start >= n_ex) @@ -164,8 +151,6 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, // printf("in filter_existing_bonds idx %d tag_i %d tag_j %d dist %f cur_ex_idx %d l_existing_bonds_list %d \n",idx,tag_1,tag_2,current_bond.z,cur_ex_idx,l_existing_bonds_list[cur_ex_idx]); } - - // test if excluded bool excluded = false; #pragma unroll @@ -175,7 +160,7 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, excluded = true; } - // add this entry back to the list if it is not excluded + // if it is excluded, overwrite that entry with (0,0,0). if (excluded) { d_all_possible_bonds[idx] = make_scalar3(0,0,0.0); @@ -183,8 +168,35 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, } + //! Kernel to copy the particle indexes into traversal order + /*! + * \param d_traverse_order List of particle indexes in traversal order. + * \param d_indexes Original indexes of the sorted primitives. + * \param d_primitives List of the primitives (sorted in LBVH order). + * \param N Number of primitives. + * + * The primitive index for this thread is first loaded. It is then mapped back + * to its original particle index, which is stored for subsequent traversal. + */ + __global__ void gpu_nlist_copy_primitives_kernel(unsigned int *d_traverse_order, + const unsigned int *d_indexes, + const unsigned int *d_primitives, + const unsigned int N) + { + // one thread per particle + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + if (idx >= N) + return; + + const unsigned int primitive = d_primitives[idx]; + d_traverse_order[idx] = __ldg(d_indexes + primitive); + } + + } //end namespace kernel + + cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds) @@ -203,7 +215,7 @@ cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bo thrust::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); CompareBondsGPU comp; - // thrust::unique only removes identical consequtive elements. + // thrust::unique only removes identical consequtive elements, so sort above is needed. thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); @@ -267,6 +279,45 @@ cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, return cudaSuccess; } + /*! + * \param d_traverse_order List of particle indexes in traversal order. + * \param d_indexes Original indexes of the sorted primitives. + * \param d_primitives List of the primitives (sorted in LBVH order). + * \param N Number of primitives. + * \param block_size Number of CUDA threads per block. + * + * \sa gpu_nlist_copy_primitives_kernel + */ + cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, + const unsigned int *d_indexes, + const unsigned int *d_primitives, + const unsigned int N, + const unsigned int block_size) + { + static unsigned int max_block_size = UINT_MAX; + if (max_block_size == UINT_MAX) + { + cudaFuncAttributes attr; + cudaFuncGetAttributes(&attr, (const void *)kernel::gpu_nlist_copy_primitives_kernel); + max_block_size = attr.maxThreadsPerBlock; + } + + int run_block_size = min(block_size,max_block_size); + kernel::gpu_nlist_copy_primitives_kernel<<>>(d_traverse_order, + d_indexes, + d_primitives, + N); + return cudaSuccess; + } } // end namespace gpu } // end namespace azplugins + +// explicit templates for neighbor::LBVH with PointMapInsertOp +template void neighbor::gpu::lbvh_gen_codes(unsigned int *, unsigned int *, const azplugins::gpu::PointMapInsertOp&, +const Scalar3, const Scalar3, const unsigned int, const unsigned int, cudaStream_t); +template void neighbor::gpu::lbvh_bubble_aabbs(const neighbor::gpu::LBVHData, const azplugins::gpu::PointMapInsertOp&, +unsigned int *, const unsigned int, const unsigned int, cudaStream_t); +template void neighbor::gpu::lbvh_one_primitive(const neighbor::gpu::LBVHData, const azplugins::gpu::PointMapInsertOp&, cudaStream_t); +template void neighbor::gpu::lbvh_traverse_ropes(azplugins::gpu::NeighborListOp&, const neighbor::gpu::LBVHCompressedData&, +const azplugins::gpu::ParticleQueryOp&, const Scalar3 *, unsigned int, unsigned int, cudaStream_t); diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index 30d9f65a..d9482ec7 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -14,11 +14,28 @@ #include #include "hoomd/HOOMDMath.h" #include "hoomd/Index1D.h" +#include "hoomd/BoxDim.h" +#include "hoomd/ParticleData.cuh" +#include + +#include "hoomd/extern/neighbor/neighbor/BoundingVolumes.h" +#include "hoomd/extern/neighbor/neighbor/InsertOps.h" +#include "hoomd/extern/neighbor/neighbor/TransformOps.h" + +#ifdef NVCC +#define DEVICE __device__ __forceinline__ +#define HOSTDEVICE __host__ __device__ __forceinline__ +#else +#define DEVICE +#define HOSTDEVICE +#endif + namespace azplugins { namespace gpu { + cudaError_t sort_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size); @@ -37,7 +54,440 @@ cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds); -} -} + + +//! Insert operation for a point under a mapping. +/*! + * Extends the base neighbor::PointInsertOp to insert a point primitive + * subject to a mapping of the indexes. This is useful for reading from + * the array of particles that is pre-sorted by type so that the original + * particle data does not need to be shuffled. + */ +struct PointMapInsertOp : public neighbor::PointInsertOp + { + //! Constructor + /*! + * \param points_ List of points to insert (w entry is unused). + * \param map_ Map of the nominal index to the index in \a points_. + * \param N_ Number of primitives to insert. + */ + PointMapInsertOp(const Scalar4 *points_, const unsigned int *map_, unsigned int N_) + : neighbor::PointInsertOp(points_, N_), map(map_) + {} + + #ifdef NVCC + //! Construct bounding box + /*! + * \param idx Nominal index of the primitive [0,N). + * \returns A neighbor::BoundingBox corresponding to the point at map[idx]. + */ + DEVICE neighbor::BoundingBox get(const unsigned int idx) const + { + const Scalar4 point = points[map[idx]]; + const Scalar3 p = make_scalar3(point.x, point.y, point.z); + // construct the bounding box for a point + return neighbor::BoundingBox(p,p); + } + #endif + + const unsigned int *map; //!< Map of particle indexes. + }; + +//! Neighbor list particle query operation. +/*! + * \tparam use_body If true, use the body fields during query. + * \tparam use_diam If true, use the diameter fields during query. + * + * This operation specifies the neighbor list traversal scheme. The + * query is between a SkippableBoundingSphere and the bounding boxes in + * the LBVH. The template parameters can be activated to engage body-filtering + * or diameter-shifting, which are defined elsewhere in HOOMD. + * + * All spheres in the traversal are given the same search radius. This is compatible + * with a traversal-per-type-per-type scheme. It was found that passing this radius + * as a constant to the traversal program decreased register pressure in the kernel + * from a traversal-per-type scheme. + * + * The particles are traversed using a \a map. Ghost particles can be included + * in this map, and they will be neglected during traversal. + */ +template +struct ParticleQueryOp + { + //! Constructor + /*! + * \param positions_ Particle positions. + * \param bodies_ Particle body tags. + * \param diams_ Particle diameters. + * \param map_ Map of the particle indexes to traverse. + * \param N_ Number of particles (total). + * \param Nown_ Number of locally owned particles. + * \param rcut_ Cutoff radius for the spheres. + * \param rlist_ Total search radius for the spheres (differs under shifting). + */ + ParticleQueryOp(const Scalar4 *positions_, + const unsigned int *bodies_, + const Scalar *diams_, + const unsigned int* map_, + unsigned int N_, + unsigned int Nown_, + const Scalar rcut_, + const Scalar rlist_, + const BoxDim& box_) + : positions(positions_), bodies(bodies_), diams(diams_), map(map_), + N(N_), Nown(Nown_), rcut(rcut_), rlist(rlist_), box(box_) + {} + + #ifdef NVCC + //! Data stored per thread for traversal + /*! + * The body tag and diameter are only actually set if these are specified + * by the template parameters. The compiler might be able to optimize them + * out if they are unused. + */ + struct ThreadData + { + HOSTDEVICE ThreadData(Scalar3 position_, + int idx_, + unsigned int body_, + Scalar diam_) + : position(position_), idx(idx_), body(body_), diam(diam_) + {} + + Scalar3 position; //!< Particle position + int idx; //!< True particle index + unsigned int body; //!< Particle body tag (may be invalid) + Scalar diam; //!< Particle diameter (may be invalid) + }; + + // specify that the traversal Volume is a bounding sphere + typedef neighbor::BoundingSphere Volume; + + //! Loads the per-thread data + /*! + * \param idx Nominal primitive index. + * \returns The ThreadData required for traversal. + * + * The ThreadData is loaded subject to a mapping. The particle position + * is always loaded. The body and diameter are only loaded if the template + * parameter requires it. + */ + DEVICE ThreadData setup(const unsigned int idx) const + { + const unsigned int pidx = map[idx]; + + const Scalar4 position = positions[pidx]; + const Scalar3 r = make_scalar3(position.x, position.y, position.z); + // printf("in ParticleQueryOp setup %d %d %f %f %f\n",idx,pidx,position.x, position.y, position.z); + unsigned int body(0xffffffff); + if (use_body) + { + body = __ldg(bodies + pidx); + } + Scalar diam(1.0); + if (use_diam) + { + diam = __ldg(diams + pidx); + } + + return ThreadData(r, pidx, body, diam); + } + + //! Return the traversal volume subject to a translation + /*! + * \param q The current thread data. + * \param image The image vector for traversal. + * \returns The traversal bounding volume. + * + * The ThreadData is converted to a search volume. The search sphere is + * made to be skipped if this is a ghost particle. + */ + DEVICE Volume get(const ThreadData& q, const Scalar3& image) const + { + return Volume(q.position+image, (q.idx < Nown) ? rlist : -1.0); + } + + //! Perform the overlap test with the LBVH + /*! + * \param v Traversal volume. + * \param box Box in LBVH to intersect with. + * \returns True if the volume and box overlap. + * + * The overlap test is implemented by the sphere. + */ + DEVICE bool overlap(const Volume& v, const neighbor::BoundingBox& box) const + { + return v.overlap(box); + } + + //! Refine the rough overlap test with a primitive + /*! + * \param q The current thread data. + * \param primitive Index of the intersected primitive. + * \returns True If the volumes still overlap after refinement. + * + * HOOMD's neighbor lists require additional filtering. This first ensures + * that the overlap is not with itself. If body filtering is enabled, + * particles in the same body do not overlap. If diameter shifting is + * enabled, the cutoff radius is adjusted based on the diameters of the + * particles. + */ + DEVICE bool refine(const ThreadData& q, const int primitive) const + { + bool exclude = (q.idx == primitive); + + // body exclusion + if (use_body && !exclude && q.body != 0xffffffff) + { + const unsigned int body = __ldg(bodies + primitive); + exclude |= (q.body == body); + } + + // diameter exclusion + if (use_diam && !exclude) + { + const Scalar4 position = positions[primitive]; + const Scalar3 r = make_scalar3(position.x, position.y, position.z); + const Scalar diam = diams[primitive]; + + // compute factor to add to base rc + const Scalar delta = (q.diam + diam) * Scalar(0.5) - Scalar(1.0); + Scalar rc2 = (rcut+delta); + rc2 *= rc2; + + // compute distance and wrap back into box + const Scalar3 dr = box.minImage(r - q.position); + const Scalar drsq = dot(dr,dr); + + // exclude if outside the sphere + exclude |= drsq > rc2; + } + + return !exclude; + } + #endif + + //! Get the number of primitives + HOSTDEVICE unsigned int size() const + { + return N; + } + + const Scalar4 *positions; //!< Particle positions + const unsigned int *bodies; //!< Particle bodies + const Scalar *diams; //!< Particle diameters + const unsigned int *map; //!< Mapping of particles to read + unsigned int N; //!< Total number of particles in map + unsigned int Nown; //!< Number of particles owned by the local rank + Scalar rcut; //!< True cutoff radius + buffer + Scalar rlist; //!< Maximum cutoff (may include shifting) + const BoxDim box; //!< Box dimensions + }; + +//! Operation to write the neighbor list +/*! + * The neighbor list is assumed to be aligned to multiples of 4. This enables + * coalescing writes into packets of 4 neighbors without adding much register pressure. + * This object maintains an internal stack to do this, and it can restart from a previous + * traversal without losing information. + */ +struct NeighborListOp + { + //! Constructor + /*! + * \param neigh_list_ Neighbor list (aligned to multiple of 4) + * \param nneigh_ Neighbor of neighbors per particle + * \param new_max_neigh_ Maximum number of neighbors to allocate if overflow occurs. + * \param first_neigh_ First index for the current particle index in the neighbor list. + * \param max_neigh_ Maximum number of neighbors to allow per particle. + * + * The \a neigh_list_ pointer is internally cast into a uint4 for coalescing. + */ + NeighborListOp(unsigned int* neigh_list_, + unsigned int* nneigh_, + unsigned int* new_max_neigh_, + const unsigned int* first_neigh_, + unsigned int max_neigh_) + : nneigh(nneigh_), new_max_neigh(new_max_neigh_), + first_neigh(first_neigh_), max_neigh(max_neigh_) + { + neigh_list = reinterpret_cast(neigh_list_); + } + + #ifdef NVCC + //! Thread-local data + /*! + * The thread-local data constitutes a stack of neighbors to write, the index of the current + * primitive, the first index to write into, and the current number of neighbors found for this thread. + */ + struct ThreadData + { + //! Constructor + /*! + * \param idx_ The index of this particle. + * \param first_ The first neighbor index of this particle. + * \param num_neigh_ The current number of neighbors of this particle. + * \param stack_ The initial values for the stack (can be all 0s if \a num_neigh_ is aligned to 4). + */ + DEVICE ThreadData(const unsigned int idx_, + const unsigned int first_, + const unsigned int num_neigh_, + const uint4 stack_) + : idx(idx_), first(first_), num_neigh(num_neigh_) + { + stack[0] = stack_.x; + stack[1] = stack_.y; + stack[2] = stack_.z; + stack[3] = stack_.w; + } + + unsigned int idx; //!< Index of primitive + unsigned int first; //!< First index to use for writing neighbors + unsigned int num_neigh; //!< Number of neighbors for this thread + unsigned int stack[4]; //!< Internal stack of neighbors + }; + + //! Setup the thread data + /*! + * \param idx Index of this thread. + * \param q Thread-local query data. + * \returns The ThreadData for output. + * + * \tparam Type of QueryData. + * + * This setup function can poach data from the query data in order to save loads. + * In this case, it makes use of the particle index mapping. + */ + template + DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const + { + const unsigned int first = __ldg(first_neigh + q.idx); + //printf("in NeighborListOp setup %d %d %d %d \n",first_neigh,first,q.idx,nneigh[q.idx]); + const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable + + // prefetch from the stack if current number of neighbors does not align with a boundary + /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if + statement, initialized with zeros, and then assigned inside (so that only + one return statement was needed). It went away using: + uint4 tmp = neigh_list[...]; + stack = tmp; + But this looked funny, so the structure below seems more human readable. + */ + if (num_neigh % 4 != 0) + { + uint4 stack = neigh_list[(first+num_neigh-1)/4]; + return ThreadData(q.idx, first, num_neigh, stack); + } + else + { + return ThreadData(q.idx, first, num_neigh, make_uint4(0,0,0,0)); + } + } + + //! Processes a newly intersected primitive. + /*! + * \param t My output thread data. + * \param primitive The index of the primitive to process. + * + * If the neighbor will fit into the allocated memory, it is pushed onto the stack. + * The stack is written to memory if it is full. The number of neighbors found for this + * thread is incremented, regardless. + */ + DEVICE void process(ThreadData& t, const int primitive) const + { + if (t.num_neigh < max_neigh) + { + // push primitive into the stack of 4, pre-increment + const unsigned int offset = t.num_neigh % 4; + t.stack[offset] = primitive; + // coalesce writes into chunks of 4 + if (offset == 3) + { + neigh_list[(t.first+t.num_neigh)/4] = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); + } + } + ++t.num_neigh; + } + + //! Finish the output job once the thread is ready to terminate. + /*! + * \param t My output thread data + * + * The number of neighbors found for this thread is written. If this value + * exceeds the current allocation, this value is atomically maximized for + * reallocation. Any values remaining on the stack are written to ensure the + * list is complete. + */ + DEVICE void finalize(const ThreadData& t) const + { + nneigh[t.idx] = t.num_neigh; + // printf("max %d %d %d\n",new_max_neigh,max_neigh,t.num_neigh); + if (t.num_neigh > max_neigh) + { + atomicMax(new_max_neigh, t.num_neigh); + printf("max overwrite %d %d %d\n",new_max_neigh,max_neigh, t.num_neigh); + } + else if (t.num_neigh % 4 != 0) + { + // write partial (leftover) stack, counting is now post-increment so need to shift by 1 + // only need to do this if didn't overflow, since all neighbors were already written due to alignment of max + neigh_list[(t.first+t.num_neigh-1)/4] = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); + } + } + #endif + + uint4* neigh_list; //!< Neighbors of each sphere + unsigned int* nneigh; //!< Number of neighbors per search sphere + unsigned int* new_max_neigh; //!< New maximum number of neighbors + const unsigned int* first_neigh; //!< Index of first neighbor + unsigned int max_neigh; //!< Maximum number of neighbors allocated + }; + +//! Sentinel for an invalid particle (e.g., ghost) +const unsigned int NeighborListTypeSentinel = 0xffffffff; + +//! Kernel driver to generate morton code-type keys for particles and reorder by type +cudaError_t gpu_nlist_mark_types(unsigned int *d_types, + unsigned int *d_indexes, + unsigned int *d_lbvh_errors, + Scalar4 *d_last_pos, + const Scalar4 *d_pos, + const unsigned int N, + const unsigned int nghosts, + const BoxDim& box, + const Scalar3 ghost_width, + const unsigned int block_size); + +//! Kernel driver to sort particles by type +uchar2 gpu_nlist_sort_types(void *d_tmp, + size_t &tmp_bytes, + unsigned int *d_types, + unsigned int *d_sorted_types, + unsigned int *d_indexes, + unsigned int *d_sorted_indexes, + const unsigned int N, + const unsigned int num_bits); + +//! Kernel driver to count particles by type +cudaError_t gpu_nlist_count_types(unsigned int *d_first, + unsigned int *d_last, + const unsigned int *d_types, + const unsigned int ntypes, + const unsigned int N, + const unsigned int block_size); + +//! Kernel driver to rearrange primitives for faster traversal +cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, + const unsigned int *d_indexes, + const unsigned int *d_primitives, + const unsigned int N, + const unsigned int block_size); + +} // end namespace gpu +} // end namespace azplugins + +#undef DEVICE +#undef HOSTDEVICE + #endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 4fc2c00e..a1130b2b 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -16,8 +16,12 @@ #endif #include "DynamicBondUpdater.h" +#include "DynamicBondUpdaterGPU.cuh" #include "hoomd/Autotuner.h" +#include "hoomd/extern/neighbor/neighbor/LBVH.h" +#include "hoomd/extern/neighbor/neighbor/LBVHTraverser.h" + namespace azplugins { @@ -45,11 +49,43 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater // virtual void update(unsigned int timestep); protected: - + virtual void findAllPossibleBonds(); virtual void filterPossibleBonds(); + virtual void updateImageVectors(); private: - std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter + GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles + neighbor::LBVH m_lbvh; //!< LBVH for group_1 + neighbor::LBVHTraverser m_traverser; //!< LBVH traverser for group_2 + + std::unique_ptr m_copy_tuner; //!< Tuner for the primitive-copy kernel + std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter + + //cudaStream_t m_stream; //!< CUDA stream for tree building + + GPUArray m_traverse_order; //!< Order to traverse primitives + GlobalVector m_image_list; //!< List of translation vectors for traversal + unsigned int m_n_images; //!< Number of translation vectors for traversal + + //! Compute the LBVH domain from the current box + BoxDim getLBVHBox() const + { + const BoxDim& box = m_pdata->getBox(); + + // ghost layer padding + Scalar ghost_layer_width(0.0); + #ifdef ENABLE_MPI + if (m_comm) ghost_layer_width = m_comm->getGhostLayerMaxWidth(); + #endif + + Scalar3 ghost_width = make_scalar3(0.0, 0.0, 0.0); + if (!box.getPeriodic().x) ghost_width.x = ghost_layer_width; + if (!box.getPeriodic().y) ghost_width.y = ghost_layer_width; + if (!box.getPeriodic().z && m_sysdef->getNDimensions() == 3) ghost_width.z = ghost_layer_width; + + return BoxDim(box.getLo()-ghost_width, box.getHi()+ghost_width, box.getPeriodic()); + } + }; diff --git a/azplugins/update.py b/azplugins/update.py index 64b477c6..dc7ef0c8 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -179,6 +179,8 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,peri # how to do handling of exclusions in the neighbor list correctly? + # todo: check that r_cut is not too large for pbc box + def set_params(self, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): # todo - cpp class right now doesn't have any set/get functions From 9557c5fc4de6d7aa8be8647868167fab447ffdf0 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Tue, 4 Aug 2020 12:38:55 -0500 Subject: [PATCH 08/45] Working version, need to check for group=all --- azplugins/DynamicBondUpdater.cc | 301 ++++++++++++++-------------- azplugins/DynamicBondUpdater.h | 36 ++-- azplugins/DynamicBondUpdaterGPU.cc | 280 +++++++++++++++++--------- azplugins/DynamicBondUpdaterGPU.cu | 198 +++++++++++++++--- azplugins/DynamicBondUpdaterGPU.cuh | 135 +++---------- azplugins/DynamicBondUpdaterGPU.h | 46 +++-- azplugins/update.py | 36 ++-- 7 files changed, 603 insertions(+), 429 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index fea842da..961d21bc 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -15,35 +15,36 @@ namespace azplugins { DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : Updater(sysdef), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut), + : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut), m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); - // m_pdata->getNumTypesChangeSignal().connect(this); m_bond_data = m_sysdef->getBondData(); - // allocate initial Memory - grows if necessary - m_max_bonds = (max_bonds_group_1 < max_bonds_group_2) ? max_bonds_group_2 : max_bonds_group_1; + m_max_bonds = 4; m_max_bonds_overflow = 0; - GPUArray all_possible_bonds(m_group_2->getNumMembers()*m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); m_num_all_possible_bonds=0; - //todo: reset all aabb componentes if group sizes changes? + // allocate initial Memory - grows if necessary + GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + + // todo: reset if group sizes changes? // if groups change during the simulation this updater might just not work properly - groups don't have a change signal? - m_aabbs.resize(m_group_1->getNumMembers()); + // can getNumTypesChangeSignal() be used as a proxy? + m_aabbs.resize(m_group_2->getNumMembers()); checkSystemSetup(); @@ -63,12 +64,14 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(unsigned int timestep) { + if (m_prof) m_prof->push("DynamicBondUpdater"); + // don't do anything if either one of the groups is empty const unsigned int group_size_1 = m_group_1->getNumMembers(); const unsigned int group_size_2 = m_group_2->getNumMembers(); - if(group_size_1 == 0 || group_size_2 == 0) + if (group_size_1 == 0 || group_size_2 == 0) return; - std::cout<<"in DynamicBondUpdater::update "<pop(); } -//todo: should go into helper class/separate file +//todo: should go into helper class/separate file? bool SortBonds(Scalar3 i, Scalar3 j) { const Scalar r_sq_1 = i.z; @@ -114,7 +115,7 @@ bool SortBonds(Scalar3 i, Scalar3 j) return r_sq_1 < r_sq_2; } -//todo: migrate to separate file/class. faster way without branching? +//todo: migrate to separate file/class? bool CompareBonds(Scalar3 i, Scalar3 j) { @@ -150,6 +151,7 @@ bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) void DynamicBondUpdater::calculateExistingBonds() { + // reset exisitng bond list ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); @@ -158,7 +160,6 @@ void DynamicBondUpdater::calculateExistingBonds() memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()*m_existing_bonds_list_indexer.getH()); ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); -// ArrayHandle h_typeval(m_bond_data->getTypeValArray(), access_location::host, access_mode::read); // for each of the bonds const unsigned int size = (unsigned int)m_bond_data->getN(); @@ -168,14 +169,14 @@ void DynamicBondUpdater::calculateExistingBonds() const typename BondData::members_t& bond = h_bonds.data[i]; unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; - // unsigned int type = h_typeval.data[i].type; - //only keep track of the bond type we are forming - does this make sense? + // only keep track of the bond type we are forming - does this make sense? // if (type == m_bond_type) // { AddtoExistingBonds(tag1,tag2); // } } + } /*! \param tag1 First particle tag in the pair @@ -196,14 +197,20 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) return false; } + void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) { - assert(tag1 <= m_pdata->getMaximumTag()); assert(tag2 <= m_pdata->getMaximumTag()); // don't add a bond twice - should not happen anyway - todo: might be able to avoid this check - if (isExistingBond(tag1, tag2)) return; + /* + if (isExistingBond(tag1, tag2)) + { + m_exec_conf->msg->warning() << "tried to add existing bond twice! "<< tag1 << " "<< tag2 << std::endl; + return; + } + */ bool overflowed = false; @@ -232,26 +239,27 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) assert(pos2 < m_existing_bonds_list_indexer.getH()); h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos2)] = tag1; h_n_existing_bonds.data[tag2]++; - } //todo: should the list be grown more than 1 at a time for efficiency? void DynamicBondUpdater::resizeExistingBondList() { unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; - m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); // update the indexer m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; + } -//todo: should the list be grown more than 1 at a time for efficiency? + void DynamicBondUpdater::resizePossibleBondlists() { - m_max_bonds=m_max_bonds_overflow; - m_max_bonds_overflow=0; - unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + // round up to nearest multiple of 4 + m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; + m_max_bonds = m_max_bonds_overflow; + m_max_bonds_overflow = 0; + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -274,114 +282,125 @@ void DynamicBondUpdater::allocateParticleArrays() memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - calculateExistingBonds(); + } // this is based on the NeighborListTree c++ implementation -void DynamicBondUpdater::findAllPossibleBonds() +void DynamicBondUpdater::buildTree() { - std::cout<< "in DynamicBondUpdater::findAllPossibleBonds"<push("buildTree"); + //todo: is it worth it to check if rebuild is necessary similar to neighbor list? + // make tree for group 2 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); - unsigned int group_size_1 = m_group_1->getNumMembers(); - for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) + unsigned int group_size_2 = m_group_2->getNumMembers(); + + for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) { - unsigned int i = m_group_1->getMemberIndex(group_idx); + unsigned int i = m_group_2->getMemberIndex(group_idx); // make a point particle AABB vec3 my_pos(h_postype.data[i]); h_aabbs.data[group_idx] = hpmc::detail::AABB(my_pos,i); } - m_aabb_tree.buildTree(&(h_aabbs.data[0]) , group_size_1); - - // reset content of possible bond list - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - - // traverse the tree - // Loop over all particles in group 2 - unsigned int group_size_2 = m_group_2->getNumMembers(); - for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) - { - unsigned int i = m_group_2->getMemberIndex(group_idx); - const unsigned int tag_i = h_tag.data[i]; - const Scalar4 postype_i = h_postype.data[i]; - const vec3 pos_i = vec3(postype_i); - - unsigned int n_curr_bond = 0; - - for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector - { - // make an AABB for the image of this particle - vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, m_r_cut); - hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; - // stackless traversal of the tree - for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) - { - if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) - { - if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) - { - for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) - { - // neighbor j - unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); - - // skip self-interaction always - bool excluded = (i == j); - //todo: bonds which already exist should be not put in the array in the first place. - // that could save us from needing to filter out the exclusions later? but why is the - // neighbor list not doing that? to take advantage of the same structure for all the neighbor lists? - if (!excluded) - { - // compute distance - Scalar4 postype_j = h_postype.data[j]; - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - vec_to_scalar3(pos_i_image); - Scalar dr_sq = dot(drij,drij); - const Scalar r_cutsq = m_r_cut*m_r_cut; - if (dr_sq <= r_cutsq) - { - if (n_curr_bond < m_max_bonds) - { - const unsigned int tag_j = h_tag.data[j]; - Scalar3 d ; - d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); - h_all_possible_bonds.data[group_idx + n_curr_bond] = d; - } - else // trigger resize current possible bonds > m_max_bonds - { - m_max_bonds_overflow = n_curr_bond; - } - ++n_curr_bond; - - } - } - } - } - } - else - { - // skip ahead - cur_node_idx += cur_aabb_tree->getNodeSkip(cur_node_idx); - } - } // end stackless search - } // end loop over images - } // end loop over group 2 + m_aabb_tree.buildTree(&(h_aabbs.data[0]) , group_size_2); + if (m_prof) m_prof->pop(); } + + // this is based on the NeighborListTree c++ implementation + void DynamicBondUpdater::traverseTree() + { + if (m_prof) m_prof->push("traverseTree"); + // reset content of possible bond list + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + const Scalar r_cutsq = m_r_cut*m_r_cut; + + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + + // traverse the tree + // Loop over all particles in group 1 + unsigned int group_size_1 = m_group_1->getNumMembers(); + for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + const Scalar4 postype_i = h_postype.data[i]; + const vec3 pos_i = vec3(postype_i); + + unsigned int n_curr_bond = 0; + + for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector + { + // make an AABB for the image of this particle + vec3 pos_i_image = pos_i + m_image_list[cur_image]; + hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, m_r_cut); + hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; + // stackless traversal of the tree + for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) + { + if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) + { + if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) + { + for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) + { + // neighbor j + unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); + + // skip self-interaction always + bool excluded = (i == j); + //todo: bonds which already exist should be not put in the array in the first place. + // that could save us from needing to filter out the exclusions later? but why is the + // neighbor list not doing that? to take advantage of the same structure for all the neighbor lists? + if (!excluded) + { + // compute distance + Scalar4 postype_j = h_postype.data[j]; + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) + - vec_to_scalar3(pos_i_image); + Scalar dr_sq = dot(drij,drij); + + if (dr_sq <= r_cutsq) + { + if (n_curr_bond < m_max_bonds) + { + const unsigned int tag_j = h_tag.data[j]; + Scalar3 d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); + h_all_possible_bonds.data[group_idx + n_curr_bond] = d; + } + else // trigger resize current possible bonds > m_max_bonds + { + m_max_bonds_overflow = n_curr_bond; + } + ++n_curr_bond; + + } + } + } + } + } + else + { + // skip ahead + cur_node_idx += cur_aabb_tree->getNodeSkip(cur_node_idx); + } + } // end stackless search + } // end loop over images + } // end loop over group 2 + if (m_prof) m_prof->pop(); + } + void DynamicBondUpdater::filterPossibleBonds() { - std::cout<< "in DynamicBondUpdater::filterPossibleBonds()"<< std::endl; + if (m_prof) m_prof->push("filterPossibleBonds"); ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; // first sort whole array by distance between particles in that particular possible bond std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds); @@ -400,6 +419,7 @@ m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. + if (m_prof) m_prof->pop(); } @@ -412,6 +432,7 @@ m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); */ void DynamicBondUpdater::updateImageVectors() { + const BoxDim& box = m_pdata->getBox(); uchar3 periodic = box.getPeriodic(); unsigned char sys3d = (this->m_sysdef->getNDimensions() == 3); @@ -484,61 +505,45 @@ if (m_bond_type >= m_bond_data -> getNTypes()) } +//todo: this function doesn't have a corresponding GPU implementation - what would make sense for this? void DynamicBondUpdater::makeBonds() { + if (m_prof) m_prof->push("makeBonds"); // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - - GPUArray total_counts(m_pdata->getRTags().size(), m_exec_conf); - ArrayHandle h_total_counts(total_counts, access_location::host, access_mode::readwrite); - memset((void*)h_total_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); - - // Loop over all possible bonds and count how many times a tag is in h_all_possible_bonds array - // save how many bonds to form (max bonds - existing bonds) in h_total_counts - for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) - { - Scalar3 d = h_all_possible_bonds.data[i]; - const int tag_i = __scalar_as_int(d.x); - const int tag_j = __scalar_as_int(d.y); - h_total_counts.data[tag_i]=m_max_bonds_group_1 - h_n_existing_bonds.data[tag_i]; - h_total_counts.data[tag_j]=m_max_bonds_group_2 - h_n_existing_bonds.data[tag_j]; - } GPUArray current_counts(m_pdata->getRTags().size(), m_exec_conf); ArrayHandle h_current_counts(current_counts, access_location::host, access_mode::readwrite); memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + bool exclusions = m_nlist->getExclusionsSet(); +// std::cout<< "add bond "; //todo: can this for loop be simplified/parallelized? - bool added_bonds = false; for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) { Scalar3 d = h_all_possible_bonds.data[i]; + unsigned int tag_i = __scalar_as_int(d.x); unsigned int tag_j = __scalar_as_int(d.y); + // std::cout<< i <<" / "<< m_num_all_possible_bonds << " ("<< tag_i << " , "<< tag_j<< ") "; //todo: put in other external criteria here, e.g. probability of bond formation etc. //todo: randomize which bonds are formed or keep them ordered by their distances? - if (h_current_counts.data[tag_i]addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); h_current_counts.data[tag_i]++; h_current_counts.data[tag_j]++; - added_bonds = true; + + if (exclusions) m_nlist->addExclusion(tag_i,tag_j); } } - - if (added_bonds) m_pdata->notifyParticleSort(); - - //todo: how to add this? m_nlist as parameter? also needs to know if exclusions are set in nlist. - //notify neighbor lists -// if (m_exclude_from_nlist) -// m_nlist->addExclusion(p_from_idx,p_to_idx); - + // std::cout<pop(); } @@ -553,7 +558,7 @@ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init, std::shared_ptr, + .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); //todo: implement needed getter/setter functions diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index aeed0054..963f9761 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -1,7 +1,7 @@ // Copyright (c) 2018-2020, Michael P. Howard // This file is part of the azplugins project, released under the Modified BSD License. -// Maintainer: mphoward +// Maintainer: astatt /*! * \file DynamicBondUpdater.h @@ -30,39 +30,40 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2); + std::shared_ptr nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2); //! Destructor virtual ~DynamicBondUpdater(); - //! update - find and make new bonds + //! update virtual void update(unsigned int timestep); protected: - //std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding + std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding std::shared_ptr m_bond_data; //!< Bond data std::shared_ptr m_group_1; //!< First particle group to form bonds with std::shared_ptr m_group_2; //!< Second particle group to form bonds with const Scalar m_r_cut; //!< cutoff for the bond forming criterion - unsigned int m_bond_type; //!< Type id of the bond to form - unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group - unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group + const unsigned int m_bond_type; //!< Type id of the bond to form + const unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group + const unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group unsigned int m_max_bonds; //!< maximum number of possible bonds which can be found unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds - GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*n_max_bonds + GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*m_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_2 GPUVector m_aabbs; //!< Flat array of AABBs of all types std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal unsigned int m_n_images; //!< The number of image vectors to check @@ -76,16 +77,17 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); - virtual void findAllPossibleBonds(); + virtual void buildTree(); + virtual void traverseTree(); void makeBonds(); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag virtual void updateImageVectors(); void checkSystemSetup(); - void resizePossibleBondlists(); + virtual void resizePossibleBondlists(); void resizeExistingBondList(); - void allocateParticleArrays(); + virtual void allocateParticleArrays(); //! Notification of a box size change void slotBoxChanged() diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 7f943821..a9c337dd 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -15,144 +15,219 @@ namespace azplugins { DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : DynamicBondUpdater(sysdef, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2), - m_num_nonzero_bonds(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) + : DynamicBondUpdater(sysdef, nlist, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2), + m_num_nonzero_bonds(m_exec_conf),m_max_bonds_overflow_flag(m_exec_conf),m_lbvh_errors(m_exec_conf), + m_lbvh_2(m_exec_conf),m_traverser(m_exec_conf) { - m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); + m_sorted_index_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_sorted_index", m_exec_conf)); m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_tree_copy", m_exec_conf)); + m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_nlist_copy", m_exec_conf)); + m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); + m_count_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_count", m_exec_conf)); + // allocate initial Memory - grows if necessary + GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + + checkSystemSetup(); } DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { + m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdaterGPU" << std::endl; } -// this is based on the LBVH NeighborListGPUTree implementation -void DynamicBondUpdaterGPU::findAllPossibleBonds() + +void DynamicBondUpdaterGPU::buildTree() { - std::cout<< "in DynamicBondUpdaterGPU::findAllPossibleBonds"<push("buildTree"); + // set the sorted index + { + // we already know the indexes of the particles in the two groups, we can simply copy them into the m_sorted_indexes array + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::overwrite); + ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); + + m_sorted_index_tuner->begin(); + azplugins::gpu::make_sorted_index_array( + d_sorted_indexes.data, + d_index_group_1.data, + d_index_group_2.data, + m_group_1->getNumMembers(), + m_group_2->getNumMembers(), + m_count_tuner->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_sorted_index_tuner->end(); - const BoxDim& box = m_pdata->getBox(); + } - ArrayHandle d_postype(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_group_1_indexes(m_group_1->getIndexArray(), access_location::device, access_mode::read); + // build a lbvh for grou_2 + { - unsigned int group_size_1 = m_group_1->getNumMembers(); - const BoxDim lbvh_box = getLBVHBox(); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - // build a lbvh for group_1 - m_lbvh.build(gpu::PointMapInsertOp(d_postype.data, d_group_1_indexes.data, group_size_1), - lbvh_box.getLo(), - lbvh_box.getHi()); + const BoxDim lbvh_box = getLBVHBox(); - //todo: need to use the neighbor list data structure, unclear how to restructure the internal cast into uint4 otherwise? - GPUArray n_bonds(m_group_2->getNumMembers(), m_exec_conf); - ArrayHandle h_n_bonds(n_bonds, access_location::host, access_mode::overwrite); - memset((void*)h_n_bonds.data, 0, sizeof(unsigned int)*m_group_2->getNumMembers()); + // this tree is traversed in traverseTree() + m_lbvh_2.build(azplugins::gpu::PointMapInsertOp(d_pos.data, d_sorted_indexes.data + m_group_1->getNumMembers(), m_group_2->getNumMembers()), + lbvh_box.getLo(), + lbvh_box.getHi()); - ArrayHandle d_n_bonds(n_bonds, access_location::device, access_mode::readwrite); + } - unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - GPUArray all_possible_bonds_int(size, m_exec_conf); - ArrayHandle h_all_possible_bonds_int(all_possible_bonds_int, access_location::host, access_mode::overwrite); - memset((void*)h_all_possible_bonds_int.data, 0, sizeof(unsigned int)*size); - ArrayHandle d_all_possible_bonds_int(all_possible_bonds_int, access_location::device, access_mode::readwrite); - GPUArray head_list(m_group_2->getNumMembers(), m_exec_conf); - ArrayHandle h_head_list(head_list, access_location::host, access_mode::overwrite); - unsigned int headAddress = 0; - for (unsigned int i=0; i < m_group_2->getNumMembers(); ++i) - { - h_head_list.data[i] = headAddress; - headAddress+=m_max_bonds; - } + if (m_prof) m_prof->pop(); + } - ArrayHandle d_head_list(head_list, access_location::device, access_mode::readwrite); - // neighbor list write op - - gpu::NeighborListOp nlist_op(d_all_possible_bonds_int.data, d_n_bonds.data,&m_max_bonds_overflow, d_head_list.data, m_max_bonds); - - /* NeighborListOp(unsigned int* neigh_list_, - unsigned int* nneigh_, - unsigned int* new_max_neigh_, - const unsigned int* first_neigh_, - unsigned int max_neigh_) - */ - - ArrayHandle d_group_2_indexes(m_group_2->getIndexArray(), access_location::device, access_mode::read); - - gpu::ParticleQueryOp query_op(d_postype.data, - NULL, - NULL, - d_group_2_indexes.data, - m_group_2->getNumMembers(), - m_group_1->getNumMembers(), - m_r_cut, - m_r_cut+0.4, - box); - - std::cout<< "m_max_bonds_overflow before "<< m_max_bonds_overflow << " m_max_bonds "<< m_max_bonds <getNumMembers()<getNumMembers()<getN()< h_group_2_indexes(m_group_2->getIndexArray(), access_location::host, access_mode::read); - std::cout<< "group 2 index "; - for (unsigned int i=0; i < m_group_2->getNumMembers(); ++i) - { - std::cout<< " "<< h_group_2_indexes.data[i] ; - } - std::cout<< std::endl; - m_traverser.traverse(nlist_op, query_op, m_lbvh, m_image_list); +void DynamicBondUpdaterGPU::traverseTree() + { + if (m_prof) m_prof->push("traverseTree"); + ArrayHandle d_nlist(m_nlist, access_location::device, access_mode::overwrite); + ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - std::cout<< "m_max_bonds_overflow after "<< m_max_bonds_overflow << " m_max_bonds "<< m_max_bonds <getMaxN()); - /* - ArrayHandle h_all_possible_bonds_int(all_possible_bonds_int, access_location::host, access_mode::read); - ArrayHandle h_n_bonds(n_bonds, access_location::host, access_mode::read); + const BoxDim& box = m_pdata->getBox(); - for (unsigned int i = 0; i < m_group_2->getNumMembers(); i++) - { - std::cout<<" d_n_bonds "<< i << " "<< h_n_bonds.data[i] << " all_possible_bonds " ; - for (unsigned int j = 0; j < m_max_bonds; j++) - { - std::cout<< " "<< h_all_possible_bonds_int.data[i*m_max_bonds+j] << " "; - } - std::cout <getNumMembers()*m_max_bonds<getNumMembers()); + m_traverser.setup(map, m_lbvh_2); + + // todo: use sorted indexes as traverse order? Is that ok? + azplugins::gpu::ParticleQueryOp query_op(d_pos.data, + d_sorted_indexes.data + 0, + m_group_1->getNumMembers(), + m_pdata->getN(), + m_r_cut, + box); + + m_traverser.traverse(nlist_op, query_op, map, m_lbvh_2, m_image_list); + + m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); + + // copy information from nlist to all_possible_bonds array, do distance checking + { + + ArrayHandle d_nlist(m_nlist, access_location::device, access_mode::read); + ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); + + // size m_group_1->getNumMembers()*m_max_bonds; + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::overwrite); + + + const BoxDim& box = m_pdata->getBox(); + + m_copy_nlist_tuner->begin(); + azplugins::gpu::nlist_copy_nlist_possible_bonds(d_all_possible_bonds.data, + d_pos.data, + d_tag.data, + d_sorted_indexes.data, + d_n_neigh.data, + d_nlist.data, + box, + m_max_bonds, + m_r_cut, + m_group_1->getNumMembers(), + m_copy_tuner->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_copy_nlist_tuner->end(); + + } + if (m_prof) m_prof->pop(); + } + + +void DynamicBondUpdaterGPU::resizePossibleBondlists() + { + + // round up to nearest multiple of 4 + m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; - // reset content of possible bond list + m_max_bonds = m_max_bonds_overflow; + m_max_bonds_overflow = 0; + m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + m_all_possible_bonds.resize(size); + m_num_all_possible_bonds=0; + GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + m_nlist.swap(nlist); - // unsigned int size = m_group_2->getNumMembers()*m_max_bonds; - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - m_num_all_possible_bonds=0; } +void DynamicBondUpdaterGPU::allocateParticleArrays() + { + + + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + + GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + + memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + + GPUArray sorted_indexes(m_pdata->getMaxN(), m_exec_conf); + m_sorted_indexes.swap(sorted_indexes); + + // allocate m_last_pos + GlobalArray last_pos(m_pdata->getMaxN(), m_exec_conf); + m_last_pos.swap(last_pos); + + // allocate the number of neighbors (per particle) + GlobalArray n_neigh(m_pdata->getMaxN(), m_exec_conf); + m_n_neigh.swap(n_neigh); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); + + // default allocation of m_max_bonds neighbors per particle for the neighborlist + GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + m_nlist.swap(nlist); + + + + calculateExistingBonds(); + + } + + + void DynamicBondUpdaterGPU::filterPossibleBonds() { - std::cout<< "in DynamicBondUpdaterGPU::filterPossibleBonds()"<< std::endl; + if (m_prof) m_prof->push("filterPossibleBonds1"); // todo: figure out in which order the thrust calls are the fastest. // is using build in thrust functions the best solution? // suspect: sort - remove zeros - unique - filter (which introduces zeros) - remove zeros ? - const unsigned int size = m_group_2->getNumMembers()*m_max_bonds; + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; // sort and remove all existing zeros ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); @@ -163,9 +238,10 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() size, m_num_nonzero_bonds.getDeviceFlags()); - + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - + if (m_prof) m_prof->pop(); +if (m_prof) m_prof->push("filterPossibleBonds2"); //filter out the existing bonds - based on neighbor list exclusion handeling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, @@ -174,18 +250,21 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_existing_bonds_list_indexer, m_num_all_possible_bonds, m_tuner_filter_bonds->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner_filter_bonds->end(); - + if (m_prof) m_prof->pop(); +if (m_prof) m_prof->push("filterPossibleBonds3"); // filtering existing bonds out introduced some zeros back into the array, remove them gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, m_num_all_possible_bonds, m_num_nonzero_bonds.getDeviceFlags()); - + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); -// at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] + if (m_prof) m_prof->pop(); +// at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. } @@ -198,7 +277,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() */ void DynamicBondUpdaterGPU::updateImageVectors() { - std::cout<< "in DynamicBondUpdaterGPU::updateImageVectors()"<getBox(); uchar3 periodic = box.getPeriodic(); unsigned char sys3d = (m_sysdef->getNDimensions() == 3); @@ -246,6 +325,7 @@ void DynamicBondUpdaterGPU::updateImageVectors() } } } + } namespace detail @@ -257,7 +337,7 @@ namespace detail { namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init, std::shared_ptr, + .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 02e0cbee..634c4808 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -16,6 +16,7 @@ #include "hoomd/extern/neighbor/neighbor/LBVH.cuh" #include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" +#include "hoomd/extern/cub/cub/cub.cuh" #include @@ -192,9 +193,82 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, d_traverse_order[idx] = __ldg(d_indexes + primitive); } + __global__ void make_sorted_index_array( unsigned int *d_sorted_indexes, + unsigned int *d_indexes_group_1, + unsigned int *d_indexes_group_2, + const unsigned int size_group_1, + const unsigned int size_group_2) + { + // one thread per element in d_sorted_indexes + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + if (idx >= size_group_1+size_group_2) + return; -} //end namespace kernel + if (idx= size) + return; + + // idx = group index , pidx = actual particle index + const unsigned int pidx_i = d_sorted_indexes[idx]; + + // get all information for this particle + Scalar4 postype_i = d_postype[pidx_i]; + const unsigned int tag_i = d_tag[pidx_i]; + const unsigned int n_neigh = d_n_neigh[pidx_i]; + + // loop over all neighbors of this particle + for (unsigned int j=0; j>>(d_sorted_indexes, + d_indexes_group_1, + d_indexes_group_2, + size_group_1, + size_group_2); + + return cudaSuccess; + } + + +/*! + * \param d_traverse_order List of particle indexes in traversal order. + * \param d_indexes Original indexes of the sorted primitives. + * \param d_primitives List of the primitives (sorted in LBVH order). + * \param N Number of primitives. + * \param block_size Number of CUDA threads per block. + * + * \sa gpu_nlist_copy_primitives_kernel + */ +cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, + const unsigned int *d_indexes, + const unsigned int *d_primitives, + const unsigned int N, + const unsigned int block_size) + { + static unsigned int max_block_size = UINT_MAX; + if (max_block_size == UINT_MAX) { - static unsigned int max_block_size = UINT_MAX; - if (max_block_size == UINT_MAX) - { - cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::gpu_nlist_copy_primitives_kernel); - max_block_size = attr.maxThreadsPerBlock; - } - - int run_block_size = min(block_size,max_block_size); - kernel::gpu_nlist_copy_primitives_kernel<<>>(d_traverse_order, - d_indexes, - d_primitives, - N); - return cudaSuccess; + cudaFuncAttributes attr; + cudaFuncGetAttributes(&attr, (const void *)kernel::gpu_nlist_copy_primitives_kernel); + max_block_size = attr.maxThreadsPerBlock; } + int run_block_size = min(block_size,max_block_size); + kernel::gpu_nlist_copy_primitives_kernel<<>>(d_traverse_order, + d_indexes, + d_primitives, + N); + return cudaSuccess; + } + + +cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, + const Scalar4 *d_postype, + const unsigned int *d_tag, + const unsigned int *d_sorted_indexes, + const unsigned int *d_n_neigh, + const unsigned int *d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const unsigned int size, + const unsigned int block_size) + { + static unsigned int max_block_size = UINT_MAX; + if (max_block_size == UINT_MAX) + { + cudaFuncAttributes attr; + cudaFuncGetAttributes(&attr, (const void *)kernel::nlist_copy_nlist_possible_bonds); + max_block_size = attr.maxThreadsPerBlock; + } + + int run_block_size = min(block_size,max_block_size); + kernel::nlist_copy_nlist_possible_bonds<<>>(d_all_possible_bonds, + d_postype, + d_tag, + d_sorted_indexes, + d_n_neigh, + d_nlist, + box, + max_bonds, + r_cut, + size); + return cudaSuccess; + } + } // end namespace gpu } // end namespace azplugins + // explicit templates for neighbor::LBVH with PointMapInsertOp template void neighbor::gpu::lbvh_gen_codes(unsigned int *, unsigned int *, const azplugins::gpu::PointMapInsertOp&, const Scalar3, const Scalar3, const unsigned int, const unsigned int, cudaStream_t); @@ -320,4 +458,4 @@ template void neighbor::gpu::lbvh_bubble_aabbs(const neighbor::gpu::LBVHData, co unsigned int *, const unsigned int, const unsigned int, cudaStream_t); template void neighbor::gpu::lbvh_one_primitive(const neighbor::gpu::LBVHData, const azplugins::gpu::PointMapInsertOp&, cudaStream_t); template void neighbor::gpu::lbvh_traverse_ropes(azplugins::gpu::NeighborListOp&, const neighbor::gpu::LBVHCompressedData&, -const azplugins::gpu::ParticleQueryOp&, const Scalar3 *, unsigned int, unsigned int, cudaStream_t); +const azplugins::gpu::ParticleQueryOp&, const Scalar3 *, unsigned int, unsigned int, cudaStream_t); diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index d9482ec7..6abf76d4 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -30,6 +30,8 @@ #define HOSTDEVICE #endif +//! Sentinel for an invalid particle (e.g., ghost) +const unsigned int NeighborListTypeSentinel = 0xffffffff; namespace azplugins { @@ -54,7 +56,24 @@ cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds); - +cudaError_t make_sorted_index_array(unsigned int *d_sorted_indexes, + unsigned int *d_indexes_group_1, + unsigned int *d_indexes_group_2, + const unsigned int size_group_1, + const unsigned int size_group_2, + const unsigned int block_size); + +cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, + const Scalar4 *d_postype, + const unsigned int * d_tag, + const unsigned int * d_sorted_indexes, + const unsigned int * d_n_neigh, + const unsigned int * d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const unsigned int size, + const unsigned int block_size); //! Insert operation for a point under a mapping. /*! @@ -111,31 +130,24 @@ struct PointMapInsertOp : public neighbor::PointInsertOp * The particles are traversed using a \a map. Ghost particles can be included * in this map, and they will be neglected during traversal. */ -template struct ParticleQueryOp { //! Constructor /*! * \param positions_ Particle positions. - * \param bodies_ Particle body tags. - * \param diams_ Particle diameters. * \param map_ Map of the particle indexes to traverse. * \param N_ Number of particles (total). * \param Nown_ Number of locally owned particles. * \param rcut_ Cutoff radius for the spheres. - * \param rlist_ Total search radius for the spheres (differs under shifting). */ ParticleQueryOp(const Scalar4 *positions_, - const unsigned int *bodies_, - const Scalar *diams_, const unsigned int* map_, unsigned int N_, unsigned int Nown_, const Scalar rcut_, - const Scalar rlist_, const BoxDim& box_) - : positions(positions_), bodies(bodies_), diams(diams_), map(map_), - N(N_), Nown(Nown_), rcut(rcut_), rlist(rlist_), box(box_) + : positions(positions_), map(map_), + N(N_), Nown(Nown_), rcut(rcut_), box(box_) {} #ifdef NVCC @@ -148,16 +160,12 @@ struct ParticleQueryOp struct ThreadData { HOSTDEVICE ThreadData(Scalar3 position_, - int idx_, - unsigned int body_, - Scalar diam_) - : position(position_), idx(idx_), body(body_), diam(diam_) + int idx_) + : position(position_), idx(idx_) {} Scalar3 position; //!< Particle position int idx; //!< True particle index - unsigned int body; //!< Particle body tag (may be invalid) - Scalar diam; //!< Particle diameter (may be invalid) }; // specify that the traversal Volume is a bounding sphere @@ -178,19 +186,8 @@ struct ParticleQueryOp const Scalar4 position = positions[pidx]; const Scalar3 r = make_scalar3(position.x, position.y, position.z); - // printf("in ParticleQueryOp setup %d %d %f %f %f\n",idx,pidx,position.x, position.y, position.z); - unsigned int body(0xffffffff); - if (use_body) - { - body = __ldg(bodies + pidx); - } - Scalar diam(1.0); - if (use_diam) - { - diam = __ldg(diams + pidx); - } - return ThreadData(r, pidx, body, diam); + return ThreadData(r, pidx); } //! Return the traversal volume subject to a translation @@ -204,7 +201,7 @@ struct ParticleQueryOp */ DEVICE Volume get(const ThreadData& q, const Scalar3& image) const { - return Volume(q.position+image, (q.idx < Nown) ? rlist : -1.0); + return Volume(q.position+image, (q.idx < Nown) ? rcut : -1.0); } //! Perform the overlap test with the LBVH @@ -226,43 +223,10 @@ struct ParticleQueryOp * \param primitive Index of the intersected primitive. * \returns True If the volumes still overlap after refinement. * - * HOOMD's neighbor lists require additional filtering. This first ensures - * that the overlap is not with itself. If body filtering is enabled, - * particles in the same body do not overlap. If diameter shifting is - * enabled, the cutoff radius is adjusted based on the diameters of the - * particles. */ DEVICE bool refine(const ThreadData& q, const int primitive) const { bool exclude = (q.idx == primitive); - - // body exclusion - if (use_body && !exclude && q.body != 0xffffffff) - { - const unsigned int body = __ldg(bodies + primitive); - exclude |= (q.body == body); - } - - // diameter exclusion - if (use_diam && !exclude) - { - const Scalar4 position = positions[primitive]; - const Scalar3 r = make_scalar3(position.x, position.y, position.z); - const Scalar diam = diams[primitive]; - - // compute factor to add to base rc - const Scalar delta = (q.diam + diam) * Scalar(0.5) - Scalar(1.0); - Scalar rc2 = (rcut+delta); - rc2 *= rc2; - - // compute distance and wrap back into box - const Scalar3 dr = box.minImage(r - q.position); - const Scalar drsq = dot(dr,dr); - - // exclude if outside the sphere - exclude |= drsq > rc2; - } - return !exclude; } #endif @@ -274,13 +238,10 @@ struct ParticleQueryOp } const Scalar4 *positions; //!< Particle positions - const unsigned int *bodies; //!< Particle bodies - const Scalar *diams; //!< Particle diameters const unsigned int *map; //!< Mapping of particles to read unsigned int N; //!< Total number of particles in map unsigned int Nown; //!< Number of particles owned by the local rank Scalar rcut; //!< True cutoff radius + buffer - Scalar rlist; //!< Maximum cutoff (may include shifting) const BoxDim box; //!< Box dimensions }; @@ -298,7 +259,6 @@ struct NeighborListOp * \param neigh_list_ Neighbor list (aligned to multiple of 4) * \param nneigh_ Neighbor of neighbors per particle * \param new_max_neigh_ Maximum number of neighbors to allocate if overflow occurs. - * \param first_neigh_ First index for the current particle index in the neighbor list. * \param max_neigh_ Maximum number of neighbors to allow per particle. * * The \a neigh_list_ pointer is internally cast into a uint4 for coalescing. @@ -306,10 +266,8 @@ struct NeighborListOp NeighborListOp(unsigned int* neigh_list_, unsigned int* nneigh_, unsigned int* new_max_neigh_, - const unsigned int* first_neigh_, unsigned int max_neigh_) - : nneigh(nneigh_), new_max_neigh(new_max_neigh_), - first_neigh(first_neigh_), max_neigh(max_neigh_) + : nneigh(nneigh_), new_max_neigh(new_max_neigh_), max_neigh(max_neigh_) { neigh_list = reinterpret_cast(neigh_list_); } @@ -361,8 +319,8 @@ struct NeighborListOp template DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const { - const unsigned int first = __ldg(first_neigh + q.idx); - //printf("in NeighborListOp setup %d %d %d %d \n",first_neigh,first,q.idx,nneigh[q.idx]); + //const unsigned int first = __ldg(first_neigh + q.idx); + const unsigned int first = q.idx+q.idx*(max_neigh-1); const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable // prefetch from the stack if current number of neighbors does not align with a boundary @@ -425,7 +383,6 @@ struct NeighborListOp if (t.num_neigh > max_neigh) { atomicMax(new_max_neigh, t.num_neigh); - printf("max overwrite %d %d %d\n",new_max_neigh,max_neigh, t.num_neigh); } else if (t.num_neigh % 4 != 0) { @@ -439,42 +396,13 @@ struct NeighborListOp uint4* neigh_list; //!< Neighbors of each sphere unsigned int* nneigh; //!< Number of neighbors per search sphere unsigned int* new_max_neigh; //!< New maximum number of neighbors - const unsigned int* first_neigh; //!< Index of first neighbor unsigned int max_neigh; //!< Maximum number of neighbors allocated }; //! Sentinel for an invalid particle (e.g., ghost) const unsigned int NeighborListTypeSentinel = 0xffffffff; -//! Kernel driver to generate morton code-type keys for particles and reorder by type -cudaError_t gpu_nlist_mark_types(unsigned int *d_types, - unsigned int *d_indexes, - unsigned int *d_lbvh_errors, - Scalar4 *d_last_pos, - const Scalar4 *d_pos, - const unsigned int N, - const unsigned int nghosts, - const BoxDim& box, - const Scalar3 ghost_width, - const unsigned int block_size); - -//! Kernel driver to sort particles by type -uchar2 gpu_nlist_sort_types(void *d_tmp, - size_t &tmp_bytes, - unsigned int *d_types, - unsigned int *d_sorted_types, - unsigned int *d_indexes, - unsigned int *d_sorted_indexes, - const unsigned int N, - const unsigned int num_bits); - -//! Kernel driver to count particles by type -cudaError_t gpu_nlist_count_types(unsigned int *d_first, - unsigned int *d_last, - const unsigned int *d_types, - const unsigned int ntypes, - const unsigned int N, - const unsigned int block_size); + //! Kernel driver to rearrange primitives for faster traversal cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, @@ -482,10 +410,11 @@ cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, const unsigned int *d_primitives, const unsigned int N, const unsigned int block_size); - } // end namespace gpu } // end namespace azplugins + + #undef DEVICE #undef HOSTDEVICE diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index a1130b2b..8b9c3ab3 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -35,38 +35,54 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater public: //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2); + std::shared_ptr nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2); //! Destructor virtual ~DynamicBondUpdaterGPU(); - //! find and make new bonds - // virtual void update(unsigned int timestep); - protected: - virtual void findAllPossibleBonds(); + virtual void filterPossibleBonds(); virtual void updateImageVectors(); + virtual void resizePossibleBondlists(); + virtual void allocateParticleArrays(); + //! Build the LBVHs using the neighbor library + virtual void buildTree(); + //! Traverse the LBVHs using the neighbor library + virtual void traverseTree(); private: - GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles - neighbor::LBVH m_lbvh; //!< LBVH for group_1 - neighbor::LBVHTraverser m_traverser; //!< LBVH traverser for group_2 + std::unique_ptr m_sorted_index_tuner; //!< Tuner for the type-count kernel + std::unique_ptr m_count_tuner; //!< Tuner for the type-count kernel std::unique_ptr m_copy_tuner; //!< Tuner for the primitive-copy kernel + std::unique_ptr m_copy_nlist_tuner; //!< Tuner for the primitive-copy kernel std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter - //cudaStream_t m_stream; //!< CUDA stream for tree building - GPUArray m_traverse_order; //!< Order to traverse primitives + GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles + GPUFlags m_max_bonds_overflow_flag;//!< GPU flags for the number of marked particles + GPUArray m_sorted_indexes; //!< Sorted particle indexes [idx group_1 ...] [idx group_2 ...] + + + GlobalArray m_nlist; //!< Neighbor list data + GlobalArray m_n_neigh; //!< Number of neighbors for each particle + GlobalArray m_last_pos; //!< coordinates of last updated particle positions + + GPUFlags m_lbvh_errors; //!< Error flags during particle marking (e.g., off rank) + neighbor::LBVH m_lbvh_2; //!< LBVH for group_2 + neighbor::LBVHTraverser m_traverser; //!< LBVH traverer GlobalVector m_image_list; //!< List of translation vectors for traversal unsigned int m_n_images; //!< Number of translation vectors for traversal + + //! Compute the LBVH domain from the current box BoxDim getLBVHBox() const { diff --git a/azplugins/update.py b/azplugins/update.py index dc7ef0c8..bbc202c0 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -136,27 +136,25 @@ def set_params(self, inside=None, outside=None, lo=None, hi=None): class dynamic_bond(hoomd.update._updater): - def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): + def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): hoomd.util.print_status_line() hoomd.update._updater.__init__(self) - if not hoomd.context.exec_conf.isCUDAEnabled(): cpp_class = _azplugins.DynamicBondUpdater else: cpp_class = _azplugins.DynamicBondUpdaterGPU - # hoomd.context.msg.error('update.dynamic_bond not implemented on the GPU \n') - # raise ValueError('update.dynamic_bond not implemented on the GPU ') - # look up the bond id based on the given name - this will throw an error if the bond types do not exist bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) + # todo: check that r_cut is not too large for pbc box self.r_cut = r_cut + self.nlist = nlist # it doesn't really make sense to allow partially overlapping groups? - # Maybe it should be excluded. + # Maybe it should be excluded. At least overlapping groups with different max bonds make no sense. # We need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different: new_cpp_group = _hoomd.ParticleGroup.groupIntersection(group_1.cpp_group, group_2.cpp_group) if new_cpp_group.getNumMembersGlobal()>0 and max_bonds_1 != max_bonds_2: @@ -167,21 +165,27 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,peri # preliminary testing indicates that it is faster on the CPU to have group_1 to be the bigger one # swap such that group 1 is the bigger of the two - if group_2.cpp_group.getNumMembersGlobal()> group_1.cpp_group.getNumMembersGlobal(): - temp_group = group_1 - group_1 = group_2 - group_2 = temp_group + + #if group_2.cpp_group.getNumMembersGlobal()> group_1.cpp_group.getNumMembersGlobal(): + # temp_group = group_1 + # group_1 = group_2 + # group_2 = temp_group self.cpp_updater = cpp_class(hoomd.context.current.system_definition, - group_1.cpp_group,group_2.cpp_group,self.r_cut, - bond_type_id,max_bonds_1,max_bonds_2) + self.nlist.cpp_nlist, + group_1.cpp_group, + group_2.cpp_group, + self.r_cut, + bond_type_id, + max_bonds_1, + max_bonds_2) + self.setupUpdater(period, phase) - # how to do handling of exclusions in the neighbor list correctly? - # todo: check that r_cut is not too large for pbc box - def set_params(self, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): - # todo - cpp class right now doesn't have any set/get functions + + def set_params(self, nlist=None, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): + # todo - class right now doesn't have any set/get functions hoomd.util.print_status_line() From e95679b3f05782ee48d1549d2241900e702200e8 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 6 Aug 2020 12:01:03 -0500 Subject: [PATCH 09/45] Add unit tests, fix small mistakes --- azplugins/DynamicBondUpdater.cc | 150 ++++++++++++++++----- azplugins/DynamicBondUpdater.h | 2 + azplugins/DynamicBondUpdaterGPU.cc | 114 ++++++++-------- azplugins/DynamicBondUpdaterGPU.cu | 180 +++++++++---------------- azplugins/DynamicBondUpdaterGPU.cuh | 10 -- azplugins/DynamicBondUpdaterGPU.h | 1 + azplugins/test-py/CMakeLists.txt | 2 + azplugins/test-py/test_dynamic_bond.py | 161 ++++++++++++++++++++++ azplugins/update.py | 3 +- 9 files changed, 402 insertions(+), 221 deletions(-) create mode 100644 azplugins/test-py/test_dynamic_bond.py diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 961d21bc..2687ba4c 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -19,10 +19,11 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar r_buff, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut), + : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut),m_r_buff(r_buff), m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), m_box_changed(true), m_max_N_changed(true) { @@ -88,19 +89,25 @@ void DynamicBondUpdater::update(unsigned int timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; + // std::cout<< "before tree "<pop(); @@ -108,24 +115,54 @@ void DynamicBondUpdater::update(unsigned int timestep) } //todo: should go into helper class/separate file? + +// bonds need to be sorted such that dublicates end up next to each other, otherwise +// unique will not work properly. If the possible bond length is different, we can +// sort according to that, but there might be the case where multiple possible bond lengths are identical, +// e.g. particles on a lattice. The tags should be ordered in (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. + +//todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing function, +// the possible bond array could be restructured into a different data structure? +// if we don't keep the possible bond length, a unsigned int array could hold all information needed +// when would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too large? + +// hiracical sorting, first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b +// this should work given that the tags are oredered within each pair. bool SortBonds(Scalar3 i, Scalar3 j) { - const Scalar r_sq_1 = i.z; - const Scalar r_sq_2 = j.z; - return r_sq_1 < r_sq_2; + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1==r_sq_2) + { + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_21 = __scalar_as_int(j.x); + if (tag_11==tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22>tag_12; + } + else + { + return tag_21>tag_11; + } + } + else + { + return r_sq_2>r_sq_1; + } } //todo: migrate to separate file/class? +// Cantor paring function can also be used for comparison bool CompareBonds(Scalar3 i, Scalar3 j) { - const unsigned int tag_11 = __scalar_as_int(i.x); const unsigned int tag_12 = __scalar_as_int(i.y); const unsigned int tag_21 = __scalar_as_int(j.x); const unsigned int tag_22 = __scalar_as_int(j.y); - if ((tag_11==tag_21 && tag_12==tag_22) || // (i,j)==(i,j) - (tag_11==tag_22 && tag_12==tag_21)) // (i,j)==(j,i) + if ((tag_11==tag_21 && tag_12==tag_22)) { return true; } @@ -141,6 +178,7 @@ bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) const unsigned int tag_1 = __scalar_as_int(i.x); const unsigned int tag_2 = __scalar_as_int(i.y); + // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to fill/reset with memset()? if (tag_1==0 && tag_2==0 ){ return true; }else{ @@ -338,8 +376,9 @@ void DynamicBondUpdater::buildTree() for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector { // make an AABB for the image of this particle + Scalar r_cut = m_r_cut+m_r_buff; vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image, m_r_cut); + hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image,r_cut); hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; // stackless traversal of the tree for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) @@ -353,7 +392,7 @@ void DynamicBondUpdater::buildTree() // neighbor j unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); - // skip self-interaction always + // skip self-interaction bool excluded = (i == j); //todo: bonds which already exist should be not put in the array in the first place. // that could save us from needing to filter out the exclusions later? but why is the @@ -366,17 +405,23 @@ void DynamicBondUpdater::buildTree() - vec_to_scalar3(pos_i_image); Scalar dr_sq = dot(drij,drij); - if (dr_sq <= r_cutsq) + if (dr_sq < r_cutsq) { + if (n_curr_bond < m_max_bonds) { const unsigned int tag_j = h_tag.data[j]; - Scalar3 d = make_scalar3(__int_as_scalar(tag_j),__int_as_scalar(tag_i),dr_sq); - h_all_possible_bonds.data[group_idx + n_curr_bond] = d; + + // sort the two tags in this possible bond pair + const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + + Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; } else // trigger resize current possible bonds > m_max_bonds { - m_max_bonds_overflow = n_curr_bond; + m_max_bonds_overflow = std::max(n_curr_bond,m_max_bonds_overflow); } ++n_curr_bond; @@ -394,31 +439,46 @@ void DynamicBondUpdater::buildTree() } // end loop over images } // end loop over group 2 if (m_prof) m_prof->pop(); + + /* for( unsigned int i=0; ipush("filterPossibleBonds"); - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + m_num_all_possible_bonds = 0; + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; -// first sort whole array by distance between particles in that particular possible bond -std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds); -// now make sure each possible bond is in the array only once by comparing tags -auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size, CompareBonds); -m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); + // first sort whole array by distance between particles in the found possible bond pairs + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds); + + + // now make sure each possible bond is in the array only once by comparing tags + auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size, CompareBonds); + + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); -// then remove a possible bond if it already exists. It also removes zeros, e.g. -// (0,0,0), which fill the unused spots in the array. -auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + m_num_all_possible_bonds, - [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + // then remove a possible bond if it already exists. It also removes zeros, e.g. + // (0,0,0), which fill the unused spots in the array. + auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + m_num_all_possible_bonds, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); -m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); -// at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] -// should contain only unique entries of possible bonds which are not yet formed. + // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] + // should contain only unique entries of possible bonds which are not yet formed. if (m_prof) m_prof->pop(); } @@ -509,31 +569,47 @@ if (m_bond_type >= m_bond_data -> getNTypes()) void DynamicBondUpdater::makeBonds() { if (m_prof) m_prof->push("makeBonds"); - // we need to count how many bonds are in the h_all_possible_bonds array for a given tag - // so that we don't end up forming too many bonds in one step + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); - GPUArray current_counts(m_pdata->getRTags().size(), m_exec_conf); + // we need to count how many bonds are in the h_all_possible_bonds array for a given tag + // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in + // h_n_existing_bonds in the for loop below as we go, so no extra book keeping should be needed. + // unfortionally, this makes is very difficult to port to the gpu. + GPUArray current_counts(m_pdata->getMaxN(), m_exec_conf); ArrayHandle h_current_counts(current_counts, access_location::host, access_mode::readwrite); - memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getMaxN()); + ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); +// std::cout<< "max bonds "<< m_max_bonds_group_1<< " "<< m_max_bonds_group_2<getExclusionsSet(); -// std::cout<< "add bond "; + // std::cout<< "add bond "; //todo: can this for loop be simplified/parallelized? + //std::cout<< "in DynamicBondUpdater::makeBonds() m_num_all_possible_bonds "<isMember(idx_i); + unsigned int max_bonds_i = is_member? m_max_bonds_group_1:m_max_bonds_group_2; + unsigned int max_bonds_j = is_member? m_max_bonds_group_2:m_max_bonds_group_1; + // std::cout<< "make bond "<< i << " tags "<< tag_i << " "<< tag_j << " "<< std::endl; + // std::cout<< "make bond "<< i << " coutns "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << " "<< std::endl; + // std::cout<< "make bond "<< i << " currewnt conuts "<< h_current_counts.data[tag_i] << " "<< h_current_counts.data[tag_j] << " "<< std::endl; - // std::cout<< i <<" / "<< m_num_all_possible_bonds << " ("<< tag_i << " , "<< tag_j<< ") "; //todo: put in other external criteria here, e.g. probability of bond formation etc. //todo: randomize which bonds are formed or keep them ordered by their distances? - if (h_current_counts.data[tag_i]< m_max_bonds_group_1 - h_n_existing_bonds.data[tag_i] && - h_current_counts.data[tag_j]< m_max_bonds_group_2 - h_n_existing_bonds.data[tag_j] ) + if ( max_bonds_i > h_n_existing_bonds.data[tag_i] && + max_bonds_j > h_n_existing_bonds.data[tag_j] ) { + // std::cout<< "make bond inside"<addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); h_current_counts.data[tag_i]++; @@ -559,7 +635,7 @@ void export_DynamicBondUpdater(pybind11::module& m) namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); + std::shared_ptr, const Scalar,const Scalar, unsigned int, unsigned int, unsigned int>()); //todo: implement needed getter/setter functions //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 963f9761..15b39b32 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -34,6 +34,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar r_buff, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2); @@ -53,6 +54,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr m_group_2; //!< Second particle group to form bonds with const Scalar m_r_cut; //!< cutoff for the bond forming criterion + const Scalar m_r_buff; //!< buffer size for neighbor list const unsigned int m_bond_type; //!< Type id of the bond to form const unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group const unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index a9c337dd..7b7461a3 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -15,18 +15,18 @@ namespace azplugins { DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2) - : DynamicBondUpdater(sysdef, nlist, group_1, group_2, r_cut, bond_type, max_bonds_group_1, max_bonds_group_2), - m_num_nonzero_bonds(m_exec_conf),m_max_bonds_overflow_flag(m_exec_conf),m_lbvh_errors(m_exec_conf), - m_lbvh_2(m_exec_conf),m_traverser(m_exec_conf) + std::shared_ptr nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + const Scalar r_buff, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2) + : DynamicBondUpdater(sysdef, nlist, group_1, group_2, r_cut, r_buff,bond_type, max_bonds_group_1, max_bonds_group_2), + m_num_nonzero_bonds(m_exec_conf),m_max_bonds_overflow_flag(m_exec_conf), + m_lbvh_errors(m_exec_conf),m_lbvh_2(m_exec_conf),m_traverser(m_exec_conf) { - m_sorted_index_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_sorted_index", m_exec_conf)); m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_tree_copy", m_exec_conf)); m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_nlist_copy", m_exec_conf)); @@ -49,44 +49,38 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() void DynamicBondUpdaterGPU::buildTree() { if (m_prof) m_prof->push("buildTree"); - // set the sorted index - { - // we already know the indexes of the particles in the two groups, we can simply copy them into the m_sorted_indexes array - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::overwrite); - ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); - - m_sorted_index_tuner->begin(); - azplugins::gpu::make_sorted_index_array( - d_sorted_indexes.data, - d_index_group_1.data, - d_index_group_2.data, - m_group_1->getNumMembers(), - m_group_2->getNumMembers(), - m_count_tuner->getParam()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_sorted_index_tuner->end(); - - } - - // build a lbvh for grou_2 - { - - ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - const BoxDim lbvh_box = getLBVHBox(); - - // this tree is traversed in traverseTree() - m_lbvh_2.build(azplugins::gpu::PointMapInsertOp(d_pos.data, d_sorted_indexes.data + m_group_1->getNumMembers(), m_group_2->getNumMembers()), - lbvh_box.getLo(), - lbvh_box.getHi()); - - } + // setup the sorted index, we already know the indexes of the particles in + // the two groups, we can simply copy them into the m_sorted_indexes array. + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::overwrite); + ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); + + m_sorted_index_tuner->begin(); + azplugins::gpu::make_sorted_index_array(d_sorted_indexes.data, + d_index_group_1.data, + d_index_group_2.data, + m_group_1->getNumMembers(), + m_group_2->getNumMembers(), + m_count_tuner->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_sorted_index_tuner->end(); + // build a lbvh for group_2 + { + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); + const BoxDim lbvh_box = getLBVHBox(); + // this tree is traversed in traverseTree() + m_lbvh_2.build(azplugins::gpu::PointMapInsertOp(d_pos.data, + d_sorted_indexes.data + m_group_1->getNumMembers(), + m_group_2->getNumMembers()), + lbvh_box.getLo(), + lbvh_box.getHi()); + } if (m_prof) m_prof->pop(); } @@ -100,6 +94,7 @@ void DynamicBondUpdaterGPU::traverseTree() // clear the neighbor counts cudaMemset(d_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); + cudaMemset( d_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); const BoxDim& box = m_pdata->getBox(); @@ -113,15 +108,17 @@ void DynamicBondUpdaterGPU::traverseTree() azplugins::gpu::ParticleQueryOp query_op(d_pos.data, d_sorted_indexes.data + 0, m_group_1->getNumMembers(), - m_pdata->getN(), - m_r_cut, + m_pdata->getMaxN(), + m_r_cut+m_r_buff, box); m_traverser.traverse(nlist_op, query_op, map, m_lbvh_2, m_image_list); m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); - // copy information from nlist to all_possible_bonds array, do distance checking + // if we didn't overflow copy information from nlist to all_possible_bonds array, do distance checking + // if it did overflow traverse tree again first to put all neighbor information into nlist and n_neigh + if( m_max_bonds_overflow <= m_max_bonds) { ArrayHandle d_nlist(m_nlist, access_location::device, access_mode::read); @@ -130,12 +127,12 @@ void DynamicBondUpdaterGPU::traverseTree() ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - // size m_group_1->getNumMembers()*m_max_bonds; ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::overwrite); + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + cudaMemset((void*) d_all_possible_bonds.data, 0, sizeof(Scalar3)*size); const BoxDim& box = m_pdata->getBox(); - m_copy_nlist_tuner->begin(); azplugins::gpu::nlist_copy_nlist_possible_bonds(d_all_possible_bonds.data, d_pos.data, @@ -158,14 +155,10 @@ void DynamicBondUpdaterGPU::traverseTree() void DynamicBondUpdaterGPU::resizePossibleBondlists() { - // round up to nearest multiple of 4 m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; - m_max_bonds = m_max_bonds_overflow; m_max_bonds_overflow = 0; - m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; @@ -173,6 +166,7 @@ void DynamicBondUpdaterGPU::resizePossibleBondlists() GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); m_nlist.swap(nlist); + m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; } @@ -212,8 +206,6 @@ void DynamicBondUpdaterGPU::allocateParticleArrays() GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); m_nlist.swap(nlist); - - calculateExistingBonds(); } @@ -226,7 +218,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() // todo: figure out in which order the thrust calls are the fastest. // is using build in thrust functions the best solution? // suspect: sort - remove zeros - unique - filter (which introduces zeros) - remove zeros ? - + m_num_all_possible_bonds = 0; const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; // sort and remove all existing zeros @@ -241,7 +233,9 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); if (m_prof) m_prof->pop(); -if (m_prof) m_prof->push("filterPossibleBonds2"); + + + if (m_prof) m_prof->push("filterPossibleBonds2"); //filter out the existing bonds - based on neighbor list exclusion handeling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, @@ -253,7 +247,7 @@ if (m_prof) m_prof->push("filterPossibleBonds2"); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner_filter_bonds->end(); if (m_prof) m_prof->pop(); -if (m_prof) m_prof->push("filterPossibleBonds3"); + if (m_prof) m_prof->push("filterPossibleBonds3"); // filtering existing bonds out introduced some zeros back into the array, remove them gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, @@ -268,6 +262,8 @@ if (m_prof) m_prof->push("filterPossibleBonds3"); // should contain only unique entries of possible bonds which are not yet formed. } + + /*! * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 26 translation vectors * when the simulation box is 3D periodic (the self-image is excluded). In 2D, there are at most 8 translation vectors. @@ -338,7 +334,7 @@ namespace detail namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, const Scalar, unsigned int, unsigned int, unsigned int>()); + std::shared_ptr, const Scalar, const Scalar, unsigned int, unsigned int, unsigned int>()); //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 634c4808..72f65085 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -24,45 +24,41 @@ namespace azplugins { //todo: migrate to separate file/class. -struct SortBondsGPUDistance{ - __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) +//this sorts according distance, then first tag, then second tag +struct SortBondsGPU{ + __host__ __device__ bool operator()(const Scalar3 &i, const Scalar3 &j) { - const Scalar r_sq_1 = in0.z; - const Scalar r_sq_2 = in1.z; - const unsigned int tag_0 = __scalar_as_int(in0.x); - const unsigned int tag_1 = __scalar_as_int(in1.x); - // todo: is this necessary for the thrust::unique to filter out all dublicates or - // would r_sq_1 < r_sq_2 be enough? What happens if two different potential - // bonds have EXACTLY same length? - if (r_sq_1 == r_sq_2) - return tag_0 < tag_1; - return r_sq_1 < r_sq_2; + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1==r_sq_2) + { + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_21 = __scalar_as_int(j.x); + if (tag_11==tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22>tag_12; + } + else + { + return tag_21>tag_11; + } + } + else + { + return r_sq_2>r_sq_1; + } } -}; -struct SortBondsGPUFirstTag{ - __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) - { - const unsigned int tag_0 = __scalar_as_int(in0.x); - const unsigned int tag_1 = __scalar_as_int(in1.x); - return tag_0 < tag_1; - } }; -struct SortBondsGPUSecondTag{ - __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) - { - const unsigned int tag_0 = __scalar_as_int(in0.y); - const unsigned int tag_1 = __scalar_as_int(in1.y); - return tag_0 < tag_1; - } -}; struct isZeroBondGPU{ - __host__ __device__ bool operator()(const Scalar3 &in0) + __host__ __device__ bool operator()(const Scalar3 &i) { - const unsigned int tag_0 = __scalar_as_int(in0.x); - const unsigned int tag_1 = __scalar_as_int(in0.y); + const unsigned int tag_0 = __scalar_as_int(i.x); + const unsigned int tag_1 = __scalar_as_int(i.y); if ( tag_0==0 && tag_1 ==0) { return true; @@ -75,23 +71,22 @@ struct isZeroBondGPU{ }; struct CompareBondsGPU{ - __host__ __device__ bool operator()(const Scalar3 &in0, const Scalar3 &in1) + __host__ __device__ bool operator()(const Scalar3 &i, const Scalar3 &j) { - const unsigned int tag_11 = __scalar_as_int(in0.x); - const unsigned int tag_12 = __scalar_as_int(in0.y); - const unsigned int tag_21 = __scalar_as_int(in1.x); - const unsigned int tag_22 = __scalar_as_int(in1.y); + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_21 = __scalar_as_int(j.x); + const unsigned int tag_22 = __scalar_as_int(j.y); - if ((tag_11==tag_21 && tag_12==tag_22) || // (i,j)==(i,j) - (tag_11==tag_22 && tag_12==tag_21)) // (i,j)==(j,i) - { - return true; - } - else - { - return false; - } + if ((tag_11==tag_21 && tag_12==tag_22)) // should work if pairs are ordered + { + return true; + } + else + { + return false; } + } }; namespace gpu @@ -169,29 +164,6 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, } - //! Kernel to copy the particle indexes into traversal order - /*! - * \param d_traverse_order List of particle indexes in traversal order. - * \param d_indexes Original indexes of the sorted primitives. - * \param d_primitives List of the primitives (sorted in LBVH order). - * \param N Number of primitives. - * - * The primitive index for this thread is first loaded. It is then mapped back - * to its original particle index, which is stored for subsequent traversal. - */ - __global__ void gpu_nlist_copy_primitives_kernel(unsigned int *d_traverse_order, - const unsigned int *d_indexes, - const unsigned int *d_primitives, - const unsigned int N) - { - // one thread per particle - const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; - if (idx >= N) - return; - - const unsigned int primitive = d_primitives[idx]; - d_traverse_order[idx] = __ldg(d_indexes + primitive); - } __global__ void make_sorted_index_array( unsigned int *d_sorted_indexes, unsigned int *d_indexes_group_1, @@ -231,6 +203,8 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, // idx = group index , pidx = actual particle index const unsigned int pidx_i = d_sorted_indexes[idx]; + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = r_cut*r_cut; // get all information for this particle Scalar4 postype_i = d_postype[pidx_i]; @@ -241,27 +215,31 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, for (unsigned int j=0; jtag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + d_all_possible_bonds[idx*max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } } @@ -270,7 +248,12 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, } //end namespace kernel +/* +profiling results: sort - find_if - unique 34.5 % + remove_if -> sort -> unique 14.6% +sort is slow. can we use Radix_sort instead? need keys. cantor pairing function? +*/ cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds) @@ -285,10 +268,10 @@ cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bo unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); // sort remainder by distance, should make all identical bonds consequtive - SortBondsGPUDistance sort; - thrust::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); - CompareBondsGPU comp; + SortBondsGPU sort; + thrust::sort(thrust::device,d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); + CompareBondsGPU comp; // thrust::unique only removes identical consequtive elements, so sort above is needed. thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); @@ -381,37 +364,6 @@ cudaError_t make_sorted_index_array( unsigned int *d_sorted_indexes, } -/*! - * \param d_traverse_order List of particle indexes in traversal order. - * \param d_indexes Original indexes of the sorted primitives. - * \param d_primitives List of the primitives (sorted in LBVH order). - * \param N Number of primitives. - * \param block_size Number of CUDA threads per block. - * - * \sa gpu_nlist_copy_primitives_kernel - */ -cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, - const unsigned int *d_indexes, - const unsigned int *d_primitives, - const unsigned int N, - const unsigned int block_size) - { - static unsigned int max_block_size = UINT_MAX; - if (max_block_size == UINT_MAX) - { - cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::gpu_nlist_copy_primitives_kernel); - max_block_size = attr.maxThreadsPerBlock; - } - - int run_block_size = min(block_size,max_block_size); - kernel::gpu_nlist_copy_primitives_kernel<<>>(d_traverse_order, - d_indexes, - d_primitives, - N); - return cudaSuccess; - } - cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, const Scalar4 *d_postype, diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index 6abf76d4..bd8dad67 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -30,9 +30,6 @@ #define HOSTDEVICE #endif -//! Sentinel for an invalid particle (e.g., ghost) -const unsigned int NeighborListTypeSentinel = 0xffffffff; - namespace azplugins { namespace gpu @@ -403,13 +400,6 @@ struct NeighborListOp const unsigned int NeighborListTypeSentinel = 0xffffffff; - -//! Kernel driver to rearrange primitives for faster traversal -cudaError_t gpu_nlist_copy_primitives(unsigned int *d_traverse_order, - const unsigned int *d_indexes, - const unsigned int *d_primitives, - const unsigned int N, - const unsigned int block_size); } // end namespace gpu } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 8b9c3ab3..07c29973 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -39,6 +39,7 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar r_buff, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2); diff --git a/azplugins/test-py/CMakeLists.txt b/azplugins/test-py/CMakeLists.txt index 95ccf1d1..47e0c0de 100644 --- a/azplugins/test-py/CMakeLists.txt +++ b/azplugins/test-py/CMakeLists.txt @@ -7,6 +7,7 @@ set(TEST_LIST_CPU test_bond_fene test_bond_fene24 test_dpd_general + test_dynamic_bond test_evaporate_implicit test_evaporate_particles test_flow_brownian @@ -42,6 +43,7 @@ set(TEST_LIST_GPU test_bond_fene test_bond_fene24 test_dpd_general + test_dynamic_bond test_evaporate_implicit test_evaporate_particles test_flow_brownian diff --git a/azplugins/test-py/test_dynamic_bond.py b/azplugins/test-py/test_dynamic_bond.py new file mode 100644 index 00000000..8f0c294f --- /dev/null +++ b/azplugins/test-py/test_dynamic_bond.py @@ -0,0 +1,161 @@ +# Copyright (c) 2018-2020, Michael P. Howard +# This file is part of the azplugins project, released under the Modified BSD License. + +# Maintainer: astatt + +import hoomd +from hoomd import md +hoomd.context.initialize() +try: + from hoomd import azplugins +except ImportError: + import azplugins +import unittest +import numpy as np + +class update_dynamic_bond_tests_two_groups(unittest.TestCase): + def setUp(self): + snap = hoomd.data.make_snapshot(N=4, box=hoomd.data.boxdim(L=20), + particle_types=['A','B'], + bond_types=['bond']) + if hoomd.comm.get_rank() == 0: + snap.particles.position[:,0] = (0,0.9,1.1,2) + snap.particles.position[:,1] = (0,0,0,0) + snap.particles.position[:,2] = (0,0,0,0) + snap.particles.typeid[:] = [1,0,1,0] + + self.s = hoomd.init.read_snapshot(snap) + self.nl = hoomd.md.nlist.cell() + + self.group_1 = hoomd.group.tag_list(name="a", tags = [0,1]) + self.group_2 = hoomd.group.tag_list(name="b", tags = [2,3]) + self.u = azplugins.update.dynamic_bond(nlist=self.nl, + r_cut=1.0, + bond_type='bond', + group_1=self.group_1, + group_2=self.group_2, + max_bonds_1=1, + max_bonds_2=2) + + def test_form_bond(self): + # test bond formation between particle 1-2 + # particle 0,1 are in the same group, so even if their distance is + # below r_cut, no bond should be formed, same for goes for particle 2,3 + hoomd.md.integrate.mode_standard(dt=0.0) + hoomd.md.integrate.nve(group=hoomd.group.all()) + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(1, snap.bonds.N) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[0]) + + # after this bond 1-2 is formed, there shouldn't be a second one formed + # e.g. a dublicate, even when the simulation continues to run + hoomd.run(10) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(1, snap.bonds.N) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[0]) + + def test_no_bond_formation_outside_rcut(self): + # put particle 1 out of range, no bonds should be formed + self.s.particles[1].position=(1.1,5,0) + hoomd.md.integrate.mode_standard(dt=0.0) + hoomd.md.integrate.nve(group=hoomd.group.all()) + + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(0, snap.bonds.N) + + + def tearDown(self): + del self.s, self.u + hoomd.context.initialize() + + +class update_dynamic_bond_tests_one_group(unittest.TestCase): + def setUp(self): + snap = hoomd.data.make_snapshot(N=6, box=hoomd.data.boxdim(L=20), + particle_types=['A','B'], + bond_types=['bond']) + if hoomd.comm.get_rank() == 0: + snap.particles.position[:,0] = (0,1,2,3,4,6) + snap.particles.position[:,1] = (0,0,0,0,0,0) + snap.particles.position[:,2] = (0,0,0,0,0,0) + # dynamic bond operates on groups, so typeids should not matter at all + snap.particles.typeid[:] = [0,0,1,1,0,0] + + self.s = hoomd.init.read_snapshot(snap) + self.nl = hoomd.md.nlist.cell() + + self.group_3 = hoomd.group.tag_list(name="a", tags = [0,1,2,3,4,5]) + self.u = azplugins.update.dynamic_bond(nlist=self.nl, + r_cut=1.1, + bond_type='bond', + group_1=self.group_3, + group_2=self.group_3, + max_bonds_1=2, + max_bonds_2=2) + + def test_form_bond(self): + # test bond formation. All are in the same group, so bonds should be formed + # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) + hoomd.md.integrate.mode_standard(dt=0.0) + hoomd.md.integrate.nve(group=hoomd.group.all()) + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(4, snap.bonds.N) + np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) + np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) + np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) + + def test_no_bond_formation_too_many_bonds(self): + # same as before, we will have formed bonds 0-1-2-3-4 in the first step + hoomd.md.integrate.mode_standard(dt=0.0) + hoomd.md.integrate.nve(group=hoomd.group.all()) + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(4, snap.bonds.N) + np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) + np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) + np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) + + # now put particle 5 in range of partice 2 and 3, but no new bonds should + # be formed since max_bonds_1=max_bonds_2=2 and that would be the third + # bond on those particles + self.s.particles[5].position=(2.5,0,0) + + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(4, snap.bonds.N) + np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) + np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) + np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) + + def test_bond_formation_too_many_bonds(self): + # now put particle 5 in range of partice 2 and 3, so in principle the bonds + # 0-1, 1-2 ,2-3, 3-4 (all length 1), 2-5 and 3-5 (length 0.5) can be formed. + # The all_possible_bonds array is sorted by bond distance, so bonds 2-5 + # and 3-5 are formed first. Then in order of index 0-1, and 1-2. + # Particle 2 now has two bonds, so 2-3 can't be formed, but 3-4 can. + + self.s.particles[5].position=(2.5,0,0) + snap = self.s.take_snapshot(bonds=True) + hoomd.run(1) + snap = self.s.take_snapshot(bonds=True) + self.assertAlmostEqual(5, snap.bonds.N) + + np.testing.assert_array_almost_equal([2,5], snap.bonds.group[0]) + np.testing.assert_array_almost_equal([3,5], snap.bonds.group[1]) + np.testing.assert_array_almost_equal([0,1], snap.bonds.group[2]) + np.testing.assert_array_almost_equal([1,2], snap.bonds.group[3]) + np.testing.assert_array_almost_equal([3,4], snap.bonds.group[4]) + + + def tearDown(self): + del self.s, self.u + hoomd.context.initialize() + +if __name__ == '__main__': + unittest.main(argv = ['test.py', '-v']) diff --git a/azplugins/update.py b/azplugins/update.py index bbc202c0..03d78c59 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -151,7 +151,7 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ # todo: check that r_cut is not too large for pbc box self.r_cut = r_cut - + self.r_buff = 0.4 self.nlist = nlist # it doesn't really make sense to allow partially overlapping groups? # Maybe it should be excluded. At least overlapping groups with different max bonds make no sense. @@ -176,6 +176,7 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ group_1.cpp_group, group_2.cpp_group, self.r_cut, + self.r_buff, bond_type_id, max_bonds_1, max_bonds_2) From 33913da64bbf35a5ba99a7e204ab91aef38a3d80 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 6 Aug 2020 12:08:57 -0500 Subject: [PATCH 10/45] Remove stray printf/cout --- azplugins/DynamicBondUpdater.cc | 65 ++++++++---------------------- azplugins/DynamicBondUpdaterGPU.cu | 6 --- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 2687ba4c..f0c0a9f0 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -89,25 +89,19 @@ void DynamicBondUpdater::update(unsigned int timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; - // std::cout<< "before tree "<pop(); @@ -118,16 +112,16 @@ void DynamicBondUpdater::update(unsigned int timestep) // bonds need to be sorted such that dublicates end up next to each other, otherwise // unique will not work properly. If the possible bond length is different, we can -// sort according to that, but there might be the case where multiple possible bond lengths are identical, -// e.g. particles on a lattice. The tags should be ordered in (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. +// sort according to that, but there might be the case where multiple possible bond lengths are exactly identical, +// e.g. particles on a lattice. +// This is hiracical sorting: first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. +// Should work given that the tags are oredered within each pair, (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. -//todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing function, +// todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing function, // the possible bond array could be restructured into a different data structure? // if we don't keep the possible bond length, a unsigned int array could hold all information needed // when would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too large? -// hiracical sorting, first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b -// this should work given that the tags are oredered within each pair. bool SortBonds(Scalar3 i, Scalar3 j) { const Scalar r_sq_1 = i.z; @@ -153,7 +147,7 @@ bool SortBonds(Scalar3 i, Scalar3 j) } } -//todo: migrate to separate file/class? +// todo: migrate to separate file/class? // Cantor paring function can also be used for comparison bool CompareBonds(Scalar3 i, Scalar3 j) { @@ -208,7 +202,7 @@ void DynamicBondUpdater::calculateExistingBonds() unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; - // only keep track of the bond type we are forming - does this make sense? + // keep track of all bond types in the system - does this make sense? // if (type == m_bond_type) // { AddtoExistingBonds(tag1,tag2); @@ -236,20 +230,12 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) } + void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) { assert(tag1 <= m_pdata->getMaximumTag()); assert(tag2 <= m_pdata->getMaximumTag()); - // don't add a bond twice - should not happen anyway - todo: might be able to avoid this check - /* - if (isExistingBond(tag1, tag2)) - { - m_exec_conf->msg->warning() << "tried to add existing bond twice! "<< tag1 << " "<< tag2 << std::endl; - return; - } - */ - bool overflowed = false; ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); @@ -261,7 +247,6 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) overflowed = true; - if (overflowed) resizeExistingBondList(); ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); @@ -327,7 +312,7 @@ void DynamicBondUpdater::allocateParticleArrays() void DynamicBondUpdater::buildTree() { if (m_prof) m_prof->push("buildTree"); - //todo: is it worth it to check if rebuild is necessary similar to neighbor list? + //todo: is it worth it to check if rebuild is necessary similar to neighbor list with keeping track of old positions? // make tree for group 2 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); @@ -392,12 +377,7 @@ void DynamicBondUpdater::buildTree() // neighbor j unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); - // skip self-interaction - bool excluded = (i == j); - //todo: bonds which already exist should be not put in the array in the first place. - // that could save us from needing to filter out the exclusions later? but why is the - // neighbor list not doing that? to take advantage of the same structure for all the neighbor lists? - if (!excluded) + if (i!=j) { // compute distance Scalar4 postype_j = h_postype.data[j]; @@ -453,7 +433,7 @@ void DynamicBondUpdater::buildTree() void DynamicBondUpdater::filterPossibleBonds() { -// std::cout<< " in filterPossibleBonds"; + if (m_prof) m_prof->push("filterPossibleBonds"); m_num_all_possible_bonds = 0; ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); @@ -575,18 +555,13 @@ void DynamicBondUpdater::makeBonds() // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in - // h_n_existing_bonds in the for loop below as we go, so no extra book keeping should be needed. - // unfortionally, this makes is very difficult to port to the gpu. - GPUArray current_counts(m_pdata->getMaxN(), m_exec_conf); - ArrayHandle h_current_counts(current_counts, access_location::host, access_mode::readwrite); - memset((void*)h_current_counts.data,0,sizeof(unsigned int)*m_pdata->getMaxN()); + // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. + // This makes it very difficult to port to the gpu. ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); -// std::cout<< "max bonds "<< m_max_bonds_group_1<< " "<< m_max_bonds_group_2<getExclusionsSet(); - // std::cout<< "add bond "; + //todo: can this for loop be simplified/parallelized? - //std::cout<< "in DynamicBondUpdater::makeBonds() m_num_all_possible_bonds "<isMember(idx_i); unsigned int max_bonds_i = is_member? m_max_bonds_group_1:m_max_bonds_group_2; unsigned int max_bonds_j = is_member? m_max_bonds_group_2:m_max_bonds_group_1; - // std::cout<< "make bond "<< i << " tags "<< tag_i << " "<< tag_j << " "<< std::endl; - // std::cout<< "make bond "<< i << " coutns "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << " "<< std::endl; - // std::cout<< "make bond "<< i << " currewnt conuts "<< h_current_counts.data[tag_i] << " "<< h_current_counts.data[tag_j] << " "<< std::endl; - //todo: put in other external criteria here, e.g. probability of bond formation etc. + //todo: put in other external criteria here, e.g. probability of bond formation, max number of bonds possible in one step, etc. //todo: randomize which bonds are formed or keep them ordered by their distances? if ( max_bonds_i > h_n_existing_bonds.data[tag_i] && max_bonds_j > h_n_existing_bonds.data[tag_j] ) { - // std::cout<< "make bond inside"<addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); - h_current_counts.data[tag_i]++; - h_current_counts.data[tag_j]++; if (exclusions) m_nlist->addExclusion(tag_i,tag_j); } } - // std::cout<pop(); } diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 72f65085..dd4bb16f 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -13,12 +13,10 @@ #include #include // todo: should azplugins have its own "extern"? - #include "hoomd/extern/neighbor/neighbor/LBVH.cuh" #include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" #include "hoomd/extern/cub/cub/cub.cuh" -#include namespace azplugins { @@ -129,8 +127,6 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, if (ex_start >= n_ex) return; -// printf("in filter_existing_bonds idx %d tag_i %d tag_j %d dist %f \n",idx,tag_1,tag_2,current_bond.z); - // count the number of existing bonds to process in this thread const unsigned int n_ex_process = n_ex - ex_start; @@ -139,12 +135,10 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, #pragma unroll for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) { - // printf("in filter_existing_bonds cur_ex_idx %d \n",cur_ex_idx); if (cur_ex_idx < n_ex_process) l_existing_bonds_list[cur_ex_idx] = d_existing_bonds_list[exli(tag_1, cur_ex_idx + ex_start)]; else l_existing_bonds_list[cur_ex_idx] = 0xffffffff; - // printf("in filter_existing_bonds idx %d tag_i %d tag_j %d dist %f cur_ex_idx %d l_existing_bonds_list %d \n",idx,tag_1,tag_2,current_bond.z,cur_ex_idx,l_existing_bonds_list[cur_ex_idx]); } // test if excluded From dcb4b32534b075fa28ca982e811a60276e73c7a4 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 7 Aug 2020 14:02:59 -0500 Subject: [PATCH 11/45] Switch to sorted possible bond pairs --- azplugins/DynamicBondUpdater.cc | 333 +++++++++++++++++++++++++--- azplugins/DynamicBondUpdater.h | 35 ++- azplugins/DynamicBondUpdaterGPU.cc | 154 +++++++------ azplugins/DynamicBondUpdaterGPU.cu | 68 ++++-- azplugins/DynamicBondUpdaterGPU.cuh | 10 +- azplugins/DynamicBondUpdaterGPU.h | 6 +- azplugins/update.py | 10 +- 7 files changed, 482 insertions(+), 134 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index f0c0a9f0..c336ae04 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -16,6 +16,7 @@ namespace azplugins DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr nlist, + bool nlist_exclusions_set, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -23,14 +24,22 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : Updater(sysdef), m_nlist(nlist), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut),m_r_buff(r_buff), + : Updater(sysdef), m_nlist(nlist), m_nlist_exclusions_set(nlist_exclusions_set), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut),m_r_buff(r_buff), m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + if (m_r_cut < 0.0 || m_r_buff < 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance or buffer radius is less than zero" << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } + m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); + m_pdata->getParticleSortSignal().connect(this); + m_bond_data = m_sysdef->getBondData(); @@ -57,6 +66,8 @@ DynamicBondUpdater::~DynamicBondUpdater() m_pdata->getBoxChangeSignal().disconnect(this); m_pdata->getGlobalParticleNumberChangeSignal().disconnect(this); + m_pdata->getParticleSortSignal().disconnect(this); + } @@ -76,6 +87,7 @@ void DynamicBondUpdater::update(unsigned int timestep) // update properties that depend on the box if (m_box_changed) { + checkBoxSize(); updateImageVectors(); m_box_changed = false; } @@ -89,17 +101,22 @@ void DynamicBondUpdater::update(unsigned int timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; + + if (needsUpdating()) + { buildTree(); do { traverseTree(); overflowed = m_max_bonds < m_max_bonds_overflow; - // if we overflowed, need to reallocate memory and re-calculate + // if we overflowed, need to reallocate memory and re-traverse the tree if (overflowed) { resizePossibleBondlists(); } } while (overflowed); + setLastUpdatedPos(); + } filterPossibleBonds(); // this function is not easily implemented on the GPU, uses addBondedGroup() @@ -108,8 +125,7 @@ void DynamicBondUpdater::update(unsigned int timestep) } -//todo: should go into helper class/separate file? - +// todo: should go into helper class/separate file? // bonds need to be sorted such that dublicates end up next to each other, otherwise // unique will not work properly. If the possible bond length is different, we can // sort according to that, but there might be the case where multiple possible bond lengths are exactly identical, @@ -273,6 +289,8 @@ void DynamicBondUpdater::resizeExistingBondList() m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; + //do we need to recalculate existing bonds when resizing? + } @@ -285,7 +303,13 @@ void DynamicBondUpdater::resizePossibleBondlists() unsigned int size = m_group_1->getNumMembers()*m_max_bonds; m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; + + GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + m_n_list.swap(nlist); + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + + forceUpdate(); } @@ -305,9 +329,142 @@ void DynamicBondUpdater::allocateParticleArrays() memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + // allocate m_last_pos + GlobalArray last_pos(m_pdata->getMaxN(), m_exec_conf); + m_last_pos.swap(last_pos); + + // allocate the number of neighbors (per particle) + GlobalArray n_neigh(m_pdata->getMaxN(), m_exec_conf); + m_n_neigh.swap(n_neigh); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_n_neigh.getNumElements()); + // default allocation of m_max_bonds neighbors per particle for the neighborlist + GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + m_n_list.swap(nlist); + + calculateExistingBonds(); + forceUpdate(); + + } + +bool DynamicBondUpdater::needsUpdating() +{ + + if (m_force_update == true) + { + m_force_update = false; + return true; } + if (m_r_buff < 1e-6) return true; + + //distance checking between last positions (at time of last update) and current positions + // todo: only check distances between particles in group_1 and 2, not all of them + { + ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); + + // sanity check + assert(h_pos.data); + + // profile + if (m_prof) m_prof->push("distcheck"); + + // temporary storage for the result + bool result = false; + + // get a local copy of the simulation box too + const BoxDim& box = m_pdata->getBox(); + + // get current nearest plane distances + Scalar3 L_g = m_pdata->getGlobalBox().getNearestPlaneDistance(); + + // Find direction of maximum box length contraction (smallest eigenvalue of deformation tensor) + Scalar3 lambda = L_g / m_last_L; + Scalar lambda_min = (lambda.x < lambda.y) ? lambda.x : lambda.y; + lambda_min = (lambda_min < lambda.z) ? lambda_min : lambda.z; + + ArrayHandle h_last_pos(m_last_pos, access_location::host, access_mode::read); + //ArrayHandle h_rcut_max(m_rcut_max, access_location::host, access_mode::read); + + for (unsigned int i = 0; i < m_pdata->getN(); i++) + { + + // minimum distance within which all particles should be included + Scalar old_rmin = m_r_cut; + + // maximum value we have checked for neighbors, defined by the buffer layer + Scalar rmax = old_rmin + m_r_buff; + + // max displacement for each particle (after subtraction of homogeneous dilations) + const Scalar delta_max = (rmax*lambda_min - old_rmin)/Scalar(2.0); + Scalar maxsq = (delta_max > 0) ? delta_max*delta_max : 0; + + Scalar3 dx = make_scalar3(h_pos.data[i].x - lambda.x*h_last_pos.data[i].x, + h_pos.data[i].y - lambda.y*h_last_pos.data[i].y, + h_pos.data[i].z - lambda.z*h_last_pos.data[i].z); + + dx = box.minImage(dx); + + if (dot(dx, dx) >= maxsq) + { + result = true; + break; + } + } + + #ifdef ENABLE_MPI + if (m_pdata->getDomainDecomposition()) + { + if (m_prof) m_prof->push("MPI allreduce"); + // check if migrate criterion is fulfilled on any rank + int local_result = result ? 1 : 0; + int global_result = 0; + MPI_Allreduce(&local_result, + &global_result, + 1, + MPI_INT, + MPI_MAX, + m_exec_conf->getMPICommunicator()); + result = (global_result > 0); + if (m_prof) m_prof->pop(); + } + #endif + + if (m_prof) m_prof->pop(); + + return result; + } + +} + +/*! Copies the current positions of all particles over to m_last_x etc... +*/ +void DynamicBondUpdater::setLastUpdatedPos() + { + ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); + + // sanity check + assert(h_pos.data); + + // profile + if (m_prof) m_prof->push("updatePos"); + + // update the last position arrays + ArrayHandle h_last_pos(m_last_pos, access_location::host, access_mode::overwrite); + for (unsigned int i = 0; i < m_pdata->getN(); i++) + { + h_last_pos.data[i] = make_scalar4(h_pos.data[i].x, h_pos.data[i].y, h_pos.data[i].z, Scalar(0.0)); + } + + // update last box nearest plane distance + m_last_L = m_pdata->getGlobalBox().getNearestPlaneDistance(); + m_last_L_local = m_pdata->getBox().getNearestPlaneDistance(); + + if (m_prof) m_prof->pop(); + } + + // this is based on the NeighborListTree c++ implementation void DynamicBondUpdater::buildTree() { @@ -337,11 +494,19 @@ void DynamicBondUpdater::buildTree() void DynamicBondUpdater::traverseTree() { if (m_prof) m_prof->push("traverseTree"); + + ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + + // clear the neighbor counts + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); + memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); + // reset content of possible bond list ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - const Scalar r_cutsq = m_r_cut*m_r_cut; + const Scalar r_cutsq = (m_r_cut+m_r_buff)*(m_r_cut+m_r_buff); ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); @@ -352,7 +517,6 @@ void DynamicBondUpdater::buildTree() for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) { unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int tag_i = h_tag.data[i]; const Scalar4 postype_i = h_postype.data[i]; const vec3 pos_i = vec3(postype_i); @@ -390,14 +554,16 @@ void DynamicBondUpdater::buildTree() if (n_curr_bond < m_max_bonds) { - const unsigned int tag_j = h_tag.data[j]; + + h_nlist.data[i*m_max_bonds + n_curr_bond] = j; // sort the two tags in this possible bond pair - const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + // const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; + // const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + + //Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; + // h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; } else // trigger resize current possible bonds > m_max_bonds { @@ -417,6 +583,7 @@ void DynamicBondUpdater::buildTree() } } // end stackless search } // end loop over images + h_n_neigh.data[i] = n_curr_bond; } // end loop over group 2 if (m_prof) m_prof->pop(); @@ -435,27 +602,101 @@ void DynamicBondUpdater::filterPossibleBonds() { if (m_prof) m_prof->push("filterPossibleBonds"); + //copy data from m_n_list to h_all_possible_bonds + { + ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + memset((void*) h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + + const BoxDim& box = m_pdata->getBox(); + + // Loop over all particles in group 1 + unsigned int group_size_1 = m_group_1->getNumMembers(); + for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + const Scalar4 postype_i = h_postype.data[i]; + + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = m_r_cut*m_r_cut; + + const unsigned int n_neigh = h_n_neigh.data[i]; + + // loop over all neighbors of this particle + for (unsigned int l=0; ltag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } + + } + } + /* + for( unsigned int i=0; i h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + // remove a possible bond if it already exists. It also removes zeros, e.g. + // (0,0,0), which fill the unused spots in the array. + auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + size, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + - // first sort whole array by distance between particles in the found possible bond pairs - std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + size, SortBonds); + // then sort array by distance between particles in the found possible bond pairs + // performance is better if remove_if happens before sort + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); // now make sure each possible bond is in the array only once by comparing tags - auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + size, CompareBonds); + auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); + } - // then remove a possible bond if it already exists. It also removes zeros, e.g. - // (0,0,0), which fill the unused spots in the array. - auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + m_num_all_possible_bonds, - [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); - - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. @@ -520,6 +761,8 @@ void DynamicBondUpdater::updateImageVectors() } } } + + forceUpdate(); } @@ -528,11 +771,20 @@ void DynamicBondUpdater::checkSystemSetup() if (m_bond_type >= m_bond_data -> getNTypes()) { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type - << " is not a valid bond type." << std::endl; + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type << " is not a valid bond type." << std::endl; throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); } + if(m_max_bonds_group_1<=0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: maximum number of bonds for group 1 is <=0. Bonds cannot be formed. " << std::endl; + } + + if(m_max_bonds_group_2<=0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: maximum number of bonds for group 2 is <=0. Bonds cannot be formed. " << std::endl; + } + if(m_group_1->getNumMembers()<=0) { m_exec_conf->msg->warning() << "DynamicBondUpdater: group 1 appears to be empty. Bonds cannot be formed. " << std::endl; @@ -543,6 +795,7 @@ if (m_bond_type >= m_bond_data -> getNTypes()) m_exec_conf->msg->warning() << "DynamicBondUpdater: group 2 appears to be empty. Bonds cannot be formed. " << std::endl; } + checkBoxSize(); } //todo: this function doesn't have a corresponding GPU implementation - what would make sense for this? @@ -556,10 +809,10 @@ void DynamicBondUpdater::makeBonds() // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. - // This makes it very difficult to port to the gpu. + // This also makes it very difficult to port to the gpu. ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); - bool exclusions = m_nlist->getExclusionsSet(); + // bool exclusions = m_nlist->getExclusionsSet(); <- doesn't work if we start with a system without any bonds //todo: can this for loop be simplified/parallelized? for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) @@ -573,8 +826,8 @@ void DynamicBondUpdater::makeBonds() // because we save the possible bond pair in an ordered fashion, we actually lost the information in which // group tag_i and tag_j is. So we need to look it up. bool is_member = m_group_1->isMember(idx_i); - unsigned int max_bonds_i = is_member? m_max_bonds_group_1:m_max_bonds_group_2; - unsigned int max_bonds_j = is_member? m_max_bonds_group_2:m_max_bonds_group_1; + unsigned int max_bonds_i = is_member ? m_max_bonds_group_1 : m_max_bonds_group_2; + unsigned int max_bonds_j = is_member ? m_max_bonds_group_2 : m_max_bonds_group_1; //todo: put in other external criteria here, e.g. probability of bond formation, max number of bonds possible in one step, etc. //todo: randomize which bonds are formed or keep them ordered by their distances? @@ -583,8 +836,7 @@ void DynamicBondUpdater::makeBonds() { m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); - - if (exclusions) m_nlist->addExclusion(tag_i,tag_j); + if (m_nlist_exclusions_set) m_nlist->addExclusion(tag_i,tag_j); // this also forces the NeighborList to update } } @@ -592,6 +844,27 @@ void DynamicBondUpdater::makeBonds() } +/*! + * Check that the largest neighbor search radius is not bigger than twice the shortest box size. + * Raises an error if this condition is not met. Otherwise, nothing happens. + */ +void DynamicBondUpdater::checkBoxSize() + { + const BoxDim& box = m_pdata->getBox(); + const uchar3 periodic = box.getPeriodic(); + + // check that rcut fits in the box + Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); + Scalar rmax = m_r_cut + m_r_buff; + + if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) || + (periodic.y && nearest_plane_distance.y <= rmax * 2.0) || + (m_sysdef->getNDimensions() == 3 && periodic.z && nearest_plane_distance.z <= rmax * 2.0)) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles would be interacting with themselves." << std::endl; + throw std::runtime_error("Error in DynamicBondUpdater, Simulation box too small."); + } + } namespace detail @@ -603,7 +876,7 @@ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init, std::shared_ptr, std::shared_ptr, + .def(py::init, std::shared_ptr, bool, std::shared_ptr, std::shared_ptr, const Scalar,const Scalar, unsigned int, unsigned int, unsigned int>()); //todo: implement needed getter/setter functions diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 15b39b32..1909317f 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -31,6 +31,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr nlist, + bool m_nlist_exclusions_set, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -48,6 +49,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater protected: std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding + bool m_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in nlist std::shared_ptr m_bond_data; //!< Bond data std::shared_ptr m_group_1; //!< First particle group to form bonds with @@ -65,7 +67,13 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*m_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_2 + GlobalArray m_n_list; //!< Neighbor list data + GlobalArray m_n_neigh; //!< Number of neighbors for each particle + GlobalArray m_last_pos; //!< coordinates of last updated particle positions + Scalar3 m_last_L; //!< Box lengths at last update + Scalar3 m_last_L_local; //!< Local Box lengths at last update + + hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 GPUVector m_aabbs; //!< Flat array of AABBs of all types std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal unsigned int m_n_images; //!< The number of image vectors to check @@ -74,6 +82,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list + + virtual void filterPossibleBonds(); bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible @@ -83,6 +93,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater virtual void traverseTree(); void makeBonds(); + void checkBoxSize(); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag virtual void updateImageVectors(); @@ -90,7 +101,9 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater virtual void resizePossibleBondlists(); void resizeExistingBondList(); virtual void allocateParticleArrays(); - + bool needsUpdating(); + void setLastUpdatedPos(); + //! Notification of a box size change void slotBoxChanged() { @@ -103,8 +116,22 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater m_max_N_changed = true; } - bool m_box_changed; //!< Flag if box dimensions changed - bool m_max_N_changed; //!< Flag if total number of particles changed + //! Notification of total particle number change + void slotParticlesSort() + { + m_particle_sort_changed = true; + } + + //! Forces a full update of the tree build and travertsal on the next call to compute() + void forceUpdate() + { + m_force_update = true; + } + + bool m_box_changed; //!< Flag if box dimensions changed + bool m_max_N_changed; //!< Flag if total number of particles changed + bool m_particle_sort_changed; //!< Flag if particle indexes got resorted + bool m_force_update; //!< Flag if the tree needs to be rebuild and traversed }; diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 7b7461a3..7bb52bf2 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -16,6 +16,7 @@ namespace azplugins DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr nlist, + bool nlist_exclusions_set, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -23,7 +24,7 @@ namespace azplugins unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : DynamicBondUpdater(sysdef, nlist, group_1, group_2, r_cut, r_buff,bond_type, max_bonds_group_1, max_bonds_group_2), + : DynamicBondUpdater(sysdef, nlist, nlist_exclusions_set, group_1, group_2, r_cut, r_buff,bond_type, max_bonds_group_1, max_bonds_group_2), m_num_nonzero_bonds(m_exec_conf),m_max_bonds_overflow_flag(m_exec_conf), m_lbvh_errors(m_exec_conf),m_lbvh_2(m_exec_conf),m_traverser(m_exec_conf) { @@ -87,14 +88,14 @@ void DynamicBondUpdaterGPU::buildTree() void DynamicBondUpdaterGPU::traverseTree() { if (m_prof) m_prof->push("traverseTree"); - ArrayHandle d_nlist(m_nlist, access_location::device, access_mode::overwrite); + ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::overwrite); ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); // clear the neighbor counts cudaMemset(d_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); - cudaMemset( d_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); + cudaMemset(d_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); const BoxDim& box = m_pdata->getBox(); @@ -116,39 +117,6 @@ void DynamicBondUpdaterGPU::traverseTree() m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); - // if we didn't overflow copy information from nlist to all_possible_bonds array, do distance checking - // if it did overflow traverse tree again first to put all neighbor information into nlist and n_neigh - if( m_max_bonds_overflow <= m_max_bonds) - { - - ArrayHandle d_nlist(m_nlist, access_location::device, access_mode::read); - ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); - ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - - ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::overwrite); - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - cudaMemset((void*) d_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - - - const BoxDim& box = m_pdata->getBox(); - m_copy_nlist_tuner->begin(); - azplugins::gpu::nlist_copy_nlist_possible_bonds(d_all_possible_bonds.data, - d_pos.data, - d_tag.data, - d_sorted_indexes.data, - d_n_neigh.data, - d_nlist.data, - box, - m_max_bonds, - m_r_cut, - m_group_1->getNumMembers(), - m_copy_tuner->getParam()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_copy_nlist_tuner->end(); - - } if (m_prof) m_prof->pop(); } @@ -164,7 +132,7 @@ void DynamicBondUpdaterGPU::resizePossibleBondlists() m_num_all_possible_bonds=0; GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); - m_nlist.swap(nlist); + m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -204,9 +172,10 @@ void DynamicBondUpdaterGPU::allocateParticleArrays() // default allocation of m_max_bonds neighbors per particle for the neighborlist GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); - m_nlist.swap(nlist); + m_n_list.swap(nlist); calculateExistingBonds(); + forceUpdate(); } @@ -214,50 +183,105 @@ void DynamicBondUpdaterGPU::allocateParticleArrays() void DynamicBondUpdaterGPU::filterPossibleBonds() { - if (m_prof) m_prof->push("filterPossibleBonds1"); + if (m_prof) m_prof->push("filterPossibleBonds copy "); + //copy data from m_n_list to d_all_possible_bonds + { + ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::read); + ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); + ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); + + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::overwrite); + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + cudaMemset((void*) d_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + + + const BoxDim& box = m_pdata->getBox(); + m_copy_nlist_tuner->begin(); + azplugins::gpu::nlist_copy_nlist_possible_bonds(d_all_possible_bonds.data, + d_pos.data, + d_tag.data, + d_sorted_indexes.data, + d_n_neigh.data, + d_nlist.data, + box, + m_max_bonds, + m_r_cut, + m_group_1->getNumMembers(), + m_copy_tuner->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_copy_nlist_tuner->end(); + } + if (m_prof) m_prof->pop(); + + + if (m_prof) m_prof->push("filterPossibleBonds remove existing"); + //filter out the existing bonds - based on neighbor list exclusion handeling + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + + // sort and remove all existing zeros + ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); + ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); + + m_tuner_filter_bonds->begin(); + gpu::filter_existing_bonds(d_all_possible_bonds.data, + d_n_existing_bonds.data, + d_existing_bonds_list.data, + m_existing_bonds_list_indexer, + size, + m_tuner_filter_bonds->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_tuner_filter_bonds->end(); + if (m_prof) m_prof->pop(); + + if (m_prof) m_prof->push("filterPossibleBonds sort_remove_1"); // todo: figure out in which order the thrust calls are the fastest. // is using build in thrust functions the best solution? - // suspect: sort - remove zeros - unique - filter (which introduces zeros) - remove zeros ? + // suspect: remove zeros - sort - unique - filter (which introduces zeros) - remove zeros ? m_num_all_possible_bonds = 0; - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; +// const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; // sort and remove all existing zeros - ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); - ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); - ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); +// ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); +// ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); +// ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); - gpu::sort_and_remove_zeros_possible_bond_array(d_all_possible_bonds.data, + gpu::sort_and_remove_zeros_possible_bond_array_1(d_all_possible_bonds.data, size, m_num_nonzero_bonds.getDeviceFlags()); + m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); + if (m_prof) m_prof->pop(); + if (m_prof) m_prof->push("filterPossibleBonds sort_remove_2"); + + gpu::sort_and_remove_zeros_possible_bond_array_2(d_all_possible_bonds.data, + m_num_all_possible_bonds, + m_num_nonzero_bonds.getDeviceFlags()); if (m_prof) m_prof->pop(); + if (m_prof) m_prof->push("filterPossibleBonds sort_remove_3"); + //m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); + gpu::sort_and_remove_zeros_possible_bond_array_3(d_all_possible_bonds.data, + m_num_all_possible_bonds, + m_num_nonzero_bonds.getDeviceFlags()); - if (m_prof) m_prof->push("filterPossibleBonds2"); - //filter out the existing bonds - based on neighbor list exclusion handeling - m_tuner_filter_bonds->begin(); - gpu::filter_existing_bonds(d_all_possible_bonds.data, - d_n_existing_bonds.data, - d_existing_bonds_list.data, - m_existing_bonds_list_indexer, - m_num_all_possible_bonds, - m_tuner_filter_bonds->getParam()); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_tuner_filter_bonds->end(); + m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); if (m_prof) m_prof->pop(); - if (m_prof) m_prof->push("filterPossibleBonds3"); + +// if (m_prof) m_prof->push("filterPossibleBonds5"); // filtering existing bonds out introduced some zeros back into the array, remove them - gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, - m_num_all_possible_bonds, - m_num_nonzero_bonds.getDeviceFlags()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); +// gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, + // m_num_all_possible_bonds, +// m_num_nonzero_bonds.getDeviceFlags()); +// if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); +// m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - if (m_prof) m_prof->pop(); +// if (m_prof) m_prof->pop(); // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. } @@ -333,7 +357,7 @@ namespace detail { namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init, std::shared_ptr, std::shared_ptr, + .def(py::init, std::shared_ptr, bool,std::shared_ptr, std::shared_ptr, const Scalar, const Scalar, unsigned int, unsigned int, unsigned int>()); //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index dd4bb16f..3ad33d80 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -51,20 +51,14 @@ struct SortBondsGPU{ }; - +// returns true if given possible bond is zero, e.g. (0,0,0.0) +// possible bonds are ordered, such that tag_a < tag_b in (tag_a,tag_b,rsq) +// meaning we only need to check tag_b == 0 struct isZeroBondGPU{ __host__ __device__ bool operator()(const Scalar3 &i) { - const unsigned int tag_0 = __scalar_as_int(i.x); const unsigned int tag_1 = __scalar_as_int(i.y); - if ( tag_0==0 && tag_1 ==0) - { - return true; - } - else - { - return false; - } + return !(bool)tag_1; } }; @@ -76,7 +70,7 @@ struct CompareBondsGPU{ const unsigned int tag_21 = __scalar_as_int(j.x); const unsigned int tag_22 = __scalar_as_int(j.y); - if ((tag_11==tag_21 && tag_12==tag_22)) // should work if pairs are ordered + if ((tag_11==tag_21 && tag_12==tag_22)) // should work because pairs are ordered { return true; } @@ -248,7 +242,7 @@ profiling results: sort - find_if - unique 34.5 % sort is slow. can we use Radix_sort instead? need keys. cantor pairing function? */ -cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, +cudaError_t sort_and_remove_zeros_possible_bond_array_1(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds) { @@ -256,25 +250,53 @@ cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bo // wrapper for pointer needed for thrust thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); - // first remove all zeros + // first remove all zeros - makes sort after faster isZeroBondGPU zero; thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, zero); unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); - // sort remainder by distance, should make all identical bonds consequtive - SortBondsGPU sort; - thrust::sort(thrust::device,d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); - - CompareBondsGPU comp; - // thrust::unique only removes identical consequtive elements, so sort above is needed. - thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); - unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); - - *d_max_non_zero_bonds=l1; + *d_max_non_zero_bonds=l0; return cudaSuccess; } + cudaError_t sort_and_remove_zeros_possible_bond_array_2(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds) + { + if (size == 0) return cudaSuccess; + // wrapper for pointer needed for thrust + thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + + // sort remainder by distance, should make all identical bonds consequtive + SortBondsGPU sort; + thrust::sort(thrust::device,d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, sort); + + *d_max_non_zero_bonds=size; + + return cudaSuccess; + } + + +cudaError_t sort_and_remove_zeros_possible_bond_array_3(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds) + { + if (size == 0) return cudaSuccess; + // wrapper for pointer needed for thrust + thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + + + CompareBondsGPU comp; + // thrust::unique only removes identical consequtive elements, so sort above is needed. + thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + size,comp); + unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); + + *d_max_non_zero_bonds=l1; + + return cudaSuccess; + } + cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds) diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index bd8dad67..64f88a9d 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -45,10 +45,18 @@ cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, const unsigned int size, const unsigned int block_size); -cudaError_t sort_and_remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, +cudaError_t sort_and_remove_zeros_possible_bond_array_1(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds); +cudaError_t sort_and_remove_zeros_possible_bond_array_2(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds); + +cudaError_t sort_and_remove_zeros_possible_bond_array_3(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds); + cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size, int *d_max_non_zero_bonds); diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 07c29973..f5a3c2b1 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -36,6 +36,7 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr nlist, + bool nlist_exclusions_set, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -71,11 +72,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater GPUFlags m_max_bonds_overflow_flag;//!< GPU flags for the number of marked particles GPUArray m_sorted_indexes; //!< Sorted particle indexes [idx group_1 ...] [idx group_2 ...] - - GlobalArray m_nlist; //!< Neighbor list data - GlobalArray m_n_neigh; //!< Number of neighbors for each particle - GlobalArray m_last_pos; //!< coordinates of last updated particle positions - GPUFlags m_lbvh_errors; //!< Error flags during particle marking (e.g., off rank) neighbor::LBVH m_lbvh_2; //!< LBVH for group_2 neighbor::LBVHTraverser m_traverser; //!< LBVH traverer diff --git a/azplugins/update.py b/azplugins/update.py index 03d78c59..d077df40 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -136,7 +136,7 @@ def set_params(self, inside=None, outside=None, lo=None, hi=None): class dynamic_bond(hoomd.update._updater): - def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,period=1, phase=0): + def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,nlist_exclusions=True,period=1, phase=0): hoomd.util.print_status_line() hoomd.update._updater.__init__(self) @@ -149,10 +149,10 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ # look up the bond id based on the given name - this will throw an error if the bond types do not exist bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) - # todo: check that r_cut is not too large for pbc box self.r_cut = r_cut self.r_buff = 0.4 self.nlist = nlist + self.nlist_exclusions = nlist_exclusions # it doesn't really make sense to allow partially overlapping groups? # Maybe it should be excluded. At least overlapping groups with different max bonds make no sense. # We need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different: @@ -173,6 +173,7 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ self.cpp_updater = cpp_class(hoomd.context.current.system_definition, self.nlist.cpp_nlist, + self.nlist_exclusions, group_1.cpp_group, group_2.cpp_group, self.r_cut, @@ -184,9 +185,6 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ self.setupUpdater(period, phase) - - - - def set_params(self, nlist=None, bond_type=None, max_bonds_1=None, max_bonds_2=None,group_1=None, group_2=None): + def set_params(self, nlist=None, bond_type=None, max_bonds_1=None, max_bonds_2=None,nlist_exclusions=None,group_1=None, group_2=None): # todo - class right now doesn't have any set/get functions hoomd.util.print_status_line() From d34e4190f859e163a944c2c293b7be84e65e2e5c Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 13 Aug 2020 10:22:05 -0500 Subject: [PATCH 12/45] Downsize nlist from Nparticles to Ngroup1 size --- azplugins/DynamicBondUpdater.cc | 161 +++------------------------- azplugins/DynamicBondUpdater.h | 8 +- azplugins/DynamicBondUpdaterGPU.cc | 58 +++++----- azplugins/DynamicBondUpdaterGPU.cu | 9 +- azplugins/DynamicBondUpdaterGPU.cuh | 10 +- azplugins/DynamicBondUpdaterGPU.h | 6 +- 6 files changed, 63 insertions(+), 189 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index c336ae04..96404e31 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -30,12 +30,6 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - if (m_r_cut < 0.0 || m_r_buff < 0.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance or buffer radius is less than zero" << std::endl; - throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); m_pdata->getParticleSortSignal().connect(this); @@ -70,7 +64,6 @@ DynamicBondUpdater::~DynamicBondUpdater() } - /*! * \param timestep Timestep update is called */ @@ -101,9 +94,6 @@ void DynamicBondUpdater::update(unsigned int timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; - - if (needsUpdating()) - { buildTree(); do { @@ -115,8 +105,6 @@ void DynamicBondUpdater::update(unsigned int timestep) resizePossibleBondlists(); } } while (overflowed); - setLastUpdatedPos(); - } filterPossibleBonds(); // this function is not easily implemented on the GPU, uses addBondedGroup() @@ -304,7 +292,7 @@ void DynamicBondUpdater::resizePossibleBondlists() m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; - GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + GlobalArray nlist(size, m_exec_conf); m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -329,18 +317,14 @@ void DynamicBondUpdater::allocateParticleArrays() memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - // allocate m_last_pos - GlobalArray last_pos(m_pdata->getMaxN(), m_exec_conf); - m_last_pos.swap(last_pos); - // allocate the number of neighbors (per particle) - GlobalArray n_neigh(m_pdata->getMaxN(), m_exec_conf); + GlobalArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); m_n_neigh.swap(n_neigh); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_n_neigh.getNumElements()); + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); // default allocation of m_max_bonds neighbors per particle for the neighborlist - GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + GlobalArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); m_n_list.swap(nlist); calculateExistingBonds(); @@ -348,121 +332,6 @@ void DynamicBondUpdater::allocateParticleArrays() } -bool DynamicBondUpdater::needsUpdating() -{ - - if (m_force_update == true) - { - m_force_update = false; - return true; - } - - if (m_r_buff < 1e-6) return true; - - //distance checking between last positions (at time of last update) and current positions - // todo: only check distances between particles in group_1 and 2, not all of them - { - ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); - - // sanity check - assert(h_pos.data); - - // profile - if (m_prof) m_prof->push("distcheck"); - - // temporary storage for the result - bool result = false; - - // get a local copy of the simulation box too - const BoxDim& box = m_pdata->getBox(); - - // get current nearest plane distances - Scalar3 L_g = m_pdata->getGlobalBox().getNearestPlaneDistance(); - - // Find direction of maximum box length contraction (smallest eigenvalue of deformation tensor) - Scalar3 lambda = L_g / m_last_L; - Scalar lambda_min = (lambda.x < lambda.y) ? lambda.x : lambda.y; - lambda_min = (lambda_min < lambda.z) ? lambda_min : lambda.z; - - ArrayHandle h_last_pos(m_last_pos, access_location::host, access_mode::read); - //ArrayHandle h_rcut_max(m_rcut_max, access_location::host, access_mode::read); - - for (unsigned int i = 0; i < m_pdata->getN(); i++) - { - - // minimum distance within which all particles should be included - Scalar old_rmin = m_r_cut; - - // maximum value we have checked for neighbors, defined by the buffer layer - Scalar rmax = old_rmin + m_r_buff; - - // max displacement for each particle (after subtraction of homogeneous dilations) - const Scalar delta_max = (rmax*lambda_min - old_rmin)/Scalar(2.0); - Scalar maxsq = (delta_max > 0) ? delta_max*delta_max : 0; - - Scalar3 dx = make_scalar3(h_pos.data[i].x - lambda.x*h_last_pos.data[i].x, - h_pos.data[i].y - lambda.y*h_last_pos.data[i].y, - h_pos.data[i].z - lambda.z*h_last_pos.data[i].z); - - dx = box.minImage(dx); - - if (dot(dx, dx) >= maxsq) - { - result = true; - break; - } - } - - #ifdef ENABLE_MPI - if (m_pdata->getDomainDecomposition()) - { - if (m_prof) m_prof->push("MPI allreduce"); - // check if migrate criterion is fulfilled on any rank - int local_result = result ? 1 : 0; - int global_result = 0; - MPI_Allreduce(&local_result, - &global_result, - 1, - MPI_INT, - MPI_MAX, - m_exec_conf->getMPICommunicator()); - result = (global_result > 0); - if (m_prof) m_prof->pop(); - } - #endif - - if (m_prof) m_prof->pop(); - - return result; - } - -} - -/*! Copies the current positions of all particles over to m_last_x etc... -*/ -void DynamicBondUpdater::setLastUpdatedPos() - { - ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); - - // sanity check - assert(h_pos.data); - - // profile - if (m_prof) m_prof->push("updatePos"); - - // update the last position arrays - ArrayHandle h_last_pos(m_last_pos, access_location::host, access_mode::overwrite); - for (unsigned int i = 0; i < m_pdata->getN(); i++) - { - h_last_pos.data[i] = make_scalar4(h_pos.data[i].x, h_pos.data[i].y, h_pos.data[i].z, Scalar(0.0)); - } - - // update last box nearest plane distance - m_last_L = m_pdata->getGlobalBox().getNearestPlaneDistance(); - m_last_L_local = m_pdata->getBox().getNearestPlaneDistance(); - - if (m_prof) m_prof->pop(); - } // this is based on the NeighborListTree c++ implementation @@ -499,13 +368,10 @@ void DynamicBondUpdater::buildTree() ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); // clear the neighbor counts - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); - memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); + unsigned int group_size_1 = m_group_1->getNumMembers(); + memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*group_size_1); // reset content of possible bond list - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); const Scalar r_cutsq = (m_r_cut+m_r_buff)*(m_r_cut+m_r_buff); ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); @@ -513,7 +379,7 @@ void DynamicBondUpdater::buildTree() // traverse the tree // Loop over all particles in group 1 - unsigned int group_size_1 = m_group_1->getNumMembers(); + for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) { unsigned int i = m_group_1->getMemberIndex(group_idx); @@ -555,7 +421,7 @@ void DynamicBondUpdater::buildTree() if (n_curr_bond < m_max_bonds) { - h_nlist.data[i*m_max_bonds + n_curr_bond] = j; + h_nlist.data[group_idx*m_max_bonds + n_curr_bond] = j; // sort the two tags in this possible bond pair // const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; @@ -583,7 +449,7 @@ void DynamicBondUpdater::buildTree() } } // end stackless search } // end loop over images - h_n_neigh.data[i] = n_curr_bond; + h_n_neigh.data[group_idx] = n_curr_bond; } // end loop over group 2 if (m_prof) m_prof->pop(); @@ -626,13 +492,13 @@ void DynamicBondUpdater::filterPossibleBonds() unsigned int n_curr_bond = 0; const Scalar r_cutsq = m_r_cut*m_r_cut; - const unsigned int n_neigh = h_n_neigh.data[i]; + const unsigned int n_neigh = h_n_neigh.data[group_idx]; // loop over all neighbors of this particle for (unsigned int l=0; lmsg->error() << "DynamicBondUpdater: Requested cutoff distance or buffer radius is less than zero" << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } if (m_bond_type >= m_bond_data -> getNTypes()) { diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index 1909317f..ac724b2b 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -69,9 +69,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GlobalArray m_n_list; //!< Neighbor list data GlobalArray m_n_neigh; //!< Number of neighbors for each particle - GlobalArray m_last_pos; //!< coordinates of last updated particle positions - Scalar3 m_last_L; //!< Box lengths at last update - Scalar3 m_last_L_local; //!< Local Box lengths at last update hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 GPUVector m_aabbs; //!< Flat array of AABBs of all types @@ -101,9 +98,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater virtual void resizePossibleBondlists(); void resizeExistingBondList(); virtual void allocateParticleArrays(); - bool needsUpdating(); - void setLastUpdatedPos(); - + + //! Notification of a box size change void slotBoxChanged() { diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 7bb52bf2..23a1d831 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -25,14 +25,16 @@ namespace azplugins unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) : DynamicBondUpdater(sysdef, nlist, nlist_exclusions_set, group_1, group_2, r_cut, r_buff,bond_type, max_bonds_group_1, max_bonds_group_2), - m_num_nonzero_bonds(m_exec_conf),m_max_bonds_overflow_flag(m_exec_conf), - m_lbvh_errors(m_exec_conf),m_lbvh_2(m_exec_conf),m_traverser(m_exec_conf) + m_num_nonzero_bonds(m_exec_conf), m_needs_updating(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), + m_lbvh_errors(m_exec_conf), m_lbvh_2(m_exec_conf), m_traverser(m_exec_conf) { m_sorted_index_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_sorted_index", m_exec_conf)); m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_tree_copy", m_exec_conf)); m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_nlist_copy", m_exec_conf)); m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); m_count_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_count", m_exec_conf)); + m_copy_last_pos_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy_last_pos_tuner", m_exec_conf)); + m_tuner_dist_check_last_pos.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy_last_pos_tuner", m_exec_conf)); // allocate initial Memory - grows if necessary GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); @@ -63,7 +65,7 @@ void DynamicBondUpdaterGPU::buildTree() d_index_group_2.data, m_group_1->getNumMembers(), m_group_2->getNumMembers(), - m_count_tuner->getParam()); + m_sorted_index_tuner->getParam()); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_sorted_index_tuner->end(); @@ -76,7 +78,7 @@ void DynamicBondUpdaterGPU::buildTree() // this tree is traversed in traverseTree() m_lbvh_2.build(azplugins::gpu::PointMapInsertOp(d_pos.data, - d_sorted_indexes.data + m_group_1->getNumMembers(), + d_index_group_2.data, m_group_2->getNumMembers()), lbvh_box.getLo(), lbvh_box.getHi()); @@ -94,30 +96,46 @@ void DynamicBondUpdaterGPU::traverseTree() ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); // clear the neighbor counts - cudaMemset(d_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); - cudaMemset(d_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_pdata->getMaxN()); + cudaMemset(d_n_neigh.data,0, sizeof(unsigned int)*m_group_1->getNumMembers()); const BoxDim& box = m_pdata->getBox(); // neighbor list write op azplugins::gpu::NeighborListOp nlist_op(d_nlist.data, d_n_neigh.data, m_max_bonds_overflow_flag.getDeviceFlags(), m_max_bonds); - neighbor::MapTransformOp map(d_sorted_indexes.data + m_group_1->getNumMembers()); + ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); + + neighbor::MapTransformOp map(d_index_group_2.data ); m_traverser.setup(map, m_lbvh_2); // todo: use sorted indexes as traverse order? Is that ok? azplugins::gpu::ParticleQueryOp query_op(d_pos.data, - d_sorted_indexes.data + 0, + d_index_group_1.data, m_group_1->getNumMembers(), m_pdata->getMaxN(), m_r_cut+m_r_buff, box); - m_traverser.traverse(nlist_op, query_op, map, m_lbvh_2, m_image_list); + m_traverser.traverse(nlist_op, query_op, map,m_lbvh_2, m_image_list); m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); if (m_prof) m_prof->pop(); + + /* ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); + for( unsigned int i=0; igetNumMembers();++i ) + { + unsigned int n_neigh = h_n_neigh.data[i]; + std::cout<< " neigh i "<< i << " num "<< n_neigh; + for( unsigned int j=0; j nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + GlobalArray nlist(size, m_exec_conf); m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -160,18 +178,14 @@ void DynamicBondUpdaterGPU::allocateParticleArrays() GPUArray sorted_indexes(m_pdata->getMaxN(), m_exec_conf); m_sorted_indexes.swap(sorted_indexes); - // allocate m_last_pos - GlobalArray last_pos(m_pdata->getMaxN(), m_exec_conf); - m_last_pos.swap(last_pos); - // allocate the number of neighbors (per particle) GlobalArray n_neigh(m_pdata->getMaxN(), m_exec_conf); m_n_neigh.swap(n_neigh); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_pdata->getMaxN()); + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); // default allocation of m_max_bonds neighbors per particle for the neighborlist - GlobalArray nlist(m_max_bonds*m_pdata->getMaxN(), m_exec_conf); + GlobalArray nlist(m_max_bonds*m_group_1->getNumMembers(), m_exec_conf); m_n_list.swap(nlist); calculateExistingBonds(); @@ -271,23 +285,11 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); if (m_prof) m_prof->pop(); -// if (m_prof) m_prof->push("filterPossibleBonds5"); - - // filtering existing bonds out introduced some zeros back into the array, remove them -// gpu::remove_zeros_possible_bond_array(d_all_possible_bonds.data, - // m_num_all_possible_bonds, -// m_num_nonzero_bonds.getDeviceFlags()); -// if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - -// m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - -// if (m_prof) m_prof->pop(); // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. } - /*! * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 26 translation vectors * when the simulation box is 3D periodic (the self-image is excluded). In 2D, there are at most 8 translation vectors. diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 3ad33d80..7b65bae5 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -153,7 +153,7 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, } - __global__ void make_sorted_index_array( unsigned int *d_sorted_indexes, + __global__ void make_sorted_index_array(unsigned int *d_sorted_indexes, unsigned int *d_indexes_group_1, unsigned int *d_indexes_group_2, const unsigned int size_group_1, @@ -197,13 +197,13 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, // get all information for this particle Scalar4 postype_i = d_postype[pidx_i]; const unsigned int tag_i = d_tag[pidx_i]; - const unsigned int n_neigh = d_n_neigh[pidx_i]; + const unsigned int n_neigh = d_n_neigh[idx]; // loop over all neighbors of this particle for (unsigned int j=0; j m_copy_tuner; //!< Tuner for the primitive-copy kernel std::unique_ptr m_copy_nlist_tuner; //!< Tuner for the primitive-copy kernel std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter + std::unique_ptr m_copy_last_pos_tuner; + std::unique_ptr m_tuner_dist_check_last_pos; GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles + GPUFlags m_needs_updating; GPUFlags m_max_bonds_overflow_flag;//!< GPU flags for the number of marked particles + GPUArray m_sorted_indexes; //!< Sorted particle indexes [idx group_1 ...] [idx group_2 ...] GPUFlags m_lbvh_errors; //!< Error flags during particle marking (e.g., off rank) @@ -79,7 +84,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater unsigned int m_n_images; //!< Number of translation vectors for traversal - //! Compute the LBVH domain from the current box BoxDim getLBVHBox() const { From 6f4bad3d63a774966becc36824eb4268323bc3e0 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 27 Aug 2020 10:55:27 -0500 Subject: [PATCH 13/45] Cleanup unused variables/functions --- azplugins/DynamicBondUpdater.cc | 999 +++++++++++++------------ azplugins/DynamicBondUpdater.h | 166 ++-- azplugins/DynamicBondUpdaterGPU.cc | 300 +++----- azplugins/DynamicBondUpdaterGPU.cu | 310 +++----- azplugins/DynamicBondUpdaterGPU.cuh | 58 +- azplugins/DynamicBondUpdaterGPU.h | 65 +- azplugins/test-py/test_dynamic_bond.py | 33 + azplugins/update.py | 150 +++- 8 files changed, 1036 insertions(+), 1045 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 96404e31..df47ca5a 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -14,44 +14,63 @@ namespace azplugins { +/*! + * \param sysdef System definition + * + * The system is initialized in a configuration that will be not forming any bonds. + * This constructor requires that the user properly initialize the system via setters. + */ +DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2) + : Updater(sysdef), + m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), + m_r_cut(0), m_bond_type(0xffffffff), + m_max_bonds_group_1(0),m_max_bonds_group_2(0), + m_pair_nlist(nullptr), m_pair_nlist_exclusions_set(false), + m_box_changed(true), m_max_N_changed(true) + { + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + + m_pdata->getBoxChangeSignal().connect(this); + m_pdata->getGlobalParticleNumberChangeSignal().connect(this); + + m_bond_data = m_sysdef->getBondData(); + + m_max_bonds = 4; + m_max_bonds_overflow = 0; + m_num_all_possible_bonds = 0; + + setGroupOverlap(); + } + DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr nlist, - bool nlist_exclusions_set, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, - const Scalar r_buff, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : Updater(sysdef), m_nlist(nlist), m_nlist_exclusions_set(nlist_exclusions_set), m_group_1(group_1), m_group_2(group_2), m_r_cut(r_cut),m_r_buff(r_buff), - m_bond_type(bond_type),m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), + : Updater(sysdef), + m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), + m_r_cut(r_cut), m_bond_type(bond_type), + m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), + m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); - m_pdata->getParticleSortSignal().connect(this); - m_bond_data = m_sysdef->getBondData(); m_max_bonds = 4; m_max_bonds_overflow = 0; - m_num_all_possible_bonds=0; - - // allocate initial Memory - grows if necessary - GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); - - // todo: reset if group sizes changes? - // if groups change during the simulation this updater might just not work properly - groups don't have a change signal? - // can getNumTypesChangeSignal() be used as a proxy? - m_aabbs.resize(m_group_2->getNumMembers()); - - checkSystemSetup(); + m_num_all_possible_bonds = 0; + setGroupOverlap(); } DynamicBondUpdater::~DynamicBondUpdater() @@ -60,62 +79,62 @@ DynamicBondUpdater::~DynamicBondUpdater() m_pdata->getBoxChangeSignal().disconnect(this); m_pdata->getGlobalParticleNumberChangeSignal().disconnect(this); - m_pdata->getParticleSortSignal().disconnect(this); } /*! - * \param timestep Timestep update is called - */ +* \param timestep Timestep update is called +*/ void DynamicBondUpdater::update(unsigned int timestep) -{ - if (m_prof) m_prof->push("DynamicBondUpdater"); + { + if (m_prof) m_prof->push("DynamicBondUpdater"); - // don't do anything if either one of the groups is empty - const unsigned int group_size_1 = m_group_1->getNumMembers(); - const unsigned int group_size_2 = m_group_2->getNumMembers(); - if (group_size_1 == 0 || group_size_2 == 0) - return; + // don't do anything if either one of the groups is empty + if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) + return; + // don't do anything if maximum number of bonds is zero + if (m_max_bonds_group_1 == 0 || m_max_bonds_group_2 == 0) + return; - // update properties that depend on the box - if (m_box_changed) - { + // update properties that depend on the box + if (m_box_changed) + { checkBoxSize(); updateImageVectors(); m_box_changed = false; - } + } - // update properties that depend on the number of particles - if (m_max_N_changed) - { + // update properties that depend on the number of particles + if (m_max_N_changed) + { allocateParticleArrays(); m_max_N_changed = false; - } + } - // rebuild the list of possible bonds until there is no overflow - bool overflowed = false; - buildTree(); - do - { + // rebuild the list of possible bonds until there is no overflow + bool overflowed = false; + buildTree(); + do + { traverseTree(); overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-traverse the tree if (overflowed) - { - resizePossibleBondlists(); - } - } while (overflowed); + { + resizePossibleBondlists(); + } + } while (overflowed); - filterPossibleBonds(); - // this function is not easily implemented on the GPU, uses addBondedGroup() - makeBonds(); - if (m_prof) m_prof->pop(); + filterPossibleBonds(); + // this function is not easily implemented on the GPU, uses addBondedGroup() + makeBonds(); + if (m_prof) m_prof->pop(); -} + } // todo: should go into helper class/separate file? // bonds need to be sorted such that dublicates end up next to each other, otherwise -// unique will not work properly. If the possible bond length is different, we can +// unique will not work properly. If the bond length of the potential bond is different, we can // sort according to that, but there might be the case where multiple possible bond lengths are exactly identical, // e.g. particles on a lattice. // This is hiracical sorting: first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. @@ -127,34 +146,34 @@ void DynamicBondUpdater::update(unsigned int timestep) // when would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too large? bool SortBonds(Scalar3 i, Scalar3 j) - { - const Scalar r_sq_1 = i.z; - const Scalar r_sq_2 = j.z; - if (r_sq_1==r_sq_2) { - const unsigned int tag_11 = __scalar_as_int(i.x); - const unsigned int tag_21 = __scalar_as_int(j.x); - if (tag_11==tag_21) + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1==r_sq_2) { - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_22 = __scalar_as_int(j.y); - return tag_22>tag_12; + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_21 = __scalar_as_int(j.x); + if (tag_11==tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22>tag_12; + } + else + { + return tag_21>tag_11; + } } else { - return tag_21>tag_11; + return r_sq_2>r_sq_1; } } - else - { - return r_sq_2>r_sq_1; - } - } // todo: migrate to separate file/class? // Cantor paring function can also be used for comparison - bool CompareBonds(Scalar3 i, Scalar3 j) - { +bool CompareBonds(Scalar3 i, Scalar3 j) + { const unsigned int tag_11 = __scalar_as_int(i.x); const unsigned int tag_12 = __scalar_as_int(i.y); const unsigned int tag_21 = __scalar_as_int(j.x); @@ -168,120 +187,120 @@ bool SortBonds(Scalar3 i, Scalar3 j) { return false; } - } + } //todo: find a better descriptive name for this function bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) -{ - const unsigned int tag_1 = __scalar_as_int(i.x); - const unsigned int tag_2 = __scalar_as_int(i.y); - - // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to fill/reset with memset()? - if (tag_1==0 && tag_2==0 ){ - return true; - }else{ - return isExistingBond(tag_1,tag_2); - } + { + const unsigned int tag_1 = __scalar_as_int(i.x); + const unsigned int tag_2 = __scalar_as_int(i.y); -} + // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to fill/reset with memset()? + if (tag_1==0 && tag_2==0 ) + { + return true; + } + else + { + return isExistingBond(tag_1,tag_2); + } + } void DynamicBondUpdater::calculateExistingBonds() -{ + { - // reset exisitng bond list - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()); + // reset exisitng bond list + ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getMaxN()); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_pdata->getRTags().size()*m_existing_bonds_list_indexer.getH()); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_group_1->getNumMembers()*m_existing_bonds_list_indexer.getH()); - ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); + ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); + + // for each of the bonds in the system - regardless of their type + const unsigned int size = (unsigned int)m_bond_data->getN(); + for (unsigned int i = 0; i < size; i++) + { + // lookup the tag of each of the particles participating in the bond + const typename BondData::members_t& bond = h_bonds.data[i]; + unsigned int tag1 = bond.tag[0]; + unsigned int tag2 = bond.tag[1]; - // for each of the bonds - const unsigned int size = (unsigned int)m_bond_data->getN(); - for (unsigned int i = 0; i < size; i++) - { - // lookup the tag of each of the particles participating in the bond - const typename BondData::members_t& bond = h_bonds.data[i]; - unsigned int tag1 = bond.tag[0]; - unsigned int tag2 = bond.tag[1]; - - // keep track of all bond types in the system - does this make sense? - // if (type == m_bond_type) - // { AddtoExistingBonds(tag1,tag2); - // } - } -} + } + + } /*! \param tag1 First particle tag in the pair \param tag2 Second particle tag in the pair \return true if the particles \a tag1 and \a tag2 are bonded */ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) -{ - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); - unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; + { + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); + unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; - for (unsigned int i = 0; i < n_existing_bonds; i++) - { + for (unsigned int i = 0; i < n_existing_bonds; i++) + { if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,i)] == tag2) - return true; - } - return false; + return true; + } + return false; } +/*! \param tag1 First particle tag in the pair + \param tag2 Second particle tag in the pair + adds a bond between the tag1 and tag2 to the existing bonds list +*/ +void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) + { + assert(tag_a <= m_pdata->getMaximumTag()); + assert(tag_b <= m_pdata->getMaximumTag()); -void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag1,unsigned int tag2) -{ - assert(tag1 <= m_pdata->getMaximumTag()); - assert(tag2 <= m_pdata->getMaximumTag()); - - bool overflowed = false; + bool overflowed = false; - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); - // resize the list if necessary - if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) + // resize the list if necessary + if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) overflowed = true; - - if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) + if (h_n_existing_bonds.data[tag_b] == m_existing_bonds_list_indexer.getH()) overflowed = true; - if (overflowed) resizeExistingBondList(); + if (overflowed) resizeExistingBondList(); + + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_a]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_a,pos_a)] = tag_b; + h_n_existing_bonds.data[tag_a]++; - // add tag2 to tag1's existing bonds list - unsigned int pos1 = h_n_existing_bonds.data[tag1]; - assert(pos1 < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos1)] = tag2; - h_n_existing_bonds.data[tag1]++; + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_b]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b,pos_b)] = tag_a; + h_n_existing_bonds.data[tag_b]++; - // add tag1 to tag2's existing bonds list - unsigned int pos2 = h_n_existing_bonds.data[tag2]; - assert(pos2 < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos2)] = tag1; - h_n_existing_bonds.data[tag2]++; -} + } -//todo: should the list be grown more than 1 at a time for efficiency? +// grows the existing bonds list and its indexer when needed void DynamicBondUpdater::resizeExistingBondList() { - unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; - m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); - // update the indexer - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; - - //do we need to recalculate existing bonds when resizing? - + unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; + m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); + // update the indexer + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; } - +// grows the all possible bonds list when needed in increments of 4, inspired by the neighbor list void DynamicBondUpdater::resizePossibleBondlists() { // round up to nearest multiple of 4 @@ -296,466 +315,458 @@ void DynamicBondUpdater::resizePossibleBondlists() m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; - - forceUpdate(); } + +// allocates all arrays depending on the particles and groups void DynamicBondUpdater::allocateParticleArrays() - { + { - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); + GPUArray all_possible_bonds(m_group_1->getNumMembers() *m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); - GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); + m_aabbs.resize(m_group_2->getNumMembers()); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); - memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); - memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); - // allocate the number of neighbors (per particle) - GlobalArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); - m_n_neigh.swap(n_neigh); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - // default allocation of m_max_bonds neighbors per particle for the neighborlist - GlobalArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); - m_n_list.swap(nlist); + memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - calculateExistingBonds(); - forceUpdate(); + // allocate the number of neighbors (per particle) for finding bonds + GlobalArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); + m_n_neigh.swap(n_neigh); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); - } + // default allocation of m_max_bonds neighbors per particle for the neighborlist + GlobalArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); + m_n_list.swap(nlist); + + calculateExistingBonds(); + } -// this is based on the NeighborListTree c++ implementation +/*! This function is based on the NeighborListTree c++ implementation. +* It builds a AABB tree for m_group_2, which is traversed in DynamicBondUpdater::traverseTree(). +*/ void DynamicBondUpdater::buildTree() - { - if (m_prof) m_prof->push("buildTree"); - //todo: is it worth it to check if rebuild is necessary similar to neighbor list with keeping track of old positions? - // make tree for group 2 - ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); + { + if (m_prof) m_prof->push("buildTree"); - unsigned int group_size_2 = m_group_2->getNumMembers(); + // make tree for group 2 + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); - for (unsigned int group_idx = 0; group_idx < group_size_2; group_idx++) - { + for (unsigned int group_idx = 0; group_idx < m_group_2->getNumMembers(); group_idx++) + { unsigned int i = m_group_2->getMemberIndex(group_idx); // make a point particle AABB vec3 my_pos(h_postype.data[i]); h_aabbs.data[group_idx] = hpmc::detail::AABB(my_pos,i); - } + } - m_aabb_tree.buildTree(&(h_aabbs.data[0]) , group_size_2); + m_aabb_tree.buildTree(&(h_aabbs.data[0]) , m_group_2->getNumMembers()); - if (m_prof) m_prof->pop(); - } + if (m_prof) m_prof->pop(); + } - // this is based on the NeighborListTree c++ implementation - void DynamicBondUpdater::traverseTree() - { - if (m_prof) m_prof->push("traverseTree"); +/*! This function is based on the NeighborListTree c++ implementation. +* It traverses the AABB tree (build for m_group_2) and finds neighbours between m_group_2 +* and m_group_1. The neighbour information is saved in m_nlist and m_n_neigh. +*/ +void DynamicBondUpdater::traverseTree() + { + if (m_prof) m_prof->push("traverseTree"); - ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); + ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - // clear the neighbor counts - unsigned int group_size_1 = m_group_1->getNumMembers(); - memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*group_size_1); + // clear the neighbor counts + memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_group_1->getNumMembers()); // reset content of possible bond list - const Scalar r_cutsq = (m_r_cut+m_r_buff)*(m_r_cut+m_r_buff); + const Scalar r_cutsq = m_r_cut*m_r_cut; ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); // traverse the tree // Loop over all particles in group 1 + for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const Scalar4 postype_i = h_postype.data[i]; + const vec3 pos_i = vec3(postype_i); - for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) - { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const Scalar4 postype_i = h_postype.data[i]; - const vec3 pos_i = vec3(postype_i); - - unsigned int n_curr_bond = 0; + unsigned int n_curr_bond = 0; - for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector + for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector + { + // make an AABB for the image of this particle + vec3 pos_i_image = pos_i + m_image_list[cur_image]; + hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image,m_r_cut); + hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; + // stackless traversal of the tree + for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) + { + if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) { - // make an AABB for the image of this particle - Scalar r_cut = m_r_cut+m_r_buff; - vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image,r_cut); - hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; - // stackless traversal of the tree - for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) + if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) + { + for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) + { + // neighbor j + unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); + + if (i!=j) { - if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) + // compute distance + Scalar4 postype_j = h_postype.data[j]; + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) + - vec_to_scalar3(pos_i_image); + Scalar dr_sq = dot(drij,drij); + + if (dr_sq < r_cutsq) + { + if (n_curr_bond < m_max_bonds) { - if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) - { - for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) - { - // neighbor j - unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); - - if (i!=j) - { - // compute distance - Scalar4 postype_j = h_postype.data[j]; - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - vec_to_scalar3(pos_i_image); - Scalar dr_sq = dot(drij,drij); - - if (dr_sq < r_cutsq) - { - - if (n_curr_bond < m_max_bonds) - { - - h_nlist.data[group_idx*m_max_bonds + n_curr_bond] = j; - - // sort the two tags in this possible bond pair - // const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; - // const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; - - //Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - - // h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; - } - else // trigger resize current possible bonds > m_max_bonds - { - m_max_bonds_overflow = std::max(n_curr_bond,m_max_bonds_overflow); - } - ++n_curr_bond; - - } - } - } - } + h_nlist.data[group_idx*m_max_bonds + n_curr_bond] = j; } - else + else // trigger resize current possible bonds > m_max_bonds { - // skip ahead - cur_node_idx += cur_aabb_tree->getNodeSkip(cur_node_idx); + m_max_bonds_overflow = std::max(n_curr_bond,m_max_bonds_overflow); } - } // end stackless search - } // end loop over images - h_n_neigh.data[group_idx] = n_curr_bond; - } // end loop over group 2 - if (m_prof) m_prof->pop(); - - /* for( unsigned int i=0; igetNodeSkip(cur_node_idx); + } + } // end stackless search + } // end loop over images + h_n_neigh.data[group_idx] = n_curr_bond; + } // end loop over group 1 + if (m_prof) m_prof->pop(); + } - } +/*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist and +* m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds array. +* Then, all invalid (0,0,0), dublicate and existing bonds are removed from m_all_possible_bonds. It +* is sorted by distance (shortest to longest) between the two particles in the possible bond. +*/ void DynamicBondUpdater::filterPossibleBonds() -{ + { + if (m_prof) m_prof->push("filterPossibleBonds"); + //copy data from h_n_list to h_all_possible_bonds - if (m_prof) m_prof->push("filterPossibleBonds"); - //copy data from m_n_list to h_all_possible_bonds - { - ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); - ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::overwrite); - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - memset((void*) h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + memset((void*) h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - const BoxDim& box = m_pdata->getBox(); + const BoxDim& box = m_pdata->getBox(); - // Loop over all particles in group 1 - unsigned int group_size_1 = m_group_1->getNumMembers(); - for (unsigned int group_idx = 0; group_idx < group_size_1; group_idx++) + // Loop over all particles in group 1 + for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int tag_i = h_tag.data[i]; - const Scalar4 postype_i = h_postype.data[i]; - - unsigned int n_curr_bond = 0; - const Scalar r_cutsq = m_r_cut*m_r_cut; + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + const Scalar4 postype_i = h_postype.data[i]; - const unsigned int n_neigh = h_n_neigh.data[group_idx]; + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = m_r_cut*m_r_cut; - // loop over all neighbors of this particle - for (unsigned int l=0; l tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + } + h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } + } + } - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - make_scalar3(postype_i.x,postype_i.y,postype_i.z); - // apply periodic boundary conditions - drij = box.minImage(drij); + //now sort and select down + m_num_all_possible_bonds = 0; - Scalar dr_sq = dot(drij,drij); + // remove a possible bond if it already exists. It also removes zeros, e.g. + // (0,0,0), which fill the unused spots in the array. + auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + size, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); - if (dr_sq < r_cutsq) - { - if (n_curr_bond < m_max_bonds) - { - // sort the two tags in this possible bond pair - const unsigned int tag_a = tag_j>tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; - Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; - } - ++n_curr_bond; - } + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + + // then sort array by distance between particles in the found possible bond pairs + // performance is better if remove_if happens before sort + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); + + // now make sure each possible bond is in the array only once by comparing tags + auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); - } - } - /* - for( unsigned int i=0; ipop(); + } + +/*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds +* and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as +* to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list +* should be updated. +*/ +void DynamicBondUpdater::makeBonds() { - m_num_all_possible_bonds = 0; - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - // remove a possible bond if it already exists. It also removes zeros, e.g. - // (0,0,0), which fill the unused spots in the array. - auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + size, - [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + if (m_prof) m_prof->push("makeBonds"); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + // we need to count how many bonds are in the h_all_possible_bonds array for a given tag + // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in + // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. + // This also makes it very difficult to do on the gpu. - // then sort array by distance between particles in the found possible bond pairs - // performance is better if remove_if happens before sort - std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); + ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); + //todo: can this for loop be simplified/parallelized? + for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) + { + Scalar3 d = h_all_possible_bonds.data[i]; - // now make sure each possible bond is in the array only once by comparing tags - auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); + unsigned int tag_i = __scalar_as_int(d.x); + unsigned int tag_j = __scalar_as_int(d.y); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); - } + //todo: put in other external criteria here, e.g. probability of bond formation, max number of bonds possible in one step, etc. + //todo: randomize which bonds are formed or keep them ordered by their distances ? + if ( m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i] && + m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j] ) + { + m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); + AddtoExistingBonds(tag_i,tag_j); + if (m_pair_nlist_exclusions_set) + { + m_pair_nlist -> addExclusion(tag_i,tag_j); + } + } + } + if (m_prof) m_prof->pop(); + } - // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] - // should contain only unique entries of possible bonds which are not yet formed. - if (m_prof) m_prof->pop(); -} /*! - * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 27 translation vectors - * when the simulation box is 3D periodic. In 2D, there are at most 9 translation vectors. In MPI runs, a ghost layer - * of particles is added from adjacent ranks, so there is no need to perform any translations in this direction. - * The translation vectors are determined by linear combination of the lattice vectors, and must be recomputed any - * time that the box resizes. - */ +* (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 27 translation vectors +* when the simulation box is 3D periodic. In 2D, there are at most 9 translation vectors. In MPI runs, a ghost layer +* of particles is added from adjacent ranks, so there is no need to perform any translations in this direction. +* The translation vectors are determined by linear combination of the lattice vectors, and must be recomputed any +* time that the box resizes. +*/ void DynamicBondUpdater::updateImageVectors() { - const BoxDim& box = m_pdata->getBox(); - uchar3 periodic = box.getPeriodic(); - unsigned char sys3d = (this->m_sysdef->getNDimensions() == 3); + const BoxDim& box = m_pdata->getBox(); + uchar3 periodic = box.getPeriodic(); + unsigned char sys3d = (this->m_sysdef->getNDimensions() == 3); - // now compute the image vectors - // each dimension increases by one power of 3 - unsigned int n_dim_periodic = (unsigned int)(periodic.x + periodic.y + sys3d*periodic.z); - m_n_images = 1; - for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) - { + // now compute the image vectors + // each dimension increases by one power of 3 + unsigned int n_dim_periodic = (unsigned int)(periodic.x + periodic.y + sys3d*periodic.z); + m_n_images = 1; + for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) + { m_n_images *= 3; - } + } - // reallocate memory if necessary - if (m_n_images > m_image_list.size()) - { + // reallocate memory if necessary + if (m_n_images > m_image_list.size()) + { m_image_list.resize(m_n_images); - } + } - vec3 latt_a = vec3(box.getLatticeVector(0)); - vec3 latt_b = vec3(box.getLatticeVector(1)); - vec3 latt_c = vec3(box.getLatticeVector(2)); + vec3 latt_a = vec3(box.getLatticeVector(0)); + vec3 latt_b = vec3(box.getLatticeVector(1)); + vec3 latt_c = vec3(box.getLatticeVector(2)); - // there is always at least 1 image, which we put as our first thing to look at - m_image_list[0] = vec3(0.0, 0.0, 0.0); + // there is always at least 1 image, which we put as our first thing to look at + m_image_list[0] = vec3(0.0, 0.0, 0.0); - // iterate over all other combinations of images, skipping those that are - unsigned int n_images = 1; - for (int i=-1; i <= 1 && n_images < m_n_images; ++i) - { + // iterate over all other combinations of images, skipping those that are + unsigned int n_images = 1; + for (int i=-1; i <= 1 && n_images < m_n_images; ++i) + { for (int j=-1; j <= 1 && n_images < m_n_images; ++j) + { + for (int k=-1; k <= 1 && n_images < m_n_images; ++k) + { + if (!(i == 0 && j == 0 && k == 0)) { - for (int k=-1; k <= 1 && n_images < m_n_images; ++k) - { - if (!(i == 0 && j == 0 && k == 0)) - { - // skip any periodic images if we don't have periodicity - if (i != 0 && !periodic.x) continue; - if (j != 0 && !periodic.y) continue; - if (k != 0 && (!sys3d || !periodic.z)) continue; + // skip any periodic images if we don't have periodicity + if (i != 0 && !periodic.x) continue; + if (j != 0 && !periodic.y) continue; + if (k != 0 && (!sys3d || !periodic.z)) continue; - m_image_list[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; - ++n_images; - } - } + m_image_list[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; + ++n_images; } + } } - - forceUpdate(); + } } +/*! +* Check that the largest neighbor search radius is not bigger than twice the shortest box size. +* Raises an error if this condition is not met. +*/ +void DynamicBondUpdater::checkBoxSize() + { + const BoxDim& box = m_pdata->getBox(); + const uchar3 periodic = box.getPeriodic(); -void DynamicBondUpdater::checkSystemSetup() -{ - if (m_r_cut < 0.0 || m_r_buff < 0.0) + // check that rcut fits in the box + Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); + Scalar rmax = m_r_cut; + + if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) || + (periodic.y && nearest_plane_distance.y <= rmax * 2.0) || + (m_sysdef->getNDimensions() == 3 && periodic.z && nearest_plane_distance.z <= rmax * 2.0)) { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance or buffer radius is less than zero" << std::endl; - throw std::runtime_error("Error initializing DynamicBondUpdater"); + m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles would be interacting with themselves." << std::endl; + throw std::runtime_error("Error in DynamicBondUpdater, Simulation box too small."); } + } -if (m_bond_type >= m_bond_data -> getNTypes()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type << " is not a valid bond type." << std::endl; - throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); - } - - if(m_max_bonds_group_1<=0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: maximum number of bonds for group 1 is <=0. Bonds cannot be formed. " << std::endl; - } - - if(m_max_bonds_group_2<=0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: maximum number of bonds for group 2 is <=0. Bonds cannot be formed. " << std::endl; - } - - if(m_group_1->getNumMembers()<=0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: group 1 appears to be empty. Bonds cannot be formed. " << std::endl; - } - - if(m_group_2->getNumMembers()<=0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: group 2 appears to be empty. Bonds cannot be formed. " << std::endl; - } - - checkBoxSize(); -} - -//todo: this function doesn't have a corresponding GPU implementation - what would make sense for this? -void DynamicBondUpdater::makeBonds() -{ - if (m_prof) m_prof->push("makeBonds"); - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); - - // we need to count how many bonds are in the h_all_possible_bonds array for a given tag - // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in - // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. - // This also makes it very difficult to port to the gpu. +/*! Calculates if the two groups have overlap or not. Returns an error if partial +* overlap is detected. +*/ +void DynamicBondUpdater::setGroupOverlap() + { + if(m_group_1->getNumMembers()==0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: First group group_1 appears to be empty. No bonds will be formed. " << std::endl; + } - ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); - // bool exclusions = m_nlist->getExclusionsSet(); <- doesn't work if we start with a system without any bonds + if(m_group_2->getNumMembers()==0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be empty. No bonds will be formed. " << std::endl; + } + //check if the two groups are either identical or have no overlap + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); - //todo: can this for loop be simplified/parallelized? - for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) + // count particles which are in both groups. Should be either zero of them or all of them. + unsigned int overlap = 0; + for (unsigned int i=0; igetNumMembers(); ++i) { - Scalar3 d = h_all_possible_bonds.data[i]; - - unsigned int tag_i = __scalar_as_int(d.x); - unsigned int tag_j = __scalar_as_int(d.y); - unsigned int idx_i = h_rtag.data[tag_i]; - - // because we save the possible bond pair in an ordered fashion, we actually lost the information in which - // group tag_i and tag_j is. So we need to look it up. - bool is_member = m_group_1->isMember(idx_i); - unsigned int max_bonds_i = is_member ? m_max_bonds_group_1 : m_max_bonds_group_2; - unsigned int max_bonds_j = is_member ? m_max_bonds_group_2 : m_max_bonds_group_1; - - //todo: put in other external criteria here, e.g. probability of bond formation, max number of bonds possible in one step, etc. - //todo: randomize which bonds are formed or keep them ordered by their distances? - if ( max_bonds_i > h_n_existing_bonds.data[tag_i] && - max_bonds_j > h_n_existing_bonds.data[tag_j] ) - { - m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); - AddtoExistingBonds(tag_i,tag_j); - if (m_nlist_exclusions_set) m_nlist->addExclusion(tag_i,tag_j); // this also forces the NeighborList to update - } + unsigned int idx = h_index_group_1.data[i]; + if (m_group_2->isMember(idx)) + overlap++; } - if (m_prof) m_prof->pop(); -} + if( overlap>0 && overlap != m_group_1->getNumMembers()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: group 1 and group 2 have " << overlap << " overlaps. Partially overlapping groups are not implemented." << std::endl; + throw std::runtime_error("Partial overlapping groups in DynamicBondUpdater"); + } + if(overlap==m_group_1->getNumMembers()) + { + m_groups_identical=true; + } + } -/*! - * Check that the largest neighbor search radius is not bigger than twice the shortest box size. - * Raises an error if this condition is not met. Otherwise, nothing happens. - */ -void DynamicBondUpdater::checkBoxSize() +// Check that the given cutoff value is valid +void DynamicBondUpdater::checkRcut() { - const BoxDim& box = m_pdata->getBox(); - const uchar3 periodic = box.getPeriodic(); - - // check that rcut fits in the box - Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); - Scalar rmax = m_r_cut + m_r_buff; - - if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) || - (periodic.y && nearest_plane_distance.y <= rmax * 2.0) || - (m_sysdef->getNDimensions() == 3 && periodic.z && nearest_plane_distance.z <= rmax * 2.0)) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles would be interacting with themselves." << std::endl; - throw std::runtime_error("Error in DynamicBondUpdater, Simulation box too small."); - } + if (m_r_cut <= 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance is less than or equal to zero" << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } + checkBoxSize(); } +// Check that the given bond type is valid +void DynamicBondUpdater::checkBondType() + { + if (m_bond_type >= m_bond_data -> getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type << " is not a valid bond type." << std::endl; + throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); + } + } namespace detail { /*! - * \param m Python module to export to - */ +* \param m Python module to export to +*/ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init, std::shared_ptr, bool, std::shared_ptr, - std::shared_ptr, const Scalar,const Scalar, unsigned int, unsigned int, unsigned int>()); - - //todo: implement needed getter/setter functions - //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) - //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) - //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) - //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); + .def(py::init< std::shared_ptr ,std::shared_ptr, std::shared_ptr>()) + .def(py::init, std::shared_ptr, std::shared_ptr, + std::shared_ptr, Scalar, unsigned int, unsigned int, unsigned int>()) + .def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) + .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + .def_property("bond_type", &DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) + .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) + .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } -} + } } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index ac724b2b..c50d26bc 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -28,50 +28,116 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater { public: - //! Constructor with parameters - DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr nlist, - bool m_nlist_exclusions_set, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - const Scalar r_buff, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2); - - //! Destructor - virtual ~DynamicBondUpdater(); - - //! update - virtual void update(unsigned int timestep); - + //! Simple constructor + DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2); + + //! Constructor with parameters + DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + const Scalar r_cut, + unsigned int bond_type, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2); + + //! Destructor + virtual ~DynamicBondUpdater(); + + //! update + virtual void update(unsigned int timestep); + + //! Set the cutoff distance for finding bonds + /*! + * \param r_cut cutoff distance between particles for finding potential bonds + */ + void setRcut(Scalar r_cut) + { + m_r_cut = r_cut; + checkRcut(); + } + //! Get the cutoff distance between particles for finding bonds + Scalar getRcut() + { + return m_r_cut; + } + //! Set the bond type of the dynamically formed bonds + /*! + * \param bond_type type of bonds to be formed + */ + void setBondType(unsigned int bond_type) + { + m_bond_type = bond_type; + checkBondType(); + } + //! Get the bond type of the dynamically formed bonds + unsigned int getBondType() + { + return m_bond_type; + } + //! Set the maximum number of bonds on particles in group_1 + /*! + * \param max_bonds_group_1 max number of bonds formed by particles in group 1 + */ + void setMaxBondsGroup1(unsigned int max_bonds_group_1) + { + m_max_bonds_group_1 = max_bonds_group_1; + } + //! Get the maximum number of bonds on particles in group_1 + unsigned int getMaxBondsGroup1() + { + return m_max_bonds_group_1; + } + //! Set the maximum number of bonds on particles in group_2 + /*! + * \param max_bonds_group_2 max number of bonds formed by particles in group_2 + */ + void setMaxBondsGroup2(unsigned int max_bonds_group_2) + { + m_max_bonds_group_2 = max_bonds_group_2; + } + //! Get the maximum number of bonds on particles in group_2 + unsigned int getMaxBondsGroup2() + { + return m_max_bonds_group_2; + } + + //! Set the hoomd neighbor list + /*! + * \param nlist hoomd NeighborList pointer + */ + void setNeighbourList( std::shared_ptr nlist) + { + m_pair_nlist = nlist; + m_pair_nlist_exclusions_set = true; + } protected: - std::shared_ptr m_nlist; //!< The neighborlist to use for bond finding - bool m_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in nlist + std::shared_ptr m_bond_data; //!< Bond data std::shared_ptr m_group_1; //!< First particle group to form bonds with std::shared_ptr m_group_2; //!< Second particle group to form bonds with + bool m_groups_identical; - const Scalar m_r_cut; //!< cutoff for the bond forming criterion - const Scalar m_r_buff; //!< buffer size for neighbor list - const unsigned int m_bond_type; //!< Type id of the bond to form - const unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group - const unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group + Scalar m_r_cut; //!< cutoff for the bond forming criterion + unsigned int m_bond_type; //!< Type id of the bond to form + unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group + unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - unsigned int m_max_bonds; //!< maximum number of possible bonds which can be found + unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds - GPUArray m_all_possible_bonds; //!< list of possible bonds, size: size(group_1)*m_max_bonds - unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds + GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds + unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - GlobalArray m_n_list; //!< Neighbor list data - GlobalArray m_n_neigh; //!< Number of neighbors for each particle + GlobalArray m_n_list; //!< Neighbor list data + GlobalArray m_n_neigh; //!< Number of neighbors for each particle hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 - GPUVector m_aabbs; //!< Flat array of AABBs of all types + GPUVector m_aabbs; //!< Flat array of AABBs of particles in group_2 std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal unsigned int m_n_images; //!< The number of image vectors to check @@ -80,25 +146,31 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list + std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set + bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set + //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); - - bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible - void calculateExistingBonds(); - + //! build the neighbor list AABB tree virtual void buildTree(); + //! traverse the neighbor list ABB tree virtual void traverseTree(); + + bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible + void calculateExistingBonds(); void makeBonds(); - void checkBoxSize(); + void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag virtual void updateImageVectors(); - void checkSystemSetup(); - virtual void resizePossibleBondlists(); + void checkBoxSize(); + void checkRcut(); + void checkBondType(); + void setGroupOverlap(); + void resizePossibleBondlists(); void resizeExistingBondList(); - virtual void allocateParticleArrays(); - + void allocateParticleArrays(); //! Notification of a box size change void slotBoxChanged() @@ -112,22 +184,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater m_max_N_changed = true; } - //! Notification of total particle number change - void slotParticlesSort() - { - m_particle_sort_changed = true; - } - - //! Forces a full update of the tree build and travertsal on the next call to compute() - void forceUpdate() - { - m_force_update = true; - } - bool m_box_changed; //!< Flag if box dimensions changed bool m_max_N_changed; //!< Flag if total number of particles changed - bool m_particle_sort_changed; //!< Flag if particle indexes got resorted - bool m_force_update; //!< Flag if the tree needs to be rebuild and traversed }; diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 23a1d831..25bffcae 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -14,33 +14,45 @@ namespace azplugins { - DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr nlist, - bool nlist_exclusions_set, +/*! + * \param sysdef System definition + * + * The system is initialized in a configuration that will be invalid on the + * first check of the types and region. This constructor requires that the user + * properly initialize the system via setters. + */ +DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2) + : DynamicBondUpdater(sysdef,group_1,group_2), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), + m_lbvh(m_exec_conf), m_traverser(m_exec_conf) + { + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; + + m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy", m_exec_conf)); + m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_nlist_copy", m_exec_conf)); + m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_filter_bonds", m_exec_conf)); + } + +DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, - const Scalar r_buff, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) - : DynamicBondUpdater(sysdef, nlist, nlist_exclusions_set, group_1, group_2, r_cut, r_buff,bond_type, max_bonds_group_1, max_bonds_group_2), - m_num_nonzero_bonds(m_exec_conf), m_needs_updating(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), - m_lbvh_errors(m_exec_conf), m_lbvh_2(m_exec_conf), m_traverser(m_exec_conf) + : DynamicBondUpdater(sysdef, pair_nlist, group_1, group_2, + r_cut,bond_type, max_bonds_group_1, max_bonds_group_2), + m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), + m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { - m_sorted_index_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_sorted_index", m_exec_conf)); - m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_tree_copy", m_exec_conf)); - m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_nlist_copy", m_exec_conf)); - m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_filter_bonds", m_exec_conf)); - m_count_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_updater_count", m_exec_conf)); - m_copy_last_pos_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy_last_pos_tuner", m_exec_conf)); - m_tuner_dist_check_last_pos.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy_last_pos_tuner", m_exec_conf)); - - // allocate initial Memory - grows if necessary - GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); - - checkSystemSetup(); + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; + + m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy", m_exec_conf)); + m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_nlist_copy", m_exec_conf)); + m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_filter_bonds", m_exec_conf)); + } DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() @@ -51,48 +63,30 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() void DynamicBondUpdaterGPU::buildTree() { - if (m_prof) m_prof->push("buildTree"); - // setup the sorted index, we already know the indexes of the particles in - // the two groups, we can simply copy them into the m_sorted_indexes array. - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::overwrite); - ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); - - m_sorted_index_tuner->begin(); - azplugins::gpu::make_sorted_index_array(d_sorted_indexes.data, - d_index_group_1.data, - d_index_group_2.data, - m_group_1->getNumMembers(), - m_group_2->getNumMembers(), - m_sorted_index_tuner->getParam()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_sorted_index_tuner->end(); + if (m_prof) m_prof->push("buildTree"); - // build a lbvh for group_2 - { + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - const BoxDim lbvh_box = getLBVHBox(); + // build a lbvh for group_2 // this tree is traversed in traverseTree() - m_lbvh_2.build(azplugins::gpu::PointMapInsertOp(d_pos.data, + m_lbvh.build(azplugins::gpu::PointMapInsertOp(d_pos.data, d_index_group_2.data, m_group_2->getNumMembers()), lbvh_box.getLo(), lbvh_box.getHi()); - } + if (m_prof) m_prof->pop(); - } + } void DynamicBondUpdaterGPU::traverseTree() { if (m_prof) m_prof->push("traverseTree"); ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::overwrite); ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); // clear the neighbor counts @@ -107,183 +101,88 @@ void DynamicBondUpdaterGPU::traverseTree() ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); neighbor::MapTransformOp map(d_index_group_2.data ); - m_traverser.setup(map, m_lbvh_2); + m_traverser.setup(map, m_lbvh); - // todo: use sorted indexes as traverse order? Is that ok? azplugins::gpu::ParticleQueryOp query_op(d_pos.data, d_index_group_1.data, m_group_1->getNumMembers(), m_pdata->getMaxN(), - m_r_cut+m_r_buff, + m_r_cut, box); - m_traverser.traverse(nlist_op, query_op, map,m_lbvh_2, m_image_list); + m_traverser.traverse(nlist_op, query_op, map,m_lbvh, m_image_list); m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); if (m_prof) m_prof->pop(); - /* ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); - for( unsigned int i=0; igetNumMembers();++i ) - { - unsigned int n_neigh = h_n_neigh.data[i]; - std::cout<< " neigh i "<< i << " num "<< n_neigh; - for( unsigned int j=0; j 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; - m_max_bonds = m_max_bonds_overflow; - m_max_bonds_overflow = 0; - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - m_all_possible_bonds.resize(size); - m_num_all_possible_bonds=0; - - GlobalArray nlist(size, m_exec_conf); - m_n_list.swap(nlist); - - m_exec_conf->msg->notice(6) << "DynamicBondUpdaterGPU: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; - } - - -void DynamicBondUpdaterGPU::allocateParticleArrays() - { - - - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); - - GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); - - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - - memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); - memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - - GPUArray sorted_indexes(m_pdata->getMaxN(), m_exec_conf); - m_sorted_indexes.swap(sorted_indexes); - - // allocate the number of neighbors (per particle) - GlobalArray n_neigh(m_pdata->getMaxN(), m_exec_conf); - m_n_neigh.swap(n_neigh); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); +void DynamicBondUpdaterGPU::filterPossibleBonds() + { + + if (m_prof) m_prof->push("filterPossibleBonds copy "); + //copy data from m_n_list to d_all_possible_bonds. nlist saves indices and the existing bonds have to be stored by tags + //so copy data first then sort out existing bonds + const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); + ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); + ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::read); + ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); + ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); - // default allocation of m_max_bonds neighbors per particle for the neighborlist - GlobalArray nlist(m_max_bonds*m_group_1->getNumMembers(), m_exec_conf); - m_n_list.swap(nlist); + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); + cudaMemset((void*)d_all_possible_bonds.data, 0, sizeof(Scalar3)*m_all_possible_bonds.getNumElements()); - calculateExistingBonds(); - forceUpdate(); + const BoxDim& box = m_pdata->getBox(); - } + m_copy_tuner->begin(); + gpu::copy_possible_bonds(d_all_possible_bonds.data, + d_pos.data, + d_tag.data, + d_index_group_1.data, + d_n_neigh.data, + d_nlist.data, + box, + m_max_bonds, + m_r_cut, + m_groups_identical, + m_group_1->getNumMembers(), + m_copy_tuner->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_copy_tuner->end(); + if (m_prof) m_prof->pop(); + if (m_prof) m_prof->push("filterPossibleBonds remove existing"); + + //filter out the existing bonds - based on neighbor list exclusion handeling + m_tuner_filter_bonds->begin(); + gpu::filter_existing_bonds(d_all_possible_bonds.data, + d_n_existing_bonds.data, + d_existing_bonds_list.data, + m_existing_bonds_list_indexer, + size, + m_tuner_filter_bonds->getParam()); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_tuner_filter_bonds->end(); + if (m_prof) m_prof->pop(); + if (m_prof) m_prof->push("filterPossibleBonds sort_remove"); -void DynamicBondUpdaterGPU::filterPossibleBonds() - { - if (m_prof) m_prof->push("filterPossibleBonds copy "); - //copy data from m_n_list to d_all_possible_bonds - { - ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::read); - ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); - ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); - ArrayHandle d_sorted_indexes(m_sorted_indexes, access_location::device, access_mode::read); - - ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::overwrite); - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - cudaMemset((void*) d_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - - - const BoxDim& box = m_pdata->getBox(); - m_copy_nlist_tuner->begin(); - azplugins::gpu::nlist_copy_nlist_possible_bonds(d_all_possible_bonds.data, - d_pos.data, - d_tag.data, - d_sorted_indexes.data, - d_n_neigh.data, - d_nlist.data, - box, - m_max_bonds, - m_r_cut, - m_group_1->getNumMembers(), - m_copy_tuner->getParam()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_copy_nlist_tuner->end(); - } - if (m_prof) m_prof->pop(); - - - if (m_prof) m_prof->push("filterPossibleBonds remove existing"); - //filter out the existing bonds - based on neighbor list exclusion handeling - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - - // sort and remove all existing zeros - ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); - ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); - ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); - - m_tuner_filter_bonds->begin(); - gpu::filter_existing_bonds(d_all_possible_bonds.data, - d_n_existing_bonds.data, - d_existing_bonds_list.data, - m_existing_bonds_list_indexer, - size, - m_tuner_filter_bonds->getParam()); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_tuner_filter_bonds->end(); - if (m_prof) m_prof->pop(); - - if (m_prof) m_prof->push("filterPossibleBonds sort_remove_1"); - // todo: figure out in which order the thrust calls are the fastest. - // is using build in thrust functions the best solution? - // suspect: remove zeros - sort - unique - filter (which introduces zeros) - remove zeros ? - m_num_all_possible_bonds = 0; -// const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - - // sort and remove all existing zeros -// ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); -// ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); -// ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); - - gpu::sort_and_remove_zeros_possible_bond_array_1(d_all_possible_bonds.data, - size, - m_num_nonzero_bonds.getDeviceFlags()); - m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); + m_num_all_possible_bonds = 0; - if (m_prof) m_prof->pop(); - if (m_prof) m_prof->push("filterPossibleBonds sort_remove_2"); + gpu::remove_zeros_and_sort_possible_bond_array(d_all_possible_bonds.data, + size, + m_num_nonzero_bonds_flag.getDeviceFlags()); - gpu::sort_and_remove_zeros_possible_bond_array_2(d_all_possible_bonds.data, - m_num_all_possible_bonds, - m_num_nonzero_bonds.getDeviceFlags()); - if (m_prof) m_prof->pop(); - if (m_prof) m_prof->push("filterPossibleBonds sort_remove_3"); + m_num_all_possible_bonds = m_num_nonzero_bonds_flag.readFlags(); - //m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - gpu::sort_and_remove_zeros_possible_bond_array_3(d_all_possible_bonds.data, - m_num_all_possible_bonds, - m_num_nonzero_bonds.getDeviceFlags()); + if (m_prof) m_prof->pop(); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_num_all_possible_bonds = m_num_nonzero_bonds.readFlags(); - if (m_prof) m_prof->pop(); // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. @@ -347,7 +246,6 @@ void DynamicBondUpdaterGPU::updateImageVectors() } } } - } namespace detail @@ -359,13 +257,9 @@ namespace detail { namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init, std::shared_ptr, bool,std::shared_ptr, - std::shared_ptr, const Scalar, const Scalar, unsigned int, unsigned int, unsigned int>()); - - //.def_property("inside", &DynamicBondUpdater::getInsideType, &DynamicBondUpdater::setInsideType) - //.def_property("outside", &DynamicBondUpdater::getOutsideType, &DynamicBondUpdater::setOutsideType) - //.def_property("lo", &DynamicBondUpdater::getRegionLo, &DynamicBondUpdater::setRegionLo) - //.def_property("hi", &DynamicBondUpdater::getRegionHi, &DynamicBondUpdater::setRegionHi); + .def(py::init,std::shared_ptr,std::shared_ptr>()) + .def(py::init, std::shared_ptr, std::shared_ptr, + std::shared_ptr, Scalar, unsigned int, unsigned int, unsigned int>()); } } // end namespace detail diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 7b65bae5..2d2b9f8d 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -84,14 +84,100 @@ struct CompareBondsGPU{ namespace gpu { -//! Number of elements of the exisitng bond list to process in each batch +//! Number of elements of the exclusion list to process in each batch const unsigned int FILTER_BATCH_SIZE = 4; namespace kernel { -/*! - This kernel is modeled after the neighbor list exclusions filtering mechanism. +__global__ void copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, + const Scalar4 *d_postype, + const unsigned int * d_tag, + const unsigned int * d_sorted_indexes, + const unsigned int * d_n_neigh, + const unsigned int * d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const bool groups_identical, + const unsigned int N) + { + + // one thread per particle in group_1 + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + + if (idx >= N) + return; + + // idx = group index , pidx = actual particle index + const unsigned int pidx_i = d_sorted_indexes[idx]; + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = r_cut*r_cut; + + // get all information for this particle + Scalar4 postype_i = d_postype[pidx_i]; + const unsigned int tag_i = d_tag[pidx_i]; + const unsigned int n_neigh = d_n_neigh[idx]; + + // loop over all neighbors of this particle + for (unsigned int j=0; jtag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + } + d_all_possible_bonds[idx*max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } + } + } + +/*! \param d_all_possible_bonds all possible bonds list + \param d_n_existing_bonds Number of existing for each particle + \param d_existing_bonds_list List of exitsting for each particle + \param exli Indexer for indexing into d_existing_bonds_list + \param size Length of d_all_possible_bonds + \param ex_start Start filtering d_all_possible_bonds from existing bond number \a ex_start + + the kernel filter_existing_bonds() processes the all possible bonds list \a d_nlist and removes any entries that already exist. To allow + for an arbitrary large number of existing bonds, these are processed in batch sizes of FILTER_BATCH_SIZE. The kernel + must be called multiple times in order to fully remove all exclusions from the nlist. + + \note The driver filter_existing_bonds properly makes as many calls as are necessary, it only needs to be called once. + + \b Implementation + + One thread is run for each particle. Existing bonds \a ex_start, \a ex_start + 1, ... are loaded in for that particle + (or the thread returns if there are no exclusions past that point). The thread then loops over the neighbor list, + comparing each entry to the list of exclusions. If the entry is not excluded, it is written back out. \a d_n_neigh + is updated to reflect the current number of particles in the list at the end of the kernel call. */ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, const unsigned int *d_n_existing_bonds, @@ -130,9 +216,13 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) { if (cur_ex_idx < n_ex_process) + { l_existing_bonds_list[cur_ex_idx] = d_existing_bonds_list[exli(tag_1, cur_ex_idx + ex_start)]; + } else + { l_existing_bonds_list[cur_ex_idx] = 0xffffffff; + } } // test if excluded @@ -143,171 +233,38 @@ __global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, if (tag_2 == l_existing_bonds_list[cur_ex_idx]) excluded = true; } - // if it is excluded, overwrite that entry with (0,0,0). if (excluded) { - d_all_possible_bonds[idx] = make_scalar3(0,0,0.0); + d_all_possible_bonds[idx] = make_scalar3(__int_as_scalar(0),__int_as_scalar(0),0.0); } - } +} // end namespace kernel - __global__ void make_sorted_index_array(unsigned int *d_sorted_indexes, - unsigned int *d_indexes_group_1, - unsigned int *d_indexes_group_2, - const unsigned int size_group_1, - const unsigned int size_group_2) - { - // one thread per element in d_sorted_indexes - const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; - if (idx >= size_group_1+size_group_2) - return; - - if (idx= size) - return; - - // idx = group index , pidx = actual particle index - const unsigned int pidx_i = d_sorted_indexes[idx]; - unsigned int n_curr_bond = 0; - const Scalar r_cutsq = r_cut*r_cut; - - // get all information for this particle - Scalar4 postype_i = d_postype[pidx_i]; - const unsigned int tag_i = d_tag[pidx_i]; - const unsigned int n_neigh = d_n_neigh[idx]; - - // loop over all neighbors of this particle - for (unsigned int j=0; jtag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; - Scalar3 d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - d_all_possible_bonds[idx*max_bonds + n_curr_bond] = d; - } - ++n_curr_bond; - } - - } - - } - -} //end namespace kernel - -/* -profiling results: sort - find_if - unique 34.5 % - remove_if -> sort -> unique 14.6% -sort is slow. can we use Radix_sort instead? need keys. cantor pairing function? - -*/ -cudaError_t sort_and_remove_zeros_possible_bond_array_1(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds) +cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds) { if (size == 0) return cudaSuccess; // wrapper for pointer needed for thrust thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); - // first remove all zeros - makes sort after faster isZeroBondGPU zero; - thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, zero); + thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap + size, zero); unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); - *d_max_non_zero_bonds=l0; + // sort remainder by distance, should make all identical bonds consequtive + SortBondsGPU sort; + thrust::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); - return cudaSuccess; - } - - cudaError_t sort_and_remove_zeros_possible_bond_array_2(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds) - { - if (size == 0) return cudaSuccess; - // wrapper for pointer needed for thrust - thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); - - // sort remainder by distance, should make all identical bonds consequtive - SortBondsGPU sort; - thrust::sort(thrust::device,d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, sort); - - *d_max_non_zero_bonds=size; - - return cudaSuccess; - } + // thrust::unique only removes identical consequtive elements, so sort above is needed. + CompareBondsGPU comp; + thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); + unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); - -cudaError_t sort_and_remove_zeros_possible_bond_array_3(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds) - { - if (size == 0) return cudaSuccess; - // wrapper for pointer needed for thrust - thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); - - - CompareBondsGPU comp; - // thrust::unique only removes identical consequtive elements, so sort above is needed. - thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + size,comp); - unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); - - *d_max_non_zero_bonds=l1; - - return cudaSuccess; - } - -cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds) - { - if (size == 0) return cudaSuccess; - // wrapper for pointer needed for thrust - thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); - // remove all zeros - isZeroBondGPU zero; - thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+size, zero); - unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); - *d_max_non_zero_bonds=l0; + *d_max_non_zero_bonds=l1; return cudaSuccess; } @@ -351,36 +308,8 @@ cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, return cudaSuccess; } -cudaError_t make_sorted_index_array( unsigned int *d_sorted_indexes, - unsigned int *d_indexes_group_1, - unsigned int *d_indexes_group_2, - const unsigned int size_group_1, - const unsigned int size_group_2, - const unsigned int block_size) - { - static unsigned int max_block_size = UINT_MAX; - if (max_block_size == UINT_MAX) - { - cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::make_sorted_index_array); - max_block_size = attr.maxThreadsPerBlock; - } - - const unsigned int run_block_size = min(block_size,max_block_size); - const unsigned int num_blocks = ((size_group_1+size_group_2) + run_block_size - 1)/run_block_size; - - kernel::make_sorted_index_array<<>>(d_sorted_indexes, - d_indexes_group_1, - d_indexes_group_2, - size_group_1, - size_group_2); - - return cudaSuccess; - } - - -cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, +cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const Scalar4 *d_postype, const unsigned int *d_tag, const unsigned int *d_sorted_indexes, @@ -389,19 +318,21 @@ cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, - const unsigned int size, + const bool groups_identical, + const unsigned int N, const unsigned int block_size) { static unsigned int max_block_size = UINT_MAX; + if (max_block_size == UINT_MAX) - { - cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::nlist_copy_nlist_possible_bonds); - max_block_size = attr.maxThreadsPerBlock; - } + { + cudaFuncAttributes attr; + cudaFuncGetAttributes(&attr, (const void *)kernel::copy_nlist_possible_bonds); + max_block_size = attr.maxThreadsPerBlock; + } + unsigned int run_block_size = min(block_size, max_block_size); - int run_block_size = min(block_size,max_block_size); - kernel::nlist_copy_nlist_possible_bonds<<>>(d_all_possible_bonds, + kernel::copy_nlist_possible_bonds<<>>(d_all_possible_bonds, d_postype, d_tag, d_sorted_indexes, @@ -410,12 +341,11 @@ cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, box, max_bonds, r_cut, - size); + groups_identical, + N); return cudaSuccess; } - - } // end namespace gpu } // end namespace azplugins diff --git a/azplugins/DynamicBondUpdaterGPU.cuh b/azplugins/DynamicBondUpdaterGPU.cuh index 98e51c18..da8f4319 100644 --- a/azplugins/DynamicBondUpdaterGPU.cuh +++ b/azplugins/DynamicBondUpdaterGPU.cuh @@ -39,46 +39,28 @@ cudaError_t sort_possible_bond_array(Scalar3 *d_all_possible_bonds, const unsigned int size); cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, - unsigned int *d_n_existing_bonds, - const unsigned int *d_existing_bonds_list, - const Index2D& exli, - const unsigned int size, - const unsigned int block_size); - -cudaError_t sort_and_remove_zeros_possible_bond_array_1(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds); - -cudaError_t sort_and_remove_zeros_possible_bond_array_2(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds); - -cudaError_t sort_and_remove_zeros_possible_bond_array_3(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds); - -cudaError_t remove_zeros_possible_bond_array(Scalar3 *d_all_possible_bonds, - const unsigned int size, - int *d_max_non_zero_bonds); - -cudaError_t make_sorted_index_array(unsigned int *d_sorted_indexes, - unsigned int *d_indexes_group_1, - unsigned int *d_indexes_group_2, - const unsigned int size_group_1, - const unsigned int size_group_2, + unsigned int *d_n_existing_bonds, + const unsigned int *d_existing_bonds_list, + const Index2D& exli, + const unsigned int size, + const unsigned int block_size); + +cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, + const Scalar4 *d_postype, + const unsigned int *d_tag, + const unsigned int *d_sorted_indexes, + const unsigned int *d_n_neigh, + const unsigned int *d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const bool groups_identical, + const unsigned int N, const unsigned int block_size); -cudaError_t nlist_copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, - const Scalar4 *d_postype, - const unsigned int *d_tag, - const unsigned int *d_sorted_indexes, - const unsigned int *d_n_neigh, - const unsigned int *d_nlist, - const BoxDim box, - const unsigned int max_bonds, - const Scalar r_cut, - const unsigned int size, - const unsigned int block_size); +cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bonds, + const unsigned int size, + int *d_max_non_zero_bonds); //! Insert operation for a point under a mapping. /*! diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 05246703..cfa38e7e 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -33,14 +33,17 @@ namespace azplugins class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater { public: + //! Simple constructor + DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr group_1, + std::shared_ptr group_2); + //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr nlist, - bool nlist_exclusions_set, std::shared_ptr group_1, std::shared_ptr group_2, - const Scalar r_cut, - const Scalar r_buff, + Scalar r_cut, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2); @@ -48,40 +51,48 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater //! Destructor virtual ~DynamicBondUpdaterGPU(); - protected: + /*! Set autotuner parameters + * \param enable Enable / disable autotuning + * \param period period (approximate) in time steps when retuning occurs + */ + virtual void setAutotunerParams(bool enable, unsigned int period) + { + DynamicBondUpdater::setAutotunerParams(enable, period); + + m_copy_tuner->setPeriod(period); + m_copy_tuner->setEnabled(enable); + + m_copy_nlist_tuner->setPeriod(period); + m_copy_nlist_tuner->setEnabled(enable); + m_tuner_filter_bonds->setPeriod(period); + m_tuner_filter_bonds->setEnabled(enable); + + } + + protected: + //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); + //! Update the vectors for traversal of pbc images virtual void updateImageVectors(); - virtual void resizePossibleBondlists(); - virtual void allocateParticleArrays(); - //! Build the LBVHs using the neighbor library + //! Build the LBVH using the neighbor library virtual void buildTree(); - //! Traverse the LBVHs using the neighbor library + //! Traverse the LBVH using the neighbor library virtual void traverseTree(); private: + std::unique_ptr m_copy_tuner; //!< Tuner for the primitive-copy kernel + std::unique_ptr m_copy_nlist_tuner; //!< Tuner for the primitive-copy kernel + std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter - std::unique_ptr m_sorted_index_tuner; //!< Tuner for the type-count kernel - std::unique_ptr m_count_tuner; //!< Tuner for the type-count kernel - std::unique_ptr m_copy_tuner; //!< Tuner for the primitive-copy kernel - std::unique_ptr m_copy_nlist_tuner; //!< Tuner for the primitive-copy kernel - std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter - std::unique_ptr m_copy_last_pos_tuner; - std::unique_ptr m_tuner_dist_check_last_pos; - - - GPUFlags m_num_nonzero_bonds;//!< GPU flags for the number of marked particles - GPUFlags m_needs_updating; - GPUFlags m_max_bonds_overflow_flag;//!< GPU flags for the number of marked particles - - GPUArray m_sorted_indexes; //!< Sorted particle indexes [idx group_1 ...] [idx group_2 ...] + GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds + GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - GPUFlags m_lbvh_errors; //!< Error flags during particle marking (e.g., off rank) - neighbor::LBVH m_lbvh_2; //!< LBVH for group_2 - neighbor::LBVHTraverser m_traverser; //!< LBVH traverer - GlobalVector m_image_list; //!< List of translation vectors for traversal - unsigned int m_n_images; //!< Number of translation vectors for traversal + neighbor::LBVH m_lbvh; //!< LBVH for group_2 + neighbor::LBVHTraverser m_traverser; //!< LBVH traverer + GlobalVector m_image_list; //!< List of translation vectors for traversal + unsigned int m_n_images; //!< Number of translation vectors for traversal //! Compute the LBVH domain from the current box diff --git a/azplugins/test-py/test_dynamic_bond.py b/azplugins/test-py/test_dynamic_bond.py index 8f0c294f..ac7e32e8 100644 --- a/azplugins/test-py/test_dynamic_bond.py +++ b/azplugins/test-py/test_dynamic_bond.py @@ -37,6 +37,25 @@ def setUp(self): max_bonds_1=1, max_bonds_2=2) + def test_set_params(self): + self.assertEqual(self.u.cpp_updater.r_cut, 1) + self.u.set_params(r_cut=1.5) + self.assertEqual(self.u.cpp_updater.r_cut, 1.5) + + # check the test of box size large enough for cutoff + with self.assertRaises(RuntimeError): + self.u.set_params(r_cut=15.0) + + self.u.set_params(max_bonds_1=3) + self.assertEqual(self.u.cpp_updater.max_bonds_group_1,3) + self.u.set_params(max_bonds_2=7) + self.assertEqual(self.u.cpp_updater.max_bonds_group_2,7) + + # check the test of bondy_type + with self.assertRaises(RuntimeError): + self.u.set_params(bond_type='not_existing') + + def test_form_bond(self): # test bond formation between particle 1-2 # particle 0,1 are in the same group, so even if their distance is @@ -144,6 +163,7 @@ def test_bond_formation_too_many_bonds(self): snap = self.s.take_snapshot(bonds=True) hoomd.run(1) snap = self.s.take_snapshot(bonds=True) + print(snap.bonds.group) self.assertAlmostEqual(5, snap.bonds.N) np.testing.assert_array_almost_equal([2,5], snap.bonds.group[0]) @@ -152,10 +172,23 @@ def test_bond_formation_too_many_bonds(self): np.testing.assert_array_almost_equal([1,2], snap.bonds.group[3]) np.testing.assert_array_almost_equal([3,4], snap.bonds.group[4]) + def test_group_partial_overlap(self): + self.group_1 = hoomd.group.tag_list(name="a", tags = [0,1,2,3]) + self.group_2 = hoomd.group.tag_list(name="b", tags = [3,4,5]) + with self.assertRaises(RuntimeError): + azplugins.update.dynamic_bond(nlist=self.nl, + r_cut=1.0, + bond_type='bond', + group_1=self.group_1, + group_2=self.group_2, + max_bonds_1=1, + max_bonds_2=2) def tearDown(self): del self.s, self.u hoomd.context.initialize() + + if __name__ == '__main__': unittest.main(argv = ['test.py', '-v']) diff --git a/azplugins/update.py b/azplugins/update.py index d077df40..9788179a 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -136,7 +136,48 @@ def set_params(self, inside=None, outside=None, lo=None, hi=None): class dynamic_bond(hoomd.update._updater): - def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,nlist_exclusions=True,period=1, phase=0): + R""" Update bonds dynamically during simulation. + + Args: + r_cut (float): Distance cutoff for making bonds between particles + bond_type (str): Type of bond to be formed + group_1 (:py:mod:`hoomd.group`): First particle group to form bonds between + group_2 (:py:mod:`hoomd.group`): Second particle group to form bonds between + max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have + max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have + nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions + period (int): Particle types will be updated every *period* time steps + phase (int): When -1, start on the current time step. Otherwise, execute + on steps where *(step + phase) % period* is 0. + + Forms bonds of type bond_type between particles in group_1 and group_2 during + the simulation, if particle distances are shorter than r_cut. If the neighborlist + used for the pair potential in the simulation is given as a parameter nlist, the + neighbor list exclusions will be updated to include the newly formed bonds. + Each particle has a number of maximum bonds which it can form, given by + max_bonds_1 for particles in group_1 and max_bonds_2 for group_2. + + The particles in the two groups group_1 and group_2 should be completely + separate with no common elements, e.g. two different types, or the two + groups should be identical, where now max_bonds_1 needs to be equal to max_bonds_2. + + + .. warning:: + If the groups group_1 and group_2 are modified during the simulation, this + Updater will not be updated to reflect the changes. It is the user's + responsibility to ensure that the groups do not change as long as this + updater is active. + + + Examples:: + + azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,bond_type='bond', + group_1=hoomd.group.all(),group_2=hoomd.group.all(), max_bonds_1=3,max_bonds_2=3) + azplugins.update.types(r_cut=1.0,bond_type='bond', + group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) + """ + + def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,nlist=None,period=1, phase=0): hoomd.util.print_status_line() hoomd.update._updater.__init__(self) @@ -146,45 +187,76 @@ def __init__(self,nlist,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_ else: cpp_class = _azplugins.DynamicBondUpdaterGPU - # look up the bond id based on the given name - this will throw an error if the bond types do not exist - bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) - - self.r_cut = r_cut - self.r_buff = 0.4 - self.nlist = nlist - self.nlist_exclusions = nlist_exclusions - # it doesn't really make sense to allow partially overlapping groups? - # Maybe it should be excluded. At least overlapping groups with different max bonds make no sense. - # We need to check that the groups have no overlap if the max_bonds_1 and max_bonds_2 are different: - new_cpp_group = _hoomd.ParticleGroup.groupIntersection(group_1.cpp_group, group_2.cpp_group) - if new_cpp_group.getNumMembersGlobal()>0 and max_bonds_1 != max_bonds_2: - hoomd.context.msg.error('update.dynamic_bond: groups are overlapping with ' + str(new_cpp_group.getNumMembersGlobal()) - + ' common members, but maximum bonds formed by each is different ' + str(max_bonds_1) - + ' != '+ str(max_bonds_2)+ '.\n') - raise ValueError('update.dynamic_bond: groups are overlapping with different number of maximum bonds') - - # preliminary testing indicates that it is faster on the CPU to have group_1 to be the bigger one - # swap such that group 1 is the bigger of the two - - #if group_2.cpp_group.getNumMembersGlobal()> group_1.cpp_group.getNumMembersGlobal(): - # temp_group = group_1 - # group_1 = group_2 - # group_2 = temp_group - - self.cpp_updater = cpp_class(hoomd.context.current.system_definition, - self.nlist.cpp_nlist, - self.nlist_exclusions, - group_1.cpp_group, - group_2.cpp_group, - self.r_cut, - self.r_buff, - bond_type_id, - max_bonds_1, - max_bonds_2) + self.cpp_updater = cpp_class(hoomd.context.current.system_definition,group_1.cpp_group,group_2.cpp_group) + self.metadata_fields = ['r_cut','bond_type','group_1', 'group_2', 'max_bonds_1','max_bonds_2','nlist'] self.setupUpdater(period, phase) + hoomd.util.quiet_status() + self.set_params(r_cut,bond_type,max_bonds_1,max_bonds_2,nlist) + hoomd.util.unquiet_status() - def set_params(self, nlist=None, bond_type=None, max_bonds_1=None, max_bonds_2=None,nlist_exclusions=None,group_1=None, group_2=None): - # todo - class right now doesn't have any set/get functions - hoomd.util.print_status_line() + + def set_params(self, r_cut=None, bond_type=None, max_bonds_1=None, max_bonds_2=None, nlist=None): + R""" Set the dynamic_bond parameters. + + Args: + r_cut (float): Distance cutoff for making bonds between particles + bond_type (str): Type of bond to be formed + max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have + max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have + nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions + + Examples:: + + bonds = azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,bond_type='bond', + group_1=hoomd.group.all(),group_2=hoomd.group.all(), max_bonds_1=3,max_bonds_2=3) + bonds.set_params(r_cut=2.0) + bonds.set_params(max_bonds_1=5,max_bonds_2=5) + + """ + if r_cut is not None: + if r_cut <=0: + hoomd.context.msg.error('update.dynamic_bond: cutoff ' + str(r_cut) + ' <=0 .\n') + raise ValueError('update.dynamic_bond: cutoff is smaller or equal to zero.') + self.r_cut = r_cut + self.cpp_updater.r_cut = self.r_cut + + if bond_type is not None: + # look up the bond id based on the given name - this will throw an error if the bond type does not exist + bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) + self.bond_type_id = bond_type_id + self.cpp_updater.bond_type = self.bond_type_id + + if max_bonds_1 is not None: + if max_bonds_1 <=0: + hoomd.context.msg.error('update.dynamic_bond: number of maximum bonds for group 1 is ' + str(max_bonds_1) + ' <=0 .\n') + raise ValueError('update.dynamic_bond: number of maximum bonds for group 1 is smaller or equal to zero.') + self.max_bonds_1 = max_bonds_1 + self.cpp_updater.max_bonds_group_1 = self.max_bonds_1 + + if max_bonds_2 is not None: + if max_bonds_2 <=0: + hoomd.context.msg.error('update.dynamic_bond: number of maximum bonds for group 2 is ' + str(max_bonds_2) + ' <=0 .\n') + raise ValueError('update.dynamic_bond: number of maximum bonds for group 2 is smaller or equal to zero.') + self.max_bonds_2 = max_bonds_2 + self.cpp_updater.max_bonds_group_2 = self.max_bonds_2 + + if nlist is not None: + self.nlist = nlist + self.cpp_updater.setNeighbourList(self.nlist.cpp_nlist) + + + + + #self.nlist = nlist + #self.nlist_exclusions = nlist_exclusions + #self.cpp_updater = cpp_class(hoomd.context.current.system_definition, + # self.nlist.cpp_nlist, + # self.nlist_exclusions, + # group_1.cpp_group, + # group_2.cpp_group, + # self.r_cut, + # bond_type_id, + # max_bonds_1, + # max_bonds_2) From 87e0a1348bb11399a72aa5be575d0161402f9269 Mon Sep 17 00:00:00 2001 From: Antonia Statt <> Date: Tue, 24 May 2022 11:07:58 +0200 Subject: [PATCH 14/45] cleanup --- azplugins/CMakeLists.txt | 3 -- azplugins/DynamicBondUpdater.cc | 70 ++++++++++++++++++------------ azplugins/DynamicBondUpdater.h | 27 +++++++++--- azplugins/DynamicBondUpdaterGPU.cc | 10 ++--- azplugins/DynamicBondUpdaterGPU.h | 3 +- azplugins/RNGIdentifiers.h | 1 + azplugins/update.py | 40 ++++++++--------- 7 files changed, 91 insertions(+), 63 deletions(-) diff --git a/azplugins/CMakeLists.txt b/azplugins/CMakeLists.txt index c2ade18c..7affa026 100644 --- a/azplugins/CMakeLists.txt +++ b/azplugins/CMakeLists.txt @@ -34,11 +34,8 @@ endif(NOT HOOMD_EXTERNAL_BUILD) set(_${COMPONENT_NAME}_sources module.cc BounceBackGeometry.cc -<<<<<<< HEAD DynamicBondUpdater.cc -======= GroupVelocityCompute.cc ->>>>>>> main ImplicitEvaporator.cc ImplicitDropletEvaporator.cc ImplicitPlaneEvaporator.cc diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index df47ca5a..87b6b64f 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -9,7 +9,8 @@ */ #include "DynamicBondUpdater.h" - +#include "hoomd/RandomNumbers.h" +#include "RNGIdentifiers.h" namespace azplugins { @@ -22,10 +23,11 @@ namespace azplugins */ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, - std::shared_ptr group_2) + std::shared_ptr group_2, + unsigned int seed) : Updater(sysdef), - m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(0), m_bond_type(0xffffffff), + m_group_1(group_1), m_group_2(group_2), m_seed(seed), m_groups_identical(false), + m_r_cut(0), m_probability(0), m_bond_type(0xffffffff), m_max_bonds_group_1(0),m_max_bonds_group_2(0), m_pair_nlist(nullptr), m_pair_nlist_exclusions_set(false), m_box_changed(true), m_max_N_changed(true) @@ -49,14 +51,16 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2) + unsigned int max_bonds_group_2, + unsigned int seed) : Updater(sysdef), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(r_cut), m_bond_type(bond_type), + m_r_cut(r_cut), m_probability(probability), m_bond_type(bond_type), m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), - m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), + m_seed(seed),m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; @@ -127,7 +131,7 @@ void DynamicBondUpdater::update(unsigned int timestep) filterPossibleBonds(); // this function is not easily implemented on the GPU, uses addBondedGroup() - makeBonds(); + makeBonds(timestep); if (m_prof) m_prof->pop(); } @@ -468,7 +472,7 @@ void DynamicBondUpdater::traverseTree() /*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist and * m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds array. -* Then, all invalid (0,0,0), dublicate and existing bonds are removed from m_all_possible_bonds. It +* Then, all invalid (0,0,0), dublicated, and existing bonds are removed from m_all_possible_bonds. It * is sorted by distance (shortest to longest) between the two particles in the possible bond. */ void DynamicBondUpdater::filterPossibleBonds() @@ -543,31 +547,34 @@ void DynamicBondUpdater::filterPossibleBonds() // remove a possible bond if it already exists. It also removes zeros, e.g. // (0,0,0), which fill the unused spots in the array. auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + size, - [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + h_all_possible_bonds.data + size, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); - // then sort array by distance between particles in the found possible bond pairs - // performance is better if remove_if happens before sort - std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); + // then sort array by distance between particles in the found possible bond pairs + // performance is better if remove_if happens before sort + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); - // now make sure each possible bond is in the array only once by comparing tags - auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); + // now make sure each possible bond is in the array only once by comparing tags + auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); + m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); - // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] - // should contain only unique entries of possible bonds which are not yet formed. - if (m_prof) m_prof->pop(); + // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] + // should contain only unique entries of possible bonds which are not yet formed. + if (m_prof) m_prof->pop(); } /*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds * and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as * to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list * should be updated. +* +* Note: this function is very hard to parallelize on the GPU since we need to go through the bonds sequentially +* to prevent forming too many bonds in one step. Have not found a good way of doing this on the GPU. */ -void DynamicBondUpdater::makeBonds() +void DynamicBondUpdater::makeBonds(unsigned int timestep) { if (m_prof) m_prof->push("makeBonds"); @@ -578,7 +585,7 @@ void DynamicBondUpdater::makeBonds() // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. - // This also makes it very difficult to do on the gpu. + // This also makes it very difficult to do on the GPU. ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); @@ -590,10 +597,16 @@ void DynamicBondUpdater::makeBonds() unsigned int tag_i = __scalar_as_int(d.x); unsigned int tag_j = __scalar_as_int(d.y); - //todo: put in other external criteria here, e.g. probability of bond formation, max number of bonds possible in one step, etc. + //todo: put in other external criteria here, e.g. max number of bonds possible in one step, etc. //todo: randomize which bonds are formed or keep them ordered by their distances ? - if ( m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i] && - m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j] ) + //todo: would it be faster/better to create the rng outside of the loop? + hoomd::RandomGenerator rng(azplugins::RNGIdentifier::DynamicBondUpdater, m_seed, i, timestep); + hoomd::UniformDistribution uniform(0, 1); + const Scalar random = uniform(rng); + + if (m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i] && + m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j] && + random < m_probability) { m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); @@ -758,11 +771,12 @@ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init< std::shared_ptr ,std::shared_ptr, std::shared_ptr>()) + .def(py::init< std::shared_ptr ,std::shared_ptr, std::shared_ptr, unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, unsigned int, unsigned int, unsigned int>()) + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int,unsigned int>()) .def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) .def_property("bond_type", &DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); diff --git a/azplugins/DynamicBondUpdater.h b/azplugins/DynamicBondUpdater.h index c50d26bc..384edc77 100644 --- a/azplugins/DynamicBondUpdater.h +++ b/azplugins/DynamicBondUpdater.h @@ -31,7 +31,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Simple constructor DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, - std::shared_ptr group_2); + std::shared_ptr group_2, + unsigned int seed); //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, @@ -39,9 +40,11 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2); + unsigned int max_bonds_group_2, + unsigned int seed); //! Destructor virtual ~DynamicBondUpdater(); @@ -103,7 +106,19 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater { return m_max_bonds_group_2; } - + //! Set the maximum number of bonds on particles in group_2 + /*! + * \param max_bonds_group_2 max number of bonds formed by particles in group_2 + */ + void setProbability(Scalar probability) + { + m_probability = probability; + } + //! Get the maximum number of bonds on particles in group_2 + unsigned int getProbability() + { + return m_probability; + } //! Set the hoomd neighbor list /*! * \param nlist hoomd NeighborList pointer @@ -122,10 +137,12 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr m_group_2; //!< Second particle group to form bonds with bool m_groups_identical; - Scalar m_r_cut; //!< cutoff for the bond forming criterion + Scalar m_r_cut; //!< cutoff for the bond forming criterion + Scalar m_probability; //!< probability of bond formation if bond can be formed (i.e. within cutoff) unsigned int m_bond_type; //!< Type id of the bond to form unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group + unsigned int m_seed; //!< seed for random number generator for bond probability unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds @@ -159,7 +176,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); - void makeBonds(); + void makeBonds(unsigned int timestep); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 25bffcae..cc572ea9 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -184,9 +184,9 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() if (m_prof) m_prof->pop(); -// at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] -// should contain only unique entries of possible bonds which are not yet formed. - } + // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] + // should contain only unique entries of possible bonds which are not yet formed. + } /*! @@ -257,9 +257,9 @@ namespace detail { namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init,std::shared_ptr,std::shared_ptr>()) + .def(py::init,std::shared_ptr,std::shared_ptr,unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, unsigned int, unsigned int, unsigned int>()); + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); } } // end namespace detail diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index cfa38e7e..0408c88d 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -43,7 +43,8 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, - Scalar r_cut, + const Scalar r_cut, + const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2); diff --git a/azplugins/RNGIdentifiers.h b/azplugins/RNGIdentifiers.h index ca5ed92f..bf2bf90f 100644 --- a/azplugins/RNGIdentifiers.h +++ b/azplugins/RNGIdentifiers.h @@ -17,6 +17,7 @@ struct RNGIdentifier { // hoomd's identifiers, changed by +/- 1 static const uint32_t DPDEvaluatorGeneralWeight = 0x4a84f5d1; + static const uint32_t DynamicBondUpdater = 0x2a84f5d1; static const uint32_t TwoStepBrownianFlow = 0x431287fe; static const uint32_t TwoStepLangevinFlow = 0x89abcdee; static const uint32_t SinusoidalChannelFiller = 0x231b87d1; diff --git a/azplugins/update.py b/azplugins/update.py index 3af0bf05..1c7a3340 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -155,6 +155,8 @@ class dynamic_bond(hoomd.update._updater): group_2 (:py:mod:`hoomd.group`): Second particle group to form bonds between max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have + seed (int): Seed to the pseudo-random number generator + probability(float): Probability of bond formation, default = 1 nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions period (int): Particle types will be updated every *period* time steps phase (int): When -1, start on the current time step. Otherwise, execute @@ -183,11 +185,11 @@ class dynamic_bond(hoomd.update._updater): azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,bond_type='bond', group_1=hoomd.group.all(),group_2=hoomd.group.all(), max_bonds_1=3,max_bonds_2=3) - azplugins.update.types(r_cut=1.0,bond_type='bond', + azplugins.update.types(r_cut=1.0,probability=1.0, bond_type='bond', group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) """ - def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,nlist=None,period=1, phase=0): + def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,seed,probability=1,nlist=None,period=1, phase=0): hoomd.util.print_status_line() hoomd.update._updater.__init__(self) @@ -197,21 +199,22 @@ def __init__(self,r_cut,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,nlis else: cpp_class = _azplugins.DynamicBondUpdaterGPU - self.cpp_updater = cpp_class(hoomd.context.current.system_definition,group_1.cpp_group,group_2.cpp_group) + self.cpp_updater = cpp_class(hoomd.context.current.system_definition,group_1.cpp_group,group_2.cpp_group,seed) - self.metadata_fields = ['r_cut','bond_type','group_1', 'group_2', 'max_bonds_1','max_bonds_2','nlist'] + self.metadata_fields = ['r_cut','probability','bond_type','group_1', 'group_2', 'max_bonds_1','max_bonds_2','nlist'] self.setupUpdater(period, phase) hoomd.util.quiet_status() - self.set_params(r_cut,bond_type,max_bonds_1,max_bonds_2,nlist) + self.set_params(r_cut,probability,bond_type,max_bonds_1,max_bonds_2,nlist) hoomd.util.unquiet_status() - def set_params(self, r_cut=None, bond_type=None, max_bonds_1=None, max_bonds_2=None, nlist=None): + def set_params(self, r_cut=None, probability=None, bond_type=None, max_bonds_1=None, max_bonds_2=None, nlist=None): R""" Set the dynamic_bond parameters. Args: r_cut (float): Distance cutoff for making bonds between particles + probability (float): Probability of bond formation bond_type (str): Type of bond to be formed max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have @@ -232,6 +235,16 @@ def set_params(self, r_cut=None, bond_type=None, max_bonds_1=None, max_bonds_2=N self.r_cut = r_cut self.cpp_updater.r_cut = self.r_cut + if probability is not None: + if probability <0: + hoomd.context.msg.error('update.dynamic_bond: probability ' + str(probability) + ' <0 .\n') + raise ValueError('update.dynamic_bond: probability is smaller than zero.') + if probability >1: + hoomd.context.msg.error('update.dynamic_bond: probability ' + str(probability) + ' >1 .\n') + raise ValueError('update.dynamic_bond: probability is larger than one.') + self.probability = probability + self.cpp_updater.probability = self.probability + if bond_type is not None: # look up the bond id based on the given name - this will throw an error if the bond type does not exist bond_type_id = hoomd.context.current.system_definition.getBondData().getTypeByName(bond_type) @@ -255,18 +268,3 @@ def set_params(self, r_cut=None, bond_type=None, max_bonds_1=None, max_bonds_2=N if nlist is not None: self.nlist = nlist self.cpp_updater.setNeighbourList(self.nlist.cpp_nlist) - - - - - #self.nlist = nlist - #self.nlist_exclusions = nlist_exclusions - #self.cpp_updater = cpp_class(hoomd.context.current.system_definition, - # self.nlist.cpp_nlist, - # self.nlist_exclusions, - # group_1.cpp_group, - # group_2.cpp_group, - # self.r_cut, - # bond_type_id, - # max_bonds_1, - # max_bonds_2) From 87aa00aa54ff7308da7133d08f929309a57df347 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 7 Jul 2022 14:42:08 -0500 Subject: [PATCH 15/45] Fix stray merge leftovers --- azplugins/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/azplugins/CMakeLists.txt b/azplugins/CMakeLists.txt index 95562c8b..d9493e25 100644 --- a/azplugins/CMakeLists.txt +++ b/azplugins/CMakeLists.txt @@ -34,11 +34,8 @@ endif(NOT HOOMD_EXTERNAL_BUILD) set(_${COMPONENT_NAME}_sources module.cc BounceBackGeometry.cc -<<<<<<< HEAD DynamicBondUpdater.cc -======= GroupVelocityCompute.cc ->>>>>>> main ImplicitEvaporator.cc ImplicitDropletEvaporator.cc ImplicitPlaneEvaporator.cc From 983005ef340e61a368a3ceb7233cb0ab4cb2d484 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Tue, 2 Aug 2022 14:18:11 -0500 Subject: [PATCH 16/45] Fix naming scheme for cuda 11 --- azplugins/DynamicBondUpdaterGPU.cu | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/azplugins/DynamicBondUpdaterGPU.cu b/azplugins/DynamicBondUpdaterGPU.cu index 2d2b9f8d..a2df9193 100644 --- a/azplugins/DynamicBondUpdaterGPU.cu +++ b/azplugins/DynamicBondUpdaterGPU.cu @@ -10,12 +10,12 @@ #include "hoomd/HOOMDMath.h" #include "DynamicBondUpdaterGPU.cuh" -#include +//#include #include // todo: should azplugins have its own "extern"? #include "hoomd/extern/neighbor/neighbor/LBVH.cuh" #include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" -#include "hoomd/extern/cub/cub/cub.cuh" +//#include "hoomd/extern/cub/cub/cub.cuh" namespace azplugins @@ -249,20 +249,20 @@ cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bo { if (size == 0) return cudaSuccess; // wrapper for pointer needed for thrust - thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + HOOMD_THRUST::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); isZeroBondGPU zero; - thrust::device_ptr last0 = thrust::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap + size, zero); - unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); + HOOMD_THRUST::device_ptr last0 = HOOMD_THRUST::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap + size, zero); + unsigned int l0 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last0); // sort remainder by distance, should make all identical bonds consequtive SortBondsGPU sort; - thrust::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); + HOOMD_THRUST::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); // thrust::unique only removes identical consequtive elements, so sort above is needed. CompareBondsGPU comp; - thrust::device_ptr last1 = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); - unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); + HOOMD_THRUST::device_ptr last1 = HOOMD_THRUST::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); + unsigned int l1 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last1); *d_max_non_zero_bonds=l1; From c7d6a044bdbf7cce87ad7549cc37fce78731559f Mon Sep 17 00:00:00 2001 From: Antonia Statt <> Date: Tue, 13 Sep 2022 14:59:53 -0500 Subject: [PATCH 17/45] Add missing argument to DynamicBondUpdateGPU --- azplugins/DynamicBondUpdater.cc | 8 ++++---- azplugins/DynamicBondUpdaterGPU.cc | 4 +++- azplugins/update.py | 9 +++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/azplugins/DynamicBondUpdater.cc b/azplugins/DynamicBondUpdater.cc index 87b6b64f..c31ffb8c 100644 --- a/azplugins/DynamicBondUpdater.cc +++ b/azplugins/DynamicBondUpdater.cc @@ -604,9 +604,9 @@ void DynamicBondUpdater::makeBonds(unsigned int timestep) hoomd::UniformDistribution uniform(0, 1); const Scalar random = uniform(rng); - if (m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i] && - m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j] && - random < m_probability) + if ((m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i]) && + (m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j]) && + (random < m_probability)) { m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); AddtoExistingBonds(tag_i,tag_j); @@ -773,7 +773,7 @@ void export_DynamicBondUpdater(pybind11::module& m) py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) .def(py::init< std::shared_ptr ,std::shared_ptr, std::shared_ptr, unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int,unsigned int>()) + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()) .def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index cc572ea9..328e0389 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -39,11 +39,12 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, + const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2) : DynamicBondUpdater(sysdef, pair_nlist, group_1, group_2, - r_cut,bond_type, max_bonds_group_1, max_bonds_group_2), + r_cut,probability,bond_type, max_bonds_group_1, max_bonds_group_2), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { @@ -256,6 +257,7 @@ namespace detail void export_DynamicBondUpdaterGPU(pybind11::module& m) { namespace py = pybind11; + py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) .def(py::init,std::shared_ptr,std::shared_ptr,unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, diff --git a/azplugins/update.py b/azplugins/update.py index 1c7a3340..accb5f0b 100644 --- a/azplugins/update.py +++ b/azplugins/update.py @@ -9,9 +9,10 @@ :nosignatures: types + dynamic_bond .. autoclass:: types - +.. autoclass:: dynamic_bond """ import hoomd from hoomd import _hoomd @@ -150,13 +151,13 @@ class dynamic_bond(hoomd.update._updater): Args: r_cut (float): Distance cutoff for making bonds between particles + probability (float): Probability of bond formation, between 0 and 1, default = 1 bond_type (str): Type of bond to be formed group_1 (:py:mod:`hoomd.group`): First particle group to form bonds between group_2 (:py:mod:`hoomd.group`): Second particle group to form bonds between max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have seed (int): Seed to the pseudo-random number generator - probability(float): Probability of bond formation, default = 1 nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions period (int): Particle types will be updated every *period* time steps phase (int): When -1, start on the current time step. Otherwise, execute @@ -236,8 +237,8 @@ def set_params(self, r_cut=None, probability=None, bond_type=None, max_bonds_1=N self.cpp_updater.r_cut = self.r_cut if probability is not None: - if probability <0: - hoomd.context.msg.error('update.dynamic_bond: probability ' + str(probability) + ' <0 .\n') + if probability <=0: + hoomd.context.msg.error('update.dynamic_bond: probability ' + str(probability) + ' <=0 .\n') raise ValueError('update.dynamic_bond: probability is smaller than zero.') if probability >1: hoomd.context.msg.error('update.dynamic_bond: probability ' + str(probability) + ' >1 .\n') From 9683c58236636db908e487951a3e9ea47b2e82b1 Mon Sep 17 00:00:00 2001 From: Antonia Statt <> Date: Tue, 13 Sep 2022 15:08:41 -0500 Subject: [PATCH 18/45] Add seed for random numbers --- azplugins/DynamicBondUpdaterGPU.cc | 10 ++++++---- azplugins/DynamicBondUpdaterGPU.h | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 328e0389..53e1ff86 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -23,8 +23,9 @@ namespace azplugins */ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr group_1, - std::shared_ptr group_2) - : DynamicBondUpdater(sysdef,group_1,group_2), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), + std::shared_ptr group_2, + unsigned int seed) + : DynamicBondUpdater(sysdef, group_1, group_2, seed), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; @@ -42,9 +43,10 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2) + unsigned int max_bonds_group_2, + unsigned int seed) : DynamicBondUpdater(sysdef, pair_nlist, group_1, group_2, - r_cut,probability,bond_type, max_bonds_group_1, max_bonds_group_2), + r_cut,probability, bond_type, max_bonds_group_1, max_bonds_group_2), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { diff --git a/azplugins/DynamicBondUpdaterGPU.h b/azplugins/DynamicBondUpdaterGPU.h index 0408c88d..16c8383f 100644 --- a/azplugins/DynamicBondUpdaterGPU.h +++ b/azplugins/DynamicBondUpdaterGPU.h @@ -36,7 +36,8 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater //! Simple constructor DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr group_1, - std::shared_ptr group_2); + std::shared_ptr group_2, + unsigned int seed); //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, @@ -47,7 +48,8 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater const Scalar probability, unsigned int bond_type, unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2); + unsigned int max_bonds_group_2, + unsigned int seed); //! Destructor virtual ~DynamicBondUpdaterGPU(); From 6e0cb0db5f8c6dac02e1a56f269b7fcb682ca47d Mon Sep 17 00:00:00 2001 From: Antonia Statt <> Date: Tue, 13 Sep 2022 15:32:26 -0500 Subject: [PATCH 19/45] Add missing seed argument --- azplugins/DynamicBondUpdaterGPU.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azplugins/DynamicBondUpdaterGPU.cc b/azplugins/DynamicBondUpdaterGPU.cc index 53e1ff86..96358d67 100644 --- a/azplugins/DynamicBondUpdaterGPU.cc +++ b/azplugins/DynamicBondUpdaterGPU.cc @@ -46,7 +46,7 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s unsigned int max_bonds_group_2, unsigned int seed) : DynamicBondUpdater(sysdef, pair_nlist, group_1, group_2, - r_cut,probability, bond_type, max_bonds_group_1, max_bonds_group_2), + r_cut, probability, bond_type, max_bonds_group_1, max_bonds_group_2,seed), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { @@ -261,7 +261,7 @@ namespace detail namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init,std::shared_ptr,std::shared_ptr,unsigned int>()) + .def(py::init, std::shared_ptr, std::shared_ptr, unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); } From 3e6acccb0305b30f5e0734b3fdcca675738c688d Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 19 Dec 2025 14:37:11 -0600 Subject: [PATCH 20/45] Update namespaces,arguments etc to compile with hoomd 5 --- src/CMakeLists.txt | 3 ++ src/DynamicBondUpdater.cc | 81 ++++++++++++++++++------------------ src/DynamicBondUpdater.h | 44 ++++++++++++-------- src/DynamicBondUpdaterGPU.cc | 23 ++-------- src/DynamicBondUpdaterGPU.h | 6 ++- src/RNGIdentifiers.h | 1 + src/module.cc | 6 +++ 7 files changed, 85 insertions(+), 79 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 64a36dde..b4711021 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(COMPONENT_NAME azplugins) # TODO: List all host C++ source code files in _${COMPONENT_NAME}_sources. set(_${COMPONENT_NAME}_sources ConstantFlow.cc + DynamicBondUpdater.cc export_ImagePotentialBondHarmonic.cc module.cc ParabolicFlow.cc @@ -11,6 +12,7 @@ set(_${COMPONENT_NAME}_sources ) if (ENABLE_HIP) list(APPEND _${COMPONENT_NAME}_sources + DynamicBondUpdaterGPU.cc export_ImagePotentialBondHarmonicGPU.cc VelocityComputeGPU.cc ) @@ -18,6 +20,7 @@ endif() # TODO: List all GPU C++ source code files in _${COMPONENT_NAME}_cu_sources. set(_${COMPONENT_NAME}_cu_sources + DynamicBondUpdaterGPU.cu ImagePotentialBondGPUKernel.cu VelocityComputeGPU.cu VelocityFieldComputeGPU.cu diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index d2be9c75..9a8c2ab6 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -12,7 +12,11 @@ #include "DynamicBondUpdater.h" #include "hoomd/RandomNumbers.h" #include "RNGIdentifiers.h" +#include "hoomd/AABBTree.h" +#include "hoomd/AABB.h" +namespace hoomd +{ namespace azplugins { @@ -23,13 +27,14 @@ namespace azplugins * This constructor requires that the user properly initialize the system via setters. */ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr trigger, std::shared_ptr group_1, std::shared_ptr group_2, - unsigned int seed) - : Updater(sysdef), - m_group_1(group_1), m_group_2(group_2), m_seed(seed), m_groups_identical(false), - m_r_cut(0), m_probability(0), m_bond_type(0xffffffff), - m_max_bonds_group_1(0),m_max_bonds_group_2(0), + uint16_t seed) + : Updater(sysdef, trigger), + m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), + m_r_cut(0), m_probability(0.0), m_bond_type(0xffffffff), + m_max_bonds_group_1(0),m_max_bonds_group_2(0), m_seed(seed), m_pair_nlist(nullptr), m_pair_nlist_exclusions_set(false), m_box_changed(true), m_max_N_changed(true) { @@ -48,7 +53,8 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, } DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr pair_nlist, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -56,8 +62,8 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2, - unsigned int seed) - : Updater(sysdef), + uint16_t seed) + : Updater(sysdef, trigger), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), m_r_cut(r_cut), m_probability(probability), m_bond_type(bond_type), m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), @@ -92,7 +98,6 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(unsigned int timestep) { - if (m_prof) m_prof->push("DynamicBondUpdater"); // don't do anything if either one of the groups is empty if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) @@ -133,7 +138,6 @@ void DynamicBondUpdater::update(unsigned int timestep) filterPossibleBonds(); // this function is not easily implemented on the GPU, uses addBondedGroup() makeBonds(timestep); - if (m_prof) m_prof->pop(); } @@ -301,7 +305,7 @@ void DynamicBondUpdater::resizeExistingBondList() unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); // update the indexer - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), new_height); + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; } @@ -316,7 +320,7 @@ void DynamicBondUpdater::resizePossibleBondlists() m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; - GlobalArray nlist(size, m_exec_conf); + GPUArray nlist(size, m_exec_conf); m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -338,7 +342,7 @@ void DynamicBondUpdater::allocateParticleArrays() GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list_indexer = Index2D(m_existing_bonds_list.getPitch(), m_existing_bonds_list.getHeight()); + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), (unsigned int)m_existing_bonds_list.getHeight()); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); @@ -347,13 +351,13 @@ void DynamicBondUpdater::allocateParticleArrays() memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); // allocate the number of neighbors (per particle) for finding bonds - GlobalArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); + GPUArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); m_n_neigh.swap(n_neigh); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); // default allocation of m_max_bonds neighbors per particle for the neighborlist - GlobalArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); + GPUArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); m_n_list.swap(nlist); calculateExistingBonds(); @@ -366,23 +370,21 @@ void DynamicBondUpdater::allocateParticleArrays() */ void DynamicBondUpdater::buildTree() { - if (m_prof) m_prof->push("buildTree"); // make tree for group 2 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); + ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); for (unsigned int group_idx = 0; group_idx < m_group_2->getNumMembers(); group_idx++) { unsigned int i = m_group_2->getMemberIndex(group_idx); // make a point particle AABB vec3 my_pos(h_postype.data[i]); - h_aabbs.data[group_idx] = hpmc::detail::AABB(my_pos,i); + h_aabbs.data[group_idx] = hoomd::detail::AABB(my_pos,i); } m_aabb_tree.buildTree(&(h_aabbs.data[0]) , m_group_2->getNumMembers()); - if (m_prof) m_prof->pop(); } @@ -392,7 +394,6 @@ void DynamicBondUpdater::buildTree() */ void DynamicBondUpdater::traverseTree() { - if (m_prof) m_prof->push("traverseTree"); ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); @@ -420,12 +421,12 @@ void DynamicBondUpdater::traverseTree() { // make an AABB for the image of this particle vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hpmc::detail::AABB aabb = hpmc::detail::AABB(pos_i_image,m_r_cut); - hpmc::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; + hoomd::detail::AABB aabb = hoomd::detail::AABB(pos_i_image,m_r_cut); + hoomd::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; // stackless traversal of the tree for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) { - if (overlap(cur_aabb_tree->getNodeAABB(cur_node_idx), aabb)) + if (aabb.overlaps(cur_aabb_tree->getNodeAABB(cur_node_idx))) { if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) { @@ -467,7 +468,6 @@ void DynamicBondUpdater::traverseTree() } // end loop over images h_n_neigh.data[group_idx] = n_curr_bond; } // end loop over group 1 - if (m_prof) m_prof->pop(); } @@ -478,7 +478,6 @@ void DynamicBondUpdater::traverseTree() */ void DynamicBondUpdater::filterPossibleBonds() { - if (m_prof) m_prof->push("filterPossibleBonds"); //copy data from h_n_list to h_all_possible_bonds ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); @@ -551,7 +550,7 @@ void DynamicBondUpdater::filterPossibleBonds() h_all_possible_bonds.data + size, [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last2); + m_num_all_possible_bonds = (unsigned int) std::distance(h_all_possible_bonds.data,last2); // then sort array by distance between particles in the found possible bond pairs // performance is better if remove_if happens before sort @@ -559,12 +558,12 @@ void DynamicBondUpdater::filterPossibleBonds() // now make sure each possible bond is in the array only once by comparing tags auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); - m_num_all_possible_bonds = std::distance(h_all_possible_bonds.data,last); + m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data,last); // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. - if (m_prof) m_prof->pop(); + } /*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds @@ -577,9 +576,6 @@ void DynamicBondUpdater::filterPossibleBonds() */ void DynamicBondUpdater::makeBonds(unsigned int timestep) { - - if (m_prof) m_prof->push("makeBonds"); - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); @@ -598,10 +594,15 @@ void DynamicBondUpdater::makeBonds(unsigned int timestep) unsigned int tag_i = __scalar_as_int(d.x); unsigned int tag_j = __scalar_as_int(d.y); - //todo: put in other external criteria here, e.g. max number of bonds possible in one step, etc. - //todo: randomize which bonds are formed or keep them ordered by their distances ? + //todo: put in other external criteria here, e.g. max number of bonds possible in one step, etc. + //todo: randomize which bonds are formed or keep them ordered by their distances ? //todo: would it be faster/better to create the rng outside of the loop? - hoomd::RandomGenerator rng(azplugins::RNGIdentifier::DynamicBondUpdater, m_seed, i, timestep); + hoomd::RandomGenerator rng( + hoomd::Seed(hoomd::azplugins::detail::RNGIdentifier::DynamicBondUpdater, + timestep, + m_seed), + hoomd::Counter()); + hoomd::UniformDistribution uniform(0, 1); const Scalar random = uniform(rng); @@ -617,8 +618,6 @@ void DynamicBondUpdater::makeBonds(unsigned int timestep) } } } - - if (m_prof) m_prof->pop(); } @@ -772,9 +771,9 @@ void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) - .def(py::init< std::shared_ptr ,std::shared_ptr, std::shared_ptr, unsigned int>()) - .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()) + .def(py::init< std::shared_ptr , std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) + .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, uint16_t>()) .def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) @@ -782,6 +781,8 @@ void export_DynamicBondUpdater(pybind11::module& m) .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } - } +} // end namespace detail } // end namespace azplugins + +} // end namespace hoomd diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 384edc77..e2db54c7 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -15,28 +15,37 @@ #error This header cannot be compiled by nvcc #endif +#ifndef __HIPCC__ +#include +#endif + #include "hoomd/AABBTree.h" #include "hoomd/md/NeighborList.h" #include "hoomd/Updater.h" #include "hoomd/ParticleGroup.h" -#include "hoomd/extern/pybind/include/pybind11/pybind11.h" + + +namespace hoomd +{ namespace azplugins { -class PYBIND11_EXPORT DynamicBondUpdater : public Updater +class PYBIND11_EXPORT DynamicBondUpdater : public hoomd::Updater { public: //! Simple constructor DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr trigger, std::shared_ptr group_1, std::shared_ptr group_2, - unsigned int seed); + uint16_t seed); //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr pair_nlist, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -44,7 +53,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2, - unsigned int seed); + uint16_t seed); //! Destructor virtual ~DynamicBondUpdater(); @@ -106,16 +115,13 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater { return m_max_bonds_group_2; } - //! Set the maximum number of bonds on particles in group_2 - /*! - * \param max_bonds_group_2 max number of bonds formed by particles in group_2 - */ + //! set probablilty void setProbability(Scalar probability) { m_probability = probability; } - //! Get the maximum number of bonds on particles in group_2 - unsigned int getProbability() + //! Get the probability + Scalar getProbability() { return m_probability; } @@ -123,7 +129,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater /*! * \param nlist hoomd NeighborList pointer */ - void setNeighbourList( std::shared_ptr nlist) + void setNeighbourList( std::shared_ptr nlist) { m_pair_nlist = nlist; m_pair_nlist_exclusions_set = true; @@ -142,7 +148,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_bond_type; //!< Type id of the bond to form unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - unsigned int m_seed; //!< seed for random number generator for bond probability + uint16_t m_seed; //!< seed for random number generator for bond probability unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds @@ -150,11 +156,11 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - GlobalArray m_n_list; //!< Neighbor list data - GlobalArray m_n_neigh; //!< Number of neighbors for each particle + GPUArray m_n_list; //!< Neighbor list data + GPUArray m_n_neigh; //!< Number of neighbors for each particle - hpmc::detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 - GPUVector m_aabbs; //!< Flat array of AABBs of particles in group_2 + detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 + GPUVector m_aabbs; //!< Flat array of AABBs of particles in group_2 std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal unsigned int m_n_images; //!< The number of image vectors to check @@ -163,7 +169,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list - std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set + std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set //! filter out existing and doublicate bonds from all found possible bonds @@ -214,4 +220,6 @@ void export_DynamicBondUpdater(pybind11::module& m); } // end namespace azplugins +} // end namespace hoomd + #endif // AZPLUGINS_TYPE_UPDATER_H_ diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 96358d67..4fc0eb0d 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -36,7 +36,7 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s } DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr pair_nlist, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, const Scalar r_cut, @@ -67,8 +67,6 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() void DynamicBondUpdaterGPU::buildTree() { - if (m_prof) m_prof->push("buildTree"); - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); const BoxDim lbvh_box = getLBVHBox(); @@ -81,13 +79,10 @@ void DynamicBondUpdaterGPU::buildTree() lbvh_box.getLo(), lbvh_box.getHi()); - - if (m_prof) m_prof->pop(); } void DynamicBondUpdaterGPU::traverseTree() { - if (m_prof) m_prof->push("traverseTree"); ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::overwrite); ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); @@ -117,8 +112,6 @@ void DynamicBondUpdaterGPU::traverseTree() m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); - if (m_prof) m_prof->pop(); - } @@ -126,7 +119,6 @@ void DynamicBondUpdaterGPU::traverseTree() void DynamicBondUpdaterGPU::filterPossibleBonds() { - if (m_prof) m_prof->push("filterPossibleBonds copy "); //copy data from m_n_list to d_all_possible_bonds. nlist saves indices and the existing bonds have to be stored by tags //so copy data first then sort out existing bonds const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; @@ -159,8 +151,6 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_copy_tuner->end(); - if (m_prof) m_prof->pop(); - if (m_prof) m_prof->push("filterPossibleBonds remove existing"); //filter out the existing bonds - based on neighbor list exclusion handeling m_tuner_filter_bonds->begin(); @@ -173,9 +163,6 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner_filter_bonds->end(); - if (m_prof) m_prof->pop(); - if (m_prof) m_prof->push("filterPossibleBonds sort_remove"); - m_num_all_possible_bonds = 0; gpu::remove_zeros_and_sort_possible_bond_array(d_all_possible_bonds.data, @@ -184,8 +171,6 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_num_all_possible_bonds = m_num_nonzero_bonds_flag.readFlags(); - if (m_prof) m_prof->pop(); - // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. @@ -261,9 +246,9 @@ namespace detail namespace py = pybind11; py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) - .def(py::init, std::shared_ptr, std::shared_ptr, unsigned int>()) - .def(py::init, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); + .def(py::init, std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) + .def(py::init,std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, uint16_t>()); } } // end namespace detail diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 16c8383f..8af565e9 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -35,12 +35,14 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater public: //! Simple constructor DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr trigger, std::shared_ptr group_1, std::shared_ptr group_2, - unsigned int seed); + uint16_t seed); //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr trigger, std::shared_ptr nlist, std::shared_ptr group_1, std::shared_ptr group_2, @@ -49,7 +51,7 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2, - unsigned int seed); + uint16_t seed); //! Destructor virtual ~DynamicBondUpdaterGPU(); diff --git a/src/RNGIdentifiers.h b/src/RNGIdentifiers.h index 283f5cb3..d7df23f5 100644 --- a/src/RNGIdentifiers.h +++ b/src/RNGIdentifiers.h @@ -24,6 +24,7 @@ struct RNGIdentifier static const uint8_t TwoStepBrownianFlow = 201; static const uint8_t TwoStepLangevinFlow = 202; static const uint8_t ParticleEvaporator = 203; + static const uint8_t DynamicBondUpdater = 204; }; } // end namespace detail diff --git a/src/module.cc b/src/module.cc index ec89000f..989d80a0 100644 --- a/src/module.cc +++ b/src/module.cc @@ -77,6 +77,9 @@ void export_PotentialPairPerturbedLennardJones(pybind11::module&); // dpd void export_PotentialPairDPDThermoGeneralWeight(pybind11::module&); +// update +void export_DynamicBondUpdater(pybind11::module&); + // wall void export_PotentialExternalWallLJ93(pybind11::module&); @@ -105,6 +108,9 @@ void export_PotentialPairPerturbedLennardJonesGPU(pybind11::module&); // dpd void export_PotentialPairDPDThermoGeneralWeightGPU(pybind11::module&); +// update +void export_DynamicBondUpdaterGPU(pybind11::module&); + // wall void export_PotentialExternalWallLJ93GPU(pybind11::module&); From e38979172c3796cde19ab9abad101d97c0266a82 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Sat, 20 Dec 2025 14:27:39 -0600 Subject: [PATCH 21/45] Add python code --- src/update.py | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/update.py diff --git a/src/update.py b/src/update.py new file mode 100644 index 00000000..13398f3d --- /dev/null +++ b/src/update.py @@ -0,0 +1,102 @@ +# Copyright (c) 2018-2020, Michael P. Howard +# Copyright (c) 2021-2022, Auburn University +# This file is part of the azplugins project, released under the Modified BSD License. + +""" Updaters. """ + +import hoomd +from hoomd.azplugins import _azplugins +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes +from hoomd import _hoomd +from hoomd.md import _md + +class dynamic_bond(hoomd.operation.Updater): + R""" Update bonds dynamically during simulation. + + Args: + r_cut (float): Distance cutoff for making bonds between particles + probability (float): Probability of bond formation, between 0 and 1, default = 1 + bond_type (str): Type of bond to be formed + group_1 (:py:mod: `hoomd.filter.ParticleFilter`): First particle group to form bonds between + group_2 (:py:mod:`hoomd.filter.ParticleFilter`): Second particle group to form bonds between + max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have + max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have + seed (int): Seed to the pseudo-random number generator + nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions + period (int): Particle types will be updated every *period* time steps + phase (int): When -1, start on the current time step. Otherwise, execute + on steps where *(step + phase) % period* is 0. + + Forms bonds of type bond_type between particles in group_1 and group_2 during + the simulation, if particle distances are shorter than r_cut. If the neighborlist + used for the pair potential in the simulation is given as a parameter nlist, the + neighbor list exclusions will be updated to include the newly formed bonds. + Each particle has a number of maximum bonds which it can form, given by + max_bonds_1 for particles in group_1 and max_bonds_2 for group_2. + + The particles in the two groups group_1 and group_2 should be completely + separate with no common elements, e.g. two different types, or the two + groups should be identical, where now max_bonds_1 needs to be equal to max_bonds_2. + + + .. warning:: + If the groups group_1 and group_2 are modified during the simulation, this + Updater will not be updated to reflect the changes. It is the user's + responsibility to ensure that the groups do not change as long as this + updater is active. + + Examples:: + + azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,probability=1.0, bond_type='bond', + group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) + """ + _ext_module = _azplugins + + def __init__(self,trigger,r_cut,nlist,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,seed,probability=1,period=1, phase=0): + super().__init__(trigger) + + param_dict = ParameterDict( + r_cut=float(r_cut), + nlist =OnlyTypes(hoomd.md.nlist.NeighborList,strict=True, allow_none=False), + group_1=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), + group_2=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), + max_bonds_1=OnlyTypes(int, allow_none=True), + max_bonds_2=OnlyTypes(int, allow_none=True), + bond_type=OnlyTypes(str,strict=True), + seed = int(seed), + probability = float(probability) + ) + param_dict["nlist"] = nlist + param_dict["group_1"] = group_1 + param_dict["group_2"] = group_2 + param_dict["max_bonds_1"] = max_bonds_1 + param_dict["max_bonds_2"] = max_bonds_2 + param_dict["bond_type"] = bond_type + + print("in init dynamic bonds") + self._param_dict.update(param_dict) + + def _attach_hook(self): + sim = self._simulation + print("in attach hook dynamic bonds") + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _azplugins.DynamicBondUpdaterGPU + else: + cpp_class = _azplugins.DynamicBondUpdater + + if self.filter is not None: + group = sim.state._get_group(self.filter) + else: + group = None + + self._cpp_obj = cpp_class( + sim.state._cpp_sys_def, + group, + self.include_mpcd_particles, + ) + + super()._attach_hook() + + + From 25573d5a09dfeb3d6b7337efb8f054a810c75287 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Sat, 20 Dec 2025 14:35:10 -0600 Subject: [PATCH 22/45] add python code, export error --- src/CMakeLists.txt | 1 + src/DynamicBondUpdater.cc | 5 ++++- src/DynamicBondUpdater.h | 11 +++++++++++ src/DynamicBondUpdaterGPU.cc | 4 ++-- src/__init__.py | 2 +- src/module.cc | 6 ++++++ src/update.py | 2 +- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4711021..4bbd790e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,7 @@ set(python_files conftest.py external.py flow.py + update.py pair.py wall.py ) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 9a8c2ab6..82e793d4 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -50,6 +50,8 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_num_all_possible_bonds = 0; setGroupOverlap(); + + std::cout<< "in constructor dynamic bonds" << std::endl; } DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, @@ -770,7 +772,8 @@ namespace detail void export_DynamicBondUpdater(pybind11::module& m) { namespace py = pybind11; - py::class_< DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdater", py::base()) + + py::class_< DynamicBondUpdater, Updater,std::shared_ptr >(m, "DynamicBondUpdater") .def(py::init< std::shared_ptr , std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, uint16_t>()) diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index e2db54c7..4c5d3525 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -25,6 +25,17 @@ #include "hoomd/ParticleGroup.h" +#ifndef __HIPCC__ +#define HOSTDEVICE __host__ __device__ +#else +#define HOSTDEVICE +#endif // __HIPCC__ + +#ifndef PYBIND11_EXPORT +#define PYBIND11_EXPORT __attribute__((visibility("default"))) +#endif + + namespace hoomd { diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 4fc0eb0d..ba1370f9 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -245,10 +245,10 @@ namespace detail { namespace py = pybind11; - py::class_< DynamicBondUpdaterGPU, std::shared_ptr >(m, "DynamicBondUpdaterGPU", py::base()) + py::class_< DynamicBondUpdaterGPU, DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdaterGPU") .def(py::init, std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) .def(py::init,std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, uint16_t>()); + std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); } } // end namespace detail diff --git a/src/__init__.py b/src/__init__.py index 3e71abf0..70b9f6ef 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -4,6 +4,6 @@ """azplugins.""" -from hoomd.azplugins import bond, compute, external, flow, pair, wall +from hoomd.azplugins import bond, compute, external, flow, update, pair, wall __version__ = "1.2.0" diff --git a/src/module.cc b/src/module.cc index 989d80a0..71e0d288 100644 --- a/src/module.cc +++ b/src/module.cc @@ -153,6 +153,9 @@ PYBIND11_MODULE(_azplugins, m) // dpd pair export_PotentialPairDPDThermoGeneralWeight(m); + // updater + export_DynamicBondUpdater(m); + // wall export_PotentialExternalWallLJ93(m); @@ -181,6 +184,9 @@ PYBIND11_MODULE(_azplugins, m) // dpd pair export_PotentialPairDPDThermoGeneralWeightGPU(m); + // updater + export_DynamicBondUpdaterGPU(m); + // wall export_PotentialExternalWallLJ93GPU(m); diff --git a/src/update.py b/src/update.py index 13398f3d..5f7845e3 100644 --- a/src/update.py +++ b/src/update.py @@ -89,7 +89,7 @@ def _attach_hook(self): group = sim.state._get_group(self.filter) else: group = None - + #todo: incomplete self._cpp_obj = cpp_class( sim.state._cpp_sys_def, group, From 90d826a0836a50e1c78a64a1c0c055cbc92cb3ed Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 22 Dec 2025 09:10:57 -0600 Subject: [PATCH 23/45] remove unneccessary imports --- src/DynamicBondUpdater.cc | 45 +++++++++++++++++++++++++------------ src/DynamicBondUpdater.h | 8 +++---- src/update.py | 47 +++++++++++++++++++++++++-------------- 3 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 82e793d4..fd7a2590 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -98,7 +98,7 @@ DynamicBondUpdater::~DynamicBondUpdater() /*! * \param timestep Timestep update is called */ -void DynamicBondUpdater::update(unsigned int timestep) +void DynamicBondUpdater::update(uint64_t timestep) { // don't do anything if either one of the groups is empty @@ -576,7 +576,7 @@ void DynamicBondUpdater::filterPossibleBonds() * Note: this function is very hard to parallelize on the GPU since we need to go through the bonds sequentially * to prevent forming too many bonds in one step. Have not found a good way of doing this on the GPU. */ -void DynamicBondUpdater::makeBonds(unsigned int timestep) +void DynamicBondUpdater::makeBonds(uint64_t timestep) { ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); @@ -769,20 +769,37 @@ namespace detail /*! * \param m Python module to export to */ + void export_DynamicBondUpdater(pybind11::module& m) { - namespace py = pybind11; - - py::class_< DynamicBondUpdater, Updater,std::shared_ptr >(m, "DynamicBondUpdater") - .def(py::init< std::shared_ptr , std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) - .def(py::init, std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, uint16_t>()) - .def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) - .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) - .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) - .def_property("bond_type", &DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) - .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) - .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); + + pybind11::class_< DynamicBondUpdater, Updater,std::shared_ptr>( + m, + "DynamicBondUpdater") + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t>()) + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + Scalar, + Scalar, + unsigned int, + unsigned int, + unsigned int, + uint16_t>()); + + + //.def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) + // .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + //.def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) + //.def_property("bond_type", &DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) + //.def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) + //.def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } } // end namespace detail diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 4c5d3525..013d01ab 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -42,7 +42,7 @@ namespace hoomd namespace azplugins { -class PYBIND11_EXPORT DynamicBondUpdater : public hoomd::Updater +class PYBIND11_EXPORT DynamicBondUpdater : public Updater { public: @@ -70,7 +70,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public hoomd::Updater virtual ~DynamicBondUpdater(); //! update - virtual void update(unsigned int timestep); + virtual void update(uint64_t timestep); //! Set the cutoff distance for finding bonds /*! @@ -193,7 +193,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public hoomd::Updater bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); - void makeBonds(unsigned int timestep); + void makeBonds(uint64_t timestep); void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag @@ -226,7 +226,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public hoomd::Updater namespace detail { //! Export the Evaporator to python -void export_DynamicBondUpdater(pybind11::module& m); +//void export_DynamicBondUpdater(pybind11::module& m); } // end namespace detail } // end namespace azplugins diff --git a/src/update.py b/src/update.py index 5f7845e3..526abd35 100644 --- a/src/update.py +++ b/src/update.py @@ -5,11 +5,10 @@ """ Updaters. """ import hoomd + from hoomd.azplugins import _azplugins from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import OnlyTypes -from hoomd import _hoomd -from hoomd.md import _md class dynamic_bond(hoomd.operation.Updater): R""" Update bonds dynamically during simulation. @@ -51,12 +50,12 @@ class dynamic_bond(hoomd.operation.Updater): azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,probability=1.0, bond_type='bond', group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) """ - _ext_module = _azplugins + #_ext_module = _azplugins def __init__(self,trigger,r_cut,nlist,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,seed,probability=1,period=1, phase=0): super().__init__(trigger) - param_dict = ParameterDict( + params = ParameterDict( r_cut=float(r_cut), nlist =OnlyTypes(hoomd.md.nlist.NeighborList,strict=True, allow_none=False), group_1=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), @@ -67,33 +66,47 @@ def __init__(self,trigger,r_cut,nlist,bond_type,group_1, group_2, max_bonds_1,ma seed = int(seed), probability = float(probability) ) - param_dict["nlist"] = nlist - param_dict["group_1"] = group_1 - param_dict["group_2"] = group_2 - param_dict["max_bonds_1"] = max_bonds_1 - param_dict["max_bonds_2"] = max_bonds_2 - param_dict["bond_type"] = bond_type + params.update( + dict( + r_cut = r_cut, + nlist=nlist, + group_1=group_1, + group_2=group_2, + max_bonds_1=max_bonds_1, + max_bonds_2=max_bonds_2, + bond_type = bond_type, + seed = seed, + probability = probability + ) + ) + print("parsed param dict") + self._param_dict.update(params) - print("in init dynamic bonds") - self._param_dict.update(param_dict) def _attach_hook(self): sim = self._simulation print("in attach hook dynamic bonds") + print(_azplugins) if isinstance(sim.device, hoomd.device.GPU): cpp_class = _azplugins.DynamicBondUpdaterGPU else: cpp_class = _azplugins.DynamicBondUpdater - if self.filter is not None: - group = sim.state._get_group(self.filter) + if self.group_1 is not None: + group_1 = sim.state._get_group(self.group_1) else: - group = None + group_1 = None + + if self.group_2 is not None: + group_2 = sim.state._get_group(self.group_2) + else: + group_2 = None + #todo: incomplete self._cpp_obj = cpp_class( sim.state._cpp_sys_def, - group, - self.include_mpcd_particles, + group_1, + group_2 ) super()._attach_hook() From c882e50739dde086ba133a7157fa5bf95206b230 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 22 Dec 2025 09:43:25 -0600 Subject: [PATCH 24/45] re-added __HIPCC__ flags --- src/DynamicBondUpdater.cc | 1 + src/DynamicBondUpdater.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index fd7a2590..238aceb4 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -72,6 +72,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_seed(seed),m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { + std::cout<< "in constructor dynamic bonds" << std::endl; m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect(this); diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 013d01ab..04da5c8b 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -226,7 +226,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater namespace detail { //! Export the Evaporator to python -//void export_DynamicBondUpdater(pybind11::module& m); +void export_DynamicBondUpdater(pybind11::module& m); } // end namespace detail } // end namespace azplugins From ddb7822ddad4ef952f8dbbb1172d176de8c22ed6 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 22 Dec 2025 18:00:45 -0600 Subject: [PATCH 25/45] fix compiler/export issues --- src/DynamicBondUpdater.cc | 133 +++++++++++++++++++++++++++----------- src/DynamicBondUpdater.h | 35 +++++----- src/update.py | 111 +++++++++++++++++++++++++------ 3 files changed, 206 insertions(+), 73 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 238aceb4..6d0766f6 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -28,15 +28,24 @@ namespace azplugins */ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) : Updater(sysdef, trigger), - m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(0), m_probability(0.0), m_bond_type(0xffffffff), - m_max_bonds_group_1(0),m_max_bonds_group_2(0), m_seed(seed), - m_pair_nlist(nullptr), m_pair_nlist_exclusions_set(false), - m_box_changed(true), m_max_N_changed(true) + m_group_1(group_1), + m_group_2(group_2), + m_groups_identical(false), + m_r_cut(0), + m_probability(0.0), + m_bond_type(0), + m_max_bonds_group_1(0), + m_max_bonds_group_2(0), + m_seed(seed), + m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(true), + m_box_changed(true), + m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; @@ -50,35 +59,43 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_num_all_possible_bonds = 0; setGroupOverlap(); - - std::cout<< "in constructor dynamic bonds" << std::endl; } + DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, + uint16_t seed, const Scalar r_cut, const Scalar probability, - unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2, - uint16_t seed) + unsigned int bond_type + ) : Updater(sysdef, trigger), - m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(r_cut), m_probability(probability), m_bond_type(bond_type), - m_max_bonds_group_1(max_bonds_group_1),m_max_bonds_group_2(max_bonds_group_2), - m_seed(seed),m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), - m_box_changed(true), m_max_N_changed(true) + m_group_1(group_1), + m_group_2(group_2), + m_groups_identical(false), + m_r_cut(r_cut), + m_probability(probability), + m_bond_type(bond_type), + m_max_bonds_group_1(max_bonds_group_1), + m_max_bonds_group_2(max_bonds_group_2), + m_seed(seed), + m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(true), + m_box_changed(true), + m_max_N_changed(true) { - std::cout<< "in constructor dynamic bonds" << std::endl; m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); m_bond_data = m_sysdef->getBondData(); + //m_bond_type = m_bond_data->getTypeByName(bond_type); m_max_bonds = 4; m_max_bonds_overflow = 0; @@ -101,7 +118,6 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - // don't do anything if either one of the groups is empty if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) return; @@ -127,9 +143,11 @@ void DynamicBondUpdater::update(uint64_t timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; buildTree(); + do { traverseTree(); + overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-traverse the tree if (overflowed) @@ -220,7 +238,7 @@ bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) void DynamicBondUpdater::calculateExistingBonds() { - + { // reset exisitng bond list ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); @@ -228,7 +246,8 @@ void DynamicBondUpdater::calculateExistingBonds() ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_group_1->getNumMembers()*m_existing_bonds_list_indexer.getH()); - + } + { ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); // for each of the bonds in the system - regardless of their type @@ -241,7 +260,7 @@ void DynamicBondUpdater::calculateExistingBonds() unsigned int tag2 = bond.tag[1]; AddtoExistingBonds(tag1,tag2); - + } } } @@ -252,6 +271,7 @@ void DynamicBondUpdater::calculateExistingBonds() */ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) { + { ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; @@ -262,6 +282,7 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) return true; } return false; + } } @@ -271,13 +292,17 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) */ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) { + assert(tag_a <= m_pdata->getMaximumTag()); assert(tag_b <= m_pdata->getMaximumTag()); bool overflowed = false; + std::cout << "inside of AddtoExistingBonds: array acsess readwrite h_n_existing_bonds " << std::endl; + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + // resize the list if necessary if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) overflowed = true; @@ -305,16 +330,19 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_ // grows the existing bonds list and its indexer when needed void DynamicBondUpdater::resizeExistingBondList() { + { unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); // update the indexer m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; + } } // grows the all possible bonds list when needed in increments of 4, inspired by the neighbor list void DynamicBondUpdater::resizePossibleBondlists() { + { // round up to nearest multiple of 4 m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; m_max_bonds = m_max_bonds_overflow; @@ -327,6 +355,7 @@ void DynamicBondUpdater::resizePossibleBondlists() m_n_list.swap(nlist); m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + } } @@ -334,7 +363,7 @@ void DynamicBondUpdater::resizePossibleBondlists() // allocates all arrays depending on the particles and groups void DynamicBondUpdater::allocateParticleArrays() { - + { GPUArray all_possible_bonds(m_group_1->getNumMembers() *m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); @@ -362,7 +391,7 @@ void DynamicBondUpdater::allocateParticleArrays() // default allocation of m_max_bonds neighbors per particle for the neighborlist GPUArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); m_n_list.swap(nlist); - + } calculateExistingBonds(); } @@ -373,7 +402,7 @@ void DynamicBondUpdater::allocateParticleArrays() */ void DynamicBondUpdater::buildTree() { - + { // make tree for group 2 ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); @@ -387,7 +416,7 @@ void DynamicBondUpdater::buildTree() } m_aabb_tree.buildTree(&(h_aabbs.data[0]) , m_group_2->getNumMembers()); - + } } @@ -397,7 +426,7 @@ void DynamicBondUpdater::buildTree() */ void DynamicBondUpdater::traverseTree() { - + { ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); @@ -471,6 +500,7 @@ void DynamicBondUpdater::traverseTree() } // end loop over images h_n_neigh.data[group_idx] = n_curr_bond; } // end loop over group 1 + } } @@ -482,7 +512,7 @@ void DynamicBondUpdater::traverseTree() void DynamicBondUpdater::filterPossibleBonds() { //copy data from h_n_list to h_all_possible_bonds - + { ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); @@ -566,7 +596,7 @@ void DynamicBondUpdater::filterPossibleBonds() // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. - + } } /*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds @@ -579,6 +609,8 @@ void DynamicBondUpdater::filterPossibleBonds() */ void DynamicBondUpdater::makeBonds(uint64_t timestep) { + + std::cout << "in makeBonds, need h_all_possible_bonds read acsess."<< std::endl; ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); @@ -621,6 +653,7 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) } } } + } @@ -755,6 +788,35 @@ void DynamicBondUpdater::checkRcut() checkBoxSize(); } +void DynamicBondUpdater::checkProbability() +{ + if (m_probability < 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is less than zero" << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } + else if (m_probability > 1.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is larger than one" << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } + +} + +void DynamicBondUpdater::checkMaxBondsGroup() +{ + if (m_max_bonds_group_1 < 0.0 or m_max_bonds_group_2 < 0.0 ) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Max number of bonds that groups can form is negative. Check parameters." << std::endl; + throw std::runtime_error("Error initializing DynamicBondUpdater"); + } + else if (m_max_bonds_group_1 > 10 or m_max_bonds_group_2 > 10 ) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Requested number of bonds that can form is very large. This can lead to performance issues." << std::endl; + } + +} + // Check that the given bond type is valid void DynamicBondUpdater::checkBondType() { @@ -765,6 +827,7 @@ void DynamicBondUpdater::checkBondType() } } + namespace detail { /*! @@ -779,6 +842,7 @@ void export_DynamicBondUpdater(pybind11::module& m) "DynamicBondUpdater") .def(pybind11::init, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, uint16_t>()) @@ -787,20 +851,17 @@ void export_DynamicBondUpdater(pybind11::module& m) std::shared_ptr, std::shared_ptr, std::shared_ptr, + uint16_t, Scalar, Scalar, unsigned int, unsigned int, - unsigned int, - uint16_t>()); - - - //.def("setNeighbourList", &DynamicBondUpdater::setNeighbourList) - // .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) - //.def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) - //.def_property("bond_type", &DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) - //.def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) - //.def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); + unsigned int>()) + .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) + .def_property("bond_type",&DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) + .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) + .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } } // end namespace detail diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 04da5c8b..12ff1485 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -49,6 +49,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Simple constructor DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, + std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed); @@ -59,12 +60,13 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, + uint16_t seed, const Scalar r_cut, const Scalar probability, - unsigned int bond_type, unsigned int max_bonds_group_1, unsigned int max_bonds_group_2, - uint16_t seed); + unsigned int bond_type + ); //! Destructor virtual ~DynamicBondUpdater(); @@ -92,14 +94,17 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater */ void setBondType(unsigned int bond_type) { - m_bond_type = bond_type; - checkBondType(); + //m_bond_data = m_sysdef->getBondData(); + //m_bond_type = m_bond_data->getTypeByName(bond_type); + m_bond_type = bond_type; + checkBondType(); } //! Get the bond type of the dynamically formed bonds unsigned int getBondType() - { - return m_bond_type; - } + { + return m_bond_type; + } + //! Set the maximum number of bonds on particles in group_1 /*! * \param max_bonds_group_1 max number of bonds formed by particles in group 1 @@ -107,6 +112,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void setMaxBondsGroup1(unsigned int max_bonds_group_1) { m_max_bonds_group_1 = max_bonds_group_1; + checkMaxBondsGroup(); } //! Get the maximum number of bonds on particles in group_1 unsigned int getMaxBondsGroup1() @@ -120,6 +126,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void setMaxBondsGroup2(unsigned int max_bonds_group_2) { m_max_bonds_group_2 = max_bonds_group_2; + checkMaxBondsGroup(); } //! Get the maximum number of bonds on particles in group_2 unsigned int getMaxBondsGroup2() @@ -130,21 +137,13 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void setProbability(Scalar probability) { m_probability = probability; + checkProbability(); } //! Get the probability Scalar getProbability() { return m_probability; } - //! Set the hoomd neighbor list - /*! - * \param nlist hoomd NeighborList pointer - */ - void setNeighbourList( std::shared_ptr nlist) - { - m_pair_nlist = nlist; - m_pair_nlist_exclusions_set = true; - } protected: @@ -161,7 +160,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group uint16_t m_seed; //!< seed for random number generator for bond probability - unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found + unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found, is resized if overflow is triggered unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds @@ -201,7 +200,9 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void checkBoxSize(); void checkRcut(); void checkBondType(); + void checkProbability(); void setGroupOverlap(); + void checkMaxBondsGroup(); void resizePossibleBondlists(); void resizeExistingBondList(); void allocateParticleArrays(); diff --git a/src/update.py b/src/update.py index 526abd35..092a3ff9 100644 --- a/src/update.py +++ b/src/update.py @@ -50,47 +50,114 @@ class dynamic_bond(hoomd.operation.Updater): azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,probability=1.0, bond_type='bond', group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) """ - #_ext_module = _azplugins - - def __init__(self,trigger,r_cut,nlist,bond_type,group_1, group_2, max_bonds_1,max_bonds_2,seed,probability=1,period=1, phase=0): + _ext_module = _azplugins + _cpp_class_name = "DynamicBondUpdater" + + def __init__(self, + trigger, + nlist, + group_1, + group_2, + bond_type=None, + max_bonds_group_1=None, + max_bonds_group_2=None, + r_cut=None, + seed=0, + probability=1): super().__init__(trigger) params = ParameterDict( - r_cut=float(r_cut), + r_cut=OnlyTypes(float, allow_none=True), nlist =OnlyTypes(hoomd.md.nlist.NeighborList,strict=True, allow_none=False), - group_1=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), - group_2=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=True), - max_bonds_1=OnlyTypes(int, allow_none=True), - max_bonds_2=OnlyTypes(int, allow_none=True), - bond_type=OnlyTypes(str,strict=True), - seed = int(seed), + group_1=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=False), + group_2=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=False), + max_bonds_group_1=OnlyTypes(int, allow_none=True), + max_bonds_group_2=OnlyTypes(int, allow_none=True), + bond_type=OnlyTypes(int,strict=True,allow_none=True), + seed = OnlyTypes(int,strict=True,allow_none=False), probability = float(probability) ) + params.update( dict( r_cut = r_cut, nlist=nlist, group_1=group_1, group_2=group_2, - max_bonds_1=max_bonds_1, - max_bonds_2=max_bonds_2, + max_bonds_group_1=max_bonds_group_1, + max_bonds_group_2=max_bonds_group_2, bond_type = bond_type, seed = seed, probability = probability ) ) - print("parsed param dict") + self._param_dict.update(params) + #self.set_params(r_cut,probability,bond_type,max_bonds_1,max_bonds_2,nlist) + + @property + def bond_type(self): + return self._cpp_obj.bond_type + + @bond_type.setter + def bond_type(self,value): + if value is not None: + self._param_dict['bond_type']=value + self._cpp_obj.setBondType(value) + + @property + def probability(self): + return self._cpp_obj.probability + + @probability.setter + def probability(self,value): + self._param_dict['probability']=value + self._cpp_obj.probability = value + + @property + def max_bonds_group_1(self): + """ + max_bonds_1 (int) + """ + return self._cpp_obj.max_bonds_group_1 + + @max_bonds_group_1.setter + def max_bonds_group_1(self, value): + self._cpp_obj.max_bonds_group_1 = value + self._param_dict['max_bonds_group_1']=value + + @property + def r_cut(self): + """ + r_cut (float): Distance cutoff for making bonds between particles + """ + return self._cpp_obj.r_cut + + @r_cut.setter + def r_cut(self, value): + self._cpp_obj.r_cut = value + self._param_dict['r_cut']=value + + @property + def max_bonds_group_2(self): + """ + max_bonds_group_2 (int) + """ + return self._cpp_obj.max_bonds_group_2 + + @max_bonds_group_2.setter + def max_bonds_group_2(self, value): + self._cpp_obj.max_bonds_group_2 = value + self._param_dict['max_bonds_group_2']=value def _attach_hook(self): sim = self._simulation - print("in attach hook dynamic bonds") - print(_azplugins) - if isinstance(sim.device, hoomd.device.GPU): - cpp_class = _azplugins.DynamicBondUpdaterGPU + """Create the c++ mirror class.""" + if isinstance(self._simulation.device, hoomd.device.CPU): + cpp_class = getattr(self._ext_module, self._cpp_class_name) else: - cpp_class = _azplugins.DynamicBondUpdater + cpp_class = getattr(self._ext_module, self._cpp_class_name + "GPU") if self.group_1 is not None: group_1 = sim.state._get_group(self.group_1) @@ -102,11 +169,15 @@ def _attach_hook(self): else: group_2 = None - #todo: incomplete + self.nlist._attach(sim) + self._cpp_obj = cpp_class( sim.state._cpp_sys_def, + self.trigger, + self.nlist._cpp_obj, group_1, - group_2 + group_2, + self.seed ) super()._attach_hook() From 9b6a93444f9aec76d3214e1c7550718bd098b4ac Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 20 Mar 2026 14:18:10 -0500 Subject: [PATCH 26/45] code dublication --- src/DynamicBondUpdater.cc | 84 +++++++++++++++++++++++++++++++++--- src/DynamicBondUpdaterGPU.cc | 2 + 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 6d0766f6..529269a6 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -259,7 +259,43 @@ void DynamicBondUpdater::calculateExistingBonds() unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; - AddtoExistingBonds(tag1,tag2); + //AddtoExistingBonds(tag1,tag2); + + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag1 <= m_pdata->getMaximumTag()); + assert(tag2 <= m_pdata->getMaximumTag()); + + bool overflowed = false; + + std::cout << "inside of CalculatingExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + + + // resize the list if necessary + if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + + if (overflowed) resizeExistingBondList(); + + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag1]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos_a)] = tag2; + h_n_existing_bonds.data[tag1]++; + + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag2]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos_b)] = tag1; + h_n_existing_bonds.data[tag2]++; + + // END DynamicBondUpdater::AddtoExistingBonds + } } @@ -292,13 +328,13 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) */ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) { - + // BEGIN DynamicBondUpdater::AddtoExistingBonds assert(tag_a <= m_pdata->getMaximumTag()); assert(tag_b <= m_pdata->getMaximumTag()); bool overflowed = false; - std::cout << "inside of AddtoExistingBonds: array acsess readwrite h_n_existing_bonds " << std::endl; + std::cout << "inside of AddtoExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); @@ -325,6 +361,8 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_ h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b,pos_b)] = tag_a; h_n_existing_bonds.data[tag_b]++; + // END DynamicBondUpdater::AddtoExistingBonds + } // grows the existing bonds list and its indexer when needed @@ -612,7 +650,9 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) std::cout << "in makeBonds, need h_all_possible_bonds read acsess."<< std::endl; ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + + // ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); // we need to count how many bonds are in the h_all_possible_bonds array for a given tag // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in @@ -646,7 +686,41 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) (random < m_probability)) { m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); - AddtoExistingBonds(tag_i,tag_j); + //AddtoExistingBonds(tag_i,tag_j); + + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_i <= m_pdata->getMaximumTag()); + assert(tag_j <= m_pdata->getMaximumTag()); + + bool overflowed = false; + + std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + + // resize the list if necessary + if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag_j] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + + if (overflowed) resizeExistingBondList(); + + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_i]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_i,pos_a)] = tag_j; + h_n_existing_bonds.data[tag_i]++; + + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_j]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j,pos_b)] = tag_i; + h_n_existing_bonds.data[tag_j]++; + + // END DynamicBondUpdater::AddtoExistingBonds + + if (m_pair_nlist_exclusions_set) { m_pair_nlist -> addExclusion(tag_i,tag_j); diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index ba1370f9..dc0af07c 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -253,3 +253,5 @@ namespace detail } // end namespace detail } // end namespace azplugins + + From 4311d06412aea47903a2806ed5040f0eb0b2e45b Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 20 Mar 2026 16:59:10 -0500 Subject: [PATCH 27/45] started fixing GPU implentation, still lots of issues --- src/DynamicBondUpdater.h | 1 + src/DynamicBondUpdaterGPU.cc | 101 ++++++++++++++++++++++++---------- src/DynamicBondUpdaterGPU.cu | 5 +- src/DynamicBondUpdaterGPU.cuh | 17 ++++-- src/DynamicBondUpdaterGPU.h | 68 ++++++++++------------- 5 files changed, 120 insertions(+), 72 deletions(-) diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 12ff1485..547ea52f 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -1,4 +1,5 @@ // Copyright (c) 2018-2020, Michael P. Howard +// Copyright (c) 2021-2025, Auburn University // This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index dc0af07c..295071b8 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -1,4 +1,5 @@ // Copyright (c) 2018-2020, Michael P. Howard +// Copyright (c) 2021-2025, Auburn University // This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt @@ -11,6 +12,8 @@ #include "DynamicBondUpdaterGPU.h" #include "DynamicBondUpdaterGPU.cuh" +namespace hoomd +{ namespace azplugins { @@ -22,39 +25,62 @@ namespace azplugins * properly initialize the system via setters. */ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr group_1, - std::shared_ptr group_2, - unsigned int seed) - : DynamicBondUpdater(sysdef, group_1, group_2, seed), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed) + : DynamicBondUpdater(sysdef, trigger, pair_nlist, group_1, group_2, seed), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { + + // only one GPU is supported + if (!m_exec_conf->isCUDAEnabled()) + { + throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); + } + + m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_exec_conf, + "dynamic_bonding_copy_nlist")); + m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_exec_conf, + "dynamic_bonding_filter_bonds")); + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; - m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy", m_exec_conf)); - m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_nlist_copy", m_exec_conf)); - m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_filter_bonds", m_exec_conf)); } DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - const Scalar probability, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int seed) - : DynamicBondUpdater(sysdef, pair_nlist, group_1, group_2, - r_cut, probability, bond_type, max_bonds_group_1, max_bonds_group_2,seed), + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type) + : DynamicBondUpdater(sysdef,trigger,pair_nlist,group_1,group_2, seed, r_cut, + probability,max_bonds_group_1,max_bonds_group_2,bond_type), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), m_lbvh(m_exec_conf), m_traverser(m_exec_conf) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; - m_copy_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_copy", m_exec_conf)); - m_copy_nlist_tuner.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_nlist_copy", m_exec_conf)); - m_tuner_filter_bonds.reset(new Autotuner(32, 1024, 32, 5, 100000, "dynamic_bond_filter_bonds", m_exec_conf)); + // only one GPU is supported + if (!m_exec_conf->isCUDAEnabled()) + { + throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); + } + + m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_exec_conf, + "dynamic_bonding_copy_nlist")); + m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_exec_conf, + "dynamic_bonding_filter_bonds")); } @@ -135,7 +161,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const BoxDim& box = m_pdata->getBox(); - m_copy_tuner->begin(); + m_tuner_copy_nlist->begin(); gpu::copy_possible_bonds(d_all_possible_bonds.data, d_pos.data, d_tag.data, @@ -147,9 +173,9 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_r_cut, m_groups_identical, m_group_1->getNumMembers(), - m_copy_tuner->getParam()); + m_tuner_copy_nlist->getParam()[0]); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_copy_tuner->end(); + m_tuner_copy_nlist->end(); //filter out the existing bonds - based on neighbor list exclusion handeling @@ -159,7 +185,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() d_existing_bonds_list.data, m_existing_bonds_list_indexer, size, - m_tuner_filter_bonds->getParam()); + m_tuner_filter_bonds->getParam()[0]); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner_filter_bonds->end(); @@ -204,7 +230,7 @@ void DynamicBondUpdaterGPU::updateImageVectors() // reallocate memory if necessary if (m_n_images > m_image_list.getNumElements()) { - GlobalVector image_list(m_n_images, m_exec_conf); + GPUVector image_list(m_n_images, m_exec_conf); m_image_list.swap(image_list); } @@ -245,13 +271,30 @@ namespace detail { namespace py = pybind11; + py::class_< DynamicBondUpdaterGPU, DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdaterGPU") - .def(py::init, std::shared_ptr,std::shared_ptr, std::shared_ptr, unsigned int>()) - .def(py::init,std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, Scalar, Scalar, unsigned int, unsigned int, unsigned int, unsigned int>()); + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t>()) + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t, + Scalar, + Scalar, + unsigned int, + unsigned int, + unsigned int>()); } } // end namespace detail } // end namespace azplugins +} // end namespace hoomd + diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index a2df9193..b760a9e0 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -1,4 +1,5 @@ // Copyright (c) 2018-2020, Michael P. Howard +// Copyright (c) 2021-2025, Auburn University // This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt @@ -17,7 +18,8 @@ #include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" //#include "hoomd/extern/cub/cub/cub.cuh" - +namespace hoomd +{ namespace azplugins { @@ -349,6 +351,7 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, } // end namespace gpu } // end namespace azplugins +} // end namespace hoomd // explicit templates for neighbor::LBVH with PointMapInsertOp template void neighbor::gpu::lbvh_gen_codes(unsigned int *, unsigned int *, const azplugins::gpu::PointMapInsertOp&, diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index da8f4319..5ad3f3d1 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -1,4 +1,5 @@ // Copyright (c) 2018-2020, Michael P. Howard +// Copyright (c) 2021-2025, Auburn University // This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt @@ -18,9 +19,14 @@ #include "hoomd/ParticleData.cuh" #include -#include "hoomd/extern/neighbor/neighbor/BoundingVolumes.h" -#include "hoomd/extern/neighbor/neighbor/InsertOps.h" -#include "hoomd/extern/neighbor/neighbor/TransformOps.h" +#include "hip/hip_runtime.h" +#include "hoomd/md/NeighborListGPUTree.cuh" + +/* +#include "hoomd/extern/neighbor/include/neighbor/BoundingVolumes.h" +#include "hoomd/extern/neighbor/include/neighbor/InsertOps.h" +#include "hoomd/extern/neighbor/include/neighbor/TransformOps.h" +*/ #ifdef NVCC #define DEVICE __device__ __forceinline__ @@ -30,6 +36,9 @@ #define HOSTDEVICE #endif + +namespace hoomd +{ namespace azplugins { namespace gpu @@ -392,7 +401,7 @@ const unsigned int NeighborListTypeSentinel = 0xffffffff; } // end namespace gpu } // end namespace azplugins - +} // end namespace hoomd #undef DEVICE diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 8af565e9..cd425648 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -1,4 +1,5 @@ // Copyright (c) 2018-2020, Michael P. Howard +// Copyright (c) 2021-2025, Auburn University // This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt @@ -19,9 +20,16 @@ #include "DynamicBondUpdaterGPU.cuh" #include "hoomd/Autotuner.h" -#include "hoomd/extern/neighbor/neighbor/LBVH.h" -#include "hoomd/extern/neighbor/neighbor/LBVHTraverser.h" +#include "hip/hip_runtime.h" +#include "hoomd/md/NeighborListGPUTree.cuh" +/* +#include "hoomd/extern/neighbor/include/neighbor/LBVH.h" +#include "hoomd/extern/neighbor/include/neighbor/LBVHTraverser.h" +*/ + +namespace hoomd +{ namespace azplugins { @@ -35,45 +43,29 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater public: //! Simple constructor DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed); + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed); + //! Constructor with parameters DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - const Scalar r_cut, - const Scalar probability, - unsigned int bond_type, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - uint16_t seed); + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type); //! Destructor virtual ~DynamicBondUpdaterGPU(); - /*! Set autotuner parameters - * \param enable Enable / disable autotuning - * \param period period (approximate) in time steps when retuning occurs - */ - virtual void setAutotunerParams(bool enable, unsigned int period) - { - DynamicBondUpdater::setAutotunerParams(enable, period); - - m_copy_tuner->setPeriod(period); - m_copy_tuner->setEnabled(enable); - - m_copy_nlist_tuner->setPeriod(period); - m_copy_nlist_tuner->setEnabled(enable); - - m_tuner_filter_bonds->setPeriod(period); - m_tuner_filter_bonds->setEnabled(enable); - - } protected: //! filter out existing and doublicate bonds from all found possible bonds @@ -87,16 +79,15 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater private: - std::unique_ptr m_copy_tuner; //!< Tuner for the primitive-copy kernel - std::unique_ptr m_copy_nlist_tuner; //!< Tuner for the primitive-copy kernel - std::unique_ptr m_tuner_filter_bonds; //!< Tuner for existing bond filter + std::shared_ptr> m_tuner_copy_nlist; //!< Tuner for the primitive-copy kernel + std::shared_ptr> m_tuner_filter_bonds; //!< Tuner for existing bond filter GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow neighbor::LBVH m_lbvh; //!< LBVH for group_2 neighbor::LBVHTraverser m_traverser; //!< LBVH traverer - GlobalVector m_image_list; //!< List of translation vectors for traversal + GPUVector m_image_list; //!< List of translation vectors for traversal unsigned int m_n_images; //!< Number of translation vectors for traversal @@ -129,5 +120,6 @@ void export_DynamicBondUpdaterGPU(pybind11::module& m); } // end namespace detail } // end namespace azplugins +} // end namespace hoomd #endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_H_ From 2f06bde658ad7a36ad99bfd1f4041f97b549c489 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 23 Mar 2026 12:02:14 -0500 Subject: [PATCH 28/45] mark issues in code --- src/DynamicBondUpdater.cc | 10 ++++++++++ src/DynamicBondUpdaterGPU.cc | 4 ++-- src/DynamicBondUpdaterGPU.cu | 4 ++++ src/DynamicBondUpdaterGPU.cuh | 6 ++++-- src/DynamicBondUpdaterGPU.h | 3 +++ 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 529269a6..6b96a385 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -259,6 +259,9 @@ void DynamicBondUpdater::calculateExistingBonds() unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) + //AddtoExistingBonds(tag1,tag2); // BEGIN DynamicBondUpdater::AddtoExistingBonds @@ -322,12 +325,16 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) } +// @mphoward: This is the function that I originally had that doesn't work anymore due to +// the changes in hoomd (GPUArray and GPUVector). + /*! \param tag1 First particle tag in the pair \param tag2 Second particle tag in the pair adds a bond between the tag1 and tag2 to the existing bonds list */ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) { + // BEGIN DynamicBondUpdater::AddtoExistingBonds assert(tag_a <= m_pdata->getMaximumTag()); assert(tag_b <= m_pdata->getMaximumTag()); @@ -688,6 +695,9 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); //AddtoExistingBonds(tag_i,tag_j); + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) + // BEGIN DynamicBondUpdater::AddtoExistingBonds assert(tag_i <= m_pdata->getMaximumTag()); assert(tag_j <= m_pdata->getMaximumTag()); diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 295071b8..b4c89c47 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -119,7 +119,7 @@ void DynamicBondUpdaterGPU::traverseTree() const BoxDim& box = m_pdata->getBox(); // neighbor list write op - azplugins::gpu::NeighborListOp nlist_op(d_nlist.data, d_n_neigh.data, m_max_bonds_overflow_flag.getDeviceFlags(), m_max_bonds); + hoomd::azplugins::NeighborListOp nlist_op(d_nlist.data, d_n_neigh.data, m_max_bonds_overflow_flag.getDeviceFlags(), m_max_bonds); ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); @@ -127,7 +127,7 @@ void DynamicBondUpdaterGPU::traverseTree() neighbor::MapTransformOp map(d_index_group_2.data ); m_traverser.setup(map, m_lbvh); - azplugins::gpu::ParticleQueryOp query_op(d_pos.data, + hoomd::azplugins::ParticleQueryOp query_op(d_pos.data, d_index_group_1.data, m_group_1->getNumMembers(), m_pdata->getMaxN(), diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index b760a9e0..cb37757e 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -13,6 +13,10 @@ #include "DynamicBondUpdaterGPU.cuh" //#include #include + +// @mphoward: There is some issue with importing the right headers from the extern/neighbor +// class. Could you please have a look at it and check which headers to import? + // todo: should azplugins have its own "extern"? #include "hoomd/extern/neighbor/neighbor/LBVH.cuh" #include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 5ad3f3d1..8d791995 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -19,9 +19,11 @@ #include "hoomd/ParticleData.cuh" #include -#include "hip/hip_runtime.h" -#include "hoomd/md/NeighborListGPUTree.cuh" +// @mphoward: There is some issue with importing the right headers from the extern/neighbor +// class. Could you please have a look at it and check which headers to import? + +// #include "hip/hip_runtime.h" /* #include "hoomd/extern/neighbor/include/neighbor/BoundingVolumes.h" #include "hoomd/extern/neighbor/include/neighbor/InsertOps.h" diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index cd425648..19228fd2 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -23,6 +23,9 @@ #include "hip/hip_runtime.h" #include "hoomd/md/NeighborListGPUTree.cuh" +// @mphoward: There is some issue with importing the right headers from the extern/neighbor +// class. Could you please have a look at it and check which headers to import? + /* #include "hoomd/extern/neighbor/include/neighbor/LBVH.h" #include "hoomd/extern/neighbor/include/neighbor/LBVHTraverser.h" From ffa911c9350993db224362f09a38ea2248eda3ca Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 29 Apr 2026 11:01:58 -0500 Subject: [PATCH 29/45] Add test, not finished --- src/pytest/CMakeLists.txt | 1 + src/pytest/test_dynamic_bond.py | 103 ++++++++++++++++---------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/pytest/CMakeLists.txt b/src/pytest/CMakeLists.txt index 157b326b..62b99a47 100644 --- a/src/pytest/CMakeLists.txt +++ b/src/pytest/CMakeLists.txt @@ -3,6 +3,7 @@ set(test_files __init__.py test_bond.py test_compute.py + test_dynamic_bond.py test_external.py test_flow.py test_pair.py diff --git a/src/pytest/test_dynamic_bond.py b/src/pytest/test_dynamic_bond.py index ac7e32e8..b2544e3c 100644 --- a/src/pytest/test_dynamic_bond.py +++ b/src/pytest/test_dynamic_bond.py @@ -1,59 +1,58 @@ # Copyright (c) 2018-2020, Michael P. Howard -# This file is part of the azplugins project, released under the Modified BSD License. +# Copyright (c) 2021-2025, Auburn University +# Part of azplugins, released under the BSD 3-Clause License. -# Maintainer: astatt import hoomd -from hoomd import md -hoomd.context.initialize() -try: - from hoomd import azplugins -except ImportError: - import azplugins -import unittest -import numpy as np - -class update_dynamic_bond_tests_two_groups(unittest.TestCase): - def setUp(self): - snap = hoomd.data.make_snapshot(N=4, box=hoomd.data.boxdim(L=20), - particle_types=['A','B'], - bond_types=['bond']) - if hoomd.comm.get_rank() == 0: - snap.particles.position[:,0] = (0,0.9,1.1,2) - snap.particles.position[:,1] = (0,0,0,0) - snap.particles.position[:,2] = (0,0,0,0) - snap.particles.typeid[:] = [1,0,1,0] - - self.s = hoomd.init.read_snapshot(snap) - self.nl = hoomd.md.nlist.cell() - - self.group_1 = hoomd.group.tag_list(name="a", tags = [0,1]) - self.group_2 = hoomd.group.tag_list(name="b", tags = [2,3]) - self.u = azplugins.update.dynamic_bond(nlist=self.nl, - r_cut=1.0, - bond_type='bond', - group_1=self.group_1, - group_2=self.group_2, - max_bonds_1=1, - max_bonds_2=2) - - def test_set_params(self): - self.assertEqual(self.u.cpp_updater.r_cut, 1) - self.u.set_params(r_cut=1.5) - self.assertEqual(self.u.cpp_updater.r_cut, 1.5) - - # check the test of box size large enough for cutoff - with self.assertRaises(RuntimeError): - self.u.set_params(r_cut=15.0) - - self.u.set_params(max_bonds_1=3) - self.assertEqual(self.u.cpp_updater.max_bonds_group_1,3) - self.u.set_params(max_bonds_2=7) - self.assertEqual(self.u.cpp_updater.max_bonds_group_2,7) - - # check the test of bondy_type - with self.assertRaises(RuntimeError): - self.u.set_params(bond_type='not_existing') +import pytest +import numpy + + + + +def update_dynamic_bond_tests_two_groups_setup(simulation_factory,two_particle_snapshot_factory): + + snap = hoomd.data.make_snapshot(N=4, box=hoomd.data.boxdim(L=20), + particle_types=['A','B'], + bond_types=['bond']) + if hoomd.comm.get_rank() == 0: + snap.particles.position[:,0] = (0,0.9,1.1,2) + snap.particles.position[:,1] = (0,0,0,0) + snap.particles.position[:,2] = (0,0,0,0) + snap.particles.typeid[:] = [1,0,1,0] + + s = hoomd.init.read_snapshot(snap) + nl = hoomd.md.nlist.cell() + + group_1 = hoomd.group.tag_list(name="a", tags = [0,1]) + group_2 = hoomd.group.tag_list(name="b", tags = [2,3]) + u = hoomd.azplugins.update.dynamic_bond(nlist=nl, + r_cut=1.0, + bond_type='bond', + group_1=group_1, + group_2=group_2, + max_bonds_1=1, + max_bonds_2=2) + if sim.device.communicator.rank == 0: + # particle 0 is outside interaction range + assert numpy.isclose(energies[0], 0.0) + + assertEqual(u.cpp_updater.r_cut, 1) + u.set_params(r_cut=1.5) + assertEqual(u.cpp_updater.r_cut, 1.5) + + # check the test of box size large enough for cutoff + with assertRaises(RuntimeError): + u.set_params(r_cut=15.0) + + u.set_params(max_bonds_1=3) + assertEqual(self.u.cpp_updater.max_bonds_group_1,3) + u.set_params(max_bonds_2=7) + assertEqual(self.u.cpp_updater.max_bonds_group_2,7) + + # check the test of bondy_type + with assertRaises(RuntimeError): + u.set_params(bond_type='not_existing') def test_form_bond(self): From 46f1927cfebf2bf5bd06902ef9b03dd1a52d3ffc Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 29 Apr 2026 11:24:27 -0500 Subject: [PATCH 30/45] resolve merge --- src/DynamicBondUpdaterGPU.cuh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 8d791995..3fd589d7 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -20,16 +20,6 @@ #include -// @mphoward: There is some issue with importing the right headers from the extern/neighbor -// class. Could you please have a look at it and check which headers to import? - -// #include "hip/hip_runtime.h" -/* -#include "hoomd/extern/neighbor/include/neighbor/BoundingVolumes.h" -#include "hoomd/extern/neighbor/include/neighbor/InsertOps.h" -#include "hoomd/extern/neighbor/include/neighbor/TransformOps.h" -*/ - #ifdef NVCC #define DEVICE __device__ __forceinline__ #define HOSTDEVICE __host__ __device__ __forceinline__ From 0023b66090987c8b603a671eca5450651684f45d Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 29 Apr 2026 11:36:05 -0500 Subject: [PATCH 31/45] need wrapper code for neighbor --- src/DynamicBondUpdaterGPU.cu | 903 ++++++++++++++++++++++++---------- src/DynamicBondUpdaterGPU.cuh | 67 +-- src/DynamicBondUpdaterGPU.h | 11 +- 3 files changed, 687 insertions(+), 294 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index cb37757e..9fe9ddef 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -9,161 +9,156 @@ * \brief Definition of kernel drivers and kernels for DynamicBondUpdaterGPU */ -#include "hoomd/HOOMDMath.h" #include "DynamicBondUpdaterGPU.cuh" -//#include +#include "NeighborListGPUTree.cuh" +#include "hoomd/HOOMDMath.h" +// #include +#include #include -// @mphoward: There is some issue with importing the right headers from the extern/neighbor -// class. Could you please have a look at it and check which headers to import? - -// todo: should azplugins have its own "extern"? -#include "hoomd/extern/neighbor/neighbor/LBVH.cuh" -#include "hoomd/extern/neighbor/neighbor/LBVHTraverser.cuh" -//#include "hoomd/extern/cub/cub/cub.cuh" - namespace hoomd -{ + { namespace azplugins -{ + { -//todo: migrate to separate file/class. -//this sorts according distance, then first tag, then second tag -struct SortBondsGPU{ - __host__ __device__ bool operator()(const Scalar3 &i, const Scalar3 &j) +// todo: migrate to separate file/class. +// this sorts according distance, then first tag, then second tag +struct SortBondsGPU { - const Scalar r_sq_1 = i.z; - const Scalar r_sq_2 = j.z; - if (r_sq_1==r_sq_2) - { - const unsigned int tag_11 = __scalar_as_int(i.x); - const unsigned int tag_21 = __scalar_as_int(j.x); - if (tag_11==tag_21) + __host__ __device__ bool operator()(const Scalar3& i, const Scalar3& j) { - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_22 = __scalar_as_int(j.y); - return tag_22>tag_12; - } + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1 == r_sq_2) + { + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_21 = __scalar_as_int(j.x); + if (tag_11 == tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22 > tag_12; + } + else + { + return tag_21 > tag_11; + } + } else - { - return tag_21>tag_11; + { + return r_sq_2 > r_sq_1; + } } - } - else - { - return r_sq_2>r_sq_1; - } - } - -}; + }; // returns true if given possible bond is zero, e.g. (0,0,0.0) // possible bonds are ordered, such that tag_a < tag_b in (tag_a,tag_b,rsq) // meaning we only need to check tag_b == 0 -struct isZeroBondGPU{ - __host__ __device__ bool operator()(const Scalar3 &i) +struct isZeroBondGPU { - const unsigned int tag_1 = __scalar_as_int(i.y); - return !(bool)tag_1; - } -}; + __host__ __device__ bool operator()(const Scalar3& i) + { + const unsigned int tag_1 = __scalar_as_int(i.y); + return !(bool)tag_1; + } + }; -struct CompareBondsGPU{ - __host__ __device__ bool operator()(const Scalar3 &i, const Scalar3 &j) +struct CompareBondsGPU { - const unsigned int tag_11 = __scalar_as_int(i.x); - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_21 = __scalar_as_int(j.x); - const unsigned int tag_22 = __scalar_as_int(j.y); - - if ((tag_11==tag_21 && tag_12==tag_22)) // should work because pairs are ordered - { - return true; - } - else - { - return false; - } - } - }; + __host__ __device__ bool operator()(const Scalar3& i, const Scalar3& j) + { + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_21 = __scalar_as_int(j.x); + const unsigned int tag_22 = __scalar_as_int(j.y); + + if ((tag_11 == tag_21 && tag_12 == tag_22)) // should work because pairs are ordered + { + return true; + } + else + { + return false; + } + } + }; namespace gpu -{ + { //! Number of elements of the exclusion list to process in each batch const unsigned int FILTER_BATCH_SIZE = 4; namespace kernel -{ - -__global__ void copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, - const Scalar4 *d_postype, - const unsigned int * d_tag, - const unsigned int * d_sorted_indexes, - const unsigned int * d_n_neigh, - const unsigned int * d_nlist, - const BoxDim box, - const unsigned int max_bonds, - const Scalar r_cut, - const bool groups_identical, - const unsigned int N) - { + { - // one thread per particle in group_1 - const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; - - if (idx >= N) - return; - - // idx = group index , pidx = actual particle index - const unsigned int pidx_i = d_sorted_indexes[idx]; - unsigned int n_curr_bond = 0; - const Scalar r_cutsq = r_cut*r_cut; - - // get all information for this particle - Scalar4 postype_i = d_postype[pidx_i]; - const unsigned int tag_i = d_tag[pidx_i]; - const unsigned int n_neigh = d_n_neigh[idx]; - - // loop over all neighbors of this particle - for (unsigned int j=0; jtag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j>tag_i ? tag_j : tag_i; - d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - } - else - { - d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); - } - d_all_possible_bonds[idx*max_bonds + n_curr_bond] = d; - } - ++n_curr_bond; - } - } +__global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, + const Scalar4* d_postype, + const unsigned int* d_tag, + const unsigned int* d_sorted_indexes, + const unsigned int* d_n_neigh, + const unsigned int* d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const bool groups_identical, + const unsigned int N) + { + // one thread per particle in group_1 + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + + if (idx >= N) + return; + + // idx = group index , pidx = actual particle index + const unsigned int pidx_i = d_sorted_indexes[idx]; + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = r_cut * r_cut; + + // get all information for this particle + Scalar4 postype_i = d_postype[pidx_i]; + const unsigned int tag_i = d_tag[pidx_i]; + const unsigned int n_neigh = d_n_neigh[idx]; + + // loop over all neighbors of this particle + for (unsigned int j = 0; j < n_neigh; ++j) + { + // get index of neighbor from neigh_list + const unsigned int pidx_j = d_nlist[idx * max_bonds + j]; + Scalar4 postype_j = d_postype[pidx_j]; + const unsigned int tag_j = d_tag[pidx_j]; + + Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) + - make_scalar3(postype_i.x, postype_i.y, postype_i.z); + + // apply periodic boundary conditions (FLOPS: 12) + drij = box.minImage(drij); + + // same as on the cpu, just not during the tree traversal + Scalar dr_sq = dot(drij, drij); + + if (dr_sq < r_cutsq) + { + if (n_curr_bond < max_bonds) + { + Scalar3 d; + if (groups_identical) + { + // sort the two tags in this possible bond pair if groups are identical + const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a), __int_as_scalar(tag_b), dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i), __int_as_scalar(tag_j), dr_sq); + } + d_all_possible_bonds[idx * max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } } + } /*! \param d_all_possible_bonds all possible bonds list \param d_n_existing_bonds Number of existing for each particle @@ -172,141 +167,149 @@ __global__ void copy_nlist_possible_bonds(Scalar3 *d_all_possible_bonds, \param size Length of d_all_possible_bonds \param ex_start Start filtering d_all_possible_bonds from existing bond number \a ex_start - the kernel filter_existing_bonds() processes the all possible bonds list \a d_nlist and removes any entries that already exist. To allow - for an arbitrary large number of existing bonds, these are processed in batch sizes of FILTER_BATCH_SIZE. The kernel - must be called multiple times in order to fully remove all exclusions from the nlist. + the kernel filter_existing_bonds() processes the all possible bonds list \a d_nlist and removes + any entries that already exist. To allow for an arbitrary large number of existing bonds, these + are processed in batch sizes of FILTER_BATCH_SIZE. The kernel must be called multiple times in + order to fully remove all exclusions from the nlist. - \note The driver filter_existing_bonds properly makes as many calls as are necessary, it only needs to be called once. + \note The driver filter_existing_bonds properly makes as many calls as are necessary, it only + needs to be called once. \b Implementation - One thread is run for each particle. Existing bonds \a ex_start, \a ex_start + 1, ... are loaded in for that particle - (or the thread returns if there are no exclusions past that point). The thread then loops over the neighbor list, - comparing each entry to the list of exclusions. If the entry is not excluded, it is written back out. \a d_n_neigh - is updated to reflect the current number of particles in the list at the end of the kernel call. + One thread is run for each particle. Existing bonds \a ex_start, \a ex_start + 1, ... are loaded + in for that particle (or the thread returns if there are no exclusions past that point). The + thread then loops over the neighbor list, comparing each entry to the list of exclusions. If the + entry is not excluded, it is written back out. \a d_n_neigh is updated to reflect the current + number of particles in the list at the end of the kernel call. */ -__global__ void filter_existing_bonds(Scalar3 *d_all_possible_bonds, - const unsigned int *d_n_existing_bonds, - const unsigned int *d_existing_bonds_list, +__global__ void filter_existing_bonds(Scalar3* d_all_possible_bonds, + const unsigned int* d_n_existing_bonds, + const unsigned int* d_existing_bonds_list, const Index2D exli, const unsigned int size, const unsigned int ex_start) - { - // compute the bond index this thread operates on - const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; - - // quit now if this thread is processing past the end of the list of all possible bonds - if (idx >= size) - return; - - Scalar3 current_bond = d_all_possible_bonds[idx]; - unsigned int tag_1 = __scalar_as_int(current_bond.x); - unsigned int tag_2 = __scalar_as_int(current_bond.y); - - if(tag_1==0 && tag_2==0) - return; - - //const unsigned int n_neigh = d_n_neigh[idx]; - const unsigned int n_ex = d_n_existing_bonds[tag_1]; - - // quit now if the ex_start flag is past the end of n_ex - if (ex_start >= n_ex) - return; - - // count the number of existing bonds to process in this thread - const unsigned int n_ex_process = n_ex - ex_start; - - // load the existing bond list into "local" memory - fully unrolled loops should dump this into registers - unsigned int l_existing_bonds_list[FILTER_BATCH_SIZE]; - #pragma unroll - for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) - { - if (cur_ex_idx < n_ex_process) - { - l_existing_bonds_list[cur_ex_idx] = d_existing_bonds_list[exli(tag_1, cur_ex_idx + ex_start)]; + { + // compute the bond index this thread operates on + const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x; + + // quit now if this thread is processing past the end of the list of all possible bonds + if (idx >= size) + return; + + Scalar3 current_bond = d_all_possible_bonds[idx]; + unsigned int tag_1 = __scalar_as_int(current_bond.x); + unsigned int tag_2 = __scalar_as_int(current_bond.y); + + if (tag_1 == 0 && tag_2 == 0) + return; + + // const unsigned int n_neigh = d_n_neigh[idx]; + const unsigned int n_ex = d_n_existing_bonds[tag_1]; + + // quit now if the ex_start flag is past the end of n_ex + if (ex_start >= n_ex) + return; + + // count the number of existing bonds to process in this thread + const unsigned int n_ex_process = n_ex - ex_start; + + // load the existing bond list into "local" memory - fully unrolled loops should dump this into + // registers + unsigned int l_existing_bonds_list[FILTER_BATCH_SIZE]; +#pragma unroll + for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) + { + if (cur_ex_idx < n_ex_process) + { + l_existing_bonds_list[cur_ex_idx] + = d_existing_bonds_list[exli(tag_1, cur_ex_idx + ex_start)]; + } + else + { + l_existing_bonds_list[cur_ex_idx] = 0xffffffff; + } } - else - { - l_existing_bonds_list[cur_ex_idx] = 0xffffffff; + + // test if excluded + bool excluded = false; +#pragma unroll + for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) + { + if (tag_2 == l_existing_bonds_list[cur_ex_idx]) + excluded = true; } - } - - // test if excluded - bool excluded = false; - #pragma unroll - for (unsigned int cur_ex_idx = 0; cur_ex_idx < FILTER_BATCH_SIZE; cur_ex_idx++) - { - if (tag_2 == l_existing_bonds_list[cur_ex_idx]) - excluded = true; - } - // if it is excluded, overwrite that entry with (0,0,0). - if (excluded) - { - d_all_possible_bonds[idx] = make_scalar3(__int_as_scalar(0),__int_as_scalar(0),0.0); - } - } - -} // end namespace kernel - - -cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bonds, + // if it is excluded, overwrite that entry with (0,0,0). + if (excluded) + { + d_all_possible_bonds[idx] = make_scalar3(__int_as_scalar(0), __int_as_scalar(0), 0.0); + } + } + + } // end namespace kernel + +cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3* d_all_possible_bonds, const unsigned int size, - int *d_max_non_zero_bonds) + int* d_max_non_zero_bonds) { - if (size == 0) return cudaSuccess; + if (size == 0) + return cudaSuccess; // wrapper for pointer needed for thrust HOOMD_THRUST::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); isZeroBondGPU zero; - HOOMD_THRUST::device_ptr last0 = HOOMD_THRUST::remove_if(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap + size, zero); + HOOMD_THRUST::device_ptr last0 + = HOOMD_THRUST::remove_if(d_all_possible_bonds_wrap, + d_all_possible_bonds_wrap + size, + zero); unsigned int l0 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last0); // sort remainder by distance, should make all identical bonds consequtive SortBondsGPU sort; - HOOMD_THRUST::sort(d_all_possible_bonds_wrap,d_all_possible_bonds_wrap+l0, sort); + HOOMD_THRUST::sort(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, sort); // thrust::unique only removes identical consequtive elements, so sort above is needed. CompareBondsGPU comp; - HOOMD_THRUST::device_ptr last1 = HOOMD_THRUST::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0,comp); + HOOMD_THRUST::device_ptr last1 + = HOOMD_THRUST::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, comp); unsigned int l1 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last1); - *d_max_non_zero_bonds=l1; + *d_max_non_zero_bonds = l1; return cudaSuccess; } - -cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, - unsigned int *d_n_existing_bonds, - const unsigned int *d_existing_bonds_list, - const Index2D& exli, - const unsigned int size, - const unsigned int block_size) +cudaError_t filter_existing_bonds(Scalar3* d_all_possible_bonds, + unsigned int* d_n_existing_bonds, + const unsigned int* d_existing_bonds_list, + const Index2D& exli, + const unsigned int size, + const unsigned int block_size) { static unsigned int max_block_size = UINT_MAX; if (max_block_size == UINT_MAX) { cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::filter_existing_bonds); + cudaFuncGetAttributes(&attr, (const void*)kernel::filter_existing_bonds); max_block_size = attr.maxThreadsPerBlock; } unsigned int run_block_size = min(block_size, max_block_size); // determine parameters for kernel launch - int n_blocks = size/run_block_size + 1; + int n_blocks = size / run_block_size + 1; // split the processing of the full exclusion list up into a number of batches - unsigned int n_batches = (unsigned int)ceil(double(exli.getH())/double(FILTER_BATCH_SIZE)); + unsigned int n_batches = (unsigned int)ceil(double(exli.getH()) / double(FILTER_BATCH_SIZE)); unsigned int ex_start = 0; for (unsigned int batch = 0; batch < n_batches; batch++) { kernel::filter_existing_bonds<<>>(d_all_possible_bonds, - d_n_existing_bonds, - d_existing_bonds_list, - exli, - size, - ex_start); + d_n_existing_bonds, + d_existing_bonds_list, + exli, + size, + ex_start); ex_start += FILTER_BATCH_SIZE; } @@ -314,54 +317,448 @@ cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, return cudaSuccess; } - -cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, - const Scalar4 *d_postype, - const unsigned int *d_tag, - const unsigned int *d_sorted_indexes, - const unsigned int *d_n_neigh, - const unsigned int *d_nlist, - const BoxDim box, - const unsigned int max_bonds, - const Scalar r_cut, - const bool groups_identical, - const unsigned int N, - const unsigned int block_size) +cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, + const Scalar4* d_postype, + const unsigned int* d_tag, + const unsigned int* d_sorted_indexes, + const unsigned int* d_n_neigh, + const unsigned int* d_nlist, + const BoxDim box, + const unsigned int max_bonds, + const Scalar r_cut, + const bool groups_identical, + const unsigned int N, + const unsigned int block_size) { static unsigned int max_block_size = UINT_MAX; if (max_block_size == UINT_MAX) { cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void *)kernel::copy_nlist_possible_bonds); + cudaFuncGetAttributes(&attr, (const void*)kernel::copy_nlist_possible_bonds); max_block_size = attr.maxThreadsPerBlock; } unsigned int run_block_size = min(block_size, max_block_size); - kernel::copy_nlist_possible_bonds<<>>(d_all_possible_bonds, - d_postype, - d_tag, - d_sorted_indexes, - d_n_neigh, - d_nlist, - box, - max_bonds, - r_cut, - groups_identical, - N); + kernel::copy_nlist_possible_bonds<<>>( + d_all_possible_bonds, + d_postype, + d_tag, + d_sorted_indexes, + d_n_neigh, + d_nlist, + box, + max_bonds, + r_cut, + groups_identical, + N); return cudaSuccess; } -} // end namespace gpu -} // end namespace azplugins + } // end namespace gpu + } // end namespace azplugins -} // end namespace hoomd + } // end namespace hoomd // explicit templates for neighbor::LBVH with PointMapInsertOp -template void neighbor::gpu::lbvh_gen_codes(unsigned int *, unsigned int *, const azplugins::gpu::PointMapInsertOp&, -const Scalar3, const Scalar3, const unsigned int, const unsigned int, cudaStream_t); -template void neighbor::gpu::lbvh_bubble_aabbs(const neighbor::gpu::LBVHData, const azplugins::gpu::PointMapInsertOp&, -unsigned int *, const unsigned int, const unsigned int, cudaStream_t); -template void neighbor::gpu::lbvh_one_primitive(const neighbor::gpu::LBVHData, const azplugins::gpu::PointMapInsertOp&, cudaStream_t); -template void neighbor::gpu::lbvh_traverse_ropes(azplugins::gpu::NeighborListOp&, const neighbor::gpu::LBVHCompressedData&, -const azplugins::gpu::ParticleQueryOp&, const Scalar3 *, unsigned int, unsigned int, cudaStream_t); +template void neighbor::gpu::lbvh_gen_codes(unsigned int*, + unsigned int*, + const azplugins::gpu::PointMapInsertOp&, + const Scalar3, + const Scalar3, + const unsigned int, + const unsigned int, + cudaStream_t); +template void neighbor::gpu::lbvh_bubble_aabbs(const neighbor::gpu::LBVHData, + const azplugins::gpu::PointMapInsertOp&, + unsigned int*, + const unsigned int, + const unsigned int, + cudaStream_t); +template void neighbor::gpu::lbvh_one_primitive(const neighbor::gpu::LBVHData, + const azplugins::gpu::PointMapInsertOp&, + cudaStream_t); +template void neighbor::gpu::lbvh_traverse_ropes(azplugins::gpu::NeighborListOp&, + const neighbor::gpu::LBVHCompressedData&, + const azplugins::gpu::ParticleQueryOp&, + const Scalar3*, + unsigned int, + unsigned int, + cudaStream_t); + +///////////////////////////////////// +// neighbor program and wrappers +///////////////////////////////////// + +#define DEVICE __device__ __forceinline__ + +//! Insert operation for a point under a mapping. +/*! + * Extends the base neighbor::PointInsertOp to insert a point primitive + * subject to a mapping of the indexes. This is useful for reading from + * the array of particles that is pre-sorted by type so that the original + * particle data does not need to be shuffled. + */ +struct PointMapInsertOp + { + //! Constructor + /*! + * \param points_ List of points to insert (w entry is unused). + * \param map_ Map of the nominal index to the index in \a points_. + * \param N_ Number of primitives to insert. + */ + PointMapInsertOp(const Scalar4* points_, const unsigned int* map_, unsigned int N_) + : points(points_), map(map_), N(N_) + { + } + + //! Construct bounding box + /*! + * \param idx Nominal index of the primitive [0,N). + * \returns A neighbor::BoundingBox corresponding to the point at map[idx]. + */ + DEVICE neighbor::BoundingBox get(const unsigned int idx) const + { + const Scalar4 point = points[map[idx]]; + const Scalar3 p = make_scalar3(point.x, point.y, point.z); + + // construct the bounding box for a point + return neighbor::BoundingBox(p, p); + } + + __host__ DEVICE unsigned int size() const + { + return N; + } + + const Scalar4* points; + const unsigned int* map; //!< Map of particle indexes. + const unsigned int N; + }; + +//! Neighbor list particle query operation. +/*! + * \tparam use_body If true, use the body fields during query. + * + * This operation specifies the neighbor list traversal scheme. The + * query is between a SkippableBoundingSphere and the bounding boxes in + * the LBVH. The template parameters can be activated to engage body-filtering + * which is defined elsewhere in HOOMD. + * + * All spheres in the traversal are given the same search radius. This is compatible + * with a traversal-per-type-per-type scheme. It was found that passing this radius + * as a constant to the traversal program decreased register pressure in the kernel + * from a traversal-per-type scheme. + * + * The particles are traversed using a \a map. Ghost particles can be included + * in this map, and they will be neglected during traversal. + */ +template struct ParticleQueryOp + { + //! Constructor + /*! + * \param positions_ Particle positions. + * \param bodies_ Particle body tags. + * \param map_ Map of the particle indexes to traverse. + * \param N_ Number of particles (total). + * \param Nown_ Number of locally owned particles. + * \param rcut_ Cutoff radius for the spheres. + * \param rlist_ Total search radius for the spheres (differs under shifting). + */ + ParticleQueryOp(const Scalar4* positions_, + const unsigned int* bodies_, + const unsigned int* map_, + unsigned int N_, + unsigned int Nown_, + const Scalar rcut_, + const Scalar rlist_, + const BoxDim& box_) + : positions(positions_), bodies(bodies_), map(map_), N(N_), Nown(Nown_), rcut(rcut_), + rlist(rlist_), box(box_) + { + } + + //! Data stored per thread for traversal + /*! + * The body tags are only actually set if these are specified + * by the template parameters. The compiler might be able to optimize them + * out if they are unused. + */ + struct ThreadData + { + DEVICE ThreadData(Scalar3 position_, int idx_, unsigned int body_) + : position(position_), idx(idx_), body(body_) + { + } + + Scalar3 position; //!< Particle position + int idx; //!< True particle index + unsigned int body; //!< Particle body tag (may be invalid) + }; + + // specify that the traversal Volume is a bounding sphere + typedef SkippableBoundingSphere Volume; + + //! Loads the per-thread data + /*! + * \param idx Nominal primitive index. + * \returns The ThreadData required for traversal. + * + * The ThreadData is loaded subject to a mapping. The particle position + * is always loaded. The body is only loaded if the template + * parameter requires it. + */ + DEVICE ThreadData setup(const unsigned int idx) const + { + const unsigned int pidx = map[idx]; + + const Scalar4 position = positions[pidx]; + const Scalar3 r = make_scalar3(position.x, position.y, position.z); + + unsigned int body(0xffffffff); + if (use_body) + { + body = __ldg(bodies + pidx); + } + + return ThreadData(r, pidx, body); + } + + //! Return the traversal volume subject to a translation + /*! + * \param q The current thread data. + * \param image The image vector for traversal. + * \returns The traversal bounding volume. + * + * The ThreadData is converted to a search volume. The search sphere is + * made to be skipped if this is a ghost particle. + */ + DEVICE Volume get(const ThreadData& q, const Scalar3& image) const + { + return Volume(q.position + image, (q.idx < Nown) ? rlist : -1.0); + } + + //! Perform the overlap test with the LBVH + /*! + * \param v Traversal volume. + * \param box Box in LBVH to intersect with. + * \returns True if the volume and box overlap. + * + * The overlap test is implemented by the sphere. + */ + DEVICE bool overlap(const Volume& v, const neighbor::BoundingBox& box) const + { + return v.overlap(box); + } + + //! Refine the rough overlap test with a primitive + /*! + * \param q The current thread data. + * \param primitive Index of the intersected primitive. + * \returns True If the volumes still overlap after refinement. + * + * HOOMD's neighbor lists require additional filtering. This first ensures + * that the overlap is not with itself. If body filtering is enabled, + * particles in the same body do not overlap. + */ + DEVICE bool refine(const ThreadData& q, const int primitive) const + { + bool exclude = (q.idx == primitive); + + // body exclusion + if (use_body && !exclude && q.body != 0xffffffff) + { + const unsigned int body = __ldg(bodies + primitive); + exclude |= (q.body == body); + } + + return !exclude; + } + + //! Get the number of primitives + __host__ DEVICE unsigned int size() const + { + return N; + } + + const Scalar4* positions; //!< Particle positions + const unsigned int* bodies; //!< Particle bodies + const unsigned int* map; //!< Mapping of particles to read + unsigned int N; //!< Total number of particles in map + unsigned int Nown; //!< Number of particles owned by the local rank + Scalar rcut; //!< True cutoff radius + buffer + Scalar rlist; //!< Maximum cutoff (may include shifting) + const BoxDim box; //!< Box dimensions + }; + +//! Operation to write the neighbor list +/*! + * The neighbor list is assumed to be aligned to multiples of 4. This enables + * coalescing writes into packets of 4 neighbors without adding much register pressure. + * This object maintains an internal stack to do this, and it can restart from a previous + * traversal without losing information. + */ +struct NeighborListOp + { + //! Constructor + /*! + * \param neigh_list_ Neighbor list (aligned to multiple of 4) + * \param nneigh_ Neighbor of neighbors per particle + * \param new_max_neigh_ Maximum number of neighbors to allocate if overflow occurs. + * \param first_neigh_ First index for the current particle index in the neighbor list. + * \param max_neigh_ Maximum number of neighbors to allow per particle. + * + * The \a neigh_list_ pointer is internally cast into a uint4 for coalescing. + */ + NeighborListOp(unsigned int* neigh_list_, + unsigned int* nneigh_, + unsigned int* new_max_neigh_, + const size_t* first_neigh_, + unsigned int max_neigh_) + : nneigh(nneigh_), new_max_neigh(new_max_neigh_), first_neigh(first_neigh_), + max_neigh(max_neigh_) + { + neigh_list = reinterpret_cast(neigh_list_); + } + + //! Thread-local data + /*! + * The thread-local data constitutes a stack of neighbors to write, the index of the current + * primitive, the first index to write into, and the current number of neighbors found for this + * thread. + */ + struct ThreadData + { + //! Constructor + /*! + * \param idx_ The index of this particle. + * \param first_ The first neighbor index of this particle. + * \param num_neigh_ The current number of neighbors of this particle. + * \param stack_ The initial values for the stack (can be all 0s if \a num_neigh_ is aligned + * to 4). + */ + DEVICE ThreadData(const unsigned int idx_, + const unsigned int first_, + const unsigned int num_neigh_, + const uint4 stack_) + : idx(idx_), first(first_), num_neigh(num_neigh_) + { + stack[0] = stack_.x; + stack[1] = stack_.y; + stack[2] = stack_.z; + stack[3] = stack_.w; + } + + unsigned int idx; //!< Index of primitive + size_t first; //!< First index to use for writing neighbors + unsigned int num_neigh; //!< Number of neighbors for this thread + unsigned int stack[4]; //!< Internal stack of neighbors + }; + + //! Setup the thread data + /*! + * \param idx Index of this thread. + * \param q Thread-local query data. + * \returns The ThreadData for output. + * + * \tparam Type of QueryData. + * + * This setup function can poach data from the query data in order to save loads. + * In this case, it makes use of the particle index mapping. + */ + template + DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const + { + const size_t first = __ldg(first_neigh + q.idx); + const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable + + // prefetch from the stack if current number of neighbors does not align with a boundary + /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if + statement, initialized with zeros, and then assigned inside (so that only + one return statement was needed). It went away using: + + uint4 tmp = neigh_list[...]; + stack = tmp; + + But this looked funny, so the structure below seems more human readable. + */ + if (num_neigh % 4 != 0) + { + uint4 stack = neigh_list[(first + num_neigh - 1) / 4]; + return ThreadData(q.idx, first, num_neigh, stack); + } + else + { + return ThreadData(q.idx, first, num_neigh, make_uint4(0, 0, 0, 0)); + } + } + + //! Processes a newly intersected primitive. + /*! + * \param t My output thread data. + * \param primitive The index of the primitive to process. + * + * If the neighbor will fit into the allocated memory, it is pushed onto the stack. + * The stack is written to memory if it is full. The number of neighbors found for this + * thread is incremented, regardless. + */ + DEVICE void process(ThreadData& t, const int primitive) const + { + if (t.num_neigh < max_neigh) + { + // push primitive into the stack of 4, pre-increment + const unsigned int offset = t.num_neigh % 4; + t.stack[offset] = primitive; + // coalesce writes into chunks of 4 + if (offset == 3) + { + neigh_list[(t.first + t.num_neigh) / 4] + = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); + } + } + ++t.num_neigh; + } + + //! Finish the output job once the thread is ready to terminate. + /*! + * \param t My output thread data + * + * The number of neighbors found for this thread is written. If this value + * exceeds the current allocation, this value is atomically maximized for + * reallocation. Any values remaining on the stack are written to ensure the + * list is complete. + */ + DEVICE void finalize(const ThreadData& t) const + { + nneigh[t.idx] = t.num_neigh; + if (t.num_neigh > max_neigh) + { + atomicMax(new_max_neigh, t.num_neigh); + } + else if (t.num_neigh % 4 != 0) + { + // write partial (leftover) stack, counting is now post-increment so need to shift by 1 + // only need to do this if didn't overflow, since all neighbors were already written due + // to alignment of max + neigh_list[(t.first + t.num_neigh - 1) / 4] + = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); + } + } + + uint4* neigh_list; //!< Neighbors of each sphere + unsigned int* nneigh; //!< Number of neighbors per search sphere + unsigned int* new_max_neigh; //!< New maximum number of neighbors + const size_t* first_neigh; //!< Index of first neighbor + unsigned int max_neigh; //!< Maximum number of neighbors allocated + }; + +//! Host function to convert a double to a float in round-down mode +float double2float_rd(double x) + { + float xf = static_cast(x); + if (static_cast(xf) > x) + { + xf = std::nextafterf(xf, -std::numeric_limits::infinity()); + } + return xf; + } \ No newline at end of file diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 3fd589d7..e44f87f7 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -19,6 +19,7 @@ #include "hoomd/ParticleData.cuh" #include +#include "hoomd/NeighborListGPUTree.cuh" #ifdef NVCC #define DEVICE __device__ __forceinline__ @@ -63,7 +64,36 @@ cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bo const unsigned int size, int *d_max_non_zero_bonds); -//! Insert operation for a point under a mapping. +/ ThreadData for output. + * + * \tparam Type of QueryData. + * + * This setup function can poach data from the query data in order to save loads. + * In this case, it makes use of the particle index mapping. + */ + template + DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const + { + //const unsigned int first = __ldg(first_neigh + q.idx); + const unsigned int first = q.idx+q.idx*(max_neigh-1); + const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable + + // prefetch from the stack if current number of neighbors does not align with a boundary + /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if + statement, initialized with zeros, and then assigned inside (so that only + one return statement was needed). It went away using: + uint4 tmp = neigh_list[...]; + stack = tmp; + But this looked funny, so the structure below seems more human readable. + */ + if (num_neigh % 4 != 0) + { + uint4 stack = neigh_list[(first+num_neigh-1)/4]; + return ThreadData(q.idx, first, num_neigh, stack); + } + else + { + return/! Insert operation for a point under a mapping. /*! * Extends the base neighbor::PointInsertOp to insert a point primitive * subject to a mapping of the indexes. This is useful for reading from @@ -118,7 +148,8 @@ struct PointMapInsertOp : public neighbor::PointInsertOp * The particles are traversed using a \a map. Ghost particles can be included * in this map, and they will be neglected during traversal. */ -struct ParticleQueryOp + +/* struct ParticleQueryOp { //! Constructor /*! @@ -297,36 +328,7 @@ struct NeighborListOp /*! * \param idx Index of this thread. * \param q Thread-local query data. - * \returns The ThreadData for output. - * - * \tparam Type of QueryData. - * - * This setup function can poach data from the query data in order to save loads. - * In this case, it makes use of the particle index mapping. - */ - template - DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const - { - //const unsigned int first = __ldg(first_neigh + q.idx); - const unsigned int first = q.idx+q.idx*(max_neigh-1); - const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable - - // prefetch from the stack if current number of neighbors does not align with a boundary - /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if - statement, initialized with zeros, and then assigned inside (so that only - one return statement was needed). It went away using: - uint4 tmp = neigh_list[...]; - stack = tmp; - But this looked funny, so the structure below seems more human readable. - */ - if (num_neigh % 4 != 0) - { - uint4 stack = neigh_list[(first+num_neigh-1)/4]; - return ThreadData(q.idx, first, num_neigh, stack); - } - else - { - return ThreadData(q.idx, first, num_neigh, make_uint4(0,0,0,0)); + * \returns The ThreadData(q.idx, first, num_neigh, make_uint4(0,0,0,0)); } } @@ -386,6 +388,7 @@ struct NeighborListOp unsigned int* new_max_neigh; //!< New maximum number of neighbors unsigned int max_neigh; //!< Maximum number of neighbors allocated }; + */ //! Sentinel for an invalid particle (e.g., ghost) const unsigned int NeighborListTypeSentinel = 0xffffffff; diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 19228fd2..bc238d27 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -23,13 +23,6 @@ #include "hip/hip_runtime.h" #include "hoomd/md/NeighborListGPUTree.cuh" -// @mphoward: There is some issue with importing the right headers from the extern/neighbor -// class. Could you please have a look at it and check which headers to import? - -/* -#include "hoomd/extern/neighbor/include/neighbor/LBVH.h" -#include "hoomd/extern/neighbor/include/neighbor/LBVHTraverser.h" -*/ namespace hoomd { @@ -88,8 +81,8 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - neighbor::LBVH m_lbvh; //!< LBVH for group_2 - neighbor::LBVHTraverser m_traverser; //!< LBVH traverer + std::vector> m_lbvh; //!< Array of LBVHs per-type + std::vector> m_traverser; //!< Array of LBVH traverers per-type GPUVector m_image_list; //!< List of translation vectors for traversal unsigned int m_n_images; //!< Number of translation vectors for traversal From 9b8707ea363db4202ed30029c8b0937d78bf0720 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 1 May 2026 12:17:23 -0500 Subject: [PATCH 32/45] remove neighbor, use wrapper from Tree Neigh list --- src/DynamicBondUpdater.h | 4 +- src/DynamicBondUpdaterGPU.cc | 111 +++++++-- src/DynamicBondUpdaterGPU.cu | 425 ++-------------------------------- src/DynamicBondUpdaterGPU.cuh | 326 +------------------------- src/DynamicBondUpdaterGPU.h | 11 +- 5 files changed, 111 insertions(+), 766 deletions(-) diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 547ea52f..013f7038 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -180,8 +180,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list - std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set - bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set + std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set + bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index b4c89c47..ebae2745 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -30,8 +30,8 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) - : DynamicBondUpdater(sysdef, trigger, pair_nlist, group_1, group_2, seed), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), - m_lbvh(m_exec_conf), m_traverser(m_exec_conf) + : DynamicBondUpdater(sysdef, trigger, pair_nlist, group_1, group_2, seed), + m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) { // only one GPU is supported @@ -64,8 +64,7 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s unsigned int bond_type) : DynamicBondUpdater(sysdef,trigger,pair_nlist,group_1,group_2, seed, r_cut, probability,max_bonds_group_1,max_bonds_group_2,bond_type), - m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf), - m_lbvh(m_exec_conf), m_traverser(m_exec_conf) + m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; @@ -75,18 +74,34 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); } - m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_tuner_copy_nlist.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, m_exec_conf, "dynamic_bonding_copy_nlist")); - m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, + m_tuner_filter_bonds.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, m_exec_conf, "dynamic_bonding_filter_bonds")); + m_tuner_build.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, + m_exec_conf, + "dynamic_bonding_build_nlist")); + + + m_tuner_traverse.reset(new Autotuner<1>({m_traverser->getTunableParameters()}, + m_exec_conf, + "dynamic_bonding_traverse_nlist")); + + + m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds ,m_tuner_build,m_tuner_traverse}); + } DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdaterGPU" << std::endl; + + // destroy all of the created stream + hipStreamDestroy(m_stream); + } @@ -97,13 +112,28 @@ void DynamicBondUpdaterGPU::buildTree() ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); const BoxDim lbvh_box = getLBVHBox(); + m_lbvh.reset(new hoomd::md::kernel::LBVHWrapper()); + m_traverser.reset(new hoomd::md::kernel::LBVHTraverserWrapper()); + hipStreamCreate(&m_stream); + + m_lbvh->setup(d_pos.data, + d_index_group_2.data, + m_group_2->getNumMembers(), + m_stream); + + hipDeviceSynchronize(); + m_tuner_build->begin(); + const unsigned int block_size = m_tuner_build->getParam()[0]; // build a lbvh for group_2 // this tree is traversed in traverseTree() - m_lbvh.build(azplugins::gpu::PointMapInsertOp(d_pos.data, - d_index_group_2.data, - m_group_2->getNumMembers()), - lbvh_box.getLo(), - lbvh_box.getHi()); + m_lbvh->build(d_pos.data, + d_index_group_2.data, + m_group_2->getNumMembers(), + lbvh_box.getLo(), + lbvh_box.getHi(), + m_stream, + block_size); + m_tuner_build->end(); } @@ -111,30 +141,63 @@ void DynamicBondUpdaterGPU::traverseTree() { ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::overwrite); ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); + ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); + ArrayHandle d_image_list(m_image_list, access_location::device, access_mode::read); + + //todo: this needs to be set/written somewhere?? + ArrayHandle d_traverse_order(m_traverse_order, access_location::device, access_mode::read); + // clear the neighbor counts cudaMemset(d_n_neigh.data,0, sizeof(unsigned int)*m_group_1->getNumMembers()); const BoxDim& box = m_pdata->getBox(); - // neighbor list write op - hoomd::azplugins::NeighborListOp nlist_op(d_nlist.data, d_n_neigh.data, m_max_bonds_overflow_flag.getDeviceFlags(), m_max_bonds); - ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); - neighbor::MapTransformOp map(d_index_group_2.data ); - m_traverser.setup(map, m_lbvh); - hoomd::azplugins::ParticleQueryOp query_op(d_pos.data, - d_index_group_1.data, - m_group_1->getNumMembers(), - m_pdata->getMaxN(), - m_r_cut, - box); + m_traverser->setup(d_index_group_1.data, + *(m_lbvh->get()), + m_stream); + + + // pack args to the traverser + hoomd::md::kernel::LBVHTraverserWrapper::TraverserArgs args; + + args.map = d_index_group_1.data; + + // particles + args.positions = d_pos.data; + //todo: does it make sense to take rigid bodies into account in this code? + args.bodies = NULL; + args.order = d_traverse_order.data; + args.N = m_group_1->getNumMembers(); + args.Nown = m_pdata->getMaxN(); + //todo: double check that this is correct + args.rcut = m_r_cut; + args.rlist = m_r_cut; + args.box = box; + + // neighbor list write op for this type + args.neigh_list = d_nlist.data; + args.nneigh = d_n_neigh.data; + args.new_max_neigh = m_max_bonds_overflow; + args.first_neigh = d_head_list.data; + args.max_neigh = m_max_bonds_overflow; + + + m_tuner_traverse->begin(); + const unsigned int block_size = m_tuner_traverse->getParam()[0]; - m_traverser.traverse(nlist_op, query_op, map,m_lbvh, m_image_list); + m_traverser->traverse(args, + *(m_lbvh->get()), + d_image_list.data, + (unsigned int)m_image_list.getNumElements(), + m_stream, + block_size); + m_tuner_traverse->end(); m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); @@ -161,7 +224,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const BoxDim& box = m_pdata->getBox(); - m_tuner_copy_nlist->begin(); + m_tuner_copy_nlist->begin(); gpu::copy_possible_bonds(d_all_possible_bonds.data, d_pos.data, d_tag.data, diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index 9fe9ddef..30f067f0 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -10,11 +10,13 @@ */ #include "DynamicBondUpdaterGPU.cuh" -#include "NeighborListGPUTree.cuh" +#include "hoomd/md/NeighborListGPUTree.cuh" #include "hoomd/HOOMDMath.h" -// #include -#include + #include +#include +#include +#include namespace hoomd { @@ -255,24 +257,24 @@ cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3* d_all_possible_bo if (size == 0) return cudaSuccess; // wrapper for pointer needed for thrust - HOOMD_THRUST::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); + thrust::device_ptr d_all_possible_bonds_wrap(d_all_possible_bonds); isZeroBondGPU zero; - HOOMD_THRUST::device_ptr last0 - = HOOMD_THRUST::remove_if(d_all_possible_bonds_wrap, + thrust::device_ptr last0 + = thrust::remove_if(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + size, zero); - unsigned int l0 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last0); + unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); // sort remainder by distance, should make all identical bonds consequtive SortBondsGPU sort; - HOOMD_THRUST::sort(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, sort); + thrust::sort(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, sort); // thrust::unique only removes identical consequtive elements, so sort above is needed. CompareBondsGPU comp; - HOOMD_THRUST::device_ptr last1 - = HOOMD_THRUST::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, comp); - unsigned int l1 = HOOMD_THRUST::distance(d_all_possible_bonds_wrap, last1); + thrust::device_ptr last1 + = thrust::unique(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + l0, comp); + unsigned int l1 = thrust::distance(d_all_possible_bonds_wrap, last1); *d_max_non_zero_bonds = l1; @@ -360,405 +362,4 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, } // end namespace hoomd -// explicit templates for neighbor::LBVH with PointMapInsertOp -template void neighbor::gpu::lbvh_gen_codes(unsigned int*, - unsigned int*, - const azplugins::gpu::PointMapInsertOp&, - const Scalar3, - const Scalar3, - const unsigned int, - const unsigned int, - cudaStream_t); -template void neighbor::gpu::lbvh_bubble_aabbs(const neighbor::gpu::LBVHData, - const azplugins::gpu::PointMapInsertOp&, - unsigned int*, - const unsigned int, - const unsigned int, - cudaStream_t); -template void neighbor::gpu::lbvh_one_primitive(const neighbor::gpu::LBVHData, - const azplugins::gpu::PointMapInsertOp&, - cudaStream_t); -template void neighbor::gpu::lbvh_traverse_ropes(azplugins::gpu::NeighborListOp&, - const neighbor::gpu::LBVHCompressedData&, - const azplugins::gpu::ParticleQueryOp&, - const Scalar3*, - unsigned int, - unsigned int, - cudaStream_t); - -///////////////////////////////////// -// neighbor program and wrappers -///////////////////////////////////// - -#define DEVICE __device__ __forceinline__ - -//! Insert operation for a point under a mapping. -/*! - * Extends the base neighbor::PointInsertOp to insert a point primitive - * subject to a mapping of the indexes. This is useful for reading from - * the array of particles that is pre-sorted by type so that the original - * particle data does not need to be shuffled. - */ -struct PointMapInsertOp - { - //! Constructor - /*! - * \param points_ List of points to insert (w entry is unused). - * \param map_ Map of the nominal index to the index in \a points_. - * \param N_ Number of primitives to insert. - */ - PointMapInsertOp(const Scalar4* points_, const unsigned int* map_, unsigned int N_) - : points(points_), map(map_), N(N_) - { - } - - //! Construct bounding box - /*! - * \param idx Nominal index of the primitive [0,N). - * \returns A neighbor::BoundingBox corresponding to the point at map[idx]. - */ - DEVICE neighbor::BoundingBox get(const unsigned int idx) const - { - const Scalar4 point = points[map[idx]]; - const Scalar3 p = make_scalar3(point.x, point.y, point.z); - - // construct the bounding box for a point - return neighbor::BoundingBox(p, p); - } - - __host__ DEVICE unsigned int size() const - { - return N; - } - - const Scalar4* points; - const unsigned int* map; //!< Map of particle indexes. - const unsigned int N; - }; - -//! Neighbor list particle query operation. -/*! - * \tparam use_body If true, use the body fields during query. - * - * This operation specifies the neighbor list traversal scheme. The - * query is between a SkippableBoundingSphere and the bounding boxes in - * the LBVH. The template parameters can be activated to engage body-filtering - * which is defined elsewhere in HOOMD. - * - * All spheres in the traversal are given the same search radius. This is compatible - * with a traversal-per-type-per-type scheme. It was found that passing this radius - * as a constant to the traversal program decreased register pressure in the kernel - * from a traversal-per-type scheme. - * - * The particles are traversed using a \a map. Ghost particles can be included - * in this map, and they will be neglected during traversal. - */ -template struct ParticleQueryOp - { - //! Constructor - /*! - * \param positions_ Particle positions. - * \param bodies_ Particle body tags. - * \param map_ Map of the particle indexes to traverse. - * \param N_ Number of particles (total). - * \param Nown_ Number of locally owned particles. - * \param rcut_ Cutoff radius for the spheres. - * \param rlist_ Total search radius for the spheres (differs under shifting). - */ - ParticleQueryOp(const Scalar4* positions_, - const unsigned int* bodies_, - const unsigned int* map_, - unsigned int N_, - unsigned int Nown_, - const Scalar rcut_, - const Scalar rlist_, - const BoxDim& box_) - : positions(positions_), bodies(bodies_), map(map_), N(N_), Nown(Nown_), rcut(rcut_), - rlist(rlist_), box(box_) - { - } - - //! Data stored per thread for traversal - /*! - * The body tags are only actually set if these are specified - * by the template parameters. The compiler might be able to optimize them - * out if they are unused. - */ - struct ThreadData - { - DEVICE ThreadData(Scalar3 position_, int idx_, unsigned int body_) - : position(position_), idx(idx_), body(body_) - { - } - - Scalar3 position; //!< Particle position - int idx; //!< True particle index - unsigned int body; //!< Particle body tag (may be invalid) - }; - - // specify that the traversal Volume is a bounding sphere - typedef SkippableBoundingSphere Volume; - - //! Loads the per-thread data - /*! - * \param idx Nominal primitive index. - * \returns The ThreadData required for traversal. - * - * The ThreadData is loaded subject to a mapping. The particle position - * is always loaded. The body is only loaded if the template - * parameter requires it. - */ - DEVICE ThreadData setup(const unsigned int idx) const - { - const unsigned int pidx = map[idx]; - - const Scalar4 position = positions[pidx]; - const Scalar3 r = make_scalar3(position.x, position.y, position.z); - - unsigned int body(0xffffffff); - if (use_body) - { - body = __ldg(bodies + pidx); - } - - return ThreadData(r, pidx, body); - } - - //! Return the traversal volume subject to a translation - /*! - * \param q The current thread data. - * \param image The image vector for traversal. - * \returns The traversal bounding volume. - * - * The ThreadData is converted to a search volume. The search sphere is - * made to be skipped if this is a ghost particle. - */ - DEVICE Volume get(const ThreadData& q, const Scalar3& image) const - { - return Volume(q.position + image, (q.idx < Nown) ? rlist : -1.0); - } - - //! Perform the overlap test with the LBVH - /*! - * \param v Traversal volume. - * \param box Box in LBVH to intersect with. - * \returns True if the volume and box overlap. - * - * The overlap test is implemented by the sphere. - */ - DEVICE bool overlap(const Volume& v, const neighbor::BoundingBox& box) const - { - return v.overlap(box); - } - - //! Refine the rough overlap test with a primitive - /*! - * \param q The current thread data. - * \param primitive Index of the intersected primitive. - * \returns True If the volumes still overlap after refinement. - * - * HOOMD's neighbor lists require additional filtering. This first ensures - * that the overlap is not with itself. If body filtering is enabled, - * particles in the same body do not overlap. - */ - DEVICE bool refine(const ThreadData& q, const int primitive) const - { - bool exclude = (q.idx == primitive); - - // body exclusion - if (use_body && !exclude && q.body != 0xffffffff) - { - const unsigned int body = __ldg(bodies + primitive); - exclude |= (q.body == body); - } - - return !exclude; - } - //! Get the number of primitives - __host__ DEVICE unsigned int size() const - { - return N; - } - - const Scalar4* positions; //!< Particle positions - const unsigned int* bodies; //!< Particle bodies - const unsigned int* map; //!< Mapping of particles to read - unsigned int N; //!< Total number of particles in map - unsigned int Nown; //!< Number of particles owned by the local rank - Scalar rcut; //!< True cutoff radius + buffer - Scalar rlist; //!< Maximum cutoff (may include shifting) - const BoxDim box; //!< Box dimensions - }; - -//! Operation to write the neighbor list -/*! - * The neighbor list is assumed to be aligned to multiples of 4. This enables - * coalescing writes into packets of 4 neighbors without adding much register pressure. - * This object maintains an internal stack to do this, and it can restart from a previous - * traversal without losing information. - */ -struct NeighborListOp - { - //! Constructor - /*! - * \param neigh_list_ Neighbor list (aligned to multiple of 4) - * \param nneigh_ Neighbor of neighbors per particle - * \param new_max_neigh_ Maximum number of neighbors to allocate if overflow occurs. - * \param first_neigh_ First index for the current particle index in the neighbor list. - * \param max_neigh_ Maximum number of neighbors to allow per particle. - * - * The \a neigh_list_ pointer is internally cast into a uint4 for coalescing. - */ - NeighborListOp(unsigned int* neigh_list_, - unsigned int* nneigh_, - unsigned int* new_max_neigh_, - const size_t* first_neigh_, - unsigned int max_neigh_) - : nneigh(nneigh_), new_max_neigh(new_max_neigh_), first_neigh(first_neigh_), - max_neigh(max_neigh_) - { - neigh_list = reinterpret_cast(neigh_list_); - } - - //! Thread-local data - /*! - * The thread-local data constitutes a stack of neighbors to write, the index of the current - * primitive, the first index to write into, and the current number of neighbors found for this - * thread. - */ - struct ThreadData - { - //! Constructor - /*! - * \param idx_ The index of this particle. - * \param first_ The first neighbor index of this particle. - * \param num_neigh_ The current number of neighbors of this particle. - * \param stack_ The initial values for the stack (can be all 0s if \a num_neigh_ is aligned - * to 4). - */ - DEVICE ThreadData(const unsigned int idx_, - const unsigned int first_, - const unsigned int num_neigh_, - const uint4 stack_) - : idx(idx_), first(first_), num_neigh(num_neigh_) - { - stack[0] = stack_.x; - stack[1] = stack_.y; - stack[2] = stack_.z; - stack[3] = stack_.w; - } - - unsigned int idx; //!< Index of primitive - size_t first; //!< First index to use for writing neighbors - unsigned int num_neigh; //!< Number of neighbors for this thread - unsigned int stack[4]; //!< Internal stack of neighbors - }; - - //! Setup the thread data - /*! - * \param idx Index of this thread. - * \param q Thread-local query data. - * \returns The ThreadData for output. - * - * \tparam Type of QueryData. - * - * This setup function can poach data from the query data in order to save loads. - * In this case, it makes use of the particle index mapping. - */ - template - DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const - { - const size_t first = __ldg(first_neigh + q.idx); - const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable - - // prefetch from the stack if current number of neighbors does not align with a boundary - /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if - statement, initialized with zeros, and then assigned inside (so that only - one return statement was needed). It went away using: - - uint4 tmp = neigh_list[...]; - stack = tmp; - - But this looked funny, so the structure below seems more human readable. - */ - if (num_neigh % 4 != 0) - { - uint4 stack = neigh_list[(first + num_neigh - 1) / 4]; - return ThreadData(q.idx, first, num_neigh, stack); - } - else - { - return ThreadData(q.idx, first, num_neigh, make_uint4(0, 0, 0, 0)); - } - } - - //! Processes a newly intersected primitive. - /*! - * \param t My output thread data. - * \param primitive The index of the primitive to process. - * - * If the neighbor will fit into the allocated memory, it is pushed onto the stack. - * The stack is written to memory if it is full. The number of neighbors found for this - * thread is incremented, regardless. - */ - DEVICE void process(ThreadData& t, const int primitive) const - { - if (t.num_neigh < max_neigh) - { - // push primitive into the stack of 4, pre-increment - const unsigned int offset = t.num_neigh % 4; - t.stack[offset] = primitive; - // coalesce writes into chunks of 4 - if (offset == 3) - { - neigh_list[(t.first + t.num_neigh) / 4] - = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); - } - } - ++t.num_neigh; - } - - //! Finish the output job once the thread is ready to terminate. - /*! - * \param t My output thread data - * - * The number of neighbors found for this thread is written. If this value - * exceeds the current allocation, this value is atomically maximized for - * reallocation. Any values remaining on the stack are written to ensure the - * list is complete. - */ - DEVICE void finalize(const ThreadData& t) const - { - nneigh[t.idx] = t.num_neigh; - if (t.num_neigh > max_neigh) - { - atomicMax(new_max_neigh, t.num_neigh); - } - else if (t.num_neigh % 4 != 0) - { - // write partial (leftover) stack, counting is now post-increment so need to shift by 1 - // only need to do this if didn't overflow, since all neighbors were already written due - // to alignment of max - neigh_list[(t.first + t.num_neigh - 1) / 4] - = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); - } - } - - uint4* neigh_list; //!< Neighbors of each sphere - unsigned int* nneigh; //!< Number of neighbors per search sphere - unsigned int* new_max_neigh; //!< New maximum number of neighbors - const size_t* first_neigh; //!< Index of first neighbor - unsigned int max_neigh; //!< Maximum number of neighbors allocated - }; - -//! Host function to convert a double to a float in round-down mode -float double2float_rd(double x) - { - float xf = static_cast(x); - if (static_cast(xf) > x) - { - xf = std::nextafterf(xf, -std::numeric_limits::infinity()); - } - return xf; - } \ No newline at end of file diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index e44f87f7..27883249 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -19,7 +19,7 @@ #include "hoomd/ParticleData.cuh" #include -#include "hoomd/NeighborListGPUTree.cuh" +#include "hoomd/md/NeighborListGPUTree.cuh" #ifdef NVCC #define DEVICE __device__ __forceinline__ @@ -64,331 +64,7 @@ cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bo const unsigned int size, int *d_max_non_zero_bonds); -/ ThreadData for output. - * - * \tparam Type of QueryData. - * - * This setup function can poach data from the query data in order to save loads. - * In this case, it makes use of the particle index mapping. - */ - template - DEVICE ThreadData setup(const unsigned int idx, const QueryDataT& q) const - { - //const unsigned int first = __ldg(first_neigh + q.idx); - const unsigned int first = q.idx+q.idx*(max_neigh-1); - const unsigned int num_neigh = nneigh[q.idx]; // no __ldg, since this is writeable - // prefetch from the stack if current number of neighbors does not align with a boundary - /* NOTE: There seemed to be a compiler error/bug when stack was declared outside this if - statement, initialized with zeros, and then assigned inside (so that only - one return statement was needed). It went away using: - uint4 tmp = neigh_list[...]; - stack = tmp; - But this looked funny, so the structure below seems more human readable. - */ - if (num_neigh % 4 != 0) - { - uint4 stack = neigh_list[(first+num_neigh-1)/4]; - return ThreadData(q.idx, first, num_neigh, stack); - } - else - { - return/! Insert operation for a point under a mapping. -/*! - * Extends the base neighbor::PointInsertOp to insert a point primitive - * subject to a mapping of the indexes. This is useful for reading from - * the array of particles that is pre-sorted by type so that the original - * particle data does not need to be shuffled. - */ -struct PointMapInsertOp : public neighbor::PointInsertOp - { - //! Constructor - /*! - * \param points_ List of points to insert (w entry is unused). - * \param map_ Map of the nominal index to the index in \a points_. - * \param N_ Number of primitives to insert. - */ - PointMapInsertOp(const Scalar4 *points_, const unsigned int *map_, unsigned int N_) - : neighbor::PointInsertOp(points_, N_), map(map_) - {} - - #ifdef NVCC - //! Construct bounding box - /*! - * \param idx Nominal index of the primitive [0,N). - * \returns A neighbor::BoundingBox corresponding to the point at map[idx]. - */ - DEVICE neighbor::BoundingBox get(const unsigned int idx) const - { - const Scalar4 point = points[map[idx]]; - const Scalar3 p = make_scalar3(point.x, point.y, point.z); - // construct the bounding box for a point - return neighbor::BoundingBox(p,p); - } - #endif - - const unsigned int *map; //!< Map of particle indexes. - }; - -//! Neighbor list particle query operation. -/*! - * \tparam use_body If true, use the body fields during query. - * \tparam use_diam If true, use the diameter fields during query. - * - * This operation specifies the neighbor list traversal scheme. The - * query is between a SkippableBoundingSphere and the bounding boxes in - * the LBVH. The template parameters can be activated to engage body-filtering - * or diameter-shifting, which are defined elsewhere in HOOMD. - * - * All spheres in the traversal are given the same search radius. This is compatible - * with a traversal-per-type-per-type scheme. It was found that passing this radius - * as a constant to the traversal program decreased register pressure in the kernel - * from a traversal-per-type scheme. - * - * The particles are traversed using a \a map. Ghost particles can be included - * in this map, and they will be neglected during traversal. - */ - -/* struct ParticleQueryOp - { - //! Constructor - /*! - * \param positions_ Particle positions. - * \param map_ Map of the particle indexes to traverse. - * \param N_ Number of particles (total). - * \param Nown_ Number of locally owned particles. - * \param rcut_ Cutoff radius for the spheres. - */ - ParticleQueryOp(const Scalar4 *positions_, - const unsigned int* map_, - unsigned int N_, - unsigned int Nown_, - const Scalar rcut_, - const BoxDim& box_) - : positions(positions_), map(map_), - N(N_), Nown(Nown_), rcut(rcut_), box(box_) - {} - - #ifdef NVCC - //! Data stored per thread for traversal - /*! - * The body tag and diameter are only actually set if these are specified - * by the template parameters. The compiler might be able to optimize them - * out if they are unused. - */ - struct ThreadData - { - HOSTDEVICE ThreadData(Scalar3 position_, - int idx_) - : position(position_), idx(idx_) - {} - - Scalar3 position; //!< Particle position - int idx; //!< True particle index - }; - - // specify that the traversal Volume is a bounding sphere - typedef neighbor::BoundingSphere Volume; - - //! Loads the per-thread data - /*! - * \param idx Nominal primitive index. - * \returns The ThreadData required for traversal. - * - * The ThreadData is loaded subject to a mapping. The particle position - * is always loaded. The body and diameter are only loaded if the template - * parameter requires it. - */ - DEVICE ThreadData setup(const unsigned int idx) const - { - const unsigned int pidx = map[idx]; - - const Scalar4 position = positions[pidx]; - const Scalar3 r = make_scalar3(position.x, position.y, position.z); - - return ThreadData(r, idx); - } - - //! Return the traversal volume subject to a translation - /*! - * \param q The current thread data. - * \param image The image vector for traversal. - * \returns The traversal bounding volume. - * - * The ThreadData is converted to a search volume. The search sphere is - * made to be skipped if this is a ghost particle. - */ - DEVICE Volume get(const ThreadData& q, const Scalar3& image) const - { - return Volume(q.position+image, (q.idx < Nown) ? rcut : -1.0); - } - - //! Perform the overlap test with the LBVH - /*! - * \param v Traversal volume. - * \param box Box in LBVH to intersect with. - * \returns True if the volume and box overlap. - * - * The overlap test is implemented by the sphere. - */ - DEVICE bool overlap(const Volume& v, const neighbor::BoundingBox& box) const - { - return v.overlap(box); - } - - //! Refine the rough overlap test with a primitive - /*! - * \param q The current thread data. - * \param primitive Index of the intersected primitive. - * \returns True If the volumes still overlap after refinement. - * - */ - DEVICE bool refine(const ThreadData& q, const int primitive) const - { - bool exclude = (q.idx == primitive); - return !exclude; - } - #endif - - //! Get the number of primitives - HOSTDEVICE unsigned int size() const - { - return N; - } - - const Scalar4 *positions; //!< Particle positions - const unsigned int *map; //!< Mapping of particles to read - unsigned int N; //!< Total number of particles in map - unsigned int Nown; //!< Number of particles owned by the local rank - Scalar rcut; //!< True cutoff radius + buffer - const BoxDim box; //!< Box dimensions - }; - -//! Operation to write the neighbor list -/*! - * The neighbor list is assumed to be aligned to multiples of 4. This enables - * coalescing writes into packets of 4 neighbors without adding much register pressure. - * This object maintains an internal stack to do this, and it can restart from a previous - * traversal without losing information. - */ -struct NeighborListOp - { - //! Constructor - /*! - * \param neigh_list_ Neighbor list (aligned to multiple of 4) - * \param nneigh_ Neighbor of neighbors per particle - * \param new_max_neigh_ Maximum number of neighbors to allocate if overflow occurs. - * \param max_neigh_ Maximum number of neighbors to allow per particle. - * - * The \a neigh_list_ pointer is internally cast into a uint4 for coalescing. - */ - NeighborListOp(unsigned int* neigh_list_, - unsigned int* nneigh_, - unsigned int* new_max_neigh_, - unsigned int max_neigh_) - : nneigh(nneigh_), new_max_neigh(new_max_neigh_), max_neigh(max_neigh_) - { - neigh_list = reinterpret_cast(neigh_list_); - } - - #ifdef NVCC - //! Thread-local data - /*! - * The thread-local data constitutes a stack of neighbors to write, the index of the current - * primitive, the first index to write into, and the current number of neighbors found for this thread. - */ - struct ThreadData - { - //! Constructor - /*! - * \param idx_ The index of this particle. - * \param first_ The first neighbor index of this particle. - * \param num_neigh_ The current number of neighbors of this particle. - * \param stack_ The initial values for the stack (can be all 0s if \a num_neigh_ is aligned to 4). - */ - DEVICE ThreadData(const unsigned int idx_, - const unsigned int first_, - const unsigned int num_neigh_, - const uint4 stack_) - : idx(idx_), first(first_), num_neigh(num_neigh_) - { - stack[0] = stack_.x; - stack[1] = stack_.y; - stack[2] = stack_.z; - stack[3] = stack_.w; - } - - unsigned int idx; //!< Index of primitive - unsigned int first; //!< First index to use for writing neighbors - unsigned int num_neigh; //!< Number of neighbors for this thread - unsigned int stack[4]; //!< Internal stack of neighbors - }; - - //! Setup the thread data - /*! - * \param idx Index of this thread. - * \param q Thread-local query data. - * \returns The ThreadData(q.idx, first, num_neigh, make_uint4(0,0,0,0)); - } - } - - //! Processes a newly intersected primitive. - /*! - * \param t My output thread data. - * \param primitive The index of the primitive to process. - * - * If the neighbor will fit into the allocated memory, it is pushed onto the stack. - * The stack is written to memory if it is full. The number of neighbors found for this - * thread is incremented, regardless. - */ - DEVICE void process(ThreadData& t, const int primitive) const - { - if (t.num_neigh < max_neigh) - { - // push primitive into the stack of 4, pre-increment - const unsigned int offset = t.num_neigh % 4; - t.stack[offset] = primitive; - // coalesce writes into chunks of 4 - if (offset == 3) - { - neigh_list[(t.first+t.num_neigh)/4] = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); - } - } - ++t.num_neigh; - } - - //! Finish the output job once the thread is ready to terminate. - /*! - * \param t My output thread data - * - * The number of neighbors found for this thread is written. If this value - * exceeds the current allocation, this value is atomically maximized for - * reallocation. Any values remaining on the stack are written to ensure the - * list is complete. - */ - DEVICE void finalize(const ThreadData& t) const - { - nneigh[t.idx] = t.num_neigh; - // printf("max %d %d %d\n",new_max_neigh,max_neigh,t.num_neigh); - if (t.num_neigh > max_neigh) - { - atomicMax(new_max_neigh, t.num_neigh); - } - else if (t.num_neigh % 4 != 0) - { - // write partial (leftover) stack, counting is now post-increment so need to shift by 1 - // only need to do this if didn't overflow, since all neighbors were already written due to alignment of max - neigh_list[(t.first+t.num_neigh-1)/4] = make_uint4(t.stack[0], t.stack[1], t.stack[2], t.stack[3]); - } - } - #endif - - uint4* neigh_list; //!< Neighbors of each sphere - unsigned int* nneigh; //!< Number of neighbors per search sphere - unsigned int* new_max_neigh; //!< New maximum number of neighbors - unsigned int max_neigh; //!< Maximum number of neighbors allocated - }; - */ //! Sentinel for an invalid particle (e.g., ghost) const unsigned int NeighborListTypeSentinel = 0xffffffff; diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index bc238d27..28195af5 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -77,14 +77,19 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater std::shared_ptr> m_tuner_copy_nlist; //!< Tuner for the primitive-copy kernel std::shared_ptr> m_tuner_filter_bonds; //!< Tuner for existing bond filter + std::shared_ptr> m_tuner_build; //!< Tuner for building neigh list + std::shared_ptr> m_tuner_traverse; //!< Tuner for traversing neigh list GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - std::vector> m_lbvh; //!< Array of LBVHs per-type - std::vector> m_traverser; //!< Array of LBVH traverers per-type + std::unique_ptr m_lbvh; //!< LBVH + std::unique_ptr m_traverser; //!< LBVH traverer + hipStream_t m_stream; //!< CUDA stream + GPUVector m_image_list; //!< List of translation vectors for traversal - unsigned int m_n_images; //!< Number of translation vectors for traversal + unsigned int m_n_images; //!< Number of translation vectors for traversal + GPUArray m_traverse_order; //!< Order to traverse primitives //! Compute the LBVH domain from the current box From 643e836d0e6f323d3c45fe961a9eefdd0a3b22fe Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 4 May 2026 15:43:11 -0500 Subject: [PATCH 33/45] add internal neigh list --- src/DynamicBondUpdater.cc | 12 ++++++++++-- src/DynamicBondUpdater.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 6b96a385..dc6cee40 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -12,8 +12,7 @@ #include "DynamicBondUpdater.h" #include "hoomd/RandomNumbers.h" #include "RNGIdentifiers.h" -#include "hoomd/AABBTree.h" -#include "hoomd/AABB.h" +#include "hoomd/md/NeighborListTree.h" namespace hoomd { @@ -54,6 +53,9 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_bond_data = m_sysdef->getBondData(); + hoomd::md::NeighborList* m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); + m_pair_internal_nlist-> setRcut(0,0,0.5); + m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; @@ -97,6 +99,10 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_bond_data = m_sysdef->getBondData(); //m_bond_type = m_bond_data->getTypeByName(bond_type); + // m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); + hoomd::md::NeighborList* m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); + m_pair_internal_nlist-> setRcut(0,0,0.5); + m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; @@ -118,6 +124,8 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { + Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple(0,0)); + std::cout << "in update "<< test << std::endl; // don't do anything if either one of the groups is empty if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) return; diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 013f7038..01ae3dd7 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -181,6 +181,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set + std::shared_ptr m_pair_internal_nlist; bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set //! filter out existing and doublicate bonds from all found possible bonds From 1c872e4ea11e7f303c2da97e085d6487c1b2b223 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 6 May 2026 11:40:54 -0500 Subject: [PATCH 34/45] replace aabb trees with neighbor list, first draft --- src/DynamicBondUpdater.cc | 288 ++++++++++++-------------------------- src/DynamicBondUpdater.h | 14 +- 2 files changed, 94 insertions(+), 208 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index dc6cee40..f159ed37 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -14,6 +14,9 @@ #include "RNGIdentifiers.h" #include "hoomd/md/NeighborListTree.h" +#include +#include + namespace hoomd { namespace azplugins @@ -53,8 +56,10 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_bond_data = m_sysdef->getBondData(); - hoomd::md::NeighborList* m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); - m_pair_internal_nlist-> setRcut(0,0,0.5); + m_pair_internal_nlist = std::shared_ptr( + new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + setCutoffs(); m_max_bonds = 4; m_max_bonds_overflow = 0; @@ -97,11 +102,11 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pdata->getGlobalParticleNumberChangeSignal().connect(this); m_bond_data = m_sysdef->getBondData(); - //m_bond_type = m_bond_data->getTypeByName(bond_type); - // m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); - hoomd::md::NeighborList* m_pair_internal_nlist = new hoomd::md::NeighborListTree(sysdef, 0.5); - m_pair_internal_nlist-> setRcut(0,0,0.5); + m_pair_internal_nlist = std::shared_ptr( + new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + setCutoffs(); m_max_bonds = 4; m_max_bonds_overflow = 0; @@ -124,8 +129,9 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple(0,0)); - std::cout << "in update "<< test << std::endl; + + //Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); + // don't do anything if either one of the groups is empty if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) return; @@ -137,7 +143,6 @@ void DynamicBondUpdater::update(uint64_t timestep) if (m_box_changed) { checkBoxSize(); - updateImageVectors(); m_box_changed = false; } @@ -150,11 +155,19 @@ void DynamicBondUpdater::update(uint64_t timestep) // rebuild the list of possible bonds until there is no overflow bool overflowed = false; - buildTree(); do { - traverseTree(); + m_pair_internal_nlist->compute(timestep); + + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); + for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int n_neigh = h_n_neigh.data[i]; + if (n_neigh > m_max_bonds) + m_max_bonds = n_neigh; + } overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-traverse the tree @@ -227,7 +240,7 @@ bool CompareBonds(Scalar3 i, Scalar3 j) } } -//todo: find a better descriptive name for this function + bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) { const unsigned int tag_1 = __scalar_as_int(i.x); @@ -278,7 +291,7 @@ void DynamicBondUpdater::calculateExistingBonds() bool overflowed = false; - std::cout << "inside of CalculatingExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + //std::cout << "inside of CalculatingExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); @@ -420,8 +433,6 @@ void DynamicBondUpdater::allocateParticleArrays() GPUArray all_possible_bonds(m_group_1->getNumMembers() *m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); - m_aabbs.resize(m_group_2->getNumMembers()); - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); m_n_existing_bonds.swap(n_existing_bonds); @@ -434,129 +445,12 @@ void DynamicBondUpdater::allocateParticleArrays() memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - - // allocate the number of neighbors (per particle) for finding bonds - GPUArray n_neigh(m_group_1->getNumMembers(), m_exec_conf); - m_n_neigh.swap(n_neigh); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - memset(h_n_neigh.data, 0, sizeof(unsigned int)*m_group_1->getNumMembers()); - - // default allocation of m_max_bonds neighbors per particle for the neighborlist - GPUArray nlist(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); - m_n_list.swap(nlist); } calculateExistingBonds(); } -/*! This function is based on the NeighborListTree c++ implementation. -* It builds a AABB tree for m_group_2, which is traversed in DynamicBondUpdater::traverseTree(). -*/ -void DynamicBondUpdater::buildTree() - { - { - // make tree for group 2 - ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_aabbs(m_aabbs, access_location::host, access_mode::readwrite); - - for (unsigned int group_idx = 0; group_idx < m_group_2->getNumMembers(); group_idx++) - { - unsigned int i = m_group_2->getMemberIndex(group_idx); - // make a point particle AABB - vec3 my_pos(h_postype.data[i]); - h_aabbs.data[group_idx] = hoomd::detail::AABB(my_pos,i); - } - - m_aabb_tree.buildTree(&(h_aabbs.data[0]) , m_group_2->getNumMembers()); - } - } - - -/*! This function is based on the NeighborListTree c++ implementation. -* It traverses the AABB tree (build for m_group_2) and finds neighbours between m_group_2 -* and m_group_1. The neighbour information is saved in m_nlist and m_n_neigh. -*/ -void DynamicBondUpdater::traverseTree() - { - { - ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::overwrite); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::overwrite); - - // clear the neighbor counts - memset(h_nlist.data,0, sizeof(unsigned int)*m_max_bonds*m_group_1->getNumMembers()); - - // reset content of possible bond list - const Scalar r_cutsq = m_r_cut*m_r_cut; - - ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - - // traverse the tree - // Loop over all particles in group 1 - for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const Scalar4 postype_i = h_postype.data[i]; - const vec3 pos_i = vec3(postype_i); - - unsigned int n_curr_bond = 0; - - for (unsigned int cur_image = 0; cur_image < m_n_images; ++cur_image) // for each image vector - { - // make an AABB for the image of this particle - vec3 pos_i_image = pos_i + m_image_list[cur_image]; - hoomd::detail::AABB aabb = hoomd::detail::AABB(pos_i_image,m_r_cut); - hoomd::detail::AABBTree *cur_aabb_tree = &m_aabb_tree; - // stackless traversal of the tree - for (unsigned int cur_node_idx = 0; cur_node_idx < cur_aabb_tree->getNumNodes(); ++cur_node_idx) - { - if (aabb.overlaps(cur_aabb_tree->getNodeAABB(cur_node_idx))) - { - if (cur_aabb_tree->isNodeLeaf(cur_node_idx)) - { - for (unsigned int cur_p = 0; cur_p < cur_aabb_tree->getNodeNumParticles(cur_node_idx); ++cur_p) - { - // neighbor j - unsigned int j = cur_aabb_tree->getNodeParticleTag(cur_node_idx, cur_p); - - if (i!=j) - { - // compute distance - Scalar4 postype_j = h_postype.data[j]; - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - vec_to_scalar3(pos_i_image); - Scalar dr_sq = dot(drij,drij); - - if (dr_sq < r_cutsq) - { - if (n_curr_bond < m_max_bonds) - { - h_nlist.data[group_idx*m_max_bonds + n_curr_bond] = j; - } - else // trigger resize current possible bonds > m_max_bonds - { - m_max_bonds_overflow = std::max(n_curr_bond,m_max_bonds_overflow); - } - ++n_curr_bond; - } - } - } - } - } - else - { - // skip ahead - cur_node_idx += cur_aabb_tree->getNodeSkip(cur_node_idx); - } - } // end stackless search - } // end loop over images - h_n_neigh.data[group_idx] = n_curr_bond; - } // end loop over group 1 - } - } - - /*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist and * m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds array. * Then, all invalid (0,0,0), dublicated, and existing bonds are removed from m_all_possible_bonds. It @@ -566,8 +460,12 @@ void DynamicBondUpdater::filterPossibleBonds() { //copy data from h_n_list to h_all_possible_bonds { - ArrayHandle h_nlist(m_n_list, access_location::host, access_mode::read); - ArrayHandle h_n_neigh(m_n_neigh, access_location::host, access_mode::read); + + ArrayHandle h_nlist(m_pair_internal_nlist->getNListArray(), access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); + ArrayHandle h_n_head_list(m_pair_internal_nlist->getHeadList(), access_location::host, access_mode::read); + + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); @@ -587,12 +485,15 @@ void DynamicBondUpdater::filterPossibleBonds() unsigned int n_curr_bond = 0; const Scalar r_cutsq = m_r_cut*m_r_cut; - const unsigned int n_neigh = h_n_neigh.data[group_idx]; + const unsigned int n_neigh = h_n_neigh.data[i]; + const size_t myHead = h_n_head_list.data[i]; + // loop over all neighbors of this particle for (unsigned int l=0; l h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); // ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); @@ -712,7 +613,7 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) bool overflowed = false; - std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + // std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << std::endl; // resize the list if necessary if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) @@ -750,65 +651,6 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) -/*! -* (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 27 translation vectors -* when the simulation box is 3D periodic. In 2D, there are at most 9 translation vectors. In MPI runs, a ghost layer -* of particles is added from adjacent ranks, so there is no need to perform any translations in this direction. -* The translation vectors are determined by linear combination of the lattice vectors, and must be recomputed any -* time that the box resizes. -*/ -void DynamicBondUpdater::updateImageVectors() - { - - const BoxDim& box = m_pdata->getBox(); - uchar3 periodic = box.getPeriodic(); - unsigned char sys3d = (this->m_sysdef->getNDimensions() == 3); - - // now compute the image vectors - // each dimension increases by one power of 3 - unsigned int n_dim_periodic = (unsigned int)(periodic.x + periodic.y + sys3d*periodic.z); - m_n_images = 1; - for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) - { - m_n_images *= 3; - } - - // reallocate memory if necessary - if (m_n_images > m_image_list.size()) - { - m_image_list.resize(m_n_images); - } - - vec3 latt_a = vec3(box.getLatticeVector(0)); - vec3 latt_b = vec3(box.getLatticeVector(1)); - vec3 latt_c = vec3(box.getLatticeVector(2)); - - // there is always at least 1 image, which we put as our first thing to look at - m_image_list[0] = vec3(0.0, 0.0, 0.0); - - // iterate over all other combinations of images, skipping those that are - unsigned int n_images = 1; - for (int i=-1; i <= 1 && n_images < m_n_images; ++i) - { - for (int j=-1; j <= 1 && n_images < m_n_images; ++j) - { - for (int k=-1; k <= 1 && n_images < m_n_images; ++k) - { - if (!(i == 0 && j == 0 && k == 0)) - { - // skip any periodic images if we don't have periodicity - if (i != 0 && !periodic.x) continue; - if (j != 0 && !periodic.y) continue; - if (k != 0 && (!sys3d || !periodic.z)) continue; - - m_image_list[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; - ++n_images; - } - } - } - } - } - /*! * Check that the largest neighbor search radius is not bigger than twice the shortest box size. * Raises an error if this condition is not met. @@ -869,6 +711,58 @@ void DynamicBondUpdater::setGroupOverlap() } } +/*! Sets cutoffs based on types present in the two groups to save some performance from +* the neighbor list. +*/ +void DynamicBondUpdater::setCutoffs() + { + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); + ArrayHandle h_index_group_2(m_group_2->getIndexArray(), access_location::host, access_mode::read); + + ArrayHandle h_pos(m_pdata->getPositions(), + access_location::host, + access_mode::readwrite); + + // set all rcuts to zero first, then only set the one between group1 and group 2 particle types to be m_r_cut + unsigned int NTypes = m_pdata -> getNTypes(); + for (unsigned int i=0; i< NTypes; ++i) + { + for (unsigned int j=0; j< NTypes; ++j) + { + m_pair_internal_nlist->setRcut(i,j,0); + } + } + + // finding all types in group 1 + std::set types_group_1; + for (unsigned int i=0; igetNumMembers(); ++i) + { + unsigned int idx = h_index_group_1.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_1.insert(__scalar_as_int(type.w)); + } + + // finding all types in group 2 + std::set types_group_2; + for (unsigned int i=0; igetNumMembers(); ++i) + { + unsigned int idx = h_index_group_2.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_2.insert(__scalar_as_int(type.w)); + } + + // looping over types in group 1 and 2 to set the cutoff to m_r_cut + for (const auto& element_1 : types_group_1) + { + for (const auto& element_2 : types_group_2) + { + m_pair_internal_nlist->setRcut(element_1,element_2,m_r_cut); + m_pair_internal_nlist->setRcut(element_2,element_1,m_r_cut); + } + } + + } + // Check that the given cutoff value is valid void DynamicBondUpdater::checkRcut() { diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 01ae3dd7..b38252dc 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -83,6 +83,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater { m_r_cut = r_cut; checkRcut(); + setCutoffs(); } //! Get the cutoff distance between particles for finding bonds Scalar getRcut() @@ -169,11 +170,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_n_list; //!< Neighbor list data GPUArray m_n_neigh; //!< Number of neighbors for each particle - - detail::AABBTree m_aabb_tree; //!< AABB tree for group_1 - GPUVector m_aabbs; //!< Flat array of AABBs of particles in group_2 - std::vector< vec3 > m_image_list; //!< List of translation vectors for tree traversal - unsigned int m_n_images; //!< The number of image vectors to check + GPUArray m_n_head_list; //!< Neighbor list data GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag @@ -186,11 +183,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); - //! build the neighbor list AABB tree - virtual void buildTree(); - //! traverse the neighbor list ABB tree - virtual void traverseTree(); - bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible void calculateExistingBonds(); @@ -198,12 +190,12 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag - virtual void updateImageVectors(); void checkBoxSize(); void checkRcut(); void checkBondType(); void checkProbability(); void setGroupOverlap(); + void setCutoffs(); void checkMaxBondsGroup(); void resizePossibleBondlists(); void resizeExistingBondList(); From 484b860757cf1e18e1f90fdfca83b2fc5ac89400 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 6 May 2026 13:42:09 -0500 Subject: [PATCH 35/45] start GPU implementation --- src/DynamicBondUpdater.cc | 16 ++--- src/DynamicBondUpdater.h | 6 +- src/DynamicBondUpdaterGPU.cc | 109 +++------------------------------- src/DynamicBondUpdaterGPU.cu | 20 +++---- src/DynamicBondUpdaterGPU.cuh | 1 + src/DynamicBondUpdaterGPU.h | 15 +---- 6 files changed, 29 insertions(+), 138 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index f159ed37..b325afb3 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -58,7 +58,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_internal_nlist = std::shared_ptr( new hoomd::md::NeighborListTree(sysdef, 0.0)); - m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + //m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); setCutoffs(); m_max_bonds = 4; @@ -105,7 +105,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_internal_nlist = std::shared_ptr( new hoomd::md::NeighborListTree(sysdef, 0.0)); - m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + //m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); setCutoffs(); m_max_bonds = 4; @@ -170,7 +170,7 @@ void DynamicBondUpdater::update(uint64_t timestep) } overflowed = m_max_bonds < m_max_bonds_overflow; - // if we overflowed, need to reallocate memory and re-traverse the tree + // if we overflowed, need to reallocate memory and re-traverse the neighbor list if (overflowed) { resizePossibleBondlists(); @@ -417,9 +417,6 @@ void DynamicBondUpdater::resizePossibleBondlists() m_all_possible_bonds.resize(size); m_num_all_possible_bonds=0; - GPUArray nlist(size, m_exec_conf); - m_n_list.swap(nlist); - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; } } @@ -486,13 +483,13 @@ void DynamicBondUpdater::filterPossibleBonds() const Scalar r_cutsq = m_r_cut*m_r_cut; const unsigned int n_neigh = h_n_neigh.data[i]; - const size_t myHead = h_n_head_list.data[i]; + const size_t head = h_n_head_list.data[i]; // loop over all neighbors of this particle for (unsigned int l=0; l h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); - // ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); // we need to count how many bonds are in the h_all_possible_bonds array for a given tag @@ -643,6 +638,7 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) if (m_pair_nlist_exclusions_set) { m_pair_nlist -> addExclusion(tag_i,tag_j); + m_pair_internal_nlist -> addExclusion(tag_i,tag_j); } } } diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index b38252dc..b2560cc6 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -168,9 +168,9 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - GPUArray m_n_list; //!< Neighbor list data - GPUArray m_n_neigh; //!< Number of neighbors for each particle - GPUArray m_n_head_list; //!< Neighbor list data + //GPUArray m_n_list; //!< Neighbor list data + // GPUArray m_n_neigh; //!< Number of neighbors for each particle + //GPUArray m_n_head_list; //!< Neighbor list data GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index ebae2745..cb4615ba 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -105,105 +105,6 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() } -void DynamicBondUpdaterGPU::buildTree() - { - - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); - ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - const BoxDim lbvh_box = getLBVHBox(); - - m_lbvh.reset(new hoomd::md::kernel::LBVHWrapper()); - m_traverser.reset(new hoomd::md::kernel::LBVHTraverserWrapper()); - hipStreamCreate(&m_stream); - - m_lbvh->setup(d_pos.data, - d_index_group_2.data, - m_group_2->getNumMembers(), - m_stream); - - hipDeviceSynchronize(); - m_tuner_build->begin(); - const unsigned int block_size = m_tuner_build->getParam()[0]; - // build a lbvh for group_2 - // this tree is traversed in traverseTree() - m_lbvh->build(d_pos.data, - d_index_group_2.data, - m_group_2->getNumMembers(), - lbvh_box.getLo(), - lbvh_box.getHi(), - m_stream, - block_size); - m_tuner_build->end(); - - } - -void DynamicBondUpdaterGPU::traverseTree() - { - ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::overwrite); - ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::overwrite); - - ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - - ArrayHandle d_image_list(m_image_list, access_location::device, access_mode::read); - - //todo: this needs to be set/written somewhere?? - ArrayHandle d_traverse_order(m_traverse_order, access_location::device, access_mode::read); - - // clear the neighbor counts - cudaMemset(d_n_neigh.data,0, sizeof(unsigned int)*m_group_1->getNumMembers()); - - const BoxDim& box = m_pdata->getBox(); - - ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); - - - m_traverser->setup(d_index_group_1.data, - *(m_lbvh->get()), - m_stream); - - - // pack args to the traverser - hoomd::md::kernel::LBVHTraverserWrapper::TraverserArgs args; - - args.map = d_index_group_1.data; - - // particles - args.positions = d_pos.data; - //todo: does it make sense to take rigid bodies into account in this code? - args.bodies = NULL; - args.order = d_traverse_order.data; - args.N = m_group_1->getNumMembers(); - args.Nown = m_pdata->getMaxN(); - //todo: double check that this is correct - args.rcut = m_r_cut; - args.rlist = m_r_cut; - args.box = box; - - // neighbor list write op for this type - args.neigh_list = d_nlist.data; - args.nneigh = d_n_neigh.data; - args.new_max_neigh = m_max_bonds_overflow; - args.first_neigh = d_head_list.data; - args.max_neigh = m_max_bonds_overflow; - - - m_tuner_traverse->begin(); - const unsigned int block_size = m_tuner_traverse->getParam()[0]; - - m_traverser->traverse(args, - *(m_lbvh->get()), - d_image_list.data, - (unsigned int)m_image_list.getNumElements(), - m_stream, - block_size); - m_tuner_traverse->end(); - - m_max_bonds_overflow = m_max_bonds_overflow_flag.readFlags(); - - } - - void DynamicBondUpdaterGPU::filterPossibleBonds() { @@ -213,8 +114,12 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); - ArrayHandle d_nlist(m_n_list, access_location::device, access_mode::read); - ArrayHandle d_n_neigh(m_n_neigh, access_location::device, access_mode::read); + + ArrayHandle d_nlist(m_pair_internal_nlist->getNListArray(), access_location::device, access_mode::read); + ArrayHandle d_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::device, access_mode::read); + ArrayHandle d_n_head_list(m_pair_internal_nlist->getHeadList(), access_location::device, access_mode::read); + + ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); @@ -225,12 +130,14 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const BoxDim& box = m_pdata->getBox(); m_tuner_copy_nlist->begin(); + //todo: add d_n_headlist and fix kernel gpu::copy_possible_bonds(d_all_possible_bonds.data, d_pos.data, d_tag.data, d_index_group_1.data, d_n_neigh.data, d_nlist.data, + d_n_head_list.data, box, m_max_bonds, m_r_cut, diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index 30f067f0..d41f7d60 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -10,13 +10,13 @@ */ #include "DynamicBondUpdaterGPU.cuh" -#include "hoomd/md/NeighborListGPUTree.cuh" #include "hoomd/HOOMDMath.h" +#include "hoomd/md/NeighborListGPUTree.cuh" #include -#include -#include #include +#include +#include namespace hoomd { @@ -100,6 +100,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int* d_sorted_indexes, const unsigned int* d_n_neigh, const unsigned int* d_nlist, + const size_t* d_nhead_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, @@ -120,13 +121,14 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, // get all information for this particle Scalar4 postype_i = d_postype[pidx_i]; const unsigned int tag_i = d_tag[pidx_i]; - const unsigned int n_neigh = d_n_neigh[idx]; + const unsigned int n_neigh = d_n_neigh[pidx_i]; + const size_t head = h_n_head_list[pidx_i]; // loop over all neighbors of this particle for (unsigned int j = 0; j < n_neigh; ++j) { // get index of neighbor from neigh_list - const unsigned int pidx_j = d_nlist[idx * max_bonds + j]; + const unsigned int pidx_j = d_nlist[head + j]; Scalar4 postype_j = d_postype[pidx_j]; const unsigned int tag_j = d_tag[pidx_j]; @@ -261,9 +263,7 @@ cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3* d_all_possible_bo isZeroBondGPU zero; thrust::device_ptr last0 - = thrust::remove_if(d_all_possible_bonds_wrap, - d_all_possible_bonds_wrap + size, - zero); + = thrust::remove_if(d_all_possible_bonds_wrap, d_all_possible_bonds_wrap + size, zero); unsigned int l0 = thrust::distance(d_all_possible_bonds_wrap, last0); // sort remainder by distance, should make all identical bonds consequtive @@ -325,6 +325,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int* d_sorted_indexes, const unsigned int* d_n_neigh, const unsigned int* d_nlist, + const size_t* d_nhead_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, @@ -349,6 +350,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, d_sorted_indexes, d_n_neigh, d_nlist, + d_nhead_list, box, max_bonds, r_cut, @@ -361,5 +363,3 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, } // end namespace azplugins } // end namespace hoomd - - diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 27883249..a7819589 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -53,6 +53,7 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const unsigned int *d_sorted_indexes, const unsigned int *d_n_neigh, const unsigned int *d_nlist, + const size_t *d_nhead_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 28195af5..0e15fcd0 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -66,12 +66,7 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater protected: //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); - //! Update the vectors for traversal of pbc images - virtual void updateImageVectors(); - //! Build the LBVH using the neighbor library - virtual void buildTree(); - //! Traverse the LBVH using the neighbor library - virtual void traverseTree(); + private: @@ -83,14 +78,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - std::unique_ptr m_lbvh; //!< LBVH - std::unique_ptr m_traverser; //!< LBVH traverer - hipStream_t m_stream; //!< CUDA stream - - GPUVector m_image_list; //!< List of translation vectors for traversal - unsigned int m_n_images; //!< Number of translation vectors for traversal - GPUArray m_traverse_order; //!< Order to traverse primitives - //! Compute the LBVH domain from the current box BoxDim getLBVHBox() const From 0c21a39040ee510d4deab5335a8a75eb853b6558 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 6 May 2026 14:19:48 -0500 Subject: [PATCH 36/45] remove stray autotuners --- src/DynamicBondUpdaterGPU.cc | 78 +++--------------------------------- src/DynamicBondUpdaterGPU.h | 2 - 2 files changed, 6 insertions(+), 74 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index cb4615ba..15905d0d 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -47,6 +47,8 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s m_exec_conf, "dynamic_bonding_filter_bonds")); + m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds}); + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; } @@ -66,7 +68,6 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s probability,max_bonds_group_1,max_bonds_group_2,bond_type), m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) { - m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; // only one GPU is supported if (!m_exec_conf->isCUDAEnabled()) @@ -74,24 +75,16 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); } - m_tuner_copy_nlist.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, + m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, "dynamic_bonding_copy_nlist")); - m_tuner_filter_bonds.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, + m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, "dynamic_bonding_filter_bonds")); - m_tuner_build.reset(new Autotuner<1>({m_lbvh->getTunableParameters()}, - m_exec_conf, - "dynamic_bonding_build_nlist")); - - - m_tuner_traverse.reset(new Autotuner<1>({m_traverser->getTunableParameters()}, - m_exec_conf, - "dynamic_bonding_traverse_nlist")); + m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds}); - - m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds ,m_tuner_build,m_tuner_traverse}); + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; } @@ -173,65 +166,6 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() } -/*! - * (Re-)computes the translation vectors for traversing the BVH tree. At most, there are 26 translation vectors - * when the simulation box is 3D periodic (the self-image is excluded). In 2D, there are at most 8 translation vectors. - * In MPI runs, a ghost layer of particles is added from adjacent ranks, so there is no need to perform any translations - * in this direction. The translation vectors are determined by linear combination of the lattice vectors, and must be - * recomputed any time that the box resizes. - */ -void DynamicBondUpdaterGPU::updateImageVectors() - { - - const BoxDim& box = m_pdata->getBox(); - uchar3 periodic = box.getPeriodic(); - unsigned char sys3d = (m_sysdef->getNDimensions() == 3); - - // now compute the image vectors - // each dimension increases by one power of 3 - unsigned int n_dim_periodic = (periodic.x + periodic.y + sys3d*periodic.z); - m_n_images = 1; - for (unsigned int dim = 0; dim < n_dim_periodic; ++dim) - { - m_n_images *= 3; - } - m_n_images -= 1; // remove the self image - - // reallocate memory if necessary - if (m_n_images > m_image_list.getNumElements()) - { - GPUVector image_list(m_n_images, m_exec_conf); - m_image_list.swap(image_list); - } - - ArrayHandle h_image_list(m_image_list, access_location::host, access_mode::overwrite); - Scalar3 latt_a = box.getLatticeVector(0); - Scalar3 latt_b = box.getLatticeVector(1); - Scalar3 latt_c = box.getLatticeVector(2); - - // iterate over all other combinations of images, skipping those that are - unsigned int n_images = 0; - for (int i=-1; i <= 1 && n_images < m_n_images; ++i) - { - for (int j=-1; j <= 1 && n_images < m_n_images; ++j) - { - for (int k=-1; k <= 1 && n_images < m_n_images; ++k) - { - if (!(i == 0 && j == 0 && k == 0)) - { - // skip any periodic images if we don't have periodicity - if (i != 0 && !periodic.x) continue; - if (j != 0 && !periodic.y) continue; - if (k != 0 && (!sys3d || !periodic.z)) continue; - - h_image_list.data[n_images] = Scalar(i) * latt_a + Scalar(j) * latt_b + Scalar(k) * latt_c; - ++n_images; - } - } - } - } - } - namespace detail { /*! diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 0e15fcd0..76c9922f 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -72,8 +72,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater std::shared_ptr> m_tuner_copy_nlist; //!< Tuner for the primitive-copy kernel std::shared_ptr> m_tuner_filter_bonds; //!< Tuner for existing bond filter - std::shared_ptr> m_tuner_build; //!< Tuner for building neigh list - std::shared_ptr> m_tuner_traverse; //!< Tuner for traversing neigh list GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow From 936af3e927cc36add016829a7dbeee4e90ac8f5b Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 6 May 2026 14:20:58 -0500 Subject: [PATCH 37/45] delete stray m_stream leftover --- src/DynamicBondUpdaterGPU.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 15905d0d..7fba7885 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -92,9 +92,6 @@ DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdaterGPU" << std::endl; - // destroy all of the created stream - hipStreamDestroy(m_stream); - } From 92ec2da52a8d06995de07efdacb52097317767bf Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 6 May 2026 14:30:32 -0500 Subject: [PATCH 38/45] add missing thrust header --- src/DynamicBondUpdaterGPU.cu | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index d41f7d60..c8cc323f 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -122,7 +123,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, Scalar4 postype_i = d_postype[pidx_i]; const unsigned int tag_i = d_tag[pidx_i]; const unsigned int n_neigh = d_n_neigh[pidx_i]; - const size_t head = h_n_head_list[pidx_i]; + const size_t head = d_nhead_list[pidx_i]; // loop over all neighbors of this particle for (unsigned int j = 0; j < n_neigh; ++j) From 171f64a9bff52962dc28015ef5190c4a48e57994 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Fri, 8 May 2026 10:27:48 -0500 Subject: [PATCH 39/45] fix unit tests for CPU code --- src/DynamicBondUpdater.cc | 98 ++++++--- src/DynamicBondUpdater.h | 6 +- src/DynamicBondUpdaterGPU.cc | 4 +- src/pytest/test_dynamic_bond.py | 366 +++++++++++++++++--------------- src/update.py | 28 +-- 5 files changed, 273 insertions(+), 229 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index b325afb3..99afa76a 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -50,6 +50,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + std::cout<< " in constructor 1"<< std::endl; m_pdata->getBoxChangeSignal().connect(this); m_pdata->getGlobalParticleNumberChangeSignal().connect(this); @@ -58,14 +59,17 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_internal_nlist = std::shared_ptr( new hoomd::md::NeighborListTree(sysdef, 0.0)); - //m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + + setGroupOverlap(); + setCutoffs(); m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; - setGroupOverlap(); + } @@ -96,6 +100,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_box_changed(true), m_max_N_changed(true) { + std::cout<< " in constructor 2"<< std::endl; m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect(this); @@ -105,14 +110,16 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_internal_nlist = std::shared_ptr( new hoomd::md::NeighborListTree(sysdef, 0.0)); - //m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + + setGroupOverlap(); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); setCutoffs(); m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; - setGroupOverlap(); + } DynamicBondUpdater::~DynamicBondUpdater() @@ -129,7 +136,7 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - + std::cout<< " in update"<< std::endl; //Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); // don't do anything if either one of the groups is empty @@ -475,10 +482,12 @@ void DynamicBondUpdater::filterPossibleBonds() // Loop over all particles in group 1 for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) { + unsigned int i = m_group_1->getMemberIndex(group_idx); const unsigned int tag_i = h_tag.data[i]; const Scalar4 postype_i = h_postype.data[i]; + unsigned int n_curr_bond = 0; const Scalar r_cutsq = m_r_cut*m_r_cut; @@ -488,38 +497,42 @@ void DynamicBondUpdater::filterPossibleBonds() // loop over all neighbors of this particle for (unsigned int l=0; lisMember(j)) + { + Scalar4 postype_j = h_postype.data[j]; + const unsigned int tag_j = h_tag.data[j]; - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - make_scalar3(postype_i.x,postype_i.y,postype_i.z); + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) + - make_scalar3(postype_i.x,postype_i.y,postype_i.z); - // apply periodic boundary conditions - drij = box.minImage(drij); - Scalar dr_sq = dot(drij,drij); + // apply periodic boundary conditions + drij = box.minImage(drij); + Scalar dr_sq = dot(drij,drij); - if (dr_sq < r_cutsq) - { - if (n_curr_bond < m_max_bonds) + if (dr_sq < r_cutsq) { - Scalar3 d; - if(m_groups_identical) - { - // sort the two tags in this possible bond pair if groups identical - const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; - d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - } - else + if (n_curr_bond < m_max_bonds) { - d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + Scalar3 d; + if(m_groups_identical) + { + // sort the two tags in this possible bond pair if groups identical + const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + } + h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; } - h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; + ++n_curr_bond; } - ++n_curr_bond; } } } @@ -683,6 +696,7 @@ void DynamicBondUpdater::setGroupOverlap() { m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be empty. No bonds will be formed. " << std::endl; } + { //check if the two groups are either identical or have no overlap ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); @@ -705,6 +719,8 @@ void DynamicBondUpdater::setGroupOverlap() { m_groups_identical=true; } + } + std::cout << "groups identical "<< m_groups_identical<< std::endl; } /*! Sets cutoffs based on types present in the two groups to save some performance from @@ -712,12 +728,7 @@ void DynamicBondUpdater::setGroupOverlap() */ void DynamicBondUpdater::setCutoffs() { - ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); - ArrayHandle h_index_group_2(m_group_2->getIndexArray(), access_location::host, access_mode::read); - - ArrayHandle h_pos(m_pdata->getPositions(), - access_location::host, - access_mode::readwrite); + { // set all rcuts to zero first, then only set the one between group1 and group 2 particle types to be m_r_cut unsigned int NTypes = m_pdata -> getNTypes(); @@ -729,6 +740,13 @@ void DynamicBondUpdater::setCutoffs() } } + ArrayHandle h_pos(m_pdata->getPositions(), + access_location::host, + access_mode::read); + + + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); + // finding all types in group 1 std::set types_group_1; for (unsigned int i=0; igetNumMembers(); ++i) @@ -738,14 +756,23 @@ void DynamicBondUpdater::setCutoffs() types_group_1.insert(__scalar_as_int(type.w)); } - // finding all types in group 2 std::set types_group_2; + if (m_groups_identical == false) + { + ArrayHandle h_index_group_2(m_group_2->getIndexArray(), access_location::host, access_mode::read); + + // finding all types in group 2 for (unsigned int i=0; igetNumMembers(); ++i) { unsigned int idx = h_index_group_2.data[i]; Scalar4 type = h_pos.data[idx]; types_group_2.insert(__scalar_as_int(type.w)); } + } + else + { + types_group_2 = types_group_1; + } // looping over types in group 1 and 2 to set the cutoff to m_r_cut for (const auto& element_1 : types_group_1) @@ -754,10 +781,13 @@ void DynamicBondUpdater::setCutoffs() { m_pair_internal_nlist->setRcut(element_1,element_2,m_r_cut); m_pair_internal_nlist->setRcut(element_2,element_1,m_r_cut); + std::cout<< " cutoffs set "<< element_1<< " " << element_2 << " " << m_r_cut << std::endl; } } } + } + // Check that the given cutoff value is valid void DynamicBondUpdater::checkRcut() diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index b2560cc6..5ffeba61 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -153,7 +153,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater std::shared_ptr m_group_1; //!< First particle group to form bonds with std::shared_ptr m_group_2; //!< Second particle group to form bonds with - bool m_groups_identical; + bool m_groups_identical; //!< whether or not the two groups are identical. Set true if they are identical. Scalar m_r_cut; //!< cutoff for the bond forming criterion Scalar m_probability; //!< probability of bond formation if bond can be formed (i.e. within cutoff) @@ -168,10 +168,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - //GPUArray m_n_list; //!< Neighbor list data - // GPUArray m_n_neigh; //!< Number of neighbors for each particle - //GPUArray m_n_head_list; //!< Neighbor list data - GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 7fba7885..b4263519 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -120,7 +120,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const BoxDim& box = m_pdata->getBox(); m_tuner_copy_nlist->begin(); - //todo: add d_n_headlist and fix kernel + //todo: check that second particle is actually in group 2 gpu::copy_possible_bonds(d_all_possible_bonds.data, d_pos.data, d_tag.data, @@ -138,7 +138,7 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_tuner_copy_nlist->end(); - //filter out the existing bonds - based on neighbor list exclusion handeling + //filter out the existing bonds - based on neighbor list exclusion handling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, d_n_existing_bonds.data, diff --git a/src/pytest/test_dynamic_bond.py b/src/pytest/test_dynamic_bond.py index b2544e3c..6131f7b8 100644 --- a/src/pytest/test_dynamic_bond.py +++ b/src/pytest/test_dynamic_bond.py @@ -2,192 +2,208 @@ # Copyright (c) 2021-2025, Auburn University # Part of azplugins, released under the BSD 3-Clause License. - import hoomd import pytest import numpy +def test_setters_getters(simulation_factory, one_particle_snapshot_factory): + + # make one particle test configuration + sim = simulation_factory(one_particle_snapshot_factory(position=[0,0,0], L=20)) + + u = hoomd.azplugins.update.dynamic_bond(nlist=hoomd.md.nlist.Cell(buffer=0.4), + r_cut=1, + trigger = hoomd.trigger.Periodic(period=10), + bond_type=0, + group_1=hoomd.filter.All(), + group_2=hoomd.filter.All(), + max_bonds_group_1=0, + max_bonds_group_2=0) + + assert numpy.equal(u.r_cut, 1) + u.r_cut = 1.5 + assert numpy.equal(u.r_cut, 1.5) + u.max_bonds_group_1 = 3 + assert numpy.equal(u.max_bonds_group_1,3) -def update_dynamic_bond_tests_two_groups_setup(simulation_factory,two_particle_snapshot_factory): + u.max_bonds_group_2 = 7 + assert numpy.equal(u.max_bonds_group_2,7) - snap = hoomd.data.make_snapshot(N=4, box=hoomd.data.boxdim(L=20), - particle_types=['A','B'], - bond_types=['bond']) - if hoomd.comm.get_rank() == 0: + # todo: this check doesn't work but it definitely throws an error - maybe wrong way to test? + # check the test of box size large enough for cutoff + #with pytest.raises(RuntimeError): + # u.r_cut=15.0 + +def test_form_bonds_same_group(simulation_factory): + + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 6 + snap.particles.types = ["A","B"] + snap.particles.position[:,0] = (0,1,2,3,4,6) + snap.particles.position[:,1] = (0,0,0,0,0,0) + snap.particles.position[:,2] = (0,0,0,0,0,0) + # dynamic bond operates on groups, so typeids should not matter at all + snap.particles.typeid[:] = [0,0,1,1,0,0] + snap.bonds.types = ['bond'] + + sim = simulation_factory(snap) + nl=hoomd.md.nlist.Cell(buffer=0.4) + + group =hoomd.filter.Tags([0,1,2,3,4,5]) + form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, + r_cut=1.1, + trigger = hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group, + group_2=group, + max_bonds_group_1=2, + max_bonds_group_2=2) + + # test bond formation. All are in the same group, so bonds should be formed + # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) + integrator = hoomd.md.Integrator(dt=0) + nve = hoomd.md.methods.ConstantVolume(filter=hoomd.filter.All()) + integrator.methods.append(nve) + sim.operations.updaters.append(form_bonds) + sim.run(1) + s = sim.state.get_snapshot() + + numpy.testing.assert_almost_equal(4, s.bonds.N) + numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) + + + # same as before, we will have formed bonds 0-1-2-3-4 in the first step, so nothing + # new should be formed + sim.run(1) + s = sim.state.get_snapshot() + + numpy.testing.assert_almost_equal(4, s.bonds.N) + numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) + + + # now put particle 5 in range of partice 2 and 3, but no new bonds should + # be formed since max_bonds_group_1=max_bonds_group_2=2 and that would be the third + # bond on those particles + s.particles.position[5]=(2.5,0,0) + sim.state.set_snapshot(s) + sim.run(1) + s = sim.state.get_snapshot() + + numpy.testing.assert_almost_equal(4, s.bonds.N) + numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) + + +def test_form_bonds_same_group_priority(simulation_factory): + + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 6 + snap.particles.types = ["A","B"] + snap.particles.position[:,0] = (0,1,2,3,4,2.5) + snap.particles.position[:,1] = (0,0,0,0,0,0) + snap.particles.position[:,2] = (0,0,0,0,0,0) + # dynamic bond operates on groups, so typeids should not matter at all + snap.particles.typeid[:] = [0,0,1,1,0,0] + snap.bonds.types = ['bond'] + + sim = simulation_factory(snap) + nl=hoomd.md.nlist.Cell(buffer=0.4) + + group =hoomd.filter.Tags([0,1,2,3,4,5]) + form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, + r_cut=1.1, + trigger = hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group, + group_2=group, + max_bonds_group_1=2, + max_bonds_group_2=2) + + # test bond formation. All are in the same group, so bonds should be formed + # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) + integrator = hoomd.md.Integrator(dt=0) + nve = hoomd.md.methods.ConstantVolume(filter=hoomd.filter.All()) + integrator.methods.append(nve) + sim.operations.updaters.append(form_bonds) + sim.run(1) + s = sim.state.get_snapshot() + + # here, particle 5 is in range of partice 2 and 3, so in principle the bonds + # 0-1, 1-2 ,2-3, 3-4 (all length 1), 2-5 and 3-5 (length 0.5) can be formed. + # The all_possible_bonds array is sorted by bond distance, so bonds 2-5 + # and 3-5 are formed first. Then in order of index 0-1, and 1-2. + # Particle 2 now has two bonds, so 2-3 can't be formed, but 3-4 can. + numpy.testing.assert_almost_equal(5, s.bonds.N) + numpy.testing.assert_array_almost_equal([2,5], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([3,5], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[3]) + numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[4]) + + +def test_update_bond_two_groups(simulation_factory): + + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 4 + snap.particles.types = ["A","B"] snap.particles.position[:,0] = (0,0.9,1.1,2) snap.particles.position[:,1] = (0,0,0,0) snap.particles.position[:,2] = (0,0,0,0) snap.particles.typeid[:] = [1,0,1,0] + snap.bonds.types = ['bond'] + + sim = simulation_factory(snap) + nl=hoomd.md.nlist.Cell(buffer=0.4) + + group_1 = hoomd.filter.Tags([0,1]) + group_2 = hoomd.filter.Tags([2,3]) + form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, + r_cut=1.0, + trigger = hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group_1, + group_2=group_2, + max_bonds_group_1=1, + max_bonds_group_2=2) + + # test bond formation between particle 1-2 + # particle 0,1 are in the same group, so even if their distance is + # below r_cut, no bond should be formed, same for goes for particle 2,3 + integrator = hoomd.md.Integrator(dt=0) + nve = hoomd.md.methods.ConstantVolume(filter=hoomd.filter.All()) + integrator.methods.append(nve) + sim.operations.updaters.append(form_bonds) + sim.run(1) + s = sim.state.get_snapshot() + + numpy.testing.assert_almost_equal(1, s.bonds.N) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[0]) + + # after this bond 1-2 is formed, there shouldn't be a second one formed + # e.g. a dublicate, even when the simulation continues to run + sim.run(10) + s = sim.state.get_snapshot() + + numpy.testing.assert_almost_equal(1, s.bonds.N) + numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[0]) + + + - s = hoomd.init.read_snapshot(snap) - nl = hoomd.md.nlist.cell() - - group_1 = hoomd.group.tag_list(name="a", tags = [0,1]) - group_2 = hoomd.group.tag_list(name="b", tags = [2,3]) - u = hoomd.azplugins.update.dynamic_bond(nlist=nl, - r_cut=1.0, - bond_type='bond', - group_1=group_1, - group_2=group_2, - max_bonds_1=1, - max_bonds_2=2) - if sim.device.communicator.rank == 0: - # particle 0 is outside interaction range - assert numpy.isclose(energies[0], 0.0) - - assertEqual(u.cpp_updater.r_cut, 1) - u.set_params(r_cut=1.5) - assertEqual(u.cpp_updater.r_cut, 1.5) - # check the test of box size large enough for cutoff - with assertRaises(RuntimeError): - u.set_params(r_cut=15.0) - - u.set_params(max_bonds_1=3) - assertEqual(self.u.cpp_updater.max_bonds_group_1,3) - u.set_params(max_bonds_2=7) - assertEqual(self.u.cpp_updater.max_bonds_group_2,7) - - # check the test of bondy_type - with assertRaises(RuntimeError): - u.set_params(bond_type='not_existing') - - - def test_form_bond(self): - # test bond formation between particle 1-2 - # particle 0,1 are in the same group, so even if their distance is - # below r_cut, no bond should be formed, same for goes for particle 2,3 - hoomd.md.integrate.mode_standard(dt=0.0) - hoomd.md.integrate.nve(group=hoomd.group.all()) - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(1, snap.bonds.N) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[0]) - - # after this bond 1-2 is formed, there shouldn't be a second one formed - # e.g. a dublicate, even when the simulation continues to run - hoomd.run(10) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(1, snap.bonds.N) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[0]) - - def test_no_bond_formation_outside_rcut(self): - # put particle 1 out of range, no bonds should be formed - self.s.particles[1].position=(1.1,5,0) - hoomd.md.integrate.mode_standard(dt=0.0) - hoomd.md.integrate.nve(group=hoomd.group.all()) - - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(0, snap.bonds.N) - - - def tearDown(self): - del self.s, self.u - hoomd.context.initialize() - - -class update_dynamic_bond_tests_one_group(unittest.TestCase): - def setUp(self): - snap = hoomd.data.make_snapshot(N=6, box=hoomd.data.boxdim(L=20), - particle_types=['A','B'], - bond_types=['bond']) - if hoomd.comm.get_rank() == 0: - snap.particles.position[:,0] = (0,1,2,3,4,6) - snap.particles.position[:,1] = (0,0,0,0,0,0) - snap.particles.position[:,2] = (0,0,0,0,0,0) - # dynamic bond operates on groups, so typeids should not matter at all - snap.particles.typeid[:] = [0,0,1,1,0,0] - - self.s = hoomd.init.read_snapshot(snap) - self.nl = hoomd.md.nlist.cell() - - self.group_3 = hoomd.group.tag_list(name="a", tags = [0,1,2,3,4,5]) - self.u = azplugins.update.dynamic_bond(nlist=self.nl, - r_cut=1.1, - bond_type='bond', - group_1=self.group_3, - group_2=self.group_3, - max_bonds_1=2, - max_bonds_2=2) - - def test_form_bond(self): - # test bond formation. All are in the same group, so bonds should be formed - # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) - hoomd.md.integrate.mode_standard(dt=0.0) - hoomd.md.integrate.nve(group=hoomd.group.all()) - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(4, snap.bonds.N) - np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) - np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) - np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) - - def test_no_bond_formation_too_many_bonds(self): - # same as before, we will have formed bonds 0-1-2-3-4 in the first step - hoomd.md.integrate.mode_standard(dt=0.0) - hoomd.md.integrate.nve(group=hoomd.group.all()) - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(4, snap.bonds.N) - np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) - np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) - np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) - - # now put particle 5 in range of partice 2 and 3, but no new bonds should - # be formed since max_bonds_1=max_bonds_2=2 and that would be the third - # bond on those particles - self.s.particles[5].position=(2.5,0,0) - - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - self.assertAlmostEqual(4, snap.bonds.N) - np.testing.assert_array_almost_equal([0,1], snap.bonds.group[0]) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[1]) - np.testing.assert_array_almost_equal([2,3], snap.bonds.group[2]) - np.testing.assert_array_almost_equal([3,4], snap.bonds.group[3]) - - def test_bond_formation_too_many_bonds(self): - # now put particle 5 in range of partice 2 and 3, so in principle the bonds - # 0-1, 1-2 ,2-3, 3-4 (all length 1), 2-5 and 3-5 (length 0.5) can be formed. - # The all_possible_bonds array is sorted by bond distance, so bonds 2-5 - # and 3-5 are formed first. Then in order of index 0-1, and 1-2. - # Particle 2 now has two bonds, so 2-3 can't be formed, but 3-4 can. - - self.s.particles[5].position=(2.5,0,0) - snap = self.s.take_snapshot(bonds=True) - hoomd.run(1) - snap = self.s.take_snapshot(bonds=True) - print(snap.bonds.group) - self.assertAlmostEqual(5, snap.bonds.N) - - np.testing.assert_array_almost_equal([2,5], snap.bonds.group[0]) - np.testing.assert_array_almost_equal([3,5], snap.bonds.group[1]) - np.testing.assert_array_almost_equal([0,1], snap.bonds.group[2]) - np.testing.assert_array_almost_equal([1,2], snap.bonds.group[3]) - np.testing.assert_array_almost_equal([3,4], snap.bonds.group[4]) - - def test_group_partial_overlap(self): - self.group_1 = hoomd.group.tag_list(name="a", tags = [0,1,2,3]) - self.group_2 = hoomd.group.tag_list(name="b", tags = [3,4,5]) - with self.assertRaises(RuntimeError): - azplugins.update.dynamic_bond(nlist=self.nl, - r_cut=1.0, - bond_type='bond', - group_1=self.group_1, - group_2=self.group_2, - max_bonds_1=1, - max_bonds_2=2) - - def tearDown(self): - del self.s, self.u - hoomd.context.initialize() - - - -if __name__ == '__main__': - unittest.main(argv = ['test.py', '-v']) diff --git a/src/update.py b/src/update.py index 092a3ff9..c1c9cbcf 100644 --- a/src/update.py +++ b/src/update.py @@ -47,7 +47,7 @@ class dynamic_bond(hoomd.operation.Updater): Examples:: - azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,probability=1.0, bond_type='bond', + azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,bond_type=0, group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) """ _ext_module = _azplugins @@ -114,18 +114,6 @@ def probability(self,value): self._param_dict['probability']=value self._cpp_obj.probability = value - @property - def max_bonds_group_1(self): - """ - max_bonds_1 (int) - """ - return self._cpp_obj.max_bonds_group_1 - - @max_bonds_group_1.setter - def max_bonds_group_1(self, value): - self._cpp_obj.max_bonds_group_1 = value - self._param_dict['max_bonds_group_1']=value - @property def r_cut(self): """ @@ -138,6 +126,19 @@ def r_cut(self, value): self._cpp_obj.r_cut = value self._param_dict['r_cut']=value + + @property + def max_bonds_group_1(self): + """ + max_bonds_1 (int) + """ + return self._cpp_obj.max_bonds_group_1 + + @max_bonds_group_1.setter + def max_bonds_group_1(self, value): + self._cpp_obj.max_bonds_group_1 = value + self._param_dict['max_bonds_group_1']=value + @property def max_bonds_group_2(self): """ @@ -151,6 +152,7 @@ def max_bonds_group_2(self, value): self._param_dict['max_bonds_group_2']=value + def _attach_hook(self): sim = self._simulation """Create the c++ mirror class.""" From f2220b99a5f96db0cf588a126afa7819f4009b03 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 11 May 2026 15:27:15 -0500 Subject: [PATCH 40/45] add group check to GPU code --- src/DynamicBondUpdater.cc | 2 +- src/DynamicBondUpdaterGPU.cc | 32 ++++++++++++++++- src/DynamicBondUpdaterGPU.cu | 67 ++++++++++++++++++++++------------- src/DynamicBondUpdaterGPU.cuh | 2 ++ src/DynamicBondUpdaterGPU.h | 20 ----------- 5 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 99afa76a..94805de6 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -501,7 +501,7 @@ void DynamicBondUpdater::filterPossibleBonds() // get index of neighbor from neigh_list const unsigned int j = h_nlist.data[head + l]; - if ( m_group_2->isMember(j)) + if (m_group_2->isMember(j)) { Scalar4 postype_j = h_postype.data[j]; const unsigned int tag_j = h_tag.data[j]; diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index b4263519..15cdb53b 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -119,12 +119,14 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() const BoxDim& box = m_pdata->getBox(); + if (m_groups_identical) + { m_tuner_copy_nlist->begin(); - //todo: check that second particle is actually in group 2 gpu::copy_possible_bonds(d_all_possible_bonds.data, d_pos.data, d_tag.data, d_index_group_1.data, + d_index_group_1.data, d_n_neigh.data, d_nlist.data, d_n_head_list.data, @@ -133,11 +135,39 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_r_cut, m_groups_identical, m_group_1->getNumMembers(), + m_group_1->getNumMembers(), m_tuner_copy_nlist->getParam()[0]); if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner_copy_nlist->end(); + }else + { + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); + m_tuner_copy_nlist->begin(); + gpu::copy_possible_bonds(d_all_possible_bonds.data, + d_pos.data, + d_tag.data, + d_index_group_1.data, + d_index_group_2.data, + d_n_neigh.data, + d_nlist.data, + d_n_head_list.data, + box, + m_max_bonds, + m_r_cut, + m_groups_identical, + m_group_2->getNumMembers(), + m_group_1->getNumMembers(), + m_tuner_copy_nlist->getParam()[0]); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + m_tuner_copy_nlist->end(); + + } + + + + //filter out the existing bonds - based on neighbor list exclusion handling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index c8cc323f..da8f7022 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -66,6 +66,7 @@ struct isZeroBondGPU } }; + struct CompareBondsGPU { __host__ __device__ bool operator()(const Scalar3& i, const Scalar3& j) @@ -99,6 +100,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, const Scalar4* d_postype, const unsigned int* d_tag, const unsigned int* d_sorted_indexes, + unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, const size_t* d_nhead_list, @@ -106,6 +108,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int max_bonds, const Scalar r_cut, const bool groups_identical, + const unsigned int size_group_2, const unsigned int N) { // one thread per particle in group_1 @@ -130,38 +133,50 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, { // get index of neighbor from neigh_list const unsigned int pidx_j = d_nlist[head + j]; - Scalar4 postype_j = d_postype[pidx_j]; - const unsigned int tag_j = d_tag[pidx_j]; - Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) - - make_scalar3(postype_i.x, postype_i.y, postype_i.z); + // test if j is in group 2 - // apply periodic boundary conditions (FLOPS: 12) - drij = box.minImage(drij); + // wrapper for pointer needed for thrust + thrust::device_ptr d_sorted_indexes_group_2_wrap(d_sorted_indexes_group_2); - // same as on the cpu, just not during the tree traversal - Scalar dr_sq = dot(drij, drij); + auto iter = thrust::find(thrust::device, d_sorted_indexes_group_2_wrap,d_sorted_indexes_group_2_wrap+size_group_2, pidx_j); - if (dr_sq < r_cutsq) - { - if (n_curr_bond < max_bonds) + if (iter != d_sorted_indexes_group_2_wrap+size_group_2) + { + + Scalar4 postype_j = d_postype[pidx_j]; + const unsigned int tag_j = d_tag[pidx_j]; + + Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) + - make_scalar3(postype_i.x, postype_i.y, postype_i.z); + + // apply periodic boundary conditions (FLOPS: 12) + drij = box.minImage(drij); + + // same as on the cpu, just not during the tree traversal + Scalar dr_sq = dot(drij, drij); + + if (dr_sq < r_cutsq) { - Scalar3 d; - if (groups_identical) - { - // sort the two tags in this possible bond pair if groups are identical - const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; - d = make_scalar3(__int_as_scalar(tag_a), __int_as_scalar(tag_b), dr_sq); - } - else + if (n_curr_bond < max_bonds) { - d = make_scalar3(__int_as_scalar(tag_i), __int_as_scalar(tag_j), dr_sq); + Scalar3 d; + if (groups_identical) + { + // sort the two tags in this possible bond pair if groups are identical + const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a), __int_as_scalar(tag_b), dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i), __int_as_scalar(tag_j), dr_sq); + } + d_all_possible_bonds[idx * max_bonds + n_curr_bond] = d; } - d_all_possible_bonds[idx * max_bonds + n_curr_bond] = d; + ++n_curr_bond; } - ++n_curr_bond; - } + } } } @@ -324,6 +339,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, const Scalar4* d_postype, const unsigned int* d_tag, const unsigned int* d_sorted_indexes, + unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, const size_t* d_nhead_list, @@ -331,6 +347,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int max_bonds, const Scalar r_cut, const bool groups_identical, + const unsigned int size_group_2, const unsigned int N, const unsigned int block_size) { @@ -349,6 +366,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, d_postype, d_tag, d_sorted_indexes, + d_sorted_indexes_group_2, d_n_neigh, d_nlist, d_nhead_list, @@ -356,6 +374,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, max_bonds, r_cut, groups_identical, + size_group_2, N); return cudaSuccess; } diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index a7819589..cd24bd7a 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -51,6 +51,7 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const Scalar4 *d_postype, const unsigned int *d_tag, const unsigned int *d_sorted_indexes, + unsigned int *d_sorted_indexes_group_2, const unsigned int *d_n_neigh, const unsigned int *d_nlist, const size_t *d_nhead_list, @@ -58,6 +59,7 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const unsigned int max_bonds, const Scalar r_cut, const bool groups_identical, + const bool size_group_2, const unsigned int N, const unsigned int block_size); diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index 76c9922f..f13dc677 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -77,26 +77,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - //! Compute the LBVH domain from the current box - BoxDim getLBVHBox() const - { - const BoxDim& box = m_pdata->getBox(); - - // ghost layer padding - Scalar ghost_layer_width(0.0); - #ifdef ENABLE_MPI - if (m_comm) ghost_layer_width = m_comm->getGhostLayerMaxWidth(); - #endif - - Scalar3 ghost_width = make_scalar3(0.0, 0.0, 0.0); - if (!box.getPeriodic().x) ghost_width.x = ghost_layer_width; - if (!box.getPeriodic().y) ghost_width.y = ghost_layer_width; - if (!box.getPeriodic().z && m_sysdef->getNDimensions() == 3) ghost_width.z = ghost_layer_width; - - return BoxDim(box.getLo()-ghost_width, box.getHi()+ghost_width, box.getPeriodic()); - } - - }; namespace detail From 42369c8e8ce09d278d9f7b5732734d7d95cfd54e Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Mon, 11 May 2026 16:30:39 -0500 Subject: [PATCH 41/45] fix copy_possible_bonds const issue --- src/DynamicBondUpdaterGPU.cu | 6 +++--- src/DynamicBondUpdaterGPU.cuh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index da8f7022..3880a32c 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -100,7 +100,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, const Scalar4* d_postype, const unsigned int* d_tag, const unsigned int* d_sorted_indexes, - unsigned int* d_sorted_indexes_group_2, + const unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, const size_t* d_nhead_list, @@ -137,7 +137,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, // test if j is in group 2 // wrapper for pointer needed for thrust - thrust::device_ptr d_sorted_indexes_group_2_wrap(d_sorted_indexes_group_2); + thrust::device_ptr d_sorted_indexes_group_2_wrap(d_sorted_indexes_group_2); auto iter = thrust::find(thrust::device, d_sorted_indexes_group_2_wrap,d_sorted_indexes_group_2_wrap+size_group_2, pidx_j); @@ -339,7 +339,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, const Scalar4* d_postype, const unsigned int* d_tag, const unsigned int* d_sorted_indexes, - unsigned int* d_sorted_indexes_group_2, + const unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, const size_t* d_nhead_list, diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index cd24bd7a..94499916 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -51,7 +51,7 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const Scalar4 *d_postype, const unsigned int *d_tag, const unsigned int *d_sorted_indexes, - unsigned int *d_sorted_indexes_group_2, + const unsigned int *d_sorted_indexes_group_2, const unsigned int *d_n_neigh, const unsigned int *d_nlist, const size_t *d_nhead_list, From dbd311eb74a2b4523745acfd8c48c1648a330184 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Tue, 12 May 2026 16:08:47 -0500 Subject: [PATCH 42/45] typo in defintion of copy_possible_bonds --- src/DynamicBondUpdaterGPU.cu | 8 ++++---- src/DynamicBondUpdaterGPU.cuh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index 3880a32c..7dcd9ec7 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -103,7 +103,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, - const size_t* d_nhead_list, + const size_t* d_n_head_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, @@ -126,7 +126,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, Scalar4 postype_i = d_postype[pidx_i]; const unsigned int tag_i = d_tag[pidx_i]; const unsigned int n_neigh = d_n_neigh[pidx_i]; - const size_t head = d_nhead_list[pidx_i]; + const size_t head = d_n_head_list[pidx_i]; // loop over all neighbors of this particle for (unsigned int j = 0; j < n_neigh; ++j) @@ -342,7 +342,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, const unsigned int* d_sorted_indexes_group_2, const unsigned int* d_n_neigh, const unsigned int* d_nlist, - const size_t* d_nhead_list, + const size_t* d_n_head_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, @@ -369,7 +369,7 @@ cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, d_sorted_indexes_group_2, d_n_neigh, d_nlist, - d_nhead_list, + d_n_head_list, box, max_bonds, r_cut, diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 94499916..6fa4cac1 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -54,12 +54,12 @@ cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, const unsigned int *d_sorted_indexes_group_2, const unsigned int *d_n_neigh, const unsigned int *d_nlist, - const size_t *d_nhead_list, + const size_t *d_n_head_list, const BoxDim box, const unsigned int max_bonds, const Scalar r_cut, const bool groups_identical, - const bool size_group_2, + const unsigned int size_group_2, const unsigned int N, const unsigned int block_size); From 3ccbb4c20d3ee977fd639702ff884f6aebe6c9d6 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 13 May 2026 08:22:12 -0500 Subject: [PATCH 43/45] fix formatting --- src/DynamicBondUpdater.cc | 1299 ++++++++++++++++--------------- src/DynamicBondUpdater.h | 360 ++++----- src/DynamicBondUpdaterGPU.cc | 281 +++---- src/DynamicBondUpdaterGPU.cu | 20 +- src/DynamicBondUpdaterGPU.cuh | 55 +- src/DynamicBondUpdaterGPU.h | 79 +- src/pytest/test_dynamic_bond.py | 192 +++-- src/update.py | 122 +-- 8 files changed, 1234 insertions(+), 1174 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 94805de6..90bddacc 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -10,17 +10,17 @@ */ #include "DynamicBondUpdater.h" -#include "hoomd/RandomNumbers.h" #include "RNGIdentifiers.h" +#include "hoomd/RandomNumbers.h" #include "hoomd/md/NeighborListTree.h" -#include #include +#include namespace hoomd -{ + { namespace azplugins -{ + { /*! * \param sysdef System definition @@ -34,82 +34,63 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) - : Updater(sysdef, trigger), - m_group_1(group_1), - m_group_2(group_2), - m_groups_identical(false), - m_r_cut(0), - m_probability(0.0), - m_bond_type(0), - m_max_bonds_group_1(0), - m_max_bonds_group_2(0), - m_seed(seed), - m_pair_nlist(pair_nlist), - m_pair_nlist_exclusions_set(true), - m_box_changed(true), - m_max_N_changed(true) + : Updater(sysdef, trigger), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), + m_r_cut(0), m_probability(0.0), m_bond_type(0), m_max_bonds_group_1(0), + m_max_bonds_group_2(0), m_seed(seed), m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { - m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - std::cout<< " in constructor 1"<< std::endl; - - m_pdata->getBoxChangeSignal().connect(this); - m_pdata->getGlobalParticleNumberChangeSignal().connect(this); - - m_bond_data = m_sysdef->getBondData(); + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + std::cout << " in constructor 1" << std::endl; - m_pair_internal_nlist = std::shared_ptr( - new hoomd::md::NeighborListTree(sysdef, 0.0)); - m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + m_pdata->getBoxChangeSignal().connect( + this); + m_pdata->getGlobalParticleNumberChangeSignal() + .connect(this); - setGroupOverlap(); + m_bond_data = m_sysdef->getBondData(); - setCutoffs(); + m_pair_internal_nlist + = std::shared_ptr(new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); - m_max_bonds = 4; - m_max_bonds_overflow = 0; - m_num_all_possible_bonds = 0; + setGroupOverlap(); + setCutoffs(); + m_max_bonds = 4; + m_max_bonds_overflow = 0; + m_num_all_possible_bonds = 0; } - DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed, - const Scalar r_cut, - const Scalar probability, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int bond_type - ) - : Updater(sysdef, trigger), - m_group_1(group_1), - m_group_2(group_2), - m_groups_identical(false), - m_r_cut(r_cut), - m_probability(probability), - m_bond_type(bond_type), - m_max_bonds_group_1(max_bonds_group_1), - m_max_bonds_group_2(max_bonds_group_2), - m_seed(seed), - m_pair_nlist(pair_nlist), - m_pair_nlist_exclusions_set(true), - m_box_changed(true), - m_max_N_changed(true) + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type) + : Updater(sysdef, trigger), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), + m_r_cut(r_cut), m_probability(probability), m_bond_type(bond_type), + m_max_bonds_group_1(max_bonds_group_1), m_max_bonds_group_2(max_bonds_group_2), m_seed(seed), + m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), + m_max_N_changed(true) { - std::cout<< " in constructor 2"<< std::endl; + std::cout << " in constructor 2" << std::endl; m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal().connect(this); - m_pdata->getGlobalParticleNumberChangeSignal().connect(this); + m_pdata->getBoxChangeSignal().connect( + this); + m_pdata->getGlobalParticleNumberChangeSignal() + .connect(this); m_bond_data = m_sysdef->getBondData(); - m_pair_internal_nlist = std::shared_ptr( - new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist + = std::shared_ptr(new hoomd::md::NeighborListTree(sysdef, 0.0)); setGroupOverlap(); m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); @@ -118,218 +99,233 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; - - } DynamicBondUpdater::~DynamicBondUpdater() { m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal().disconnect(this); - m_pdata->getGlobalParticleNumberChangeSignal().disconnect(this); - + m_pdata->getBoxChangeSignal() + .disconnect(this); + m_pdata->getGlobalParticleNumberChangeSignal() + .disconnect(this); } /*! -* \param timestep Timestep update is called -*/ + * \param timestep Timestep update is called + */ void DynamicBondUpdater::update(uint64_t timestep) { - std::cout<< " in update"<< std::endl; - //Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); - - // don't do anything if either one of the groups is empty - if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) - return; - // don't do anything if maximum number of bonds is zero - if (m_max_bonds_group_1 == 0 || m_max_bonds_group_2 == 0) - return; - - // update properties that depend on the box - if (m_box_changed) - { + std::cout << " in update" << std::endl; + // Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); + + // don't do anything if either one of the groups is empty + if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) + return; + // don't do anything if maximum number of bonds is zero + if (m_max_bonds_group_1 == 0 || m_max_bonds_group_2 == 0) + return; + + // update properties that depend on the box + if (m_box_changed) + { checkBoxSize(); m_box_changed = false; - } + } - // update properties that depend on the number of particles - if (m_max_N_changed) - { + // update properties that depend on the number of particles + if (m_max_N_changed) + { allocateParticleArrays(); m_max_N_changed = false; - } + } - // rebuild the list of possible bonds until there is no overflow - bool overflowed = false; + // rebuild the list of possible bonds until there is no overflow + bool overflowed = false; - do - { + do + { m_pair_internal_nlist->compute(timestep); - ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), + access_location::host, + access_mode::read); for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int n_neigh = h_n_neigh.data[i]; - if (n_neigh > m_max_bonds) - m_max_bonds = n_neigh; - } + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int n_neigh = h_n_neigh.data[i]; + if (n_neigh > m_max_bonds) + m_max_bonds = n_neigh; + } overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-traverse the neighbor list if (overflowed) - { - resizePossibleBondlists(); - } - } while (overflowed); - - filterPossibleBonds(); - // this function is not easily implemented on the GPU, uses addBondedGroup() - makeBonds(timestep); + { + resizePossibleBondlists(); + } + } while (overflowed); + filterPossibleBonds(); + // this function is not easily implemented on the GPU, uses addBondedGroup() + makeBonds(timestep); } // todo: should go into helper class/separate file? // bonds need to be sorted such that dublicates end up next to each other, otherwise // unique will not work properly. If the bond length of the potential bond is different, we can -// sort according to that, but there might be the case where multiple possible bond lengths are exactly identical, -// e.g. particles on a lattice. -// This is hiracical sorting: first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. -// Should work given that the tags are oredered within each pair, (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. +// sort according to that, but there might be the case where multiple possible bond lengths are +// exactly identical, e.g. particles on a lattice. This is hiracical sorting: first according to +// possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. Should work +// given that the tags are oredered within each pair, (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. -// todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing function, -// the possible bond array could be restructured into a different data structure? -// if we don't keep the possible bond length, a unsigned int array could hold all information needed -// when would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too large? +// todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing +// function, the possible bond array could be restructured into a different data structure? if we +// don't keep the possible bond length, a unsigned int array could hold all information needed when +// would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too +// large? bool SortBonds(Scalar3 i, Scalar3 j) { - const Scalar r_sq_1 = i.z; - const Scalar r_sq_2 = j.z; - if (r_sq_1==r_sq_2) - { + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1 == r_sq_2) + { const unsigned int tag_11 = __scalar_as_int(i.x); const unsigned int tag_21 = __scalar_as_int(j.x); - if (tag_11==tag_21) - { - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_22 = __scalar_as_int(j.y); - return tag_22>tag_12; - } + if (tag_11 == tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22 > tag_12; + } else + { + return tag_21 > tag_11; + } + } + else { - return tag_21>tag_11; + return r_sq_2 > r_sq_1; } - } - else - { - return r_sq_2>r_sq_1; - } } // todo: migrate to separate file/class? // Cantor paring function can also be used for comparison bool CompareBonds(Scalar3 i, Scalar3 j) { - const unsigned int tag_11 = __scalar_as_int(i.x); - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_21 = __scalar_as_int(j.x); - const unsigned int tag_22 = __scalar_as_int(j.y); + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_21 = __scalar_as_int(j.x); + const unsigned int tag_22 = __scalar_as_int(j.y); - if ((tag_11==tag_21 && tag_12==tag_22)) - { + if ((tag_11 == tag_21 && tag_12 == tag_22)) + { return true; - } - else - { + } + else + { return false; - } + } } - bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) { - const unsigned int tag_1 = __scalar_as_int(i.x); - const unsigned int tag_2 = __scalar_as_int(i.y); + const unsigned int tag_1 = __scalar_as_int(i.x); + const unsigned int tag_2 = __scalar_as_int(i.y); - // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to fill/reset with memset()? - if (tag_1==0 && tag_2==0 ) - { + // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to + // fill/reset with memset()? + if (tag_1 == 0 && tag_2 == 0) + { return true; - } - else - { - return isExistingBond(tag_1,tag_2); - } + } + else + { + return isExistingBond(tag_1, tag_2); + } } void DynamicBondUpdater::calculateExistingBonds() { - { - // reset exisitng bond list - ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getMaxN()); - - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_group_1->getNumMembers()*m_existing_bonds_list_indexer.getH()); - } - { - ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); - - // for each of the bonds in the system - regardless of their type - const unsigned int size = (unsigned int)m_bond_data->getN(); - for (unsigned int i = 0; i < size; i++) - { - // lookup the tag of each of the particles participating in the bond - const typename BondData::members_t& bond = h_bonds.data[i]; - unsigned int tag1 = bond.tag[0]; - unsigned int tag2 = bond.tag[1]; - - // @mphoward: The next section is the one that needed to be dublicated to accomodate - // the changes in hoomd (GPUArray and GPUVector) - - //AddtoExistingBonds(tag1,tag2); + { + // reset exisitng bond list + ArrayHandle h_rtag(m_pdata->getRTags(), + access_location::host, + access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::overwrite); + memset((void*)h_n_existing_bonds.data, 0, sizeof(unsigned int) * m_pdata->getMaxN()); + + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::overwrite); + memset((void*)h_existing_bonds_list.data, + 0, + sizeof(unsigned int) * m_group_1->getNumMembers() + * m_existing_bonds_list_indexer.getH()); + } + { + ArrayHandle h_bonds(m_bond_data->getMembersArray(), + access_location::host, + access_mode::read); - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag1 <= m_pdata->getMaximumTag()); - assert(tag2 <= m_pdata->getMaximumTag()); + // for each of the bonds in the system - regardless of their type + const unsigned int size = (unsigned int)m_bond_data->getN(); + for (unsigned int i = 0; i < size; i++) + { + // lookup the tag of each of the particles participating in the bond + const typename BondData::members_t& bond = h_bonds.data[i]; + unsigned int tag1 = bond.tag[0]; + unsigned int tag2 = bond.tag[1]; - bool overflowed = false; + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) - //std::cout << "inside of CalculatingExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + // AddtoExistingBonds(tag1,tag2); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag1 <= m_pdata->getMaximumTag()); + assert(tag2 <= m_pdata->getMaximumTag()); + bool overflowed = false; - // resize the list if necessary - if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) - overflowed = true; + // std::cout << "inside of CalculatingExistingBonds: array accsess readwrite + // h_n_existing_bonds " << std::endl; - if (overflowed) resizeExistingBondList(); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::readwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + // resize the list if necessary + if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag1]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos_a)] = tag2; - h_n_existing_bonds.data[tag1]++; + if (overflowed) + resizeExistingBondList(); - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag2]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos_b)] = tag1; - h_n_existing_bonds.data[tag2]++; + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::readwrite); - // END DynamicBondUpdater::AddtoExistingBonds + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag1]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1, pos_a)] = tag2; + h_n_existing_bonds.data[tag1]++; - } - } + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag2]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2, pos_b)] = tag1; + h_n_existing_bonds.data[tag2]++; + // END DynamicBondUpdater::AddtoExistingBonds + } + } } /*! \param tag1 First particle tag in the pair @@ -338,21 +334,24 @@ void DynamicBondUpdater::calculateExistingBonds() */ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) { - { - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); - unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; - - for (unsigned int i = 0; i < n_existing_bonds; i++) - { - if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,i)] == tag2) - return true; - } - return false; - } + { + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::read); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::read); + unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; + + for (unsigned int i = 0; i < n_existing_bonds; i++) + { + if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1, i)] == tag2) + return true; + } + return false; + } } - // @mphoward: This is the function that I originally had that doesn't work anymore due to // the changes in hoomd (GPUArray and GPUVector). @@ -360,523 +359,577 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) \param tag2 Second particle tag in the pair adds a bond between the tag1 and tag2 to the existing bonds list */ -void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) +void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a, unsigned int tag_b) { + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_a <= m_pdata->getMaximumTag()); + assert(tag_b <= m_pdata->getMaximumTag()); - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag_a <= m_pdata->getMaximumTag()); - assert(tag_b <= m_pdata->getMaximumTag()); - - bool overflowed = false; - - std::cout << "inside of AddtoExistingBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + bool overflowed = false; - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + std::cout << "inside of AddtoExistingBonds: array accsess readwrite h_n_existing_bonds " + << std::endl; + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::readwrite); - // resize the list if necessary - if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag_b] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - - if (overflowed) resizeExistingBondList(); + // resize the list if necessary + if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag_b] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + if (overflowed) + resizeExistingBondList(); - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag_a]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_a,pos_a)] = tag_b; - h_n_existing_bonds.data[tag_a]++; + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::readwrite); - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag_b]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b,pos_b)] = tag_a; - h_n_existing_bonds.data[tag_b]++; + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_a]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_a, pos_a)] = tag_b; + h_n_existing_bonds.data[tag_a]++; - // END DynamicBondUpdater::AddtoExistingBonds + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_b]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b, pos_b)] = tag_a; + h_n_existing_bonds.data[tag_b]++; + // END DynamicBondUpdater::AddtoExistingBonds } // grows the existing bonds list and its indexer when needed void DynamicBondUpdater::resizeExistingBondList() { - { - unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; - m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); - // update the indexer - m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; - } + { + unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; + m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); + // update the indexer + m_existing_bonds_list_indexer + = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " + << new_height << " bonds per particle " << std::endl; + } } // grows the all possible bonds list when needed in increments of 4, inspired by the neighbor list void DynamicBondUpdater::resizePossibleBondlists() { - { - // round up to nearest multiple of 4 - m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; - m_max_bonds = m_max_bonds_overflow; - m_max_bonds_overflow = 0; - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - m_all_possible_bonds.resize(size); - m_num_all_possible_bonds=0; - - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; - } + { + // round up to nearest multiple of 4 + m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; + m_max_bonds = m_max_bonds_overflow; + m_max_bonds_overflow = 0; + unsigned int size = m_group_1->getNumMembers() * m_max_bonds; + m_all_possible_bonds.resize(size); + m_num_all_possible_bonds = 0; + + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " + << m_max_bonds << " bonds per particle " << std::endl; + } } - - // allocates all arrays depending on the particles and groups void DynamicBondUpdater::allocateParticleArrays() { - { - GPUArray all_possible_bonds(m_group_1->getNumMembers() *m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); - - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); - - GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), (unsigned int)m_existing_bonds_list.getHeight()); - - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); - - memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); - memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); - } - calculateExistingBonds(); + { + GPUArray all_possible_bonds(m_group_1->getNumMembers() * m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + + GPUArray existing_bonds_list(m_pdata->getRTags().size(), 1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), + (unsigned int)m_existing_bonds_list.getHeight()); + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::overwrite); + + memset(h_n_existing_bonds.data, + 0, + sizeof(unsigned int) * m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, + 0, + sizeof(unsigned int) * m_existing_bonds_list.getNumElements()); + } + calculateExistingBonds(); } - - -/*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist and -* m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds array. -* Then, all invalid (0,0,0), dublicated, and existing bonds are removed from m_all_possible_bonds. It -* is sorted by distance (shortest to longest) between the two particles in the possible bond. -*/ +/*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist + * and m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds + * array. Then, all invalid (0,0,0), dublicated, and existing bonds are removed from + * m_all_possible_bonds. It is sorted by distance (shortest to longest) between the two particles + * in the possible bond. + */ void DynamicBondUpdater::filterPossibleBonds() { - //copy data from h_n_list to h_all_possible_bonds - { - - ArrayHandle h_nlist(m_pair_internal_nlist->getNListArray(), access_location::host, access_mode::read); - ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); - ArrayHandle h_n_head_list(m_pair_internal_nlist->getHeadList(), access_location::host, access_mode::read); - - - ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); - unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - memset((void*) h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); - - const BoxDim& box = m_pdata->getBox(); - - // Loop over all particles in group 1 - for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - - unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int tag_i = h_tag.data[i]; - const Scalar4 postype_i = h_postype.data[i]; - - - unsigned int n_curr_bond = 0; - const Scalar r_cutsq = m_r_cut*m_r_cut; - - const unsigned int n_neigh = h_n_neigh.data[i]; - const size_t head = h_n_head_list.data[i]; - - // loop over all neighbors of this particle - for (unsigned int l=0; l h_nlist(m_pair_internal_nlist->getNListArray(), + access_location::host, + access_mode::read); + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), + access_location::host, + access_mode::read); + ArrayHandle h_n_head_list(m_pair_internal_nlist->getHeadList(), + access_location::host, + access_mode::read); + + ArrayHandle h_postype(m_pdata->getPositions(), + access_location::host, + access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), + access_location::host, + access_mode::read); + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, + access_location::host, + access_mode::readwrite); + unsigned int size = m_group_1->getNumMembers() * m_max_bonds; + memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3) * size); + + const BoxDim& box = m_pdata->getBox(); + + // Loop over all particles in group 1 + for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + const Scalar4 postype_i = h_postype.data[i]; - // get index of neighbor from neigh_list - const unsigned int j = h_nlist.data[head + l]; - - if (m_group_2->isMember(j)) - { - Scalar4 postype_j = h_postype.data[j]; - const unsigned int tag_j = h_tag.data[j]; - - Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - - make_scalar3(postype_i.x,postype_i.y,postype_i.z); + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = m_r_cut * m_r_cut; - // apply periodic boundary conditions - drij = box.minImage(drij); - Scalar dr_sq = dot(drij,drij); + const unsigned int n_neigh = h_n_neigh.data[i]; + const size_t head = h_n_head_list.data[i]; - if (dr_sq < r_cutsq) - { - if (n_curr_bond < m_max_bonds) - { - Scalar3 d; - if(m_groups_identical) - { - // sort the two tags in this possible bond pair if groups identical - const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; - d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - } - else + // loop over all neighbors of this particle + for (unsigned int l = 0; l < n_neigh; ++l) { - d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + // get index of neighbor from neigh_list + const unsigned int j = h_nlist.data[head + l]; + + if (m_group_2->isMember(j)) + { + Scalar4 postype_j = h_postype.data[j]; + const unsigned int tag_j = h_tag.data[j]; + + Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) + - make_scalar3(postype_i.x, postype_i.y, postype_i.z); + + // apply periodic boundary conditions + drij = box.minImage(drij); + Scalar dr_sq = dot(drij, drij); + + if (dr_sq < r_cutsq) + { + if (n_curr_bond < m_max_bonds) + { + Scalar3 d; + if (m_groups_identical) + { + // sort the two tags in this possible bond pair if groups identical + const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a), + __int_as_scalar(tag_b), + dr_sq); + } + else + { + d = make_scalar3(__int_as_scalar(tag_i), + __int_as_scalar(tag_j), + dr_sq); + } + h_all_possible_bonds.data[group_idx * m_max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; + } + } } - h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; - } - ++n_curr_bond; } - } - } - } - - //now sort and select down - m_num_all_possible_bonds = 0; + // now sort and select down + m_num_all_possible_bonds = 0; - // remove a possible bond if it already exists. It also removes zeros, e.g. - // (0,0,0), which fill the unused spots in the array. - auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + size, - [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); + // remove a possible bond if it already exists. It also removes zeros, e.g. + // (0,0,0), which fill the unused spots in the array. + auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + size, + [this](Scalar3 i) { return CheckisExistingLegalBond(i); }); - m_num_all_possible_bonds = (unsigned int) std::distance(h_all_possible_bonds.data,last2); + m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data, last2); - // then sort array by distance between particles in the found possible bond pairs - // performance is better if remove_if happens before sort - std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); + // then sort array by distance between particles in the found possible bond pairs + // performance is better if remove_if happens before sort + std::sort(h_all_possible_bonds.data, + h_all_possible_bonds.data + m_num_all_possible_bonds, + SortBonds); - // now make sure each possible bond is in the array only once by comparing tags - auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); - m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data,last); + // now make sure each possible bond is in the array only once by comparing tags + auto last = std::unique(h_all_possible_bonds.data, + h_all_possible_bonds.data + m_num_all_possible_bonds, + CompareBonds); + m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data, last); - - // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] - // should contain only unique entries of possible bonds which are not yet formed. - } - } + // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] + // should contain only unique entries of possible bonds which are not yet formed. + } + } /*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds -* and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as -* to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list -* should be updated. -* -* Note: this function is very hard to parallelize on the GPU since we need to go through the bonds sequentially -* to prevent forming too many bonds in one step. Have not found a good way of doing this on the GPU. -*/ + * and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as + * to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list + * should be updated. + * + * Note: this function is very hard to parallelize on the GPU since we need to go through the bonds + * sequentially to prevent forming too many bonds in one step. Have not found a good way of doing + * this on the GPU. + */ void DynamicBondUpdater::makeBonds(uint64_t timestep) - { - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); + { + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, + access_location::host, + access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, + access_location::host, + access_mode::readwrite); // we need to count how many bonds are in the h_all_possible_bonds array for a given tag - // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in - // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. - // This also makes it very difficult to do on the GPU. + // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases + // the count in h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping + // should be needed. This also makes it very difficult to do on the GPU. ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); - //todo: can this for loop be simplified/parallelized? + // todo: can this for loop be simplified/parallelized? for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) - { - Scalar3 d = h_all_possible_bonds.data[i]; - - unsigned int tag_i = __scalar_as_int(d.x); - unsigned int tag_j = __scalar_as_int(d.y); - - //todo: put in other external criteria here, e.g. max number of bonds possible in one step, etc. - //todo: randomize which bonds are formed or keep them ordered by their distances ? - //todo: would it be faster/better to create the rng outside of the loop? - hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::azplugins::detail::RNGIdentifier::DynamicBondUpdater, - timestep, - m_seed), - hoomd::Counter()); - - hoomd::UniformDistribution uniform(0, 1); - const Scalar random = uniform(rng); - - if ((m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i]) && - (m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j]) && - (random < m_probability)) { - m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); - //AddtoExistingBonds(tag_i,tag_j); + Scalar3 d = h_all_possible_bonds.data[i]; - // @mphoward: The next section is the one that needed to be dublicated to accomodate - // the changes in hoomd (GPUArray and GPUVector) + unsigned int tag_i = __scalar_as_int(d.x); + unsigned int tag_j = __scalar_as_int(d.y); - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag_i <= m_pdata->getMaximumTag()); - assert(tag_j <= m_pdata->getMaximumTag()); + // todo: put in other external criteria here, e.g. max number of bonds possible in one step, + // etc. todo: randomize which bonds are formed or keep them ordered by their distances ? + // todo: would it be faster/better to create the rng outside of the loop? + hoomd::RandomGenerator rng( + hoomd::Seed(hoomd::azplugins::detail::RNGIdentifier::DynamicBondUpdater, + timestep, + m_seed), + hoomd::Counter()); - bool overflowed = false; + hoomd::UniformDistribution uniform(0, 1); + const Scalar random = uniform(rng); - // std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << std::endl; + if ((m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i]) + && (m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j]) && (random < m_probability)) + { + m_bond_data->addBondedGroup(Bond(m_bond_type, tag_i, tag_j)); + // AddtoExistingBonds(tag_i,tag_j); - // resize the list if necessary - if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag_j] == m_existing_bonds_list_indexer.getH()) - overflowed = true; + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) - if (overflowed) resizeExistingBondList(); + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_i <= m_pdata->getMaximumTag()); + assert(tag_j <= m_pdata->getMaximumTag()); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + bool overflowed = false; - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag_i]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_i,pos_a)] = tag_j; - h_n_existing_bonds.data[tag_i]++; + // std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << + // std::endl; - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag_j]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j,pos_b)] = tag_i; - h_n_existing_bonds.data[tag_j]++; + // resize the list if necessary + if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag_j] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - // END DynamicBondUpdater::AddtoExistingBonds + if (overflowed) + resizeExistingBondList(); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, + access_location::host, + access_mode::readwrite); - if (m_pair_nlist_exclusions_set) - { - m_pair_nlist -> addExclusion(tag_i,tag_j); - m_pair_internal_nlist -> addExclusion(tag_i,tag_j); - } - } - } + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_i]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_i, pos_a)] = tag_j; + h_n_existing_bonds.data[tag_i]++; - } + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_j]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j, pos_b)] = tag_i; + h_n_existing_bonds.data[tag_j]++; + // END DynamicBondUpdater::AddtoExistingBonds + if (m_pair_nlist_exclusions_set) + { + m_pair_nlist->addExclusion(tag_i, tag_j); + m_pair_internal_nlist->addExclusion(tag_i, tag_j); + } + } + } + } /*! -* Check that the largest neighbor search radius is not bigger than twice the shortest box size. -* Raises an error if this condition is not met. -*/ + * Check that the largest neighbor search radius is not bigger than twice the shortest box size. + * Raises an error if this condition is not met. + */ void DynamicBondUpdater::checkBoxSize() { - const BoxDim& box = m_pdata->getBox(); - const uchar3 periodic = box.getPeriodic(); - - // check that rcut fits in the box - Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); - Scalar rmax = m_r_cut; - - if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) || - (periodic.y && nearest_plane_distance.y <= rmax * 2.0) || - (m_sysdef->getNDimensions() == 3 && periodic.z && nearest_plane_distance.z <= rmax * 2.0)) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles would be interacting with themselves." << std::endl; + const BoxDim& box = m_pdata->getBox(); + const uchar3 periodic = box.getPeriodic(); + + // check that rcut fits in the box + Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); + Scalar rmax = m_r_cut; + + if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) + || (periodic.y && nearest_plane_distance.y <= rmax * 2.0) + || (m_sysdef->getNDimensions() == 3 && periodic.z + && nearest_plane_distance.z <= rmax * 2.0)) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles " + "would be interacting with themselves." + << std::endl; throw std::runtime_error("Error in DynamicBondUpdater, Simulation box too small."); - } + } } /*! Calculates if the two groups have overlap or not. Returns an error if partial -* overlap is detected. -*/ + * overlap is detected. + */ void DynamicBondUpdater::setGroupOverlap() { - if(m_group_1->getNumMembers()==0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: First group group_1 appears to be empty. No bonds will be formed. " << std::endl; - } - - if(m_group_2->getNumMembers()==0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be empty. No bonds will be formed. " << std::endl; - } - { - //check if the two groups are either identical or have no overlap - ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); - - // count particles which are in both groups. Should be either zero of them or all of them. - unsigned int overlap = 0; - for (unsigned int i=0; igetNumMembers(); ++i) - { - unsigned int idx = h_index_group_1.data[i]; - if (m_group_2->isMember(idx)) - overlap++; - } - - if( overlap>0 && overlap != m_group_1->getNumMembers()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: group 1 and group 2 have " << overlap << " overlaps. Partially overlapping groups are not implemented." << std::endl; - throw std::runtime_error("Partial overlapping groups in DynamicBondUpdater"); - } - - if(overlap==m_group_1->getNumMembers()) - { - m_groups_identical=true; - } - } - std::cout << "groups identical "<< m_groups_identical<< std::endl; + if (m_group_1->getNumMembers() == 0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: First group group_1 appears to be " + "empty. No bonds will be formed. " + << std::endl; + } + + if (m_group_2->getNumMembers() == 0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be " + "empty. No bonds will be formed. " + << std::endl; + } + { + // check if the two groups are either identical or have no overlap + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), + access_location::host, + access_mode::read); + + // count particles which are in both groups. Should be either zero of them or all of them. + unsigned int overlap = 0; + for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) + { + unsigned int idx = h_index_group_1.data[i]; + if (m_group_2->isMember(idx)) + overlap++; + } + + if (overlap > 0 && overlap != m_group_1->getNumMembers()) + { + m_exec_conf->msg->error() + << "DynamicBondUpdater: group 1 and group 2 have " << overlap + << " overlaps. Partially overlapping groups are not implemented." << std::endl; + throw std::runtime_error("Partial overlapping groups in DynamicBondUpdater"); + } + + if (overlap == m_group_1->getNumMembers()) + { + m_groups_identical = true; + } + } + std::cout << "groups identical " << m_groups_identical << std::endl; } /*! Sets cutoffs based on types present in the two groups to save some performance from -* the neighbor list. -*/ + * the neighbor list. + */ void DynamicBondUpdater::setCutoffs() { - { + { + // set all rcuts to zero first, then only set the one between group1 and group 2 particle + // types to be m_r_cut + unsigned int NTypes = m_pdata->getNTypes(); + for (unsigned int i = 0; i < NTypes; ++i) + { + for (unsigned int j = 0; j < NTypes; ++j) + { + m_pair_internal_nlist->setRcut(i, j, 0); + } + } - // set all rcuts to zero first, then only set the one between group1 and group 2 particle types to be m_r_cut - unsigned int NTypes = m_pdata -> getNTypes(); - for (unsigned int i=0; i< NTypes; ++i) - { - for (unsigned int j=0; j< NTypes; ++j) - { - m_pair_internal_nlist->setRcut(i,j,0); - } - } - - ArrayHandle h_pos(m_pdata->getPositions(), - access_location::host, - access_mode::read); - - - ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); - - // finding all types in group 1 - std::set types_group_1; - for (unsigned int i=0; igetNumMembers(); ++i) - { - unsigned int idx = h_index_group_1.data[i]; - Scalar4 type = h_pos.data[idx]; - types_group_1.insert(__scalar_as_int(type.w)); - } - - std::set types_group_2; - if (m_groups_identical == false) - { - ArrayHandle h_index_group_2(m_group_2->getIndexArray(), access_location::host, access_mode::read); - - // finding all types in group 2 - for (unsigned int i=0; igetNumMembers(); ++i) - { - unsigned int idx = h_index_group_2.data[i]; - Scalar4 type = h_pos.data[idx]; - types_group_2.insert(__scalar_as_int(type.w)); - } - } - else - { - types_group_2 = types_group_1; - } + ArrayHandle h_pos(m_pdata->getPositions(), + access_location::host, + access_mode::read); - // looping over types in group 1 and 2 to set the cutoff to m_r_cut - for (const auto& element_1 : types_group_1) - { - for (const auto& element_2 : types_group_2) - { - m_pair_internal_nlist->setRcut(element_1,element_2,m_r_cut); - m_pair_internal_nlist->setRcut(element_2,element_1,m_r_cut); - std::cout<< " cutoffs set "<< element_1<< " " << element_2 << " " << m_r_cut << std::endl; - } - } + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), + access_location::host, + access_mode::read); - } - } + // finding all types in group 1 + std::set types_group_1; + for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) + { + unsigned int idx = h_index_group_1.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_1.insert(__scalar_as_int(type.w)); + } + + std::set types_group_2; + if (m_groups_identical == false) + { + ArrayHandle h_index_group_2(m_group_2->getIndexArray(), + access_location::host, + access_mode::read); + // finding all types in group 2 + for (unsigned int i = 0; i < m_group_2->getNumMembers(); ++i) + { + unsigned int idx = h_index_group_2.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_2.insert(__scalar_as_int(type.w)); + } + } + else + { + types_group_2 = types_group_1; + } + + // looping over types in group 1 and 2 to set the cutoff to m_r_cut + for (const auto& element_1 : types_group_1) + { + for (const auto& element_2 : types_group_2) + { + m_pair_internal_nlist->setRcut(element_1, element_2, m_r_cut); + m_pair_internal_nlist->setRcut(element_2, element_1, m_r_cut); + std::cout << " cutoffs set " << element_1 << " " << element_2 << " " << m_r_cut + << std::endl; + } + } + } + } // Check that the given cutoff value is valid void DynamicBondUpdater::checkRcut() { - if (m_r_cut <= 0.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance is less than or equal to zero" << std::endl; + if (m_r_cut <= 0.0) + { + m_exec_conf->msg->error() + << "DynamicBondUpdater: Requested cutoff distance is less than or equal to zero" + << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - checkBoxSize(); + } + checkBoxSize(); } void DynamicBondUpdater::checkProbability() -{ - if (m_probability < 0.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is less than zero" << std::endl; + { + if (m_probability < 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is less than zero" + << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - else if (m_probability > 1.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is larger than one" << std::endl; + } + else if (m_probability > 1.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is larger than one" + << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - -} + } + } void DynamicBondUpdater::checkMaxBondsGroup() -{ - if (m_max_bonds_group_1 < 0.0 or m_max_bonds_group_2 < 0.0 ) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Max number of bonds that groups can form is negative. Check parameters." << std::endl; + { + if (m_max_bonds_group_1 < 0.0 or m_max_bonds_group_2 < 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Max number of bonds that groups can form " + "is negative. Check parameters." + << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - else if (m_max_bonds_group_1 > 10 or m_max_bonds_group_2 > 10 ) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: Requested number of bonds that can form is very large. This can lead to performance issues." << std::endl; - } - -} + } + else if (m_max_bonds_group_1 > 10 or m_max_bonds_group_2 > 10) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Requested number of bonds that can " + "form is very large. This can lead to performance issues." + << std::endl; + } + } // Check that the given bond type is valid void DynamicBondUpdater::checkBondType() { - if (m_bond_type >= m_bond_data -> getNTypes()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type << " is not a valid bond type." << std::endl; + if (m_bond_type >= m_bond_data->getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type + << " is not a valid bond type." << std::endl; throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); - } + } } - namespace detail -{ + { /*! -* \param m Python module to export to -*/ + * \param m Python module to export to + */ void export_DynamicBondUpdater(pybind11::module& m) { - - pybind11::class_< DynamicBondUpdater, Updater,std::shared_ptr>( - m, - "DynamicBondUpdater") - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t>()) - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t, - Scalar, - Scalar, - unsigned int, - unsigned int, - unsigned int>()) - .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) - .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) - .def_property("bond_type",&DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) - .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) - .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); + pybind11::class_>( + m, + "DynamicBondUpdater") + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t>()) + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t, + Scalar, + Scalar, + unsigned int, + unsigned int, + unsigned int>()) + .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + .def_property("probability", + &DynamicBondUpdater::getProbability, + &DynamicBondUpdater::setProbability) + .def_property("bond_type", + &DynamicBondUpdater::getBondType, + &DynamicBondUpdater::setBondType) + .def_property("max_bonds_group_1", + &DynamicBondUpdater::getMaxBondsGroup1, + &DynamicBondUpdater::setMaxBondsGroup1) + .def_property("max_bonds_group_2", + &DynamicBondUpdater::getMaxBondsGroup2, + &DynamicBondUpdater::setMaxBondsGroup2); } -} // end namespace detail + } // end namespace detail -} // end namespace azplugins + } // end namespace azplugins -} // end namespace hoomd + } // end namespace hoomd diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index 5ffeba61..caa376dd 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -21,10 +21,9 @@ #endif #include "hoomd/AABBTree.h" -#include "hoomd/md/NeighborList.h" -#include "hoomd/Updater.h" #include "hoomd/ParticleGroup.h" - +#include "hoomd/Updater.h" +#include "hoomd/md/NeighborList.h" #ifndef __HIPCC__ #define HOSTDEVICE __host__ __device__ @@ -36,192 +35,205 @@ #define PYBIND11_EXPORT __attribute__((visibility("default"))) #endif - namespace hoomd -{ + { namespace azplugins -{ + { class PYBIND11_EXPORT DynamicBondUpdater : public Updater { public: - - //! Simple constructor - DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed); - - //! Constructor with parameters - DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed, - const Scalar r_cut, - const Scalar probability, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int bond_type - ); - - //! Destructor - virtual ~DynamicBondUpdater(); - - //! update - virtual void update(uint64_t timestep); - - //! Set the cutoff distance for finding bonds - /*! - * \param r_cut cutoff distance between particles for finding potential bonds - */ - void setRcut(Scalar r_cut) - { - m_r_cut = r_cut; - checkRcut(); - setCutoffs(); - } - //! Get the cutoff distance between particles for finding bonds - Scalar getRcut() - { - return m_r_cut; - } - //! Set the bond type of the dynamically formed bonds - /*! - * \param bond_type type of bonds to be formed - */ - void setBondType(unsigned int bond_type) - { - //m_bond_data = m_sysdef->getBondData(); - //m_bond_type = m_bond_data->getTypeByName(bond_type); - m_bond_type = bond_type; - checkBondType(); - } - //! Get the bond type of the dynamically formed bonds - unsigned int getBondType() - { + //! Simple constructor + DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed); + + //! Constructor with parameters + DynamicBondUpdater(std::shared_ptr sysdef, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type); + + //! Destructor + virtual ~DynamicBondUpdater(); + + //! update + virtual void update(uint64_t timestep); + + //! Set the cutoff distance for finding bonds + /*! + * \param r_cut cutoff distance between particles for finding potential bonds + */ + void setRcut(Scalar r_cut) + { + m_r_cut = r_cut; + checkRcut(); + setCutoffs(); + } + //! Get the cutoff distance between particles for finding bonds + Scalar getRcut() + { + return m_r_cut; + } + //! Set the bond type of the dynamically formed bonds + /*! + * \param bond_type type of bonds to be formed + */ + void setBondType(unsigned int bond_type) + { + // m_bond_data = m_sysdef->getBondData(); + // m_bond_type = m_bond_data->getTypeByName(bond_type); + m_bond_type = bond_type; + checkBondType(); + } + //! Get the bond type of the dynamically formed bonds + unsigned int getBondType() + { return m_bond_type; - } - - //! Set the maximum number of bonds on particles in group_1 - /*! - * \param max_bonds_group_1 max number of bonds formed by particles in group 1 - */ - void setMaxBondsGroup1(unsigned int max_bonds_group_1) - { - m_max_bonds_group_1 = max_bonds_group_1; - checkMaxBondsGroup(); - } - //! Get the maximum number of bonds on particles in group_1 - unsigned int getMaxBondsGroup1() - { - return m_max_bonds_group_1; - } - //! Set the maximum number of bonds on particles in group_2 - /*! - * \param max_bonds_group_2 max number of bonds formed by particles in group_2 - */ - void setMaxBondsGroup2(unsigned int max_bonds_group_2) - { - m_max_bonds_group_2 = max_bonds_group_2; - checkMaxBondsGroup(); - } - //! Get the maximum number of bonds on particles in group_2 - unsigned int getMaxBondsGroup2() - { - return m_max_bonds_group_2; - } - //! set probablilty - void setProbability(Scalar probability) - { - m_probability = probability; - checkProbability(); - } - //! Get the probability - Scalar getProbability() - { - return m_probability; - } + } + + //! Set the maximum number of bonds on particles in group_1 + /*! + * \param max_bonds_group_1 max number of bonds formed by particles in group 1 + */ + void setMaxBondsGroup1(unsigned int max_bonds_group_1) + { + m_max_bonds_group_1 = max_bonds_group_1; + checkMaxBondsGroup(); + } + //! Get the maximum number of bonds on particles in group_1 + unsigned int getMaxBondsGroup1() + { + return m_max_bonds_group_1; + } + //! Set the maximum number of bonds on particles in group_2 + /*! + * \param max_bonds_group_2 max number of bonds formed by particles in group_2 + */ + void setMaxBondsGroup2(unsigned int max_bonds_group_2) + { + m_max_bonds_group_2 = max_bonds_group_2; + checkMaxBondsGroup(); + } + //! Get the maximum number of bonds on particles in group_2 + unsigned int getMaxBondsGroup2() + { + return m_max_bonds_group_2; + } + //! set probablilty + void setProbability(Scalar probability) + { + m_probability = probability; + checkProbability(); + } + //! Get the probability + Scalar getProbability() + { + return m_probability; + } protected: - - std::shared_ptr m_bond_data; //!< Bond data - - std::shared_ptr m_group_1; //!< First particle group to form bonds with - std::shared_ptr m_group_2; //!< Second particle group to form bonds with - bool m_groups_identical; //!< whether or not the two groups are identical. Set true if they are identical. - - Scalar m_r_cut; //!< cutoff for the bond forming criterion - Scalar m_probability; //!< probability of bond formation if bond can be formed (i.e. within cutoff) - unsigned int m_bond_type; //!< Type id of the bond to form - unsigned int m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group - unsigned int m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group - uint16_t m_seed; //!< seed for random number generator for bond probability - - unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be found, is resized if overflow is triggered - unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of possible bonds - - GPUArray m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds - unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of m_all_possible_bonds - - GPUArray m_existing_bonds_list; //!< List of existing bonded particles referenced by tag - GPUArray m_n_existing_bonds; //!< Number of existing bonds for a given particle tag - unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded particles - Index2D m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list - - std::shared_ptr m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds need to be set - std::shared_ptr m_pair_internal_nlist; - bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the hoomd particle neighborlist. Set to true when m_pair_nlist is set - - //! filter out existing and doublicate bonds from all found possible bonds - virtual void filterPossibleBonds(); - - bool CheckisExistingLegalBond(Scalar3 i); //this acesses info in m_existing_bonds_list_tag. todo: rename to something sensible - void calculateExistingBonds(); - void makeBonds(uint64_t timestep); - - void AddtoExistingBonds(unsigned int tag1,unsigned int tag2); - bool isExistingBond(unsigned int tag1,unsigned int tag2); //this acesses info in m_existing_bonds_list_tag - void checkBoxSize(); - void checkRcut(); - void checkBondType(); - void checkProbability(); - void setGroupOverlap(); - void setCutoffs(); - void checkMaxBondsGroup(); - void resizePossibleBondlists(); - void resizeExistingBondList(); - void allocateParticleArrays(); - - //! Notification of a box size change - void slotBoxChanged() - { - m_box_changed = true; - } - - //! Notification of total particle number change - void slotNumParticlesChanged() - { - m_max_N_changed = true; - } - - bool m_box_changed; //!< Flag if box dimensions changed - bool m_max_N_changed; //!< Flag if total number of particles changed - + std::shared_ptr m_bond_data; //!< Bond data + + std::shared_ptr m_group_1; //!< First particle group to form bonds with + std::shared_ptr m_group_2; //!< Second particle group to form bonds with + bool m_groups_identical; //!< whether or not the two groups are identical. Set true if they are + //!< identical. + + Scalar m_r_cut; //!< cutoff for the bond forming criterion + Scalar + m_probability; //!< probability of bond formation if bond can be formed (i.e. within cutoff) + unsigned int m_bond_type; //!< Type id of the bond to form + unsigned int + m_max_bonds_group_1; //!< maximum number of bonds which can be formed by the first group + unsigned int + m_max_bonds_group_2; //!< maximum number of bonds which can be formed by the second group + uint16_t m_seed; //!< seed for random number generator for bond probability + + unsigned int m_max_bonds; //!< maximum number of possible bonds (or neighbors) which can be + //!< found, is resized if overflow is triggered + unsigned int m_max_bonds_overflow; //!< registers if there is an overflow in maximum number of + //!< possible bonds + + GPUArray + m_all_possible_bonds; //!< list of possible bonds, size: NumMembers(group_1)*m_max_bonds + unsigned int m_num_all_possible_bonds; //!< number of valid possible bonds at the beginning of + //!< m_all_possible_bonds + + GPUArray + m_existing_bonds_list; //!< List of existing bonded particles referenced by tag + GPUArray + m_n_existing_bonds; //!< Number of existing bonds for a given particle tag + unsigned int m_max_existing_bonds_list; //!< maximum number of bonds in list of existing bonded + //!< particles + Index2D + m_existing_bonds_list_indexer; //!< Indexer for accessing the by-tag bonded particle list + + std::shared_ptr + m_pair_nlist; //!< The hoomd neighborlist, only used if exclusions of the newly formed bonds + //!< need to be set + std::shared_ptr m_pair_internal_nlist; + bool m_pair_nlist_exclusions_set; //!< whether or not the bonds are set as exclusions in the + //!< hoomd particle neighborlist. Set to true when + //!< m_pair_nlist is set + + //! filter out existing and doublicate bonds from all found possible bonds + virtual void filterPossibleBonds(); + + bool CheckisExistingLegalBond(Scalar3 i); // this acesses info in m_existing_bonds_list_tag. + // todo: rename to something sensible + void calculateExistingBonds(); + void makeBonds(uint64_t timestep); + + void AddtoExistingBonds(unsigned int tag1, unsigned int tag2); + bool isExistingBond(unsigned int tag1, + unsigned int tag2); // this acesses info in m_existing_bonds_list_tag + void checkBoxSize(); + void checkRcut(); + void checkBondType(); + void checkProbability(); + void setGroupOverlap(); + void setCutoffs(); + void checkMaxBondsGroup(); + void resizePossibleBondlists(); + void resizeExistingBondList(); + void allocateParticleArrays(); + + //! Notification of a box size change + void slotBoxChanged() + { + m_box_changed = true; + } + + //! Notification of total particle number change + void slotNumParticlesChanged() + { + m_max_N_changed = true; + } + + bool m_box_changed; //!< Flag if box dimensions changed + bool m_max_N_changed; //!< Flag if total number of particles changed }; namespace detail -{ + { //! Export the Evaporator to python void export_DynamicBondUpdater(pybind11::module& m); -} // end namespace detail + } // end namespace detail -} // end namespace azplugins + } // end namespace azplugins -} // end namespace hoomd + } // end namespace hoomd #endif // AZPLUGINS_TYPE_UPDATER_H_ diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 15cdb53b..846c8958 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -13,9 +13,9 @@ #include "DynamicBondUpdaterGPU.cuh" namespace hoomd -{ + { namespace azplugins -{ + { /*! * \param sysdef System definition @@ -25,150 +25,165 @@ namespace azplugins * properly initialize the system via setters. */ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed) - : DynamicBondUpdater(sysdef, trigger, pair_nlist, group_1, group_2, seed), - m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed) + : DynamicBondUpdater(sysdef, trigger, pair_nlist, group_1, group_2, seed), + m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) { - - // only one GPU is supported + // only one GPU is supported if (!m_exec_conf->isCUDAEnabled()) { throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); } m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, - m_exec_conf, - "dynamic_bonding_copy_nlist")); + m_exec_conf, + "dynamic_bonding_copy_nlist")); m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, - m_exec_conf, - "dynamic_bonding_filter_bonds")); + m_exec_conf, + "dynamic_bonding_filter_bonds")); m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds}); m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; - } DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed, - const Scalar r_cut, - const Scalar probability, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int bond_type) - : DynamicBondUpdater(sysdef,trigger,pair_nlist,group_1,group_2, seed, r_cut, - probability,max_bonds_group_1,max_bonds_group_2,bond_type), - m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type) + : DynamicBondUpdater(sysdef, + trigger, + pair_nlist, + group_1, + group_2, + seed, + r_cut, + probability, + max_bonds_group_1, + max_bonds_group_2, + bond_type), + m_num_nonzero_bonds_flag(m_exec_conf), m_max_bonds_overflow_flag(m_exec_conf) { - - // only one GPU is supported + // only one GPU is supported if (!m_exec_conf->isCUDAEnabled()) { throw std::runtime_error("Cannot initialize DynamicBondUpdaterGPU on a CPU device."); } m_tuner_copy_nlist.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, - m_exec_conf, - "dynamic_bonding_copy_nlist")); + m_exec_conf, + "dynamic_bonding_copy_nlist")); m_tuner_filter_bonds.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, - m_exec_conf, - "dynamic_bonding_filter_bonds")); + m_exec_conf, + "dynamic_bonding_filter_bonds")); m_autotuners.insert(m_autotuners.end(), {m_tuner_copy_nlist, m_tuner_filter_bonds}); m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; - } DynamicBondUpdaterGPU::~DynamicBondUpdaterGPU() { - m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdaterGPU" << std::endl; - + m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdaterGPU" << std::endl; } - - void DynamicBondUpdaterGPU::filterPossibleBonds() - { - - //copy data from m_n_list to d_all_possible_bonds. nlist saves indices and the existing bonds have to be stored by tags - //so copy data first then sort out existing bonds - const unsigned int size = m_group_1->getNumMembers()*m_max_bonds; - ArrayHandle d_n_existing_bonds(m_n_existing_bonds, access_location::device, access_mode::read); - ArrayHandle d_existing_bonds_list(m_existing_bonds_list, access_location::device, access_mode::read); - - ArrayHandle d_nlist(m_pair_internal_nlist->getNListArray(), access_location::device, access_mode::read); - ArrayHandle d_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::device, access_mode::read); - ArrayHandle d_n_head_list(m_pair_internal_nlist->getHeadList(), access_location::device, access_mode::read); - + { + // copy data from m_n_list to d_all_possible_bonds. nlist saves indices and the existing bonds + // have to be stored by tags so copy data first then sort out existing bonds + const unsigned int size = m_group_1->getNumMembers() * m_max_bonds; + ArrayHandle d_n_existing_bonds(m_n_existing_bonds, + access_location::device, + access_mode::read); + ArrayHandle d_existing_bonds_list(m_existing_bonds_list, + access_location::device, + access_mode::read); + + ArrayHandle d_nlist(m_pair_internal_nlist->getNListArray(), + access_location::device, + access_mode::read); + ArrayHandle d_n_neigh(m_pair_internal_nlist->getNNeighArray(), + access_location::device, + access_mode::read); + ArrayHandle d_n_head_list(m_pair_internal_nlist->getHeadList(), + access_location::device, + access_mode::read); ArrayHandle d_tag(m_pdata->getTags(), access_location::device, access_mode::read); ArrayHandle d_pos(m_pdata->getPositions(), access_location::device, access_mode::read); - ArrayHandle d_index_group_1(m_group_1->getIndexArray(), access_location::device, access_mode::read); + ArrayHandle d_index_group_1(m_group_1->getIndexArray(), + access_location::device, + access_mode::read); - ArrayHandle d_all_possible_bonds(m_all_possible_bonds, access_location::device, access_mode::readwrite); - cudaMemset((void*)d_all_possible_bonds.data, 0, sizeof(Scalar3)*m_all_possible_bonds.getNumElements()); + ArrayHandle d_all_possible_bonds(m_all_possible_bonds, + access_location::device, + access_mode::readwrite); + cudaMemset((void*)d_all_possible_bonds.data, + 0, + sizeof(Scalar3) * m_all_possible_bonds.getNumElements()); const BoxDim& box = m_pdata->getBox(); if (m_groups_identical) - { - m_tuner_copy_nlist->begin(); - gpu::copy_possible_bonds(d_all_possible_bonds.data, - d_pos.data, - d_tag.data, - d_index_group_1.data, - d_index_group_1.data, - d_n_neigh.data, - d_nlist.data, - d_n_head_list.data, - box, - m_max_bonds, - m_r_cut, - m_groups_identical, - m_group_1->getNumMembers(), - m_group_1->getNumMembers(), - m_tuner_copy_nlist->getParam()[0]); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); - m_tuner_copy_nlist->end(); - - - }else - { - ArrayHandle d_index_group_2(m_group_2->getIndexArray(), access_location::device, access_mode::read); + { m_tuner_copy_nlist->begin(); gpu::copy_possible_bonds(d_all_possible_bonds.data, - d_pos.data, - d_tag.data, - d_index_group_1.data, - d_index_group_2.data, - d_n_neigh.data, - d_nlist.data, - d_n_head_list.data, - box, - m_max_bonds, - m_r_cut, - m_groups_identical, - m_group_2->getNumMembers(), - m_group_1->getNumMembers(), - m_tuner_copy_nlist->getParam()[0]); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + d_pos.data, + d_tag.data, + d_index_group_1.data, + d_index_group_1.data, + d_n_neigh.data, + d_nlist.data, + d_n_head_list.data, + box, + m_max_bonds, + m_r_cut, + m_groups_identical, + m_group_1->getNumMembers(), + m_group_1->getNumMembers(), + m_tuner_copy_nlist->getParam()[0]); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) + CHECK_CUDA_ERROR(); m_tuner_copy_nlist->end(); + } + else + { + ArrayHandle d_index_group_2(m_group_2->getIndexArray(), + access_location::device, + access_mode::read); + m_tuner_copy_nlist->begin(); + gpu::copy_possible_bonds(d_all_possible_bonds.data, + d_pos.data, + d_tag.data, + d_index_group_1.data, + d_index_group_2.data, + d_n_neigh.data, + d_nlist.data, + d_n_head_list.data, + box, + m_max_bonds, + m_r_cut, + m_groups_identical, + m_group_2->getNumMembers(), + m_group_1->getNumMembers(), + m_tuner_copy_nlist->getParam()[0]); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) + CHECK_CUDA_ERROR(); + m_tuner_copy_nlist->end(); + } - } - - - - - //filter out the existing bonds - based on neighbor list exclusion handling + // filter out the existing bonds - based on neighbor list exclusion handling m_tuner_filter_bonds->begin(); gpu::filter_existing_bonds(d_all_possible_bonds.data, d_n_existing_bonds.data, @@ -176,7 +191,8 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_existing_bonds_list_indexer, size, m_tuner_filter_bonds->getParam()[0]); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); + if (m_exec_conf->isCUDAErrorCheckingEnabled()) + CHECK_CUDA_ERROR(); m_tuner_filter_bonds->end(); m_num_all_possible_bonds = 0; @@ -187,45 +203,42 @@ void DynamicBondUpdaterGPU::filterPossibleBonds() m_num_all_possible_bonds = m_num_nonzero_bonds_flag.readFlags(); - // at this point, the sub-array: d_all_possible_bonds[0,m_num_all_possible_bonds] // should contain only unique entries of possible bonds which are not yet formed. } - namespace detail -{ + { /*! * \param m Python module to export to */ - void export_DynamicBondUpdaterGPU(pybind11::module& m) - { - namespace py = pybind11; - - - py::class_< DynamicBondUpdaterGPU, DynamicBondUpdater, std::shared_ptr >(m, "DynamicBondUpdaterGPU") - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t>()) - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t, - Scalar, - Scalar, - unsigned int, - unsigned int, - unsigned int>()); - } - -} // end namespace detail -} // end namespace azplugins - -} // end namespace hoomd +void export_DynamicBondUpdaterGPU(pybind11::module& m) + { + namespace py = pybind11; + + py::class_>( + m, + "DynamicBondUpdaterGPU") + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t>()) + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t, + Scalar, + Scalar, + unsigned int, + unsigned int, + unsigned int>()); + } + } // end namespace detail + } // end namespace azplugins + } // end namespace hoomd diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index 7dcd9ec7..3e3eb1cd 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -66,7 +66,6 @@ struct isZeroBondGPU } }; - struct CompareBondsGPU { __host__ __device__ bool operator()(const Scalar3& i, const Scalar3& j) @@ -137,18 +136,21 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, // test if j is in group 2 // wrapper for pointer needed for thrust - thrust::device_ptr d_sorted_indexes_group_2_wrap(d_sorted_indexes_group_2); - - auto iter = thrust::find(thrust::device, d_sorted_indexes_group_2_wrap,d_sorted_indexes_group_2_wrap+size_group_2, pidx_j); + thrust::device_ptr d_sorted_indexes_group_2_wrap( + d_sorted_indexes_group_2); - if (iter != d_sorted_indexes_group_2_wrap+size_group_2) - { + auto iter = thrust::find(thrust::device, + d_sorted_indexes_group_2_wrap, + d_sorted_indexes_group_2_wrap + size_group_2, + pidx_j); + if (iter != d_sorted_indexes_group_2_wrap + size_group_2) + { Scalar4 postype_j = d_postype[pidx_j]; const unsigned int tag_j = d_tag[pidx_j]; Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) - - make_scalar3(postype_i.x, postype_i.y, postype_i.z); + - make_scalar3(postype_i.x, postype_i.y, postype_i.z); // apply periodic boundary conditions (FLOPS: 12) drij = box.minImage(drij); @@ -176,7 +178,7 @@ __global__ void copy_nlist_possible_bonds(Scalar3* d_all_possible_bonds, } ++n_curr_bond; } - } + } } } diff --git a/src/DynamicBondUpdaterGPU.cuh b/src/DynamicBondUpdaterGPU.cuh index 6fa4cac1..74d13bfe 100644 --- a/src/DynamicBondUpdaterGPU.cuh +++ b/src/DynamicBondUpdaterGPU.cuh @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -12,11 +12,11 @@ #ifndef AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ #define AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ -#include +#include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" #include "hoomd/Index1D.h" -#include "hoomd/BoxDim.h" #include "hoomd/ParticleData.cuh" +#include #include #include "hoomd/md/NeighborListGPUTree.cuh" @@ -29,57 +29,50 @@ #define HOSTDEVICE #endif - namespace hoomd -{ + { namespace azplugins -{ + { namespace gpu -{ + { -cudaError_t sort_possible_bond_array(Scalar3 *d_all_possible_bonds, - const unsigned int size); +cudaError_t sort_possible_bond_array(Scalar3* d_all_possible_bonds, const unsigned int size); -cudaError_t filter_existing_bonds(Scalar3 *d_all_possible_bonds, - unsigned int *d_n_existing_bonds, - const unsigned int *d_existing_bonds_list, +cudaError_t filter_existing_bonds(Scalar3* d_all_possible_bonds, + unsigned int* d_n_existing_bonds, + const unsigned int* d_existing_bonds_list, const Index2D& exli, const unsigned int size, const unsigned int block_size); -cudaError_t copy_possible_bonds(Scalar3 *d_all_possible_bonds, - const Scalar4 *d_postype, - const unsigned int *d_tag, - const unsigned int *d_sorted_indexes, - const unsigned int *d_sorted_indexes_group_2, - const unsigned int *d_n_neigh, - const unsigned int *d_nlist, - const size_t *d_n_head_list, +cudaError_t copy_possible_bonds(Scalar3* d_all_possible_bonds, + const Scalar4* d_postype, + const unsigned int* d_tag, + const unsigned int* d_sorted_indexes, + const unsigned int* d_sorted_indexes_group_2, + const unsigned int* d_n_neigh, + const unsigned int* d_nlist, + const size_t* d_n_head_list, const BoxDim box, const unsigned int max_bonds, - const Scalar r_cut, + const Scalar r_cut, const bool groups_identical, const unsigned int size_group_2, const unsigned int N, const unsigned int block_size); -cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3 *d_all_possible_bonds, +cudaError_t remove_zeros_and_sort_possible_bond_array(Scalar3* d_all_possible_bonds, const unsigned int size, - int *d_max_non_zero_bonds); - - + int* d_max_non_zero_bonds); //! Sentinel for an invalid particle (e.g., ghost) const unsigned int NeighborListTypeSentinel = 0xffffffff; - -} // end namespace gpu -} // end namespace azplugins -} // end namespace hoomd - + } // end namespace gpu + } // end namespace azplugins + } // end namespace hoomd #undef DEVICE #undef HOSTDEVICE - #endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_CUH_ diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index f13dc677..ebecf886 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// This file is part of the azplugins project, released under the Modified BSD License. +// Part of azplugins, released under the BSD 3-Clause License. // Maintainer: astatt @@ -23,11 +23,10 @@ #include "hip/hip_runtime.h" #include "hoomd/md/NeighborListGPUTree.cuh" - namespace hoomd -{ + { namespace azplugins -{ + { //! Particle type updater on the GPU /*! @@ -37,55 +36,49 @@ namespace azplugins class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater { public: - //! Simple constructor - DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed); - - - //! Constructor with parameters - DynamicBondUpdaterGPU(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed, - const Scalar r_cut, - const Scalar probability, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int bond_type); - - //! Destructor - virtual ~DynamicBondUpdaterGPU(); - + //! Simple constructor + DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed); + + //! Constructor with parameters + DynamicBondUpdaterGPU(std::shared_ptr sysdef, + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type); + + //! Destructor + virtual ~DynamicBondUpdaterGPU(); protected: - //! filter out existing and doublicate bonds from all found possible bonds - virtual void filterPossibleBonds(); - + //! filter out existing and doublicate bonds from all found possible bonds + virtual void filterPossibleBonds(); private: + std::shared_ptr> m_tuner_copy_nlist; //!< Tuner for the primitive-copy kernel + std::shared_ptr> m_tuner_filter_bonds; //!< Tuner for existing bond filter - std::shared_ptr> m_tuner_copy_nlist; //!< Tuner for the primitive-copy kernel - std::shared_ptr> m_tuner_filter_bonds; //!< Tuner for existing bond filter - - GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds - GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow - - + GPUFlags m_num_nonzero_bonds_flag; //!< GPU flag for the number of valid bonds + GPUFlags m_max_bonds_overflow_flag; //!< GPU flag for overflow }; namespace detail -{ + { //! Export DynamicBondUpdaterGPU to python void export_DynamicBondUpdaterGPU(pybind11::module& m); -} // end namespace detail + } // end namespace detail -} // end namespace azplugins -} // end namespace hoomd + } // end namespace azplugins + } // end namespace hoomd #endif // AZPLUGINS_DYNAMIC_BOND_UPDATER_GPU_H_ diff --git a/src/pytest/test_dynamic_bond.py b/src/pytest/test_dynamic_bond.py index 6131f7b8..009bfe98 100644 --- a/src/pytest/test_dynamic_bond.py +++ b/src/pytest/test_dynamic_bond.py @@ -3,65 +3,62 @@ # Part of azplugins, released under the BSD 3-Clause License. import hoomd -import pytest import numpy def test_setters_getters(simulation_factory, one_particle_snapshot_factory): - # make one particle test configuration - sim = simulation_factory(one_particle_snapshot_factory(position=[0,0,0], L=20)) - - u = hoomd.azplugins.update.dynamic_bond(nlist=hoomd.md.nlist.Cell(buffer=0.4), - r_cut=1, - trigger = hoomd.trigger.Periodic(period=10), - bond_type=0, - group_1=hoomd.filter.All(), - group_2=hoomd.filter.All(), - max_bonds_group_1=0, - max_bonds_group_2=0) + simulation_factory(one_particle_snapshot_factory(position=[0, 0, 0], L=20)) + + u = hoomd.azplugins.update.DynamicBond( + nlist=hoomd.md.nlist.Cell(buffer=0.4), + r_cut=1, + trigger=hoomd.trigger.Periodic(period=10), + bond_type=0, + group_1=hoomd.filter.All(), + group_2=hoomd.filter.All(), + max_bonds_group_1=0, + max_bonds_group_2=0, + ) assert numpy.equal(u.r_cut, 1) u.r_cut = 1.5 assert numpy.equal(u.r_cut, 1.5) u.max_bonds_group_1 = 3 - assert numpy.equal(u.max_bonds_group_1,3) + assert numpy.equal(u.max_bonds_group_1, 3) u.max_bonds_group_2 = 7 - assert numpy.equal(u.max_bonds_group_2,7) + assert numpy.equal(u.max_bonds_group_2, 7) - # todo: this check doesn't work but it definitely throws an error - maybe wrong way to test? - # check the test of box size large enough for cutoff - #with pytest.raises(RuntimeError): - # u.r_cut=15.0 def test_form_bonds_same_group(simulation_factory): - snap = hoomd.Snapshot() if snap.communicator.rank == 0: snap.configuration.box = [20, 20, 20, 0, 0, 0] snap.particles.N = 6 - snap.particles.types = ["A","B"] - snap.particles.position[:,0] = (0,1,2,3,4,6) - snap.particles.position[:,1] = (0,0,0,0,0,0) - snap.particles.position[:,2] = (0,0,0,0,0,0) + snap.particles.types = ["A", "B"] + snap.particles.position[:, 0] = (0, 1, 2, 3, 4, 6) + snap.particles.position[:, 1] = (0, 0, 0, 0, 0, 0) + snap.particles.position[:, 2] = (0, 0, 0, 0, 0, 0) # dynamic bond operates on groups, so typeids should not matter at all - snap.particles.typeid[:] = [0,0,1,1,0,0] - snap.bonds.types = ['bond'] + snap.particles.typeid[:] = [0, 0, 1, 1, 0, 0] + snap.bonds.types = ["bond"] sim = simulation_factory(snap) - nl=hoomd.md.nlist.Cell(buffer=0.4) - - group =hoomd.filter.Tags([0,1,2,3,4,5]) - form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, - r_cut=1.1, - trigger = hoomd.trigger.Periodic(period=1), - bond_type=0, - group_1=group, - group_2=group, - max_bonds_group_1=2, - max_bonds_group_2=2) + nl = hoomd.md.nlist.Cell(buffer=0.4) + + group = hoomd.filter.Tags([0, 1, 2, 3, 4, 5]) + form_bonds = hoomd.azplugins.update.DynamicBond( + nlist=nl, + r_cut=1.1, + trigger=hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group, + group_2=group, + max_bonds_group_1=2, + max_bonds_group_2=2, + ) # test bond formation. All are in the same group, so bonds should be formed # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) @@ -73,11 +70,10 @@ def test_form_bonds_same_group(simulation_factory): s = sim.state.get_snapshot() numpy.testing.assert_almost_equal(4, s.bonds.N) - numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) - numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) - numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) - + numpy.testing.assert_array_almost_equal([0, 1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2, 3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3, 4], s.bonds.group[3]) # same as before, we will have formed bonds 0-1-2-3-4 in the first step, so nothing # new should be formed @@ -85,53 +81,53 @@ def test_form_bonds_same_group(simulation_factory): s = sim.state.get_snapshot() numpy.testing.assert_almost_equal(4, s.bonds.N) - numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) - numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) - numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) - + numpy.testing.assert_array_almost_equal([0, 1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2, 3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3, 4], s.bonds.group[3]) # now put particle 5 in range of partice 2 and 3, but no new bonds should # be formed since max_bonds_group_1=max_bonds_group_2=2 and that would be the third # bond on those particles - s.particles.position[5]=(2.5,0,0) + s.particles.position[5] = (2.5, 0, 0) sim.state.set_snapshot(s) sim.run(1) s = sim.state.get_snapshot() numpy.testing.assert_almost_equal(4, s.bonds.N) - numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[0]) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[1]) - numpy.testing.assert_array_almost_equal([2,3], s.bonds.group[2]) - numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[3]) + numpy.testing.assert_array_almost_equal([0, 1], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([2, 3], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([3, 4], s.bonds.group[3]) def test_form_bonds_same_group_priority(simulation_factory): - snap = hoomd.Snapshot() if snap.communicator.rank == 0: snap.configuration.box = [20, 20, 20, 0, 0, 0] snap.particles.N = 6 - snap.particles.types = ["A","B"] - snap.particles.position[:,0] = (0,1,2,3,4,2.5) - snap.particles.position[:,1] = (0,0,0,0,0,0) - snap.particles.position[:,2] = (0,0,0,0,0,0) + snap.particles.types = ["A", "B"] + snap.particles.position[:, 0] = (0, 1, 2, 3, 4, 2.5) + snap.particles.position[:, 1] = (0, 0, 0, 0, 0, 0) + snap.particles.position[:, 2] = (0, 0, 0, 0, 0, 0) # dynamic bond operates on groups, so typeids should not matter at all - snap.particles.typeid[:] = [0,0,1,1,0,0] - snap.bonds.types = ['bond'] + snap.particles.typeid[:] = [0, 0, 1, 1, 0, 0] + snap.bonds.types = ["bond"] sim = simulation_factory(snap) - nl=hoomd.md.nlist.Cell(buffer=0.4) - - group =hoomd.filter.Tags([0,1,2,3,4,5]) - form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, - r_cut=1.1, - trigger = hoomd.trigger.Periodic(period=1), - bond_type=0, - group_1=group, - group_2=group, - max_bonds_group_1=2, - max_bonds_group_2=2) + nl = hoomd.md.nlist.Cell(buffer=0.4) + + group = hoomd.filter.Tags([0, 1, 2, 3, 4, 5]) + form_bonds = hoomd.azplugins.update.DynamicBond( + nlist=nl, + r_cut=1.1, + trigger=hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group, + group_2=group, + max_bonds_group_1=2, + max_bonds_group_2=2, + ) # test bond formation. All are in the same group, so bonds should be formed # between 0-1, 1-2, 2-3, and 3-4 (but not 5, too far away) @@ -148,39 +144,40 @@ def test_form_bonds_same_group_priority(simulation_factory): # and 3-5 are formed first. Then in order of index 0-1, and 1-2. # Particle 2 now has two bonds, so 2-3 can't be formed, but 3-4 can. numpy.testing.assert_almost_equal(5, s.bonds.N) - numpy.testing.assert_array_almost_equal([2,5], s.bonds.group[0]) - numpy.testing.assert_array_almost_equal([3,5], s.bonds.group[1]) - numpy.testing.assert_array_almost_equal([0,1], s.bonds.group[2]) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[3]) - numpy.testing.assert_array_almost_equal([3,4], s.bonds.group[4]) + numpy.testing.assert_array_almost_equal([2, 5], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([3, 5], s.bonds.group[1]) + numpy.testing.assert_array_almost_equal([0, 1], s.bonds.group[2]) + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[3]) + numpy.testing.assert_array_almost_equal([3, 4], s.bonds.group[4]) def test_update_bond_two_groups(simulation_factory): - snap = hoomd.Snapshot() if snap.communicator.rank == 0: snap.configuration.box = [20, 20, 20, 0, 0, 0] snap.particles.N = 4 - snap.particles.types = ["A","B"] - snap.particles.position[:,0] = (0,0.9,1.1,2) - snap.particles.position[:,1] = (0,0,0,0) - snap.particles.position[:,2] = (0,0,0,0) - snap.particles.typeid[:] = [1,0,1,0] - snap.bonds.types = ['bond'] + snap.particles.types = ["A", "B"] + snap.particles.position[:, 0] = (0, 0.9, 1.1, 2) + snap.particles.position[:, 1] = (0, 0, 0, 0) + snap.particles.position[:, 2] = (0, 0, 0, 0) + snap.particles.typeid[:] = [1, 0, 1, 0] + snap.bonds.types = ["bond"] sim = simulation_factory(snap) - nl=hoomd.md.nlist.Cell(buffer=0.4) - - group_1 = hoomd.filter.Tags([0,1]) - group_2 = hoomd.filter.Tags([2,3]) - form_bonds = hoomd.azplugins.update.dynamic_bond(nlist=nl, - r_cut=1.0, - trigger = hoomd.trigger.Periodic(period=1), - bond_type=0, - group_1=group_1, - group_2=group_2, - max_bonds_group_1=1, - max_bonds_group_2=2) + nl = hoomd.md.nlist.Cell(buffer=0.4) + + group_1 = hoomd.filter.Tags([0, 1]) + group_2 = hoomd.filter.Tags([2, 3]) + form_bonds = hoomd.azplugins.update.DynamicBond( + nlist=nl, + r_cut=1.0, + trigger=hoomd.trigger.Periodic(period=1), + bond_type=0, + group_1=group_1, + group_2=group_2, + max_bonds_group_1=1, + max_bonds_group_2=2, + ) # test bond formation between particle 1-2 # particle 0,1 are in the same group, so even if their distance is @@ -193,7 +190,7 @@ def test_update_bond_two_groups(simulation_factory): s = sim.state.get_snapshot() numpy.testing.assert_almost_equal(1, s.bonds.N) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[0]) + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[0]) # after this bond 1-2 is formed, there shouldn't be a second one formed # e.g. a dublicate, even when the simulation continues to run @@ -201,9 +198,4 @@ def test_update_bond_two_groups(simulation_factory): s = sim.state.get_snapshot() numpy.testing.assert_almost_equal(1, s.bonds.N) - numpy.testing.assert_array_almost_equal([1,2], s.bonds.group[0]) - - - - - + numpy.testing.assert_array_almost_equal([1, 2], s.bonds.group[0]) diff --git a/src/update.py b/src/update.py index c1c9cbcf..a304c51c 100644 --- a/src/update.py +++ b/src/update.py @@ -1,8 +1,8 @@ # Copyright (c) 2018-2020, Michael P. Howard -# Copyright (c) 2021-2022, Auburn University -# This file is part of the azplugins project, released under the Modified BSD License. +# Copyright (c) 2021-2025, Auburn University +# Part of azplugins, released under the BSD 3-Clause License. -""" Updaters. """ +"""Updaters.""" import hoomd @@ -10,33 +10,35 @@ from hoomd.data.parameterdicts import ParameterDict from hoomd.data.typeconverter import OnlyTypes -class dynamic_bond(hoomd.operation.Updater): - R""" Update bonds dynamically during simulation. + +class DynamicBond(hoomd.operation.Updater): + R"""Update bonds dynamically during simulation. Args: r_cut (float): Distance cutoff for making bonds between particles probability (float): Probability of bond formation, between 0 and 1, default = 1 bond_type (str): Type of bond to be formed - group_1 (:py:mod: `hoomd.filter.ParticleFilter`): First particle group to form bonds between - group_2 (:py:mod:`hoomd.filter.ParticleFilter`): Second particle group to form bonds between + group_1 (:py:mod: `hoomd.filter.ParticleFilter`): First particle group + group_2 (:py:mod:`hoomd.filter.ParticleFilter`): Second particle group max_bonds_1 (int): Maximum number of bonds a particle in group_1 can have max_bonds_2 (int): Maximum number of bonds a particle in group_2 can have seed (int): Seed to the pseudo-random number generator - nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) for updating the exclusions + nlist (:py:mod:`hoomd.md.nlist`): NeighborList (optional) to update exclusions period (int): Particle types will be updated every *period* time steps phase (int): When -1, start on the current time step. Otherwise, execute on steps where *(step + phase) % period* is 0. Forms bonds of type bond_type between particles in group_1 and group_2 during - the simulation, if particle distances are shorter than r_cut. If the neighborlist - used for the pair potential in the simulation is given as a parameter nlist, the - neighbor list exclusions will be updated to include the newly formed bonds. - Each particle has a number of maximum bonds which it can form, given by - max_bonds_1 for particles in group_1 and max_bonds_2 for group_2. + the simulation, if particle distances are shorter than r_cut. If the + neighborlist used for the pair potential in the simulation is given as a + parameter nlist, the neighbor list exclusions will be updated to include the + newly formed bonds. Each particle has a number of maximum bonds which it can + form, given by max_bonds_1 for particles in group_1 and max_bonds_2 for group_2. The particles in the two groups group_1 and group_2 should be completely separate with no common elements, e.g. two different types, or the two - groups should be identical, where now max_bonds_1 needs to be equal to max_bonds_2. + groups should be identical, where now max_bonds_1 needs to be equal to + max_bonds_2. .. warning:: @@ -47,115 +49,118 @@ class dynamic_bond(hoomd.operation.Updater): Examples:: - azplugins.update.dynamic_bond(nlist=nl,r_cut=1.0,bond_type=0, - group_1=hoomd.group.type(type='A'),group_2=hoomd.group.type(type='B'),max_bonds_1=3,max_bonds_2=2) + azplugins.update.dynamic_bond( + nlist=nl, + r_cut=1.0, + bond_type=0, + group_1=hoomd.group.type(type="A"), + group_2=hoomd.group.type(type="B"), + max_bonds_1=3, + max_bonds_2=2, + ) """ + _ext_module = _azplugins _cpp_class_name = "DynamicBondUpdater" - def __init__(self, - trigger, - nlist, - group_1, - group_2, - bond_type=None, - max_bonds_group_1=None, - max_bonds_group_2=None, - r_cut=None, - seed=0, - probability=1): + def __init__( + self, + trigger, + nlist, + group_1, + group_2, + bond_type=None, + max_bonds_group_1=None, + max_bonds_group_2=None, + r_cut=None, + seed=0, + probability=1, + ): super().__init__(trigger) params = ParameterDict( r_cut=OnlyTypes(float, allow_none=True), - nlist =OnlyTypes(hoomd.md.nlist.NeighborList,strict=True, allow_none=False), + nlist=OnlyTypes(hoomd.md.nlist.NeighborList, strict=True, allow_none=False), group_1=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=False), group_2=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=False), max_bonds_group_1=OnlyTypes(int, allow_none=True), max_bonds_group_2=OnlyTypes(int, allow_none=True), - bond_type=OnlyTypes(int,strict=True,allow_none=True), - seed = OnlyTypes(int,strict=True,allow_none=False), - probability = float(probability) + bond_type=OnlyTypes(int, strict=True, allow_none=True), + seed=OnlyTypes(int, strict=True, allow_none=False), + probability=float(probability), ) params.update( dict( - r_cut = r_cut, + r_cut=r_cut, nlist=nlist, group_1=group_1, group_2=group_2, max_bonds_group_1=max_bonds_group_1, max_bonds_group_2=max_bonds_group_2, - bond_type = bond_type, - seed = seed, - probability = probability + bond_type=bond_type, + seed=seed, + probability=probability, ) ) self._param_dict.update(params) - #self.set_params(r_cut,probability,bond_type,max_bonds_1,max_bonds_2,nlist) + # self.set_params(r_cut,probability,bond_type,max_bonds_1,max_bonds_2,nlist) @property def bond_type(self): + """bond_type (int): Type of the bonds that are made by this updater.""" return self._cpp_obj.bond_type @bond_type.setter - def bond_type(self,value): + def bond_type(self, value): if value is not None: - self._param_dict['bond_type']=value + self._param_dict["bond_type"] = value self._cpp_obj.setBondType(value) @property def probability(self): + """Probability (float): Probability of forming bonds. Value between 0 and 1.""" return self._cpp_obj.probability @probability.setter - def probability(self,value): - self._param_dict['probability']=value + def probability(self, value): + self._param_dict["probability"] = value self._cpp_obj.probability = value @property def r_cut(self): - """ - r_cut (float): Distance cutoff for making bonds between particles - """ + """r_cut (float): Distance cutoff for making bonds between particles.""" return self._cpp_obj.r_cut @r_cut.setter def r_cut(self, value): self._cpp_obj.r_cut = value - self._param_dict['r_cut']=value - + self._param_dict["r_cut"] = value @property def max_bonds_group_1(self): - """ - max_bonds_1 (int) - """ + """max_bonds_1 (int): Max bonds on group 1.""" return self._cpp_obj.max_bonds_group_1 @max_bonds_group_1.setter def max_bonds_group_1(self, value): self._cpp_obj.max_bonds_group_1 = value - self._param_dict['max_bonds_group_1']=value + self._param_dict["max_bonds_group_1"] = value @property def max_bonds_group_2(self): - """ - max_bonds_group_2 (int) - """ + """max_bonds_2 (int): Max bonds on group 2.""" return self._cpp_obj.max_bonds_group_2 @max_bonds_group_2.setter def max_bonds_group_2(self, value): self._cpp_obj.max_bonds_group_2 = value - self._param_dict['max_bonds_group_2']=value - - + self._param_dict["max_bonds_group_2"] = value def _attach_hook(self): - sim = self._simulation """Create the c++ mirror class.""" + sim = self._simulation if isinstance(self._simulation.device, hoomd.device.CPU): cpp_class = getattr(self._ext_module, self._cpp_class_name) else: @@ -179,10 +184,7 @@ def _attach_hook(self): self.nlist._cpp_obj, group_1, group_2, - self.seed + self.seed, ) super()._attach_hook() - - - From ccb3fddab10864a358cfc6cfd2de8949b4433ddd Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 13 May 2026 08:23:22 -0500 Subject: [PATCH 44/45] remove leftover couts --- src/DynamicBondUpdater.cc | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 90bddacc..181e5dd3 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -40,7 +40,6 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - std::cout << " in constructor 1" << std::endl; m_pdata->getBoxChangeSignal().connect( this); @@ -79,7 +78,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) { - std::cout << " in constructor 2" << std::endl; + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; m_pdata->getBoxChangeSignal().connect( @@ -116,8 +115,6 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - std::cout << " in update" << std::endl; - // Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); // don't do anything if either one of the groups is empty if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) @@ -291,9 +288,6 @@ void DynamicBondUpdater::calculateExistingBonds() bool overflowed = false; - // std::cout << "inside of CalculatingExistingBonds: array accsess readwrite - // h_n_existing_bonds " << std::endl; - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); @@ -367,9 +361,6 @@ void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a, unsigned int tag bool overflowed = false; - std::cout << "inside of AddtoExistingBonds: array accsess readwrite h_n_existing_bonds " - << std::endl; - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); @@ -648,9 +639,6 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) bool overflowed = false; - // std::cout << "inside of makeBonds: array accsess readwrite h_n_existing_bonds " << - // std::endl; - // resize the list if necessary if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) overflowed = true; @@ -758,7 +746,6 @@ void DynamicBondUpdater::setGroupOverlap() m_groups_identical = true; } } - std::cout << "groups identical " << m_groups_identical << std::endl; } /*! Sets cutoffs based on types present in the two groups to save some performance from @@ -822,8 +809,6 @@ void DynamicBondUpdater::setCutoffs() { m_pair_internal_nlist->setRcut(element_1, element_2, m_r_cut); m_pair_internal_nlist->setRcut(element_2, element_1, m_r_cut); - std::cout << " cutoffs set " << element_1 << " " << element_2 << " " << m_r_cut - << std::endl; } } } From 7335d1d3fa4a5c81474aec1730005ba093904355 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 13 May 2026 13:49:13 -0500 Subject: [PATCH 45/45] whitespace fix --- src/DynamicBondUpdater.cc | 1278 ++++++++++++++++++------------------- 1 file changed, 615 insertions(+), 663 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 181e5dd3..0f463c98 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -1,6 +1,6 @@ // Copyright (c) 2018-2020, Michael P. Howard // Copyright (c) 2021-2025, Auburn University -// Part of azplugins, released under the BSD 3-Clause License. +// This file is part of the azplugins project, released under the Modified BSD License. // Maintainer: astatt @@ -10,17 +10,17 @@ */ #include "DynamicBondUpdater.h" -#include "RNGIdentifiers.h" #include "hoomd/RandomNumbers.h" +#include "RNGIdentifiers.h" #include "hoomd/md/NeighborListTree.h" -#include #include +#include namespace hoomd - { +{ namespace azplugins - { +{ /*! * \param sysdef System definition @@ -34,62 +34,81 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) - : Updater(sysdef, trigger), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(0), m_probability(0.0), m_bond_type(0), m_max_bonds_group_1(0), - m_max_bonds_group_2(0), m_seed(seed), m_pair_nlist(pair_nlist), - m_pair_nlist_exclusions_set(true), m_box_changed(true), m_max_N_changed(true) + : Updater(sysdef, trigger), + m_group_1(group_1), + m_group_2(group_2), + m_groups_identical(false), + m_r_cut(0), + m_probability(0.0), + m_bond_type(0), + m_max_bonds_group_1(0), + m_max_bonds_group_2(0), + m_seed(seed), + m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(true), + m_box_changed(true), + m_max_N_changed(true) { - m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; + m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal().connect( - this); - m_pdata->getGlobalParticleNumberChangeSignal() - .connect(this); + m_pdata->getBoxChangeSignal().connect(this); + m_pdata->getGlobalParticleNumberChangeSignal().connect(this); - m_bond_data = m_sysdef->getBondData(); + m_bond_data = m_sysdef->getBondData(); - m_pair_internal_nlist - = std::shared_ptr(new hoomd::md::NeighborListTree(sysdef, 0.0)); - m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); + m_pair_internal_nlist = std::shared_ptr( + new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); - setGroupOverlap(); + setGroupOverlap(); + + setCutoffs(); + + m_max_bonds = 4; + m_max_bonds_overflow = 0; + m_num_all_possible_bonds = 0; - setCutoffs(); - m_max_bonds = 4; - m_max_bonds_overflow = 0; - m_num_all_possible_bonds = 0; } + DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, - std::shared_ptr trigger, - std::shared_ptr pair_nlist, - std::shared_ptr group_1, - std::shared_ptr group_2, - uint16_t seed, - const Scalar r_cut, - const Scalar probability, - unsigned int max_bonds_group_1, - unsigned int max_bonds_group_2, - unsigned int bond_type) - : Updater(sysdef, trigger), m_group_1(group_1), m_group_2(group_2), m_groups_identical(false), - m_r_cut(r_cut), m_probability(probability), m_bond_type(bond_type), - m_max_bonds_group_1(max_bonds_group_1), m_max_bonds_group_2(max_bonds_group_2), m_seed(seed), - m_pair_nlist(pair_nlist), m_pair_nlist_exclusions_set(true), m_box_changed(true), - m_max_N_changed(true) + std::shared_ptr trigger, + std::shared_ptr pair_nlist, + std::shared_ptr group_1, + std::shared_ptr group_2, + uint16_t seed, + const Scalar r_cut, + const Scalar probability, + unsigned int max_bonds_group_1, + unsigned int max_bonds_group_2, + unsigned int bond_type + ) + : Updater(sysdef, trigger), + m_group_1(group_1), + m_group_2(group_2), + m_groups_identical(false), + m_r_cut(r_cut), + m_probability(probability), + m_bond_type(bond_type), + m_max_bonds_group_1(max_bonds_group_1), + m_max_bonds_group_2(max_bonds_group_2), + m_seed(seed), + m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(true), + m_box_changed(true), + m_max_N_changed(true) { m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal().connect( - this); - m_pdata->getGlobalParticleNumberChangeSignal() - .connect(this); + m_pdata->getBoxChangeSignal().connect(this); + m_pdata->getGlobalParticleNumberChangeSignal().connect(this); m_bond_data = m_sysdef->getBondData(); - m_pair_internal_nlist - = std::shared_ptr(new hoomd::md::NeighborListTree(sysdef, 0.0)); + m_pair_internal_nlist = std::shared_ptr( + new hoomd::md::NeighborListTree(sysdef, 0.0)); setGroupOverlap(); m_pair_internal_nlist->setStorageMode(hoomd::md::NeighborList::full); @@ -98,228 +117,215 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_bonds = 4; m_max_bonds_overflow = 0; m_num_all_possible_bonds = 0; + + } DynamicBondUpdater::~DynamicBondUpdater() { m_exec_conf->msg->notice(5) << "Destroying DynamicBondUpdater" << std::endl; - m_pdata->getBoxChangeSignal() - .disconnect(this); - m_pdata->getGlobalParticleNumberChangeSignal() - .disconnect(this); + m_pdata->getBoxChangeSignal().disconnect(this); + m_pdata->getGlobalParticleNumberChangeSignal().disconnect(this); + } /*! - * \param timestep Timestep update is called - */ +* \param timestep Timestep update is called +*/ void DynamicBondUpdater::update(uint64_t timestep) { - - // don't do anything if either one of the groups is empty - if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) - return; - // don't do anything if maximum number of bonds is zero - if (m_max_bonds_group_1 == 0 || m_max_bonds_group_2 == 0) - return; - - // update properties that depend on the box - if (m_box_changed) - { + //Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); + + // don't do anything if either one of the groups is empty + if (m_group_1->getNumMembers() == 0 || m_group_2->getNumMembers() == 0) + return; + // don't do anything if maximum number of bonds is zero + if (m_max_bonds_group_1 == 0 || m_max_bonds_group_2 == 0) + return; + + // update properties that depend on the box + if (m_box_changed) + { checkBoxSize(); m_box_changed = false; - } + } - // update properties that depend on the number of particles - if (m_max_N_changed) - { + // update properties that depend on the number of particles + if (m_max_N_changed) + { allocateParticleArrays(); m_max_N_changed = false; - } + } - // rebuild the list of possible bonds until there is no overflow - bool overflowed = false; + // rebuild the list of possible bonds until there is no overflow + bool overflowed = false; - do - { + do + { m_pair_internal_nlist->compute(timestep); - ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), - access_location::host, - access_mode::read); + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int n_neigh = h_n_neigh.data[i]; - if (n_neigh > m_max_bonds) - m_max_bonds = n_neigh; - } + { + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int n_neigh = h_n_neigh.data[i]; + if (n_neigh > m_max_bonds) + m_max_bonds = n_neigh; + } overflowed = m_max_bonds < m_max_bonds_overflow; // if we overflowed, need to reallocate memory and re-traverse the neighbor list if (overflowed) - { - resizePossibleBondlists(); - } - } while (overflowed); + { + resizePossibleBondlists(); + } + } while (overflowed); + + filterPossibleBonds(); + // this function is not easily implemented on the GPU, uses addBondedGroup() + makeBonds(timestep); - filterPossibleBonds(); - // this function is not easily implemented on the GPU, uses addBondedGroup() - makeBonds(timestep); } // todo: should go into helper class/separate file? // bonds need to be sorted such that dublicates end up next to each other, otherwise // unique will not work properly. If the bond length of the potential bond is different, we can -// sort according to that, but there might be the case where multiple possible bond lengths are -// exactly identical, e.g. particles on a lattice. This is hiracical sorting: first according to -// possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. Should work -// given that the tags are oredered within each pair, (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. +// sort according to that, but there might be the case where multiple possible bond lengths are exactly identical, +// e.g. particles on a lattice. +// This is hiracical sorting: first according to possible bond distance r_ab_sq, then after first tag_a, last after second tag_b. +// Should work given that the tags are oredered within each pair, (tag_a,tag_b,d_ab_sq) with tag_a < tag_b. -// todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing -// function, the possible bond array could be restructured into a different data structure? if we -// don't keep the possible bond length, a unsigned int array could hold all information needed when -// would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too -// large? +// todo: because it's possible uniquely map a pair to a single int (and back!) with a pairing function, +// the possible bond array could be restructured into a different data structure? +// if we don't keep the possible bond length, a unsigned int array could hold all information needed +// when would that lead to problems with overflow from 0.5*(tag_a+tag_b)*(tag_a+tag_b+1)+tag_b being too large? bool SortBonds(Scalar3 i, Scalar3 j) { - const Scalar r_sq_1 = i.z; - const Scalar r_sq_2 = j.z; - if (r_sq_1 == r_sq_2) - { + const Scalar r_sq_1 = i.z; + const Scalar r_sq_2 = j.z; + if (r_sq_1==r_sq_2) + { const unsigned int tag_11 = __scalar_as_int(i.x); const unsigned int tag_21 = __scalar_as_int(j.x); - if (tag_11 == tag_21) - { - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_22 = __scalar_as_int(j.y); - return tag_22 > tag_12; - } - else - { - return tag_21 > tag_11; - } + if (tag_11==tag_21) + { + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_22 = __scalar_as_int(j.y); + return tag_22>tag_12; } - else + else { - return r_sq_2 > r_sq_1; + return tag_21>tag_11; } + } + else + { + return r_sq_2>r_sq_1; + } } // todo: migrate to separate file/class? // Cantor paring function can also be used for comparison bool CompareBonds(Scalar3 i, Scalar3 j) { - const unsigned int tag_11 = __scalar_as_int(i.x); - const unsigned int tag_12 = __scalar_as_int(i.y); - const unsigned int tag_21 = __scalar_as_int(j.x); - const unsigned int tag_22 = __scalar_as_int(j.y); + const unsigned int tag_11 = __scalar_as_int(i.x); + const unsigned int tag_12 = __scalar_as_int(i.y); + const unsigned int tag_21 = __scalar_as_int(j.x); + const unsigned int tag_22 = __scalar_as_int(j.y); - if ((tag_11 == tag_21 && tag_12 == tag_22)) - { + if ((tag_11==tag_21 && tag_12==tag_22)) + { return true; - } - else - { + } + else + { return false; - } + } } + bool DynamicBondUpdater::CheckisExistingLegalBond(Scalar3 i) { - const unsigned int tag_1 = __scalar_as_int(i.x); - const unsigned int tag_2 = __scalar_as_int(i.y); + const unsigned int tag_1 = __scalar_as_int(i.x); + const unsigned int tag_2 = __scalar_as_int(i.y); - // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to - // fill/reset with memset()? - if (tag_1 == 0 && tag_2 == 0) - { + // (0,0,0.0) is the default "empty" value - todo: have a "invalid" unsigned int ? how to fill/reset with memset()? + if (tag_1==0 && tag_2==0 ) + { return true; - } - else - { - return isExistingBond(tag_1, tag_2); - } + } + else + { + return isExistingBond(tag_1,tag_2); + } } void DynamicBondUpdater::calculateExistingBonds() { - { - // reset exisitng bond list - ArrayHandle h_rtag(m_pdata->getRTags(), - access_location::host, - access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::overwrite); - memset((void*)h_n_existing_bonds.data, 0, sizeof(unsigned int) * m_pdata->getMaxN()); - - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::overwrite); - memset((void*)h_existing_bonds_list.data, - 0, - sizeof(unsigned int) * m_group_1->getNumMembers() - * m_existing_bonds_list_indexer.getH()); - } - { - ArrayHandle h_bonds(m_bond_data->getMembersArray(), - access_location::host, - access_mode::read); + { + // reset exisitng bond list + ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + memset((void*)h_n_existing_bonds.data,0,sizeof(unsigned int)*m_pdata->getMaxN()); - // for each of the bonds in the system - regardless of their type - const unsigned int size = (unsigned int)m_bond_data->getN(); - for (unsigned int i = 0; i < size; i++) - { - // lookup the tag of each of the particles participating in the bond - const typename BondData::members_t& bond = h_bonds.data[i]; - unsigned int tag1 = bond.tag[0]; - unsigned int tag2 = bond.tag[1]; + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + memset((void*)h_existing_bonds_list.data,0,sizeof(unsigned int)*m_group_1->getNumMembers()*m_existing_bonds_list_indexer.getH()); + } + { + ArrayHandle h_bonds(m_bond_data->getMembersArray(), access_location::host, access_mode::read); - // @mphoward: The next section is the one that needed to be dublicated to accomodate - // the changes in hoomd (GPUArray and GPUVector) + // for each of the bonds in the system - regardless of their type + const unsigned int size = (unsigned int)m_bond_data->getN(); + for (unsigned int i = 0; i < size; i++) + { + // lookup the tag of each of the particles participating in the bond + const typename BondData::members_t& bond = h_bonds.data[i]; + unsigned int tag1 = bond.tag[0]; + unsigned int tag2 = bond.tag[1]; - // AddtoExistingBonds(tag1,tag2); + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag1 <= m_pdata->getMaximumTag()); - assert(tag2 <= m_pdata->getMaximumTag()); + //AddtoExistingBonds(tag1,tag2); - bool overflowed = false; + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag1 <= m_pdata->getMaximumTag()); + assert(tag2 <= m_pdata->getMaximumTag()); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::readwrite); + bool overflowed = false; - // resize the list if necessary - if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) - overflowed = true; + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); - if (overflowed) - resizeExistingBondList(); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::readwrite); + // resize the list if necessary + if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag1]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1, pos_a)] = tag2; - h_n_existing_bonds.data[tag1]++; + if (overflowed) resizeExistingBondList(); - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag2]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2, pos_b)] = tag1; - h_n_existing_bonds.data[tag2]++; + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag1]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,pos_a)] = tag2; + h_n_existing_bonds.data[tag1]++; + + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag2]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag2,pos_b)] = tag1; + h_n_existing_bonds.data[tag2]++; + + // END DynamicBondUpdater::AddtoExistingBonds + + } + } - // END DynamicBondUpdater::AddtoExistingBonds - } - } } /*! \param tag1 First particle tag in the pair @@ -328,24 +334,21 @@ void DynamicBondUpdater::calculateExistingBonds() */ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) { - { - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::read); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::read); - unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; - - for (unsigned int i = 0; i < n_existing_bonds; i++) - { - if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1, i)] == tag2) - return true; - } - return false; - } + { + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::read); + unsigned int n_existing_bonds = h_n_existing_bonds.data[tag1]; + + for (unsigned int i = 0; i < n_existing_bonds; i++) + { + if (h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag1,i)] == tag2) + return true; + } + return false; + } } + // @mphoward: This is the function that I originally had that doesn't work anymore due to // the changes in hoomd (GPUArray and GPUVector). @@ -353,568 +356,517 @@ bool DynamicBondUpdater::isExistingBond(unsigned int tag1, unsigned int tag2) \param tag2 Second particle tag in the pair adds a bond between the tag1 and tag2 to the existing bonds list */ -void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a, unsigned int tag_b) +void DynamicBondUpdater::AddtoExistingBonds(unsigned int tag_a,unsigned int tag_b) { - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag_a <= m_pdata->getMaximumTag()); - assert(tag_b <= m_pdata->getMaximumTag()); - bool overflowed = false; + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_a <= m_pdata->getMaximumTag()); + assert(tag_b <= m_pdata->getMaximumTag()); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::readwrite); + bool overflowed = false; - // resize the list if necessary - if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag_b] == m_existing_bonds_list_indexer.getH()) - overflowed = true; + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + + + // resize the list if necessary + if (h_n_existing_bonds.data[tag_a] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag_b] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - if (overflowed) - resizeExistingBondList(); + if (overflowed) resizeExistingBondList(); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::readwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag_a]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_a, pos_a)] = tag_b; - h_n_existing_bonds.data[tag_a]++; + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_a]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_a,pos_a)] = tag_b; + h_n_existing_bonds.data[tag_a]++; - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag_b]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b, pos_b)] = tag_a; - h_n_existing_bonds.data[tag_b]++; + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_b]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_b,pos_b)] = tag_a; + h_n_existing_bonds.data[tag_b]++; + + // END DynamicBondUpdater::AddtoExistingBonds - // END DynamicBondUpdater::AddtoExistingBonds } // grows the existing bonds list and its indexer when needed void DynamicBondUpdater::resizeExistingBondList() { - { - unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; - m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); - // update the indexer - m_existing_bonds_list_indexer - = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " - << new_height << " bonds per particle " << std::endl; - } + { + unsigned int new_height = m_existing_bonds_list_indexer.getH() + 1; + m_existing_bonds_list.resize(m_pdata->getRTags().size(), new_height); + // update the indexer + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), new_height); + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size existing bond list, new size " << new_height << " bonds per particle " << std::endl; + } } // grows the all possible bonds list when needed in increments of 4, inspired by the neighbor list void DynamicBondUpdater::resizePossibleBondlists() { - { - // round up to nearest multiple of 4 - m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; - m_max_bonds = m_max_bonds_overflow; - m_max_bonds_overflow = 0; - unsigned int size = m_group_1->getNumMembers() * m_max_bonds; - m_all_possible_bonds.resize(size); - m_num_all_possible_bonds = 0; - - m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " - << m_max_bonds << " bonds per particle " << std::endl; - } + { + // round up to nearest multiple of 4 + m_max_bonds_overflow = (m_max_bonds_overflow > 4) ? (m_max_bonds_overflow + 3) & ~3 : 4; + m_max_bonds = m_max_bonds_overflow; + m_max_bonds_overflow = 0; + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + m_all_possible_bonds.resize(size); + m_num_all_possible_bonds=0; + + m_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + } } + + // allocates all arrays depending on the particles and groups void DynamicBondUpdater::allocateParticleArrays() { - { - GPUArray all_possible_bonds(m_group_1->getNumMembers() * m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); - - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); - - GPUArray existing_bonds_list(m_pdata->getRTags().size(), 1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), - (unsigned int)m_existing_bonds_list.getHeight()); - - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::overwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::overwrite); - - memset(h_n_existing_bonds.data, - 0, - sizeof(unsigned int) * m_n_existing_bonds.getNumElements()); - memset(h_existing_bonds_list.data, - 0, - sizeof(unsigned int) * m_existing_bonds_list.getNumElements()); - } - calculateExistingBonds(); + { + GPUArray all_possible_bonds(m_group_1->getNumMembers() *m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + + GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + + GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), (unsigned int)m_existing_bonds_list.getHeight()); + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::overwrite); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::overwrite); + + memset(h_n_existing_bonds.data, 0, sizeof(unsigned int)*m_n_existing_bonds.getNumElements()); + memset(h_existing_bonds_list.data, 0, sizeof(unsigned int)*m_existing_bonds_list.getNumElements()); + } + calculateExistingBonds(); } -/*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist - * and m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds - * array. Then, all invalid (0,0,0), dublicated, and existing bonds are removed from - * m_all_possible_bonds. It is sorted by distance (shortest to longest) between the two particles - * in the possible bond. - */ + + +/*! This function takes the information about neighbors between group_2 and group_1 saved in m_nlist and +* m_n_neigh and copies pairs within the m_r_cut cutoff distance into the m_all_possible_bonds array. +* Then, all invalid (0,0,0), dublicated, and existing bonds are removed from m_all_possible_bonds. It +* is sorted by distance (shortest to longest) between the two particles in the possible bond. +*/ void DynamicBondUpdater::filterPossibleBonds() { - // copy data from h_n_list to h_all_possible_bonds + //copy data from h_n_list to h_all_possible_bonds + { + + ArrayHandle h_nlist(m_pair_internal_nlist->getNListArray(), access_location::host, access_mode::read); + ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), access_location::host, access_mode::read); + ArrayHandle h_n_head_list(m_pair_internal_nlist->getHeadList(), access_location::host, access_mode::read); + + + ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); + ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); + unsigned int size = m_group_1->getNumMembers()*m_max_bonds; + memset((void*) h_all_possible_bonds.data, 0, sizeof(Scalar3)*size); + + const BoxDim& box = m_pdata->getBox(); + + // Loop over all particles in group 1 + for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) + { + + unsigned int i = m_group_1->getMemberIndex(group_idx); + const unsigned int tag_i = h_tag.data[i]; + const Scalar4 postype_i = h_postype.data[i]; + + + unsigned int n_curr_bond = 0; + const Scalar r_cutsq = m_r_cut*m_r_cut; + + const unsigned int n_neigh = h_n_neigh.data[i]; + const size_t head = h_n_head_list.data[i]; + + // loop over all neighbors of this particle + for (unsigned int l=0; l h_nlist(m_pair_internal_nlist->getNListArray(), - access_location::host, - access_mode::read); - ArrayHandle h_n_neigh(m_pair_internal_nlist->getNNeighArray(), - access_location::host, - access_mode::read); - ArrayHandle h_n_head_list(m_pair_internal_nlist->getHeadList(), - access_location::host, - access_mode::read); - - ArrayHandle h_postype(m_pdata->getPositions(), - access_location::host, - access_mode::read); - ArrayHandle h_tag(m_pdata->getTags(), - access_location::host, - access_mode::read); - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, - access_location::host, - access_mode::readwrite); - unsigned int size = m_group_1->getNumMembers() * m_max_bonds; - memset((void*)h_all_possible_bonds.data, 0, sizeof(Scalar3) * size); - - const BoxDim& box = m_pdata->getBox(); - - // Loop over all particles in group 1 - for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - unsigned int i = m_group_1->getMemberIndex(group_idx); - const unsigned int tag_i = h_tag.data[i]; - const Scalar4 postype_i = h_postype.data[i]; - unsigned int n_curr_bond = 0; - const Scalar r_cutsq = m_r_cut * m_r_cut; + // get index of neighbor from neigh_list + const unsigned int j = h_nlist.data[head + l]; + + if (m_group_2->isMember(j)) + { + Scalar4 postype_j = h_postype.data[j]; + const unsigned int tag_j = h_tag.data[j]; - const unsigned int n_neigh = h_n_neigh.data[i]; - const size_t head = h_n_head_list.data[i]; + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) + - make_scalar3(postype_i.x,postype_i.y,postype_i.z); - // loop over all neighbors of this particle - for (unsigned int l = 0; l < n_neigh; ++l) + // apply periodic boundary conditions + drij = box.minImage(drij); + Scalar dr_sq = dot(drij,drij); + + if (dr_sq < r_cutsq) + { + if (n_curr_bond < m_max_bonds) + { + Scalar3 d; + if(m_groups_identical) + { + // sort the two tags in this possible bond pair if groups identical + const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; + const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; + d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); + } + else { - // get index of neighbor from neigh_list - const unsigned int j = h_nlist.data[head + l]; - - if (m_group_2->isMember(j)) - { - Scalar4 postype_j = h_postype.data[j]; - const unsigned int tag_j = h_tag.data[j]; - - Scalar3 drij = make_scalar3(postype_j.x, postype_j.y, postype_j.z) - - make_scalar3(postype_i.x, postype_i.y, postype_i.z); - - // apply periodic boundary conditions - drij = box.minImage(drij); - Scalar dr_sq = dot(drij, drij); - - if (dr_sq < r_cutsq) - { - if (n_curr_bond < m_max_bonds) - { - Scalar3 d; - if (m_groups_identical) - { - // sort the two tags in this possible bond pair if groups identical - const unsigned int tag_a = tag_j > tag_i ? tag_i : tag_j; - const unsigned int tag_b = tag_j > tag_i ? tag_j : tag_i; - d = make_scalar3(__int_as_scalar(tag_a), - __int_as_scalar(tag_b), - dr_sq); - } - else - { - d = make_scalar3(__int_as_scalar(tag_i), - __int_as_scalar(tag_j), - dr_sq); - } - h_all_possible_bonds.data[group_idx * m_max_bonds + n_curr_bond] = d; - } - ++n_curr_bond; - } - } + d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); } + h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; + } + ++n_curr_bond; } + } + } + } - // now sort and select down - m_num_all_possible_bonds = 0; - // remove a possible bond if it already exists. It also removes zeros, e.g. - // (0,0,0), which fill the unused spots in the array. - auto last2 = std::remove_if(h_all_possible_bonds.data, - h_all_possible_bonds.data + size, - [this](Scalar3 i) { return CheckisExistingLegalBond(i); }); + //now sort and select down + m_num_all_possible_bonds = 0; - m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data, last2); + // remove a possible bond if it already exists. It also removes zeros, e.g. + // (0,0,0), which fill the unused spots in the array. + auto last2 = std::remove_if(h_all_possible_bonds.data, + h_all_possible_bonds.data + size, + [this](Scalar3 i) {return CheckisExistingLegalBond(i); }); - // then sort array by distance between particles in the found possible bond pairs - // performance is better if remove_if happens before sort - std::sort(h_all_possible_bonds.data, - h_all_possible_bonds.data + m_num_all_possible_bonds, - SortBonds); + m_num_all_possible_bonds = (unsigned int) std::distance(h_all_possible_bonds.data,last2); - // now make sure each possible bond is in the array only once by comparing tags - auto last = std::unique(h_all_possible_bonds.data, - h_all_possible_bonds.data + m_num_all_possible_bonds, - CompareBonds); - m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data, last); + // then sort array by distance between particles in the found possible bond pairs + // performance is better if remove_if happens before sort + std::sort(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, SortBonds); - // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] - // should contain only unique entries of possible bonds which are not yet formed. - } - } + // now make sure each possible bond is in the array only once by comparing tags + auto last = std::unique(h_all_possible_bonds.data, h_all_possible_bonds.data + m_num_all_possible_bonds, CompareBonds); + m_num_all_possible_bonds = (unsigned int)std::distance(h_all_possible_bonds.data,last); + + + // at this point, the sub-array: h_all_possible_bonds[0,m_num_all_possible_bonds] + // should contain only unique entries of possible bonds which are not yet formed. + } + } /*! This function actually creates the bonds by looping over the entries in m_all_possible_bonds - * and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as - * to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list - * should be updated. - * - * Note: this function is very hard to parallelize on the GPU since we need to go through the bonds - * sequentially to prevent forming too many bonds in one step. Have not found a good way of doing - * this on the GPU. - */ +* and adding them to the system (m_bond_data->addBondedGroup), to the existing bonds, as well as +* to the neighbor list used by the rest of the simulation if the exclusions of that neighbor list +* should be updated. +* +* Note: this function is very hard to parallelize on the GPU since we need to go through the bonds sequentially +* to prevent forming too many bonds in one step. Have not found a good way of doing this on the GPU. +*/ void DynamicBondUpdater::makeBonds(uint64_t timestep) - { - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, - access_location::host, - access_mode::read); + { + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::read); - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, - access_location::host, - access_mode::readwrite); + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); // we need to count how many bonds are in the h_all_possible_bonds array for a given tag - // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases - // the count in h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping - // should be needed. This also makes it very difficult to do on the GPU. + // so that we don't end up forming too many bonds in one step. "AddtoExistingBonds" increases the count in + // h_n_existing_bonds in the for loop below as we go, so no extra bookkeeping should be needed. + // This also makes it very difficult to do on the GPU. ArrayHandle h_rtag(m_pdata->getRTags(), access_location::host, access_mode::read); - // todo: can this for loop be simplified/parallelized? + //todo: can this for loop be simplified/parallelized? for (unsigned int i = 0; i < m_num_all_possible_bonds; i++) + { + Scalar3 d = h_all_possible_bonds.data[i]; + + unsigned int tag_i = __scalar_as_int(d.x); + unsigned int tag_j = __scalar_as_int(d.y); + + //todo: put in other external criteria here, e.g. max number of bonds possible in one step, etc. + //todo: randomize which bonds are formed or keep them ordered by their distances ? + //todo: would it be faster/better to create the rng outside of the loop? + hoomd::RandomGenerator rng( + hoomd::Seed(hoomd::azplugins::detail::RNGIdentifier::DynamicBondUpdater, + timestep, + m_seed), + hoomd::Counter()); + + hoomd::UniformDistribution uniform(0, 1); + const Scalar random = uniform(rng); + + if ((m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i]) && + (m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j]) && + (random < m_probability)) { - Scalar3 d = h_all_possible_bonds.data[i]; - - unsigned int tag_i = __scalar_as_int(d.x); - unsigned int tag_j = __scalar_as_int(d.y); - - // todo: put in other external criteria here, e.g. max number of bonds possible in one step, - // etc. todo: randomize which bonds are formed or keep them ordered by their distances ? - // todo: would it be faster/better to create the rng outside of the loop? - hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::azplugins::detail::RNGIdentifier::DynamicBondUpdater, - timestep, - m_seed), - hoomd::Counter()); + m_bond_data->addBondedGroup(Bond(m_bond_type,tag_i,tag_j)); + //AddtoExistingBonds(tag_i,tag_j); - hoomd::UniformDistribution uniform(0, 1); - const Scalar random = uniform(rng); + // @mphoward: The next section is the one that needed to be dublicated to accomodate + // the changes in hoomd (GPUArray and GPUVector) - if ((m_max_bonds_group_1 > h_n_existing_bonds.data[tag_i]) - && (m_max_bonds_group_2 > h_n_existing_bonds.data[tag_j]) && (random < m_probability)) - { - m_bond_data->addBondedGroup(Bond(m_bond_type, tag_i, tag_j)); - // AddtoExistingBonds(tag_i,tag_j); - - // @mphoward: The next section is the one that needed to be dublicated to accomodate - // the changes in hoomd (GPUArray and GPUVector) + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_i <= m_pdata->getMaximumTag()); + assert(tag_j <= m_pdata->getMaximumTag()); - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag_i <= m_pdata->getMaximumTag()); - assert(tag_j <= m_pdata->getMaximumTag()); + bool overflowed = false; - bool overflowed = false; + // resize the list if necessary + if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) + overflowed = true; + if (h_n_existing_bonds.data[tag_j] == m_existing_bonds_list_indexer.getH()) + overflowed = true; - // resize the list if necessary - if (h_n_existing_bonds.data[tag_i] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - if (h_n_existing_bonds.data[tag_j] == m_existing_bonds_list_indexer.getH()) - overflowed = true; + if (overflowed) resizeExistingBondList(); - if (overflowed) - resizeExistingBondList(); + ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, - access_location::host, - access_mode::readwrite); + // add tag_b to tag_a's existing bonds list + unsigned int pos_a = h_n_existing_bonds.data[tag_i]; + assert(pos_a < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_i,pos_a)] = tag_j; + h_n_existing_bonds.data[tag_i]++; - // add tag_b to tag_a's existing bonds list - unsigned int pos_a = h_n_existing_bonds.data[tag_i]; - assert(pos_a < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_i, pos_a)] = tag_j; - h_n_existing_bonds.data[tag_i]++; + // add tag_a to tag_b's existing bonds list + unsigned int pos_b = h_n_existing_bonds.data[tag_j]; + assert(pos_b < m_existing_bonds_list_indexer.getH()); + h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j,pos_b)] = tag_i; + h_n_existing_bonds.data[tag_j]++; - // add tag_a to tag_b's existing bonds list - unsigned int pos_b = h_n_existing_bonds.data[tag_j]; - assert(pos_b < m_existing_bonds_list_indexer.getH()); - h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j, pos_b)] = tag_i; - h_n_existing_bonds.data[tag_j]++; + // END DynamicBondUpdater::AddtoExistingBonds - // END DynamicBondUpdater::AddtoExistingBonds - if (m_pair_nlist_exclusions_set) - { - m_pair_nlist->addExclusion(tag_i, tag_j); - m_pair_internal_nlist->addExclusion(tag_i, tag_j); - } + if (m_pair_nlist_exclusions_set) + { + m_pair_nlist -> addExclusion(tag_i,tag_j); + m_pair_internal_nlist -> addExclusion(tag_i,tag_j); } } + } + } + + /*! - * Check that the largest neighbor search radius is not bigger than twice the shortest box size. - * Raises an error if this condition is not met. - */ +* Check that the largest neighbor search radius is not bigger than twice the shortest box size. +* Raises an error if this condition is not met. +*/ void DynamicBondUpdater::checkBoxSize() { - const BoxDim& box = m_pdata->getBox(); - const uchar3 periodic = box.getPeriodic(); - - // check that rcut fits in the box - Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); - Scalar rmax = m_r_cut; - - if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) - || (periodic.y && nearest_plane_distance.y <= rmax * 2.0) - || (m_sysdef->getNDimensions() == 3 && periodic.z - && nearest_plane_distance.z <= rmax * 2.0)) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles " - "would be interacting with themselves." - << std::endl; + const BoxDim& box = m_pdata->getBox(); + const uchar3 periodic = box.getPeriodic(); + + // check that rcut fits in the box + Scalar3 nearest_plane_distance = box.getNearestPlaneDistance(); + Scalar rmax = m_r_cut; + + if ((periodic.x && nearest_plane_distance.x <= rmax * 2.0) || + (periodic.y && nearest_plane_distance.y <= rmax * 2.0) || + (m_sysdef->getNDimensions() == 3 && periodic.z && nearest_plane_distance.z <= rmax * 2.0)) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Simulation box is too small! Particles would be interacting with themselves." << std::endl; throw std::runtime_error("Error in DynamicBondUpdater, Simulation box too small."); - } + } } /*! Calculates if the two groups have overlap or not. Returns an error if partial - * overlap is detected. - */ +* overlap is detected. +*/ void DynamicBondUpdater::setGroupOverlap() { - if (m_group_1->getNumMembers() == 0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: First group group_1 appears to be " - "empty. No bonds will be formed. " - << std::endl; - } - - if (m_group_2->getNumMembers() == 0) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be " - "empty. No bonds will be formed. " - << std::endl; - } - { - // check if the two groups are either identical or have no overlap - ArrayHandle h_index_group_1(m_group_1->getIndexArray(), - access_location::host, - access_mode::read); - - // count particles which are in both groups. Should be either zero of them or all of them. - unsigned int overlap = 0; - for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) - { - unsigned int idx = h_index_group_1.data[i]; - if (m_group_2->isMember(idx)) - overlap++; - } - - if (overlap > 0 && overlap != m_group_1->getNumMembers()) - { - m_exec_conf->msg->error() - << "DynamicBondUpdater: group 1 and group 2 have " << overlap - << " overlaps. Partially overlapping groups are not implemented." << std::endl; - throw std::runtime_error("Partial overlapping groups in DynamicBondUpdater"); - } - - if (overlap == m_group_1->getNumMembers()) - { - m_groups_identical = true; - } - } + if(m_group_1->getNumMembers()==0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: First group group_1 appears to be empty. No bonds will be formed. " << std::endl; + } + + if(m_group_2->getNumMembers()==0) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Second group group_2 appears to be empty. No bonds will be formed. " << std::endl; + } + { + //check if the two groups are either identical or have no overlap + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); + + // count particles which are in both groups. Should be either zero of them or all of them. + unsigned int overlap = 0; + for (unsigned int i=0; igetNumMembers(); ++i) + { + unsigned int idx = h_index_group_1.data[i]; + if (m_group_2->isMember(idx)) + overlap++; + } + + if( overlap>0 && overlap != m_group_1->getNumMembers()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: group 1 and group 2 have " << overlap << " overlaps. Partially overlapping groups are not implemented." << std::endl; + throw std::runtime_error("Partial overlapping groups in DynamicBondUpdater"); + } + + if(overlap==m_group_1->getNumMembers()) + { + m_groups_identical=true; + } + } } /*! Sets cutoffs based on types present in the two groups to save some performance from - * the neighbor list. - */ +* the neighbor list. +*/ void DynamicBondUpdater::setCutoffs() { - { - // set all rcuts to zero first, then only set the one between group1 and group 2 particle - // types to be m_r_cut - unsigned int NTypes = m_pdata->getNTypes(); - for (unsigned int i = 0; i < NTypes; ++i) - { - for (unsigned int j = 0; j < NTypes; ++j) - { - m_pair_internal_nlist->setRcut(i, j, 0); - } - } - - ArrayHandle h_pos(m_pdata->getPositions(), - access_location::host, - access_mode::read); - - ArrayHandle h_index_group_1(m_group_1->getIndexArray(), - access_location::host, - access_mode::read); - - // finding all types in group 1 - std::set types_group_1; - for (unsigned int i = 0; i < m_group_1->getNumMembers(); ++i) - { - unsigned int idx = h_index_group_1.data[i]; - Scalar4 type = h_pos.data[idx]; - types_group_1.insert(__scalar_as_int(type.w)); - } - - std::set types_group_2; - if (m_groups_identical == false) - { - ArrayHandle h_index_group_2(m_group_2->getIndexArray(), - access_location::host, - access_mode::read); + { - // finding all types in group 2 - for (unsigned int i = 0; i < m_group_2->getNumMembers(); ++i) - { - unsigned int idx = h_index_group_2.data[i]; - Scalar4 type = h_pos.data[idx]; - types_group_2.insert(__scalar_as_int(type.w)); - } - } - else - { - types_group_2 = types_group_1; - } + // set all rcuts to zero first, then only set the one between group1 and group 2 particle types to be m_r_cut + unsigned int NTypes = m_pdata -> getNTypes(); + for (unsigned int i=0; i< NTypes; ++i) + { + for (unsigned int j=0; j< NTypes; ++j) + { + m_pair_internal_nlist->setRcut(i,j,0); + } + } + + ArrayHandle h_pos(m_pdata->getPositions(), + access_location::host, + access_mode::read); + + + ArrayHandle h_index_group_1(m_group_1->getIndexArray(), access_location::host, access_mode::read); + + // finding all types in group 1 + std::set types_group_1; + for (unsigned int i=0; igetNumMembers(); ++i) + { + unsigned int idx = h_index_group_1.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_1.insert(__scalar_as_int(type.w)); + } + + std::set types_group_2; + if (m_groups_identical == false) + { + ArrayHandle h_index_group_2(m_group_2->getIndexArray(), access_location::host, access_mode::read); + + // finding all types in group 2 + for (unsigned int i=0; igetNumMembers(); ++i) + { + unsigned int idx = h_index_group_2.data[i]; + Scalar4 type = h_pos.data[idx]; + types_group_2.insert(__scalar_as_int(type.w)); + } + } + else + { + types_group_2 = types_group_1; + } - // looping over types in group 1 and 2 to set the cutoff to m_r_cut - for (const auto& element_1 : types_group_1) - { - for (const auto& element_2 : types_group_2) - { - m_pair_internal_nlist->setRcut(element_1, element_2, m_r_cut); - m_pair_internal_nlist->setRcut(element_2, element_1, m_r_cut); - } - } + // looping over types in group 1 and 2 to set the cutoff to m_r_cut + for (const auto& element_1 : types_group_1) + { + for (const auto& element_2 : types_group_2) + { + m_pair_internal_nlist->setRcut(element_1,element_2,m_r_cut); + m_pair_internal_nlist->setRcut(element_2,element_1,m_r_cut); } + } + } + } + // Check that the given cutoff value is valid void DynamicBondUpdater::checkRcut() { - if (m_r_cut <= 0.0) - { - m_exec_conf->msg->error() - << "DynamicBondUpdater: Requested cutoff distance is less than or equal to zero" - << std::endl; + if (m_r_cut <= 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested cutoff distance is less than or equal to zero" << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - checkBoxSize(); + } + checkBoxSize(); } void DynamicBondUpdater::checkProbability() - { - if (m_probability < 0.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is less than zero" - << std::endl; +{ + if (m_probability < 0.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is less than zero" << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - else if (m_probability > 1.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is larger than one" - << std::endl; + } + else if (m_probability > 1.0) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Requested probability is larger than one" << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - } + } + +} void DynamicBondUpdater::checkMaxBondsGroup() - { - if (m_max_bonds_group_1 < 0.0 or m_max_bonds_group_2 < 0.0) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: Max number of bonds that groups can form " - "is negative. Check parameters." - << std::endl; +{ + if (m_max_bonds_group_1 < 0.0 or m_max_bonds_group_2 < 0.0 ) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: Max number of bonds that groups can form is negative. Check parameters." << std::endl; throw std::runtime_error("Error initializing DynamicBondUpdater"); - } - else if (m_max_bonds_group_1 > 10 or m_max_bonds_group_2 > 10) - { - m_exec_conf->msg->warning() << "DynamicBondUpdater: Requested number of bonds that can " - "form is very large. This can lead to performance issues." - << std::endl; - } - } + } + else if (m_max_bonds_group_1 > 10 or m_max_bonds_group_2 > 10 ) + { + m_exec_conf->msg->warning() << "DynamicBondUpdater: Requested number of bonds that can form is very large. This can lead to performance issues." << std::endl; + } + +} // Check that the given bond type is valid void DynamicBondUpdater::checkBondType() { - if (m_bond_type >= m_bond_data->getNTypes()) - { - m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type - << " is not a valid bond type." << std::endl; + if (m_bond_type >= m_bond_data -> getNTypes()) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: bond type id " << m_bond_type << " is not a valid bond type." << std::endl; throw std::runtime_error("Invalid bond type for DynamicBondUpdater"); - } + } } + namespace detail - { +{ /*! - * \param m Python module to export to - */ +* \param m Python module to export to +*/ void export_DynamicBondUpdater(pybind11::module& m) { - pybind11::class_>( - m, - "DynamicBondUpdater") - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t>()) - .def(pybind11::init, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr, - uint16_t, - Scalar, - Scalar, - unsigned int, - unsigned int, - unsigned int>()) - .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) - .def_property("probability", - &DynamicBondUpdater::getProbability, - &DynamicBondUpdater::setProbability) - .def_property("bond_type", - &DynamicBondUpdater::getBondType, - &DynamicBondUpdater::setBondType) - .def_property("max_bonds_group_1", - &DynamicBondUpdater::getMaxBondsGroup1, - &DynamicBondUpdater::setMaxBondsGroup1) - .def_property("max_bonds_group_2", - &DynamicBondUpdater::getMaxBondsGroup2, - &DynamicBondUpdater::setMaxBondsGroup2); + + pybind11::class_< DynamicBondUpdater, Updater,std::shared_ptr>( + m, + "DynamicBondUpdater") + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t>()) + .def(pybind11::init, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + uint16_t, + Scalar, + Scalar, + unsigned int, + unsigned int, + unsigned int>()) + .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) + .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) + .def_property("bond_type",&DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) + .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) + .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } - } // end namespace detail +} // end namespace detail - } // end namespace azplugins +} // end namespace azplugins - } // end namespace hoomd +} // end namespace hoomd \ No newline at end of file