|
| 1 | +// Copyright (c) 2018-2020, Michael P. Howard |
| 2 | +// Copyright (c) 2021-2025, Auburn University |
| 3 | +// Part of azplugins, released under the BSD 3-Clause License. |
| 4 | + |
| 5 | +// File modified from HOOMD-blue |
| 6 | +// Copyright (c) 2009-2025 The Regents of the University of Michigan. |
| 7 | +// Part of HOOMD-blue, released under the BSD 3-Clause License. |
| 8 | + |
| 9 | +#include "hoomd/ForceCompute.h" |
| 10 | +#include "hoomd/GPUArray.h" |
| 11 | +#include "hoomd/MeshDefinition.h" |
| 12 | +#include "hoomd/md/PotentialBond.h" |
| 13 | +#include <memory> |
| 14 | + |
| 15 | +#include <vector> |
| 16 | + |
| 17 | +/*! \file ImagePotentialBond.h |
| 18 | + \brief Declares ImagePotentialBond |
| 19 | +*/ |
| 20 | + |
| 21 | +#ifdef __HIPCC__ |
| 22 | +#error This header cannot be compiled by nvcc |
| 23 | +#endif |
| 24 | + |
| 25 | +#include <pybind11/pybind11.h> |
| 26 | + |
| 27 | +#ifndef AZPLUGINS_IMAGE_POTENTIAL_BOND_H_ |
| 28 | +#define AZPLUGINS_IMAGE_POTENTIAL_BOND_H_ |
| 29 | + |
| 30 | +namespace hoomd |
| 31 | + { |
| 32 | +namespace azplugins |
| 33 | + { |
| 34 | +/*! Bond potential using unwrapped coordinates |
| 35 | +
|
| 36 | + \ingroup computes |
| 37 | +*/ |
| 38 | +template<class evaluator, class Bonds> |
| 39 | +class ImagePotentialBond : public md::PotentialBond<evaluator, Bonds> |
| 40 | + { |
| 41 | + public: |
| 42 | + //! Inherit constructors from base class |
| 43 | + using md::PotentialBond<evaluator, Bonds>::PotentialBond; |
| 44 | + |
| 45 | +#ifdef ENABLE_MPI |
| 46 | + //! Get ghost particle fields requested by this pair potential |
| 47 | + CommFlags getRequestedCommFlags(uint64_t timestep) override; |
| 48 | +#endif |
| 49 | + |
| 50 | + protected: |
| 51 | + void computeForces(uint64_t timestep) override; |
| 52 | + }; |
| 53 | + |
| 54 | +/*! Actually perform the force computation |
| 55 | + \param timestep Current time step |
| 56 | + */ |
| 57 | +template<class evaluator, class Bonds> |
| 58 | +void ImagePotentialBond<evaluator, Bonds>::computeForces(uint64_t timestep) |
| 59 | + { |
| 60 | + // access the particle data arrays |
| 61 | + ArrayHandle<Scalar4> h_pos(this->m_pdata->getPositions(), |
| 62 | + access_location::host, |
| 63 | + access_mode::read); |
| 64 | + ArrayHandle<int3> h_image(this->m_pdata->getImages(), access_location::host, access_mode::read); |
| 65 | + ArrayHandle<unsigned int> h_rtag(this->m_pdata->getRTags(), |
| 66 | + access_location::host, |
| 67 | + access_mode::read); |
| 68 | + ArrayHandle<Scalar> h_charge(this->m_pdata->getCharges(), |
| 69 | + access_location::host, |
| 70 | + access_mode::read); |
| 71 | + |
| 72 | + ArrayHandle<Scalar4> h_force(this->m_force, access_location::host, access_mode::readwrite); |
| 73 | + ArrayHandle<Scalar> h_virial(this->m_virial, access_location::host, access_mode::readwrite); |
| 74 | + |
| 75 | + // access the parameters |
| 76 | + ArrayHandle<typename evaluator::param_type> h_params(this->m_params, |
| 77 | + access_location::host, |
| 78 | + access_mode::read); |
| 79 | + |
| 80 | + // Zero data for force calculation |
| 81 | + this->m_force.zeroFill(); |
| 82 | + this->m_virial.zeroFill(); |
| 83 | + |
| 84 | + const BoxDim box = this->m_pdata->getGlobalBox(); |
| 85 | + |
| 86 | + PDataFlags flags = this->m_pdata->getFlags(); |
| 87 | + bool compute_virial = flags[pdata_flag::pressure_tensor]; |
| 88 | + |
| 89 | + Scalar bond_virial[6]; |
| 90 | + for (unsigned int i = 0; i < 6; i++) |
| 91 | + bond_virial[i] = Scalar(0.0); |
| 92 | + |
| 93 | + ArrayHandle<typename Bonds::members_t> h_bonds(this->m_bond_data->getMembersArray(), |
| 94 | + access_location::host, |
| 95 | + access_mode::read); |
| 96 | + ArrayHandle<typeval_t> h_typeval(this->m_bond_data->getTypeValArray(), |
| 97 | + access_location::host, |
| 98 | + access_mode::read); |
| 99 | + |
| 100 | + unsigned int max_local = this->m_pdata->getN() + this->m_pdata->getNGhosts(); |
| 101 | + |
| 102 | + // for each of the bonds |
| 103 | + const unsigned int size = (unsigned int)this->m_bond_data->getN(); |
| 104 | + |
| 105 | + for (unsigned int i = 0; i < size; i++) |
| 106 | + { |
| 107 | + // lookup the tag of each of the particles participating in the bond |
| 108 | + const typename Bonds::members_t& bond = h_bonds.data[i]; |
| 109 | + |
| 110 | + // transform a and b into indices into the particle data arrays |
| 111 | + // (MEM TRANSFER: 4 integers) |
| 112 | + unsigned int idx_a = h_rtag.data[bond.tag[0]]; |
| 113 | + unsigned int idx_b = h_rtag.data[bond.tag[1]]; |
| 114 | + |
| 115 | + // throw an error if this bond is incomplete |
| 116 | + if (idx_a >= max_local || idx_b >= max_local) |
| 117 | + { |
| 118 | + std::ostringstream stream; |
| 119 | + stream << "Error: bond " << bond.tag[0] << " " << bond.tag[1] << " is incomplete."; |
| 120 | + throw std::runtime_error(stream.str()); |
| 121 | + } |
| 122 | + |
| 123 | + // calculate d\vec{r} |
| 124 | + // (MEM TRANSFER: 6 Scalars / FLOPS: 3) |
| 125 | + Scalar3 posa = make_scalar3(h_pos.data[idx_a].x, h_pos.data[idx_a].y, h_pos.data[idx_a].z); |
| 126 | + Scalar3 posb = make_scalar3(h_pos.data[idx_b].x, h_pos.data[idx_b].y, h_pos.data[idx_b].z); |
| 127 | + |
| 128 | + // access charge (if needed) |
| 129 | + Scalar charge_a = Scalar(0.0); |
| 130 | + Scalar charge_b = Scalar(0.0); |
| 131 | + if (evaluator::needsCharge()) |
| 132 | + { |
| 133 | + charge_a = h_charge.data[idx_a]; |
| 134 | + charge_b = h_charge.data[idx_b]; |
| 135 | + } |
| 136 | + |
| 137 | + // get images of particles |
| 138 | + const int3 img_a = h_image.data[idx_a]; |
| 139 | + const int3 img_b = h_image.data[idx_b]; |
| 140 | + |
| 141 | + // get relative vector between particles |
| 142 | + const Scalar3 dx = box.shift(posb - posa, img_b - img_a); |
| 143 | + |
| 144 | + // calculate r_ab squared |
| 145 | + Scalar rsq = dot(dx, dx); |
| 146 | + |
| 147 | + // compute the force and potential energy |
| 148 | + Scalar force_divr = Scalar(0.0); |
| 149 | + Scalar bond_eng = Scalar(0.0); |
| 150 | + evaluator eval(rsq, h_params.data[h_typeval.data[i].type]); |
| 151 | + if (evaluator::needsCharge()) |
| 152 | + eval.setCharge(charge_a, charge_b); |
| 153 | + |
| 154 | + bool evaluated = eval.evalForceAndEnergy(force_divr, bond_eng); |
| 155 | + |
| 156 | + // Bond energy must be halved |
| 157 | + bond_eng *= Scalar(0.5); |
| 158 | + |
| 159 | + if (evaluated) |
| 160 | + { |
| 161 | + // calculate virial |
| 162 | + if (compute_virial) |
| 163 | + { |
| 164 | + Scalar force_div2r = Scalar(1.0 / 2.0) * force_divr; |
| 165 | + bond_virial[0] = dx.x * dx.x * force_div2r; // xx |
| 166 | + bond_virial[1] = dx.x * dx.y * force_div2r; // xy |
| 167 | + bond_virial[2] = dx.x * dx.z * force_div2r; // xz |
| 168 | + bond_virial[3] = dx.y * dx.y * force_div2r; // yy |
| 169 | + bond_virial[4] = dx.y * dx.z * force_div2r; // yz |
| 170 | + bond_virial[5] = dx.z * dx.z * force_div2r; // zz |
| 171 | + } |
| 172 | + |
| 173 | + // add the force to the particles (only for non-ghost particles) |
| 174 | + if (idx_b < this->m_pdata->getN()) |
| 175 | + { |
| 176 | + h_force.data[idx_b].x += force_divr * dx.x; |
| 177 | + h_force.data[idx_b].y += force_divr * dx.y; |
| 178 | + h_force.data[idx_b].z += force_divr * dx.z; |
| 179 | + h_force.data[idx_b].w += bond_eng; |
| 180 | + if (compute_virial) |
| 181 | + for (unsigned int i = 0; i < 6; i++) |
| 182 | + h_virial.data[i * this->m_virial_pitch + idx_b] += bond_virial[i]; |
| 183 | + } |
| 184 | + |
| 185 | + if (idx_a < this->m_pdata->getN()) |
| 186 | + { |
| 187 | + h_force.data[idx_a].x -= force_divr * dx.x; |
| 188 | + h_force.data[idx_a].y -= force_divr * dx.y; |
| 189 | + h_force.data[idx_a].z -= force_divr * dx.z; |
| 190 | + h_force.data[idx_a].w += bond_eng; |
| 191 | + if (compute_virial) |
| 192 | + for (unsigned int i = 0; i < 6; i++) |
| 193 | + h_virial.data[i * this->m_virial_pitch + idx_a] += bond_virial[i]; |
| 194 | + } |
| 195 | + } |
| 196 | + else |
| 197 | + { |
| 198 | + this->m_exec_conf->msg->error() |
| 199 | + << "bond." << evaluator::getName() << ": bond out of bounds" << std::endl |
| 200 | + << std::endl; |
| 201 | + throw std::runtime_error("Error in bond calculation"); |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | +#ifdef ENABLE_MPI |
| 207 | +/*! \param timestep Current time step |
| 208 | + */ |
| 209 | +template<class evaluator, class Bonds> |
| 210 | +CommFlags ImagePotentialBond<evaluator, Bonds>::getRequestedCommFlags(uint64_t timestep) |
| 211 | + { |
| 212 | + CommFlags flags = md::PotentialBond<evaluator, Bonds>::getRequestedCommFlags(timestep); |
| 213 | + flags[comm_flag::image] = 1; |
| 214 | + |
| 215 | + return flags; |
| 216 | + } |
| 217 | +#endif |
| 218 | + |
| 219 | +namespace detail |
| 220 | + { |
| 221 | +//! Exports the ImagePotentialBond class to python |
| 222 | +/*! \param name Name of the class in the exported python module |
| 223 | + \tparam T Evaluator type to export. |
| 224 | +*/ |
| 225 | +template<class T> void export_ImagePotentialBond(pybind11::module& m, const std::string& name) |
| 226 | + { |
| 227 | + pybind11::class_<ImagePotentialBond<T, BondData>, |
| 228 | + md::PotentialBond<T, BondData>, |
| 229 | + std::shared_ptr<ImagePotentialBond<T, BondData>>>(m, name.c_str()) |
| 230 | + .def(pybind11::init<std::shared_ptr<SystemDefinition>>()); |
| 231 | + } |
| 232 | + |
| 233 | + } // end namespace detail |
| 234 | + } // end namespace azplugins |
| 235 | + } // end namespace hoomd |
| 236 | + |
| 237 | +#endif |
0 commit comments