From 26d43f8641578723bbf7f3d42a360ed0790d8dcc Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 10 Jun 2020 11:51:28 -0500 Subject: [PATCH 01/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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 From 30ab6583b365fae8cd531f924e64648fe0c86603 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 4 Jun 2026 12:30:21 -0500 Subject: [PATCH 46/56] fix segfault issue --- src/DynamicBondUpdater.cc | 242 ++++++++++++++++++-------------------- src/DynamicBondUpdater.h | 3 +- 2 files changed, 114 insertions(+), 131 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 0f463c98..57d9a471 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -135,8 +135,7 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - //Scalar test = m_pair_internal_nlist->getRCut(pybind11::make_tuple('A','A')); - + std::cout<< " DynamicBondUpdater::update "<< timestep << 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; @@ -158,29 +157,30 @@ void DynamicBondUpdater::update(uint64_t timestep) m_max_N_changed = false; } - // rebuild the list of possible bonds until there is no overflow - bool overflowed = false; + { // in its own scope such that the neighbor list can be accsessed in filterPossibleBonds() - do - { - m_pair_internal_nlist->compute(timestep); + // rebuild the list of possible bonds to the size of the maximum number of neighbors + // any particle currently has + 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++) - { + //find maximum number of neighbors any particle in group one has + 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 (n_neigh > m_max_bonds_overflow) + m_max_bonds_overflow = n_neigh; + } + //if more neighbors than m_max_bonds, resize the list that saves the possible + // bonds for all particles + std::cout<< "in DynamicBondUpdater::update max bonds "<< m_max_bonds <<" "<< m_max_bonds_overflow << " resize? "<< (m_max_bonds > m_max_bonds_overflow) < 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()); - } - { + // reset exisitng bond lists to zero + m_n_existing_bonds.zeroFill(); + m_existing_bonds_list.zeroFill(); + + std::cout << "DynamicBondUpdater::calculateExistingBonds "< h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); 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 @@ -285,11 +282,6 @@ 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 assert(tag1 <= m_pdata->getMaximumTag()); assert(tag2 <= m_pdata->getMaximumTag()); @@ -297,8 +289,7 @@ void DynamicBondUpdater::calculateExistingBonds() bool overflowed = false; ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); - - + std::cout<< " in calculating existing bonds "<< h_n_existing_bonds.data[tag1] << " "<< h_n_existing_bonds.data[tag2] << std::endl; // resize the list if necessary if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) overflowed = true; @@ -307,8 +298,8 @@ void DynamicBondUpdater::calculateExistingBonds() if (overflowed) resizeExistingBondList(); - ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - + { // explicit scoping such that resizeExistingBondList can resize the m_existing_bonds_list array before this + 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()); @@ -320,12 +311,20 @@ void DynamicBondUpdater::calculateExistingBonds() 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 + } } + + ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + + std::cout << "in DynamicBondUpdater::calculateExistingBonds "<getMaxN(); i++) + { + std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << (unsigned int)h_n_existing_bonds.data[i] << " "<< __int_as_scalar(h_n_existing_bonds.data[i]) << " " << __scalar_as_int(h_n_existing_bonds.data[i]) < 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]; + // std::cout << "in isExistingBond "<getMaxN(); i++) + // { + // std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << (unsigned int)h_n_existing_bonds.data[i] << " "<< __int_as_scalar(h_n_existing_bonds.data[i]) << " " << __scalar_as_int(h_n_existing_bonds.data[i]) <getMaximumTag()); - assert(tag_b <= m_pdata->getMaximumTag()); - - bool overflowed = false; - - 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(); - - 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_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); + m_existing_bonds_list.resize(m_pdata->getMaxN(), 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() { - { + std::cout<< "in DynamicBondUpdater::resizePossibleBondlist "<< m_max_bonds << " "<< 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; + std::cout<< "in DynamicBondUpdater::resizePossibleBondlist before resize "<< size <msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; - } + } @@ -427,22 +391,24 @@ 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); + std::cout<< " DynamicBondUpdater::allocateParticleArrays "<< std::endl; + { // explicit scoping so that calculateExistingBonds can accsess the arrays after + GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); + m_all_possible_bonds.zeroFill(); - GPUArray n_existing_bonds(m_pdata->getRTags().size(), m_exec_conf); + GPUArray n_existing_bonds(m_pdata->getMaxN(), m_exec_conf); m_n_existing_bonds.swap(n_existing_bonds); + m_n_existing_bonds.zeroFill(); - GPUArray existing_bonds_list(m_pdata->getRTags().size(),1, m_exec_conf); + GPUArray existing_bonds_list(m_pdata->getMaxN(),1, m_exec_conf); m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list.zeroFill(); + 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); + std::cout << " length existing bond array "<< m_pdata->getMaxN()<< " Group1 size "<< m_group_1->getNumMembers() << std::endl; - 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(); } @@ -456,22 +422,22 @@ void DynamicBondUpdater::allocateParticleArrays() */ void DynamicBondUpdater::filterPossibleBonds() { - //copy data from h_n_list to h_all_possible_bonds - { + std::cout<< " DynamicBondUpdater::filterPossibleBonds "<< std::endl; + //copy data from neighbor 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); + m_all_possible_bonds.zeroFill(); const BoxDim& box = m_pdata->getBox(); + // std::cout<< " DynamicBondUpdater::filterPossibleBonds before h_all_possible_bonds handle"<< std::endl; + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); + // std::cout<< " DynamicBondUpdater::filterPossibleBonds after h_all_possible_bonds handle"<< std::endl; // Loop over all particles in group 1 for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) @@ -480,26 +446,31 @@ void DynamicBondUpdater::filterPossibleBonds() 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 typei = __scalar_as_int(postype_i.w); 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 = (unsigned int)h_n_neigh.data[i]; const size_t head = h_n_head_list.data[i]; - + //std::cout << "Particle group idx "<< group_idx << " idx "<< i << " tag "<< tag_i << " type "<< typei << std::endl; + //std::cout << "Neigh n_neigh "<< n_neigh << std::endl; // 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]; - + //Scalar4 postype_j = h_postype.data[j]; + // get tag from index + //const unsigned int tag_j = h_tag.data[j]; + //std::cout << "Particle tag_i "<< tag_i << " index "<< i << " neighbor tag_j "<< tag_j << " index "<< j << std::endl; Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - make_scalar3(postype_i.x,postype_i.y,postype_i.z); @@ -517,13 +488,16 @@ void DynamicBondUpdater::filterPossibleBonds() // 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; + std::cout << "Possible bond "<< group_idx*m_max_bonds + n_curr_bond << " " << group_idx << " " << m_max_bonds << " " << n_curr_bond << " tag_i " << __scalar_as_int(d.x) << " tag_j " << __scalar_as_int(d.y) << " "<< d.z <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, @@ -551,11 +527,12 @@ void DynamicBondUpdater::filterPossibleBonds() 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 @@ -567,25 +544,31 @@ void DynamicBondUpdater::filterPossibleBonds() */ void DynamicBondUpdater::makeBonds(uint64_t timestep) { - + std::cout<< " DynamicBondUpdater::makeBonds "<< 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::readwrite); + // std::cout << "in makeBonds h_n_existing_bonds list"<getMaxN(); i++) + // { + // std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << h_n_existing_bonds.data[i] < 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]; - unsigned int tag_i = __scalar_as_int(d.x); - unsigned int tag_j = __scalar_as_int(d.y); + unsigned int tag_i = (unsigned int)__scalar_as_int(d.x); + unsigned int tag_j = (unsigned int)__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 ? @@ -604,10 +587,6 @@ 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); - - // @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()); @@ -623,8 +602,11 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) if (overflowed) resizeExistingBondList(); + {// explicit scoping such that resizeExistingBondList can resize the m_existing_bonds_list array before this ArrayHandle h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); + std::cout<< " in make bonds "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << std::endl; + // 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()); @@ -637,6 +619,9 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j,pos_b)] = tag_i; h_n_existing_bonds.data[tag_j]++; + std::cout<< " in make bonds "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << std::endl; + + } // END DynamicBondUpdater::AddtoExistingBonds @@ -647,9 +632,8 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) } } } - } - + } /*! diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index caa376dd..dc45dbb6 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -192,11 +192,10 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater 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(); From be581993daa5ca5f584a79f4ce85a52a97774592 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 4 Jun 2026 14:53:35 -0500 Subject: [PATCH 47/56] removed cout statements --- src/DynamicBondUpdater.cc | 84 +++++++++------------------------------ src/DynamicBondUpdater.h | 2 + src/update.py | 5 ++- 3 files changed, 25 insertions(+), 66 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 57d9a471..2fe7733b 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -31,6 +31,7 @@ namespace azplugins DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr pair_nlist, + bool update_exclusions, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) @@ -45,7 +46,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_bonds_group_2(0), m_seed(seed), m_pair_nlist(pair_nlist), - m_pair_nlist_exclusions_set(true), + m_pair_nlist_exclusions_set(update_exclusions), m_box_changed(true), m_max_N_changed(true) { @@ -75,6 +76,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr pair_nlist, + bool update_exclusions, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed, @@ -95,7 +97,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, m_max_bonds_group_2(max_bonds_group_2), m_seed(seed), m_pair_nlist(pair_nlist), - m_pair_nlist_exclusions_set(true), + m_pair_nlist_exclusions_set(update_exclusions), m_box_changed(true), m_max_N_changed(true) { @@ -135,7 +137,6 @@ DynamicBondUpdater::~DynamicBondUpdater() */ void DynamicBondUpdater::update(uint64_t timestep) { - std::cout<< " DynamicBondUpdater::update "<< timestep << 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; @@ -172,9 +173,7 @@ void DynamicBondUpdater::update(uint64_t timestep) if (n_neigh > m_max_bonds_overflow) m_max_bonds_overflow = n_neigh; } - //if more neighbors than m_max_bonds, resize the list that saves the possible - // bonds for all particles - std::cout<< "in DynamicBondUpdater::update max bonds "<< m_max_bonds <<" "<< m_max_bonds_overflow << " resize? "<< (m_max_bonds > m_max_bonds_overflow) < h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); 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 @@ -282,14 +277,12 @@ void DynamicBondUpdater::calculateExistingBonds() unsigned int tag1 = bond.tag[0]; unsigned int tag2 = bond.tag[1]; - // BEGIN DynamicBondUpdater::AddtoExistingBonds assert(tag1 <= m_pdata->getMaximumTag()); assert(tag2 <= m_pdata->getMaximumTag()); bool overflowed = false; ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); - std::cout<< " in calculating existing bonds "<< h_n_existing_bonds.data[tag1] << " "<< h_n_existing_bonds.data[tag2] << std::endl; // resize the list if necessary if (h_n_existing_bonds.data[tag1] == m_existing_bonds_list_indexer.getH()) overflowed = true; @@ -311,20 +304,11 @@ void DynamicBondUpdater::calculateExistingBonds() 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 - } - - } - ArrayHandle h_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::read); + } - std::cout << "in DynamicBondUpdater::calculateExistingBonds "<getMaxN(); i++) - { - std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << (unsigned int)h_n_existing_bonds.data[i] << " "<< __int_as_scalar(h_n_existing_bonds.data[i]) << " " << __scalar_as_int(h_n_existing_bonds.data[i]) < 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); - // std::cout << "in isExistingBond "<getMaxN(); i++) - // { - // std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << (unsigned int)h_n_existing_bonds.data[i] << " "<< __int_as_scalar(h_n_existing_bonds.data[i]) << " " << __scalar_as_int(h_n_existing_bonds.data[i]) < 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; - std::cout<< "in DynamicBondUpdater::resizePossibleBondlist before resize "<< size <msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; @@ -391,7 +365,7 @@ void DynamicBondUpdater::resizePossibleBondlists() // allocates all arrays depending on the particles and groups void DynamicBondUpdater::allocateParticleArrays() { - std::cout<< " DynamicBondUpdater::allocateParticleArrays "<< std::endl; + { // explicit scoping so that calculateExistingBonds can accsess the arrays after GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); m_all_possible_bonds.swap(all_possible_bonds); @@ -406,9 +380,6 @@ void DynamicBondUpdater::allocateParticleArrays() m_existing_bonds_list.zeroFill(); m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), (unsigned int)m_existing_bonds_list.getHeight()); - - std::cout << " length existing bond array "<< m_pdata->getMaxN()<< " Group1 size "<< m_group_1->getNumMembers() << std::endl; - } calculateExistingBonds(); } @@ -422,7 +393,6 @@ void DynamicBondUpdater::allocateParticleArrays() */ void DynamicBondUpdater::filterPossibleBonds() { - std::cout<< " DynamicBondUpdater::filterPossibleBonds "<< std::endl; //copy data from neighbor list to h_all_possible_bonds ArrayHandle h_nlist(m_pair_internal_nlist->getNListArray(), access_location::host, access_mode::read); @@ -432,12 +402,12 @@ void DynamicBondUpdater::filterPossibleBonds() ArrayHandle h_postype(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); + // reset the array of possible bonds so it can be re-populated below m_all_possible_bonds.zeroFill(); const BoxDim& box = m_pdata->getBox(); - // std::cout<< " DynamicBondUpdater::filterPossibleBonds before h_all_possible_bonds handle"<< std::endl; + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); - // std::cout<< " DynamicBondUpdater::filterPossibleBonds after h_all_possible_bonds handle"<< std::endl; // Loop over all particles in group 1 for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) @@ -446,15 +416,13 @@ void DynamicBondUpdater::filterPossibleBonds() 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 typei = __scalar_as_int(postype_i.w); unsigned int n_curr_bond = 0; const Scalar r_cutsq = m_r_cut*m_r_cut; const unsigned int n_neigh = (unsigned int)h_n_neigh.data[i]; const size_t head = h_n_head_list.data[i]; - //std::cout << "Particle group idx "<< group_idx << " idx "<< i << " tag "<< tag_i << " type "<< typei << std::endl; - //std::cout << "Neigh n_neigh "<< n_neigh << std::endl; + // loop over all neighbors of this particle for (unsigned int l=0; lisMember(j)) { - //Scalar4 postype_j = h_postype.data[j]; - // get tag from index - //const unsigned int tag_j = h_tag.data[j]; - //std::cout << "Particle tag_i "<< tag_i << " index "<< i << " neighbor tag_j "<< tag_j << " index "<< j << std::endl; + Scalar3 drij = make_scalar3(postype_j.x,postype_j.y,postype_j.z) - make_scalar3(postype_i.x,postype_i.y,postype_i.z); @@ -497,7 +461,6 @@ void DynamicBondUpdater::filterPossibleBonds() } h_all_possible_bonds.data[group_idx*m_max_bonds + n_curr_bond] = d; - std::cout << "Possible bond "<< group_idx*m_max_bonds + n_curr_bond << " " << group_idx << " " << m_max_bonds << " " << n_curr_bond << " tag_i " << __scalar_as_int(d.x) << " tag_j " << __scalar_as_int(d.y) << " "<< d.z <getNumMembers()*m_max_bonds; // remove a possible bond if it already exists. It also removes zeros, e.g. @@ -544,18 +507,11 @@ void DynamicBondUpdater::filterPossibleBonds() */ void DynamicBondUpdater::makeBonds(uint64_t timestep) { - std::cout<< " DynamicBondUpdater::makeBonds "<< 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::readwrite); - // std::cout << "in makeBonds h_n_existing_bonds list"<getMaxN(); i++) - // { - // std::cout <<" i " << i << " h_n_existing_bonds.data[i] " << h_n_existing_bonds.data[i] < h_existing_bonds_list(m_existing_bonds_list, access_location::host, access_mode::readwrite); - std::cout<< " in make bonds "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << std::endl; - // 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()); @@ -619,8 +573,6 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) h_existing_bonds_list.data[m_existing_bonds_list_indexer(tag_j,pos_b)] = tag_i; h_n_existing_bonds.data[tag_j]++; - std::cout<< " in make bonds "<< h_n_existing_bonds.data[tag_i] << " "<< h_n_existing_bonds.data[tag_j] << std::endl; - } // END DynamicBondUpdater::AddtoExistingBonds @@ -632,8 +584,8 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) } } } - } - } + + } /*! @@ -829,12 +781,14 @@ void export_DynamicBondUpdater(pybind11::module& m) .def(pybind11::init, std::shared_ptr, std::shared_ptr, + bool, std::shared_ptr, std::shared_ptr, uint16_t>()) .def(pybind11::init, std::shared_ptr, std::shared_ptr, + bool, std::shared_ptr, std::shared_ptr, uint16_t, diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index dc45dbb6..c578c9ad 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -48,6 +48,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr pair_nlist, + bool update_exclusions, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed); @@ -56,6 +57,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr pair_nlist, + bool update_exclusions, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed, diff --git a/src/update.py b/src/update.py index a304c51c..1a640698 100644 --- a/src/update.py +++ b/src/update.py @@ -69,6 +69,7 @@ def __init__( nlist, group_1, group_2, + update_exclusions=True, bond_type=None, max_bonds_group_1=None, max_bonds_group_2=None, @@ -83,6 +84,7 @@ def __init__( 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), + update_exclusions=OnlyTypes(bool,allow_none=True), 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), @@ -96,6 +98,7 @@ def __init__( nlist=nlist, group_1=group_1, group_2=group_2, + update_exclusions=update_exclusions, max_bonds_group_1=max_bonds_group_1, max_bonds_group_2=max_bonds_group_2, bond_type=bond_type, @@ -105,7 +108,6 @@ def __init__( ) 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): @@ -182,6 +184,7 @@ def _attach_hook(self): sim.state._cpp_sys_def, self.trigger, self.nlist._cpp_obj, + self.update_exclusions, group_1, group_2, self.seed, From f4e0ad398dfe3430506e4547b9e951638eb37c6e Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Sat, 6 Jun 2026 10:41:33 -0500 Subject: [PATCH 48/56] make argument list cleaner --- src/DynamicBondUpdater.cc | 27 +++---- src/DynamicBondUpdater.h | 18 +++-- src/pytest/test_dynamic_bond.py | 24 ++---- src/update.py | 139 ++++++++++++++++++-------------- 4 files changed, 109 insertions(+), 99 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index 2fe7733b..cda5769f 100644 --- a/src/DynamicBondUpdater.cc +++ b/src/DynamicBondUpdater.cc @@ -30,8 +30,7 @@ namespace azplugins */ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, - std::shared_ptr pair_nlist, - bool update_exclusions, + // std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed) @@ -45,8 +44,8 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, 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(update_exclusions), + // m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(false), m_box_changed(true), m_max_N_changed(true) { @@ -75,8 +74,7 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, - std::shared_ptr pair_nlist, - bool update_exclusions, + //std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed, @@ -96,8 +94,8 @@ DynamicBondUpdater::DynamicBondUpdater(std::shared_ptr sysdef, 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(update_exclusions), + // m_pair_nlist(pair_nlist), + m_pair_nlist_exclusions_set(false), m_box_changed(true), m_max_N_changed(true) { @@ -574,14 +572,12 @@ void DynamicBondUpdater::makeBonds(uint64_t timestep) 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); } + // the internal neigh list should always get the exclusions updates to not re-find already existing bonds over and over + m_pair_internal_nlist -> addExclusion(tag_i,tag_j); } } @@ -780,15 +776,13 @@ void export_DynamicBondUpdater(pybind11::module& m) "DynamicBondUpdater") .def(pybind11::init, std::shared_ptr, - std::shared_ptr, - bool, + //std::shared_ptr, std::shared_ptr, std::shared_ptr, uint16_t>()) .def(pybind11::init, std::shared_ptr, - std::shared_ptr, - bool, + //std::shared_ptr, std::shared_ptr, std::shared_ptr, uint16_t, @@ -800,6 +794,7 @@ void export_DynamicBondUpdater(pybind11::module& m) .def_property("r_cut", &DynamicBondUpdater::getRcut, &DynamicBondUpdater::setRcut) .def_property("probability", &DynamicBondUpdater::getProbability, &DynamicBondUpdater::setProbability) .def_property("bond_type",&DynamicBondUpdater::getBondType, &DynamicBondUpdater::setBondType) + .def("setNlist",&DynamicBondUpdater::setNeighborList) .def_property("max_bonds_group_1", &DynamicBondUpdater::getMaxBondsGroup1, &DynamicBondUpdater::setMaxBondsGroup1) .def_property("max_bonds_group_2", &DynamicBondUpdater::getMaxBondsGroup2, &DynamicBondUpdater::setMaxBondsGroup2); } diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index c578c9ad..e68bf4b4 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -47,8 +47,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Simple constructor DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, - std::shared_ptr pair_nlist, - bool update_exclusions, + // std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed); @@ -56,8 +55,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! Constructor with parameters DynamicBondUpdater(std::shared_ptr sysdef, std::shared_ptr trigger, - std::shared_ptr pair_nlist, - bool update_exclusions, + // std::shared_ptr pair_nlist, std::shared_ptr group_1, std::shared_ptr group_2, uint16_t seed, @@ -125,6 +123,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater */ void setMaxBondsGroup2(unsigned int max_bonds_group_2) { + std::cout << " in setmaxbondsgroup2 "<< max_bonds_group_2< pair_nlist) + { + std::cout << "in setNeighborList "<< pair_nlist << std::endl; + m_pair_nlist = pair_nlist; + m_pair_nlist_exclusions_set = true; + } + protected: std::shared_ptr m_bond_data; //!< Bond data @@ -185,11 +191,13 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater 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 + std::shared_ptr m_pair_internal_nlist; // internal list for finding all possible bonds + + //! filter out existing and doublicate bonds from all found possible bonds virtual void filterPossibleBonds(); diff --git a/src/pytest/test_dynamic_bond.py b/src/pytest/test_dynamic_bond.py index 009bfe98..f2e1bd8d 100644 --- a/src/pytest/test_dynamic_bond.py +++ b/src/pytest/test_dynamic_bond.py @@ -15,10 +15,8 @@ def test_setters_getters(simulation_factory, one_particle_snapshot_factory): 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, + groups=[hoomd.filter.All()], + max_bonds = [0], ) assert numpy.equal(u.r_cut, 1) @@ -54,10 +52,8 @@ def test_form_bonds_same_group(simulation_factory): 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, + groups=[group], + max_bonds=[2] ) # test bond formation. All are in the same group, so bonds should be formed @@ -123,10 +119,8 @@ def test_form_bonds_same_group_priority(simulation_factory): 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, + groups=[group], + max_bonds=[2] ) # test bond formation. All are in the same group, so bonds should be formed @@ -173,10 +167,8 @@ def test_update_bond_two_groups(simulation_factory): 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, + groups=[group_1,group_2], + max_bonds=[1,2], ) # test bond formation between particle 1-2 diff --git a/src/update.py b/src/update.py index 1a640698..2a255cd0 100644 --- a/src/update.py +++ b/src/update.py @@ -53,10 +53,8 @@ class DynamicBond(hoomd.operation.Updater): 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, + groups=[hoomd.group.type(type="A"),hoomd.group.type(type="B")], + max_bonds=[2,3] ) """ @@ -66,41 +64,32 @@ class DynamicBond(hoomd.operation.Updater): def __init__( self, trigger, - nlist, - group_1, - group_2, - update_exclusions=True, - bond_type=None, - max_bonds_group_1=None, - max_bonds_group_2=None, - r_cut=None, + groups, + max_bonds, + bond_type, + nlist = None, + r_cut=1.0, seed=0, - probability=1, + probability=1.0, ): super().__init__(trigger) params = ParameterDict( - 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=False), - group_2=OnlyTypes(hoomd.filter.ParticleFilter, allow_none=False), - update_exclusions=OnlyTypes(bool,allow_none=True), - 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), + r_cut=OnlyTypes(float, allow_none=False), + nlist=OnlyTypes(hoomd.md.nlist.NeighborList, strict=True, allow_none=True), + groups=list(groups), + max_bonds =list(max_bonds), + bond_type=OnlyTypes(int, strict=True, allow_none=False), seed=OnlyTypes(int, strict=True, allow_none=False), - probability=float(probability), + probability=OnlyTypes(float, strict=False,allow_none=True), ) params.update( dict( r_cut=r_cut, nlist=nlist, - group_1=group_1, - group_2=group_2, - update_exclusions=update_exclusions, - max_bonds_group_1=max_bonds_group_1, - max_bonds_group_2=max_bonds_group_2, + groups=groups, + max_bonds=max_bonds, bond_type=bond_type, seed=seed, probability=probability, @@ -109,6 +98,52 @@ def __init__( self._param_dict.update(params) + def _parse_groups(self,vec_groups,vec_max_bonds,sim): + """Converts Groups depending if there is one or two given. + + Args: + vec_groups (Sequence[filter]): A sequence of length 2 or 1 of type ``hoomd.filter.ParticleFilter``. + vec_max_bonds(Sequence[int]): A sequence of length 2 or 1 of type ``int``. + """ + try: + l_vec = len(vec_groups) + except: + raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + if l_vec == 1: + if isinstance(vec_groups[0], hoomd.filter.ParticleFilter): + self.group_1 = sim.state._get_group(vec_groups[0]) + self.group_2 = sim.state._get_group(vec_groups[0]) + self._param_dict["group_1"] = self.group_1 + self._param_dict["group_2"] = self.group_2 + if len(vec_max_bonds) !=1: + raise ValueError("Expected array[int] of same length as `groups` for argument `max_bonds`.") + else: + self.max_bonds_group_1 = vec_max_bonds[0] + self._param_dict["max_bonds_group_1"] = vec_max_bonds[0] + self.max_bonds_group_2 = vec_max_bonds[0] + self._param_dict["max_bonds_group_2"] = vec_max_bonds[0] + else: + raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + elif l_vec == 2: + if isinstance(vec_groups[0], hoomd.filter.ParticleFilter) and\ + isinstance(vec_groups[1], hoomd.filter.ParticleFilter): + self.group_1 = sim.state._get_group(vec_groups[0]) + self.group_2 = sim.state._get_group(vec_groups[1]) + self._param_dict["group_1"] = self.group_1 + self._param_dict["group_2"] = self.group_2 + if len(vec_max_bonds) != 2: + raise ValueError("Expected array[int] of same length as `groups` for argument `max_bonds`.") + else: + self.max_bonds_group_1 = vec_max_bonds[0] + self._param_dict["max_bonds_group_1"] = vec_max_bonds[0] + self.max_bonds_group_2 = vec_max_bonds[1] + self._param_dict["max_bonds_group_2"] = vec_max_bonds[1] + else: + raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + else: + raise ValueError( + "Expected an array of one or two hoomd.filter.ParticleFilter for argument `groups`.") + @property def bond_type(self): """bond_type (int): Type of the bonds that are made by this updater.""" @@ -140,54 +175,34 @@ 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): 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 - - @property - def max_bonds_group_2(self): - """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 - def _attach_hook(self): """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: 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) - else: - group_1 = None - - if self.group_2 is not None: - group_2 = sim.state._get_group(self.group_2) - else: - group_2 = None - - self.nlist._attach(sim) + self._parse_groups(self.groups,self.max_bonds,sim) self._cpp_obj = cpp_class( sim.state._cpp_sys_def, self.trigger, - self.nlist._cpp_obj, - self.update_exclusions, - group_1, - group_2, + self.group_1, + self.group_2, self.seed, + self.r_cut, + self.probability, + self.max_bonds_group_1, + self.max_bonds_group_2, + self.bond_type ) + if self.nlist is not None: + self.nlist._attach(sim) + self._cpp_obj.setNlist(self.nlist._cpp_obj) + + super()._attach_hook() From 2cecc92449017c0a29811da0174d4ed07eecf2bb Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 10 Jun 2026 15:16:21 -0500 Subject: [PATCH 49/56] remove neigh list argument in GPU code --- src/DynamicBondUpdater.h | 2 -- src/DynamicBondUpdaterGPU.cc | 7 +------ src/DynamicBondUpdaterGPU.h | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index e68bf4b4..d05a36a4 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -47,7 +47,6 @@ 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); @@ -55,7 +54,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //! 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, diff --git a/src/DynamicBondUpdaterGPU.cc b/src/DynamicBondUpdaterGPU.cc index 846c8958..96d80e53 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -26,11 +26,10 @@ namespace azplugins */ 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), + : DynamicBondUpdater(sysdef, trigger, 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 @@ -53,7 +52,6 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s 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, @@ -64,7 +62,6 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s unsigned int bond_type) : DynamicBondUpdater(sysdef, trigger, - pair_nlist, group_1, group_2, seed, @@ -221,13 +218,11 @@ void export_DynamicBondUpdaterGPU(pybind11::module& 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, diff --git a/src/DynamicBondUpdaterGPU.h b/src/DynamicBondUpdaterGPU.h index ebecf886..1f698a55 100644 --- a/src/DynamicBondUpdaterGPU.h +++ b/src/DynamicBondUpdaterGPU.h @@ -39,7 +39,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater //! 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); @@ -47,7 +46,6 @@ class PYBIND11_EXPORT DynamicBondUpdaterGPU : public DynamicBondUpdater //! 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, From 4438dc8dc1e32955dfb03642e1abb36824592c1a Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Wed, 10 Jun 2026 16:25:32 -0500 Subject: [PATCH 50/56] formatting fixes --- src/DynamicBondUpdater.cc | 1188 ++++++++++++++++--------------- src/DynamicBondUpdater.h | 20 +- src/DynamicBondUpdaterGPU.cc | 20 +- src/DynamicBondUpdaterGPU.cu | 1 - src/pytest/test_dynamic_bond.py | 10 +- src/update.py | 109 +-- 6 files changed, 722 insertions(+), 626 deletions(-) diff --git a/src/DynamicBondUpdater.cc b/src/DynamicBondUpdater.cc index cda5769f..ab5e44ac 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,105 +10,109 @@ */ #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 + * \param trigger Trigger of how often this updater gets executed + * \param group_1 first group of particles to make bonds between + * \param group_2 second group of particles to make bonds between (can be identical to group_1) + * \param seed seed for the random number generator for bond probability checking * - * 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 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(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(false), - 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_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_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdater" << std::endl; - m_bond_data = m_sysdef->getBondData(); + if (m_exec_conf->getNRanks() > 1) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: MPI not implemented." << std::endl; + throw std::runtime_error("Error in DynamicBondUpdater, MPI not implemented."); + } - 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; } - +/*! + * \param sysdef System definition + * \param trigger Trigger of how often this updater gets executed + * \param group_1 first group of particles to make bonds between + * \param group_2 second group of particles to make bonds between (can be identical to group_1) + * \param seed seed for the random number generator for bond probability checking + * \param r_cut radius to search for neighbors for potential bonding pairs in + * \param probability bond forming probability + * \param max_bonds_group_1 maximum bonds a particle in group 1 can have + * \param max_bonds_group_2 maximum bonds a particle in group 2 can have (can be identical + * to max_bonds_group_1 ) + * \param bond_type type of bond this updater will form + */ 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(false), - m_box_changed(true), - m_max_N_changed(true) + std::shared_ptr trigger, + 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_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); + if (m_exec_conf->getNRanks() > 1) + { + m_exec_conf->msg->error() << "DynamicBondUpdater: MPI not implemented." << std::endl; + throw std::runtime_error("Error in DynamicBondUpdater, MPI not implemented."); + } + + 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); @@ -117,159 +121,156 @@ 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) - { + // 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; - } + } - { // in its own scope such that the neighbor list can be accsessed in filterPossibleBonds() + { // in its own scope such that the neighbor list can be accsessed in filterPossibleBonds() - // rebuild the list of possible bonds to the size of the maximum number of neighbors - // any particle currently has - m_pair_internal_nlist->compute(timestep); + // rebuild the list of possible bonds to the size of the maximum number of neighbors + // any particle currently has + m_pair_internal_nlist->compute(timestep); - //find maximum number of neighbors any particle in group one has - 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_overflow) - m_max_bonds_overflow = n_neigh; - } - //if more neighbors than m_max_bonds, resize the list that saves the possible bonds for all particles - // if we overflowed, need to reallocate memory and re-traverse the neighbor list + // find maximum number of neighbors any particle in group one has + 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_overflow) + m_max_bonds_overflow = n_neigh; + } + // if more neighbors than m_max_bonds, resize the list that saves the possible bonds for all + // particles + // if we overflowed, need to reallocate memory and re-traverse the neighbor list if (m_max_bonds < m_max_bonds_overflow) - { - resizePossibleBondlists(); + { + resizePossibleBondlists(); + } } - } - - 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. - -// 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? - +// 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. 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; - } } // 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 = (unsigned int)__scalar_as_int(i.x); - const unsigned int tag_2 = (unsigned int)__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 ) - { + const unsigned int tag_1 = (unsigned int)__scalar_as_int(i.x); + const unsigned int tag_2 = (unsigned int)__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); - } + } + else + { + return isExistingBond(tag_1, tag_2); + } } void DynamicBondUpdater::calculateExistingBonds() { + // reset exisitng bond lists to zero + m_n_existing_bonds.zeroFill(); + m_existing_bonds_list.zeroFill(); - // reset exisitng bond lists to zero - m_n_existing_bonds.zeroFill(); - m_existing_bonds_list.zeroFill(); + 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++) - { + // 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]; @@ -280,33 +281,36 @@ void DynamicBondUpdater::calculateExistingBonds() 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()) - overflowed = true; + overflowed = true; if (h_n_existing_bonds.data[tag2] == m_existing_bonds_list_indexer.getH()) - overflowed = true; - - if (overflowed) resizeExistingBondList(); - - { // explicit scoping such that resizeExistingBondList can resize the m_existing_bonds_list array before this - 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]++; - + overflowed = true; + + if (overflowed) + resizeExistingBondList(); + + { // explicit scoping such that resizeExistingBondList can resize the + // m_existing_bonds_list array before this + 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]++; + } } - - } - } /*! \param tag1 First particle tag in the pair @@ -315,491 +319,529 @@ 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 = (unsigned int)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 = (unsigned int)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; + } } - // 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->getMaxN(), 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->getMaxN(), 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_all_possible_bonds.zeroFill(); - m_num_all_possible_bonds=0; + // 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_exec_conf->msg->notice(6) << "DynamicBondUpdater: (Re-)size possible bond list, new size " << m_max_bonds << " bonds per particle " << std::endl; + m_all_possible_bonds.resize(size); + m_all_possible_bonds.zeroFill(); + 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() { + { // explicit scoping so that calculateExistingBonds can accsess the arrays after + GPUArray all_possible_bonds(m_group_1->getNumMembers() * m_max_bonds, m_exec_conf); + m_all_possible_bonds.swap(all_possible_bonds); + m_all_possible_bonds.zeroFill(); - { // explicit scoping so that calculateExistingBonds can accsess the arrays after - GPUArray all_possible_bonds(m_group_1->getNumMembers()*m_max_bonds, m_exec_conf); - m_all_possible_bonds.swap(all_possible_bonds); - m_all_possible_bonds.zeroFill(); - - GPUArray n_existing_bonds(m_pdata->getMaxN(), m_exec_conf); - m_n_existing_bonds.swap(n_existing_bonds); - m_n_existing_bonds.zeroFill(); + GPUArray n_existing_bonds(m_pdata->getMaxN(), m_exec_conf); + m_n_existing_bonds.swap(n_existing_bonds); + m_n_existing_bonds.zeroFill(); - GPUArray existing_bonds_list(m_pdata->getMaxN(),1, m_exec_conf); - m_existing_bonds_list.swap(existing_bonds_list); - m_existing_bonds_list.zeroFill(); + GPUArray existing_bonds_list(m_pdata->getMaxN(), 1, m_exec_conf); + m_existing_bonds_list.swap(existing_bonds_list); + m_existing_bonds_list.zeroFill(); - m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), (unsigned int)m_existing_bonds_list.getHeight()); - } - calculateExistingBonds(); + m_existing_bonds_list_indexer = Index2D((unsigned int)m_existing_bonds_list.getPitch(), + (unsigned int)m_existing_bonds_list.getHeight()); + } + 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 neighbor 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); - - // reset the array of possible bonds so it can be re-populated below - m_all_possible_bonds.zeroFill(); - - const BoxDim& box = m_pdata->getBox(); - - ArrayHandle h_all_possible_bonds(m_all_possible_bonds, access_location::host, access_mode::readwrite); - - // Loop over all particles in group 1 - for (unsigned int group_idx = 0; group_idx < m_group_1->getNumMembers(); group_idx++) - { - + // copy data from neighbor 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); + + // reset the array of possible bonds so it can be re-populated below + m_all_possible_bonds.zeroFill(); + + const BoxDim& box = m_pdata->getBox(); + + ArrayHandle h_all_possible_bonds(m_all_possible_bonds, + access_location::host, + access_mode::readwrite); + + // 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 Scalar r_cutsq = m_r_cut * m_r_cut; const unsigned int n_neigh = (unsigned int)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; lisMember(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) + for (unsigned int l = 0; l < n_neigh; ++l) { - 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; + // get index of neighbor from neigh_list + const unsigned int j = h_nlist.data[head + l]; + const unsigned int tag_j = h_tag.data[j]; + Scalar4 postype_j = h_postype.data[j]; - d = make_scalar3(__int_as_scalar(tag_a),__int_as_scalar(tag_b),dr_sq); - } - else + if (m_group_2->isMember(j)) { - d = make_scalar3(__int_as_scalar(tag_i),__int_as_scalar(tag_j),dr_sq); + 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; - 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 = (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); - - // 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. + { + // now sort and select down + m_num_all_possible_bonds = 0; + 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 = (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); + + // 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_n_existing_bonds(m_n_existing_bonds, access_location::host, access_mode::readwrite); + { + 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); // 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. - //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 = (unsigned int)__scalar_as_int(d.x); - unsigned int tag_j = (unsigned int)__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)); + Scalar3 d = h_all_possible_bonds.data[i]; - // BEGIN DynamicBondUpdater::AddtoExistingBonds - assert(tag_i <= m_pdata->getMaximumTag()); - assert(tag_j <= m_pdata->getMaximumTag()); + unsigned int tag_i = (unsigned int)__scalar_as_int(d.x); + unsigned int tag_j = (unsigned int)__scalar_as_int(d.y); - bool overflowed = false; + // 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()); - // 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; + hoomd::UniformDistribution uniform(0, 1); + const Scalar random = uniform(rng); - if (overflowed) resizeExistingBondList(); - - {// explicit scoping such that resizeExistingBondList can resize the m_existing_bonds_list array before this - 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]++; - - } - if (m_pair_nlist_exclusions_set) + 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_pair_nlist -> addExclusion(tag_i,tag_j); + m_bond_data->addBondedGroup(Bond(m_bond_type, tag_i, tag_j)); + + // BEGIN DynamicBondUpdater::AddtoExistingBonds + assert(tag_i <= m_pdata->getMaximumTag()); + assert(tag_j <= m_pdata->getMaximumTag()); + + 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; + + if (overflowed) + resizeExistingBondList(); + + { // explicit scoping such that resizeExistingBondList can resize the + // m_existing_bonds_list array before this + 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]++; + } + if (m_pair_nlist_exclusions_set) + { + m_pair_nlist->addExclusion(tag_i, tag_j); + } + // the internal neigh list should always get the exclusions updates to not re-find + // already existing bonds over and over + m_pair_internal_nlist->addExclusion(tag_i, tag_j); } - // the internal neigh list should always get the exclusions updates to not re-find already existing bonds over and over - 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; - } - } + 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; + } + } } /*! 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); - } - } + 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); + } + } + } + } // 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("setNlist",&DynamicBondUpdater::setNeighborList) - .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("setNlist", &DynamicBondUpdater::setNeighborList) + .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 \ No newline at end of file + } // end namespace hoomd diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index d05a36a4..ed261d3b 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -41,6 +41,18 @@ namespace hoomd namespace azplugins { +//! Dynamic bond formation +/*! + * Adds bonds between two groups of particles, which can either be 100% identical or + * completely different. It uses a cutoff and a maximum number of bonds on each + * particle, and optionally a probability of bond formation. Internally, a tree + * neighbor list is used to efficiently find all possible bond formation pairs, which + * is then sorted and down selected before the bonds are added to the system sequentially. + * + * \warning This updater should not be used with MPI. + * + */ + class PYBIND11_EXPORT DynamicBondUpdater : public Updater { public: @@ -121,7 +133,7 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater */ void setMaxBondsGroup2(unsigned int max_bonds_group_2) { - std::cout << " in setmaxbondsgroup2 "<< max_bonds_group_2< pair_nlist) { - std::cout << "in setNeighborList "<< pair_nlist << std::endl; + std::cout << "in setNeighborList " << pair_nlist << std::endl; m_pair_nlist = pair_nlist; m_pair_nlist_exclusions_set = true; } @@ -193,8 +205,8 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater //!< hoomd particle neighborlist. Set to true when //!< m_pair_nlist is set - std::shared_ptr m_pair_internal_nlist; // internal list for finding all possible bonds - + std::shared_ptr + m_pair_internal_nlist; // internal list for finding all possible bonds //! 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 96d80e53..6243250f 100644 --- a/src/DynamicBondUpdaterGPU.cc +++ b/src/DynamicBondUpdaterGPU.cc @@ -19,10 +19,11 @@ namespace azplugins /*! * \param sysdef System definition + * \param trigger Trigger of how often this updater gets executed + * \param group_1 first group of particles to make bonds between + * \param group_2 second group of particles to make bonds between (can be identical to group_1) + * \param seed seed for the random number generator for bond probability checking * - * 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 trigger, @@ -50,6 +51,19 @@ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr s m_exec_conf->msg->notice(5) << "Constructing DynamicBondUpdaterGPU" << std::endl; } +/*! + * \param sysdef System definition + * \param trigger Trigger of how often this updater gets executed + * \param group_1 first group of particles to make bonds between + * \param group_2 second group of particles to make bonds between (can be identical to group_1) + * \param seed seed for the random number generator for bond probability checking + * \param r_cut radius to search for neighbors for potential bonding pairs in + * \param probability bond forming probability + * \param max_bonds_group_1 maximum bonds a particle in group 1 can have + * \param max_bonds_group_2 maximum bonds a particle in group 2 can have (can be identical + * to max_bonds_group_1 ) + * \param bond_type type of bond this updater will form + */ DynamicBondUpdaterGPU::DynamicBondUpdaterGPU(std::shared_ptr sysdef, std::shared_ptr trigger, std::shared_ptr group_1, diff --git a/src/DynamicBondUpdaterGPU.cu b/src/DynamicBondUpdaterGPU.cu index 3e3eb1cd..a4abfc3a 100644 --- a/src/DynamicBondUpdaterGPU.cu +++ b/src/DynamicBondUpdaterGPU.cu @@ -24,7 +24,6 @@ namespace hoomd namespace azplugins { -// todo: migrate to separate file/class. // this sorts according distance, then first tag, then second tag struct SortBondsGPU { diff --git a/src/pytest/test_dynamic_bond.py b/src/pytest/test_dynamic_bond.py index f2e1bd8d..a6229a53 100644 --- a/src/pytest/test_dynamic_bond.py +++ b/src/pytest/test_dynamic_bond.py @@ -16,7 +16,7 @@ def test_setters_getters(simulation_factory, one_particle_snapshot_factory): trigger=hoomd.trigger.Periodic(period=10), bond_type=0, groups=[hoomd.filter.All()], - max_bonds = [0], + max_bonds=[0], ) assert numpy.equal(u.r_cut, 1) @@ -53,7 +53,7 @@ def test_form_bonds_same_group(simulation_factory): trigger=hoomd.trigger.Periodic(period=1), bond_type=0, groups=[group], - max_bonds=[2] + max_bonds=[2], ) # test bond formation. All are in the same group, so bonds should be formed @@ -120,7 +120,7 @@ def test_form_bonds_same_group_priority(simulation_factory): trigger=hoomd.trigger.Periodic(period=1), bond_type=0, groups=[group], - max_bonds=[2] + max_bonds=[2], ) # test bond formation. All are in the same group, so bonds should be formed @@ -167,8 +167,8 @@ def test_update_bond_two_groups(simulation_factory): r_cut=1.0, trigger=hoomd.trigger.Periodic(period=1), bond_type=0, - groups=[group_1,group_2], - max_bonds=[1,2], + groups=[group_1, group_2], + max_bonds=[1, 2], ) # test bond formation between particle 1-2 diff --git a/src/update.py b/src/update.py index 2a255cd0..deb9ca3d 100644 --- a/src/update.py +++ b/src/update.py @@ -16,45 +16,56 @@ class DynamicBond(hoomd.operation.Updater): Args: r_cut (float): Distance cutoff for making bonds between particles - probability (float): Probability of bond formation, between 0 and 1, default = 1 + trigger (:py:mod: `hoomd.trigger.Trigger`): The trigger to activate this updater bond_type (str): Type of bond to be formed - 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) 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 + groups (list of :py:mod: `hoomd.filter.ParticleFilter`): particle groups + max_bonds (list of int): Maximum number of bonds particles can have + probability (float, optional): Probability of bond formation, between 0 and 1, + default = 1 + seed (int, optional): Seed for the number generator for bond formation + probability checking, default = 0 + + nlist (:py:mod:`hoomd.md.nlist`, optional): NeighborList (optional) to update + exclusions + + Forms bonds of type bond_type between particles in the groups=[group_1,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. + form, given by max_bonds=[max_bonds_1,max_bonds_2] 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. - + separate with no common elements, e.g. two different types. Alternatively, only + one element in the groups list and only one element in the max_bonds list can + be given, then the bonds will be formed within that given group. .. warning:: - If the groups group_1 and group_2 are modified during the simulation, this + If the groups group_1 or 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. + .. warning:: + This updater is not implemeted for MPI. + Examples:: azplugins.update.dynamic_bond( nlist=nl, r_cut=1.0, bond_type=0, - groups=[hoomd.group.type(type="A"),hoomd.group.type(type="B")], - max_bonds=[2,3] + probability=0.8, + groups=[hoomd.group.type(type="A"), hoomd.group.type(type="B")], + max_bonds=[2, 3], + ) + + azplugins.update.dynamic_bond( + r_cut=1.0, + bond_type=0, + groups=[hoomd.group.type(type="A")], + max_bonds=[2], ) """ @@ -67,7 +78,7 @@ def __init__( groups, max_bonds, bond_type, - nlist = None, + nlist=None, r_cut=1.0, seed=0, probability=1.0, @@ -78,10 +89,10 @@ def __init__( r_cut=OnlyTypes(float, allow_none=False), nlist=OnlyTypes(hoomd.md.nlist.NeighborList, strict=True, allow_none=True), groups=list(groups), - max_bonds =list(max_bonds), + max_bonds=list(max_bonds), bond_type=OnlyTypes(int, strict=True, allow_none=False), seed=OnlyTypes(int, strict=True, allow_none=False), - probability=OnlyTypes(float, strict=False,allow_none=True), + probability=OnlyTypes(float, strict=False, allow_none=True), ) params.update( @@ -98,51 +109,71 @@ def __init__( self._param_dict.update(params) - def _parse_groups(self,vec_groups,vec_max_bonds,sim): + def _parse_groups(self, vec_groups, vec_max_bonds, sim): """Converts Groups depending if there is one or two given. Args: - vec_groups (Sequence[filter]): A sequence of length 2 or 1 of type ``hoomd.filter.ParticleFilter``. + vec_groups (Sequence[filter]): A sequence of length 2 or 1 of + type ``hoomd.filter.ParticleFilter``. vec_max_bonds(Sequence[int]): A sequence of length 2 or 1 of type ``int``. + sim: Simulation state. """ try: l_vec = len(vec_groups) - except: - raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + except TypeError: + raise ValueError( + "Expected array of hoomd.filter.ParticleFilter for\ + argument `groups`." + ) if l_vec == 1: if isinstance(vec_groups[0], hoomd.filter.ParticleFilter): self.group_1 = sim.state._get_group(vec_groups[0]) self.group_2 = sim.state._get_group(vec_groups[0]) self._param_dict["group_1"] = self.group_1 self._param_dict["group_2"] = self.group_2 - if len(vec_max_bonds) !=1: - raise ValueError("Expected array[int] of same length as `groups` for argument `max_bonds`.") + if len(vec_max_bonds) != 1: + raise ValueError( + "Expected array[int] of same length as `groups`\ + for argument `max_bonds`." + ) else: self.max_bonds_group_1 = vec_max_bonds[0] self._param_dict["max_bonds_group_1"] = vec_max_bonds[0] self.max_bonds_group_2 = vec_max_bonds[0] self._param_dict["max_bonds_group_2"] = vec_max_bonds[0] else: - raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + raise ValueError( + "Expected array of hoomd.filter.ParticleFilter for\ + argument `groups`." + ) elif l_vec == 2: - if isinstance(vec_groups[0], hoomd.filter.ParticleFilter) and\ - isinstance(vec_groups[1], hoomd.filter.ParticleFilter): + if isinstance(vec_groups[0], hoomd.filter.ParticleFilter) and isinstance( + vec_groups[1], hoomd.filter.ParticleFilter + ): self.group_1 = sim.state._get_group(vec_groups[0]) self.group_2 = sim.state._get_group(vec_groups[1]) self._param_dict["group_1"] = self.group_1 self._param_dict["group_2"] = self.group_2 if len(vec_max_bonds) != 2: - raise ValueError("Expected array[int] of same length as `groups` for argument `max_bonds`.") + raise ValueError( + "Expected array[int] of same length as `groups`\ + for argument `max_bonds`." + ) else: self.max_bonds_group_1 = vec_max_bonds[0] self._param_dict["max_bonds_group_1"] = vec_max_bonds[0] self.max_bonds_group_2 = vec_max_bonds[1] self._param_dict["max_bonds_group_2"] = vec_max_bonds[1] else: - raise ValueError("Expected array of hoomd.filter.ParticleFilter for argument `groups`.") + raise ValueError( + "Expected array of hoomd.filter.ParticleFilter for\ + argument `groups`." + ) else: raise ValueError( - "Expected an array of one or two hoomd.filter.ParticleFilter for argument `groups`.") + "Expected an array of one or two hoomd.filter.ParticleFilter for\ + argument `groups`." + ) @property def bond_type(self): @@ -177,7 +208,6 @@ def r_cut(self, value): def _attach_hook(self): """Create the c++ mirror class.""" - sim = self._simulation if isinstance(self._simulation.device, hoomd.device.CPU): @@ -185,7 +215,7 @@ def _attach_hook(self): else: cpp_class = getattr(self._ext_module, self._cpp_class_name + "GPU") - self._parse_groups(self.groups,self.max_bonds,sim) + self._parse_groups(self.groups, self.max_bonds, sim) self._cpp_obj = cpp_class( sim.state._cpp_sys_def, @@ -197,12 +227,11 @@ def _attach_hook(self): self.probability, self.max_bonds_group_1, self.max_bonds_group_2, - self.bond_type + self.bond_type, ) if self.nlist is not None: self.nlist._attach(sim) self._cpp_obj.setNlist(self.nlist._cpp_obj) - super()._attach_hook() From d256d39961887568b3951646525d77461fc5ead2 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:17:58 -0500 Subject: [PATCH 51/56] add documentation --- doc/module-azplugins-update.rst | 23 +++++++++++++++++++++++ src/DynamicBondUpdater.h | 2 -- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 doc/module-azplugins-update.rst diff --git a/doc/module-azplugins-update.rst b/doc/module-azplugins-update.rst new file mode 100644 index 00000000..6557b6eb --- /dev/null +++ b/doc/module-azplugins-update.rst @@ -0,0 +1,23 @@ +.. Copyright (c) 2018-2020, Michael P. Howard +.. Copyright (c) 2021-2025, Auburn University +.. Part of azplugins, released under the BSD 3-Clause License. + +azplugins.bond +-------------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.azplugins.update + +.. autosummary:: + :nosignatures: + + DynamicBond + +.. rubric:: Details + +.. automodule:: hoomd.azplugins.update + :synopsis: Updaters. + :members: DynamicBond + :no-inherited-members: + :show-inheritance: diff --git a/src/DynamicBondUpdater.h b/src/DynamicBondUpdater.h index ed261d3b..d72e3544 100644 --- a/src/DynamicBondUpdater.h +++ b/src/DynamicBondUpdater.h @@ -133,7 +133,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater */ void setMaxBondsGroup2(unsigned int max_bonds_group_2) { - std::cout << " in setmaxbondsgroup2 " << max_bonds_group_2 << std::endl; m_max_bonds_group_2 = max_bonds_group_2; checkMaxBondsGroup(); } @@ -156,7 +155,6 @@ class PYBIND11_EXPORT DynamicBondUpdater : public Updater void setNeighborList(std::shared_ptr pair_nlist) { - std::cout << "in setNeighborList " << pair_nlist << std::endl; m_pair_nlist = pair_nlist; m_pair_nlist_exclusions_set = true; } From b8c2eb01cca1ad12f0459f9a3bff74ae9b50d23e Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:21:16 -0500 Subject: [PATCH 52/56] fix doc rst config --- doc/module-azplugins-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/module-azplugins-update.rst b/doc/module-azplugins-update.rst index 6557b6eb..1d8b1a94 100644 --- a/doc/module-azplugins-update.rst +++ b/doc/module-azplugins-update.rst @@ -18,6 +18,6 @@ azplugins.bond .. automodule:: hoomd.azplugins.update :synopsis: Updaters. - :members: DynamicBond + :members: DynamicBond, :no-inherited-members: :show-inheritance: From a1abffc799fbc3d00af20facdc9cf7f1941e8630 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:23:25 -0500 Subject: [PATCH 53/56] add doc file to index --- doc/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/index.rst b/doc/index.rst index 4bd98c6a..6b0559f0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -55,6 +55,7 @@ Contents module-azplugins-compute module-azplugins-external module-azplugins-flow + module-azplugins-update module-azplugins-pair module-azplugins-wall From d336a7bd9380733ab3d665950a5cc108e6637ad9 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:28:05 -0500 Subject: [PATCH 54/56] fix indentation --- src/update.py | 76 +++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/update.py b/src/update.py index deb9ca3d..bf78a8f0 100644 --- a/src/update.py +++ b/src/update.py @@ -28,45 +28,45 @@ class DynamicBond(hoomd.operation.Updater): nlist (:py:mod:`hoomd.md.nlist`, optional): NeighborList (optional) to update exclusions - Forms bonds of type bond_type between particles in the groups=[group_1,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=[max_bonds_1,max_bonds_2] 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. Alternatively, only - one element in the groups list and only one element in the max_bonds list can - be given, then the bonds will be formed within that given group. - - .. warning:: - If the groups group_1 or 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. - - .. warning:: - This updater is not implemeted for MPI. - - Examples:: - - azplugins.update.dynamic_bond( - nlist=nl, - r_cut=1.0, - bond_type=0, - probability=0.8, - groups=[hoomd.group.type(type="A"), hoomd.group.type(type="B")], - max_bonds=[2, 3], - ) + Forms bonds of type bond_type between particles in the groups=[group_1,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=[max_bonds_1,max_bonds_2] 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. Alternatively, only + one element in the groups list and only one element in the max_bonds list can + be given, then the bonds will be formed within that given group. + + .. warning:: + If the groups group_1 or 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. + + .. warning:: + This updater is not implemeted for MPI. + + Examples:: + + azplugins.update.dynamic_bond( + nlist=nl, + r_cut=1.0, + bond_type=0, + probability=0.8, + groups=[hoomd.group.type(type="A"), hoomd.group.type(type="B")], + max_bonds=[2, 3], + ) - azplugins.update.dynamic_bond( - r_cut=1.0, - bond_type=0, - groups=[hoomd.group.type(type="A")], - max_bonds=[2], - ) + azplugins.update.dynamic_bond( + r_cut=1.0, + bond_type=0, + groups=[hoomd.group.type(type="A")], + max_bonds=[2], + ) """ _ext_module = _azplugins From b63a769dff2f7a6c4ecda61a1e41eb7d33c6615d Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:30:32 -0500 Subject: [PATCH 55/56] fix header in doc --- doc/module-azplugins-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/module-azplugins-update.rst b/doc/module-azplugins-update.rst index 1d8b1a94..daa68879 100644 --- a/doc/module-azplugins-update.rst +++ b/doc/module-azplugins-update.rst @@ -2,7 +2,7 @@ .. Copyright (c) 2021-2025, Auburn University .. Part of azplugins, released under the BSD 3-Clause License. -azplugins.bond +azplugins.update -------------- .. rubric:: Overview From 0b75035b5819e04cab58629d6e41232d843a1dc3 Mon Sep 17 00:00:00 2001 From: Antonia Statt Date: Thu, 11 Jun 2026 09:50:14 -0500 Subject: [PATCH 56/56] fix underline --- doc/module-azplugins-update.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/module-azplugins-update.rst b/doc/module-azplugins-update.rst index daa68879..b72d12d4 100644 --- a/doc/module-azplugins-update.rst +++ b/doc/module-azplugins-update.rst @@ -3,7 +3,7 @@ .. Part of azplugins, released under the BSD 3-Clause License. azplugins.update --------------- +---------------- .. rubric:: Overview