diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46a57e0b08..d09aceccb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -93,7 +93,6 @@ repos: exclude: | (?x)( ^hoomd/extern/| - ^hoomd/mpcd/| ^hoomd/metal/ ) - repo: https://github.com/pre-commit/mirrors-clang-format diff --git a/BUILDING.rst b/BUILDING.rst index b87f19dcf6..2bdbb11b42 100644 --- a/BUILDING.rst +++ b/BUILDING.rst @@ -110,8 +110,7 @@ Install prerequisites For **HOOMD-blue** on AMD GPUs, the following limitations currently apply. 1. Certain kernels trigger an `unknown HSA error `_. - 2. The ``mpcd`` component is disabled on AMD GPUs. - 3. Multi-GPU execution via unified memory is not available. + 2. Multi-GPU execution via unified memory is not available. .. note:: @@ -222,6 +221,8 @@ Other option changes take effect at any time: - ``BUILD_HPMC`` - When enabled, build the ``hoomd.hpmc`` module (default: ``on``). - ``BUILD_MD`` - When enabled, build the ``hoomd.md`` module (default: ``on``). - ``BUILD_METAL`` - When enabled, build the ``hoomd.metal`` module (default: ``on``). +- ``BUILD_MPCD`` - When enabled, build the ``hoomd.mpcd`` module. ``hoomd.md`` must also be built. + (default: same as ``BUILD_MD``). - ``BUILD_TESTING`` - When enabled, build unit tests (default: ``on``). - ``CMAKE_BUILD_TYPE`` - Sets the build type (case sensitive) Options: diff --git a/CMakeLists.txt b/CMakeLists.txt index 03751e5512..5e2ddffbec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ else () option(BUILD_HPMC "Build the hpmc package" off) endif() option(BUILD_METAL "Build the metal package" on) +option(BUILD_MPCD "Build the mpcd package" ${BUILD_MD}) # Optionally use TBB for threading option(ENABLE_TBB "Enable support for Threading Building Blocks (TBB)" off) @@ -124,12 +125,6 @@ include(HOOMDPythonSetup) include (hoomd-macros) -if (NOT ENABLE_HIP OR HIP_PLATFORM STREQUAL "nvcc") - option(BUILD_MPCD "Build the mpcd package" on) -else() - option(BUILD_MPCD "Build the mpcd package" off) -endif() - find_package(Eigen3 3.2 CONFIG REQUIRED) if (Eigen3_FOUND) find_package_message(EIGEN3 "Found eigen: ${Eigen3_DIR} ${EIGEN3_INCLUDE_DIR} (version ${Eigen3_VERSION})" "[${Eigen3_DIR}][${EIGEN3_INCLUDE_DIR}]") diff --git a/hoomd/CMakeLists.txt b/hoomd/CMakeLists.txt index f83f1c4d87..52ebc15594 100644 --- a/hoomd/CMakeLists.txt +++ b/hoomd/CMakeLists.txt @@ -143,8 +143,6 @@ set(_hoomd_headers GPUArray.h GPUFlags.h GPUPartition.cuh - GPUPolymorph.h - GPUPolymorph.cuh GPUVector.h GSD.h GSDDequeWriter.h @@ -213,7 +211,7 @@ set(_hoomd_cu_sources BondedGroupData.cu SFCPackTunerGPU.cu) # add the MPCD base parts that should go into _hoomd (i.e., core particle data) -if (BUILD_MPCD AND (NOT ENABLE_HIP OR HIP_PLATFORM STREQUAL "nvcc")) +if (BUILD_MPCD) list(APPEND _hoomd_sources mpcd/ParticleData.cc mpcd/ParticleDataSnapshot.cc @@ -407,7 +405,7 @@ if (BUILD_METAL AND BUILD_MD) add_subdirectory(metal) endif() -if (BUILD_MPCD AND (NOT ENABLE_HIP OR HIP_PLATFORM STREQUAL "nvcc")) +if (BUILD_MPCD) target_compile_definitions(_hoomd PUBLIC BUILD_MPCD) add_subdirectory(mpcd) endif() diff --git a/hoomd/GPUPolymorph.cuh b/hoomd/GPUPolymorph.cuh deleted file mode 100644 index 9145e8193b..0000000000 --- a/hoomd/GPUPolymorph.cuh +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file GPUPolymorph.cuh - * \brief Defines supporting CUDA functions for GPUPolymorph. - */ - -#ifndef HOOMD_GPU_POLYMORPH_CUH_ -#define HOOMD_GPU_POLYMORPH_CUH_ - -#include -#include - -namespace hoomd - { -namespace gpu - { -//! Method to initialize an object within a kernel -template T* device_new(Args... args); - -//! Method to delete an object initialized within a kernel -template void device_delete(T* data); - -#ifdef __HIPCC__ -namespace kernel - { -//! Kernel to initialize and place object into allocated memory. -/*! - * \tparam T Type of object to initialize. - * \tparam Args Argument types for constructor of \a T. - * \param data Allocated device memory to initialize. - * \param args Argument values to construct \a T. - * \returns Allocated, initialized pointer to a \a T object. - * - * Only one thread executes to avoid race conditions. The object is placed into \a data. - * - * \sa hoomd::gpu::device_new - */ -template __global__ void device_construct(void* data, Args... args) - { - const int index = blockIdx.x * blockDim.x + threadIdx.x; - if (index != 0) - return; - new (data) T(args...); - } - -template __global__ void device_destroy(T* data) - { - const int index = blockIdx.x * blockDim.x + threadIdx.x; - if (index != 0) - return; - data->~T(); - } - } // end namespace kernel - -/*! - * \tparam T Type of object to initialize. - * \tparam Args Argument types for constructor of \a T. - * \param args Argument values to construct \a T. - * \returns Allocated, initialized pointer to a \a T object. - * - * Global memory on the device is first allocated with cudaMalloc. Then, kernel::device_construct is - * called to initialize and place the object in that memory. Unlike calling new within a kernel, - * this ensures that the object resides in normal global memory (and not in the limited dynamically - * allocatable global memory). The device is synchronized to ensure the object is available - * immediately after the pointer is returned. - * - * Note that the \a Args parameter pack has all references removed before forwarding to the kernel. - * This should ensure that all arguments are passed by copy, even if the user forwards them by - * reference for efficiency. - */ -template T* device_new(Args... args) - { - T* data; - hipMalloc((void**)&data, sizeof(T)); - kernel::device_construct::type...> - <<<1, 1>>>(data, args...); - hipDeviceSynchronize(); - return data; - } - -/*! - * \tparam T Type of object to delete. - * \param data Object to delete. - * - * The destructor for \a data is first called from within a kernel using kernel::device_destroy. - * In principle, virtual destructors should be chained together if \a T is the base class. (?) - * After destruction, the memory can be deallocated using cudaFree(). - */ -template void device_delete(T* data) - { - if (data) - { - kernel::device_destroy<<<1, 1>>>(data); - hipDeviceSynchronize(); - hipFree((void*)data); - } - } -#endif // __HIPCC__ - - } // end namespace gpu - } // end namespace hoomd - -#endif // HOOMD_GPU_POLYMORPH_CUH_ diff --git a/hoomd/GPUPolymorph.h b/hoomd/GPUPolymorph.h deleted file mode 100644 index 4716816a39..0000000000 --- a/hoomd/GPUPolymorph.h +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file GPUPolymorph.h - * \brief Defines the GPUPolymorph class for polymorphic device objects. - */ - -#ifndef HOOMD_GPU_POLYMORPH_H_ -#define HOOMD_GPU_POLYMORPH_H_ - -#include "ExecutionConfiguration.h" -#include "GPUArray.h" - -#include -#include -#include -#include - -#ifdef ENABLE_HIP -#include "GPUPolymorph.cuh" -#endif - -namespace hoomd - { -//! Wrapper for a polymorphic object accessible on the host or device. -/*! - * Polymorphic objects cannot be passed directly to a CUDA kernel because the host and device have - * different virtual tables. Moreover, simple copies of objects between host and device memory also - * do not work because they could write over those tables. One possible solution is implemented - * here. Both a host-memory and a device-memory copy of the polymorphic object are allocated and - * initialized. The device-memory object must be constructed inside a CUDA kernel, and it is placed - * into the allocated global memory. Both of these objects are managed by std::unique_ptr, and so - * they are safely destroyed when the wrapper is reset or goes out of scope. - * - * The wrapper is constructed using the \a Base class as a template parameter. On construction, the - * host and device memory is not allocated. The objects can be allocated as type \a Derived using - * the ::reset method, with an appropriate set of arguments for the constructor of \a Derived. Each - * pointer can then be obtained using the - * ::get method with the appropriate access mode. - * - * There are a few underlying assumptions of this wrapper that must be satisfied: - * - * 1. Constructor arguments must be forwarded to a device kernel from the host. Don't make them - * anything too bulky. - * 2. Device-memory objects are allocated and initialized by hoomd::gpu::device_new. You will need - * to explicitly instantiate this template in a *.cu file, or you will get undefined symbol errors. - * 3. When the allocations are reset or the object goes out of scope, the device-memory object will - * be destructed and freed. The base destructor is first called from inside a kernel; it should be - * virtual to ensure proper destructor chaining. Afterwards, the memory is simply freed. You will - * need to explicitly instantiate hoomd::gpu::device_delete for the base class in a *.cu file, or - * you will get undefined symbol errors. - * - * This wrapper essentially acts like a factory class that also manages the necessary objects. - * - * When instantiating the CUDA functions, you will need to use separable CUDA compilation because of - * the way HOOMD handles the device object virtual tables. This may place some restrictions on what - * can be implemented through a plugin interface since the device functions are all resolved at - * compile-time. - * - * \tparam Base Base class for the polymorphic object. - */ -template class GPUPolymorph - { - static_assert(std::is_polymorphic::value, "Base should be a polymorphic class."); - - private: - typedef std::unique_ptr host_ptr; - -#ifdef ENABLE_HIP - //! Simple custom deleter for the base class. - /*! - * This custom deleter is used by std::unique_ptr to free the memory allocation. - */ - class CUDADeleter - { - public: - //! Default constructor (gives null exec. conf) - CUDADeleter() { } - - //! Constructor with an execution configuration - CUDADeleter(std::shared_ptr exec_conf) - : m_exec_conf(exec_conf) - { - } - - //! Delete operator - /*! - * \param p Pointer to a possibly polymorphic \a Base object - * - * If the allocation for a device pointer is still valid, the deleter will first - * call the destructor for the \a Base class from inside a kernel. Then, the memory - * will be freed by cudaFree(). - */ - void operator()(Base* p) const - { - if (m_exec_conf && m_exec_conf->isCUDAEnabled() && p) - { - m_exec_conf->msg->notice(5) - << "Freeing device memory from GPUPolymorph [Base = " << typeid(Base).name() - << "]" << std::endl; - gpu::device_delete(p); - } - } - - private: - std::shared_ptr - m_exec_conf; //!< HOOMD execution configuration - }; - - typedef std::unique_ptr device_ptr; -#endif // ENABLE_HIP - - public: - //! Constructor - /*! - * \param exec_conf HOOMD execution configuration. - * \post The host-memory and device-memory pointers are not allocated (null values). - */ - GPUPolymorph(std::shared_ptr exec_conf) noexcept(true) - : m_exec_conf(exec_conf), m_host_data(nullptr) - { - m_exec_conf->msg->notice(4) - << "Constructing GPUPolymorph [Base = " << typeid(Base).name() << "]" << std::endl; -#ifdef ENABLE_HIP - m_device_data = device_ptr(nullptr, CUDADeleter(m_exec_conf)); -#endif // ENABLE_HIP - } - - //! Destructor - ~GPUPolymorph() - { - m_exec_conf->msg->notice(4) - << "Destroying GPUPolymorph [Base = " << typeid(Base).name() << "]" << std::endl; - } - - //! Copy constructor. - /*! - * Copy is not supported for underlying unique_ptr data. - */ - GPUPolymorph(const GPUPolymorph& other) = delete; - - //! Copy assignment. - /*! - * Copy is not supported for underlying unique_ptr data. - */ - GPUPolymorph& operator=(const GPUPolymorph& other) = delete; - - //! Move constructor. - /*! - * \param other Another GPUPolymorph. - * \a The underlying data of \a other is pilfered to construct this object. - */ - GPUPolymorph(GPUPolymorph&& other) noexcept(true) - : m_exec_conf(std::move(other.m_exec_conf)), m_host_data(std::move(other.m_host_data)) - { -#ifdef ENABLE_HIP - m_device_data = std::move(other.m_device_data); -#endif // ENABLE_HIP - } - - //! Move assignment. - /*! - * \param other Another GPUPolymorph. - * \a The underlying data of \a other is pilfered and assigned to this object. - */ - GPUPolymorph& operator=(GPUPolymorph&& other) noexcept(true) - { - if (*this != other) - { - m_exec_conf = std::move(other.m_exec_conf); - m_host_data = std::move(other.m_host_data); -#ifdef ENABLE_HIP - m_device_data = std::move(other.m_device_data); -#endif // ENABLE_HIP - } - return *this; - } - - //! Reset (and allocate) the host-memory and device-memory objects. - /*! - * \tparam Derived Polymorphic object type to create. - * \tparam Args Argument types for constructor of \a Derived object to call. - * \param args Argument values to construct \a Derived object. - * - * The host-memory copy is allocated and initialized using the new keyword. If CUDA is available - * for the execution configuration, the device-memory object is created by - * hoomd::gpu::device_new. - * - * \a Derived must be derived from \a Base for this wrapper to make sense. A compile-time - * assertion will fail otherwise. - */ - template void reset(Args... args) - { - static_assert(std::is_base_of::value, - "Polymorph must be derived from Base."); - - m_exec_conf->msg->notice(4) - << "Resetting GPUPolymorph [Derived = " << typeid(Derived).name() - << ", Base = " << typeid(Base).name() << "] (" << sizeof(Derived) << " bytes)" - << std::endl; - - m_host_data.reset(new Derived(args...)); -#ifdef ENABLE_HIP - if (m_exec_conf->isCUDAEnabled()) - { - m_device_data.reset(gpu::device_new(args...)); - if (m_exec_conf->isCUDAErrorCheckingEnabled()) - CHECK_CUDA_ERROR(); - } -#endif // ENABLE_HIP - } - - //! Get the raw pointer associated with a given copy. - /*! - * \param location Access location (host, device) for object. - * \returns Pointer to polymorphic object with type \a Base. - * - * If the object has not been initialized or the access location is not recognized, - * a nullptr is returned. - */ - Base* get(const access_location::Enum location) const - { - if (location == access_location::host) - { - return m_host_data.get(); - } -#ifdef ENABLE_HIP - else if (location == access_location::device) - { - return m_device_data.get(); - } -#endif // ENABLE_HIP - else - { - return nullptr; - } - } - - private: - std::shared_ptr m_exec_conf; //!< HOOMD execution configuration - host_ptr m_host_data; //!< Host-memory copy -#ifdef ENABLE_HIP - device_ptr m_device_data; //!< Device-memory copy -#endif // ENABLE_HIP - }; - - } // end namespace hoomd - -#endif // HOOMD_GPU_POLYMORPH_H_ diff --git a/hoomd/RNGIdentifiers.h b/hoomd/RNGIdentifiers.h index 6fd2cab8cb..2fe7704aa3 100644 --- a/hoomd/RNGIdentifiers.h +++ b/hoomd/RNGIdentifiers.h @@ -55,8 +55,8 @@ struct RNGIdentifier static const uint8_t ATCollisionMethod = 29; static const uint8_t CollisionMethod = 30; static const uint8_t SRDCollisionMethod = 31; - static const uint8_t SlitGeometryFiller = 32; - static const uint8_t SlitPoreGeometryFiller = 33; + static const uint8_t ParallelPlateGeometryFiller = 32; + static const uint8_t PlanarPoreGeometryFiller = 33; static const uint8_t UpdaterQuickCompress = 34; static const uint8_t ParticleGroupThermalize = 35; static const uint8_t HPMCDepletantsAccept = 36; @@ -70,6 +70,7 @@ struct RNGIdentifier static const uint8_t HPMCShapeMoveUpdateOrder = 44; static const uint8_t BussiThermostat = 45; static const uint8_t ConstantPressure = 46; + static const uint8_t MPCDCellList = 47; }; } // namespace hoomd diff --git a/hoomd/__init__.py b/hoomd/__init__.py index 6ab8536822..614533c9f6 100644 --- a/hoomd/__init__.py +++ b/hoomd/__init__.py @@ -81,8 +81,8 @@ from hoomd import hpmc # if version.metal_built: # from hoomd import metal -# if version.mpcd_built: -# from hoomd import mpcd +if version.mpcd_built: + from hoomd import mpcd from hoomd.simulation import Simulation from hoomd.state import State diff --git a/hoomd/mpcd/ATCollisionMethod.cc b/hoomd/mpcd/ATCollisionMethod.cc index 9f5440aefc..9941f53c7c 100644 --- a/hoomd/mpcd/ATCollisionMethod.cc +++ b/hoomd/mpcd/ATCollisionMethod.cc @@ -256,7 +256,9 @@ void mpcd::detail::export_ATCollisionMethod(pybind11::module& m) uint64_t, int, std::shared_ptr>()) - .def("setTemperature", &mpcd::ATCollisionMethod::setTemperature); + .def_property("kT", + &mpcd::ATCollisionMethod::getTemperature, + &mpcd::ATCollisionMethod::setTemperature); } } // end namespace hoomd diff --git a/hoomd/mpcd/ATCollisionMethod.h b/hoomd/mpcd/ATCollisionMethod.h index 6226c1c8db..9bac7772eb 100644 --- a/hoomd/mpcd/ATCollisionMethod.h +++ b/hoomd/mpcd/ATCollisionMethod.h @@ -37,7 +37,13 @@ class PYBIND11_EXPORT ATCollisionMethod : public mpcd::CollisionMethod void setCellList(std::shared_ptr cl); - //! Set the temperature and enable the thermostat + //! Get the temperature + std::shared_ptr getTemperature() const + { + return m_T; + } + + //! Set the temperature void setTemperature(std::shared_ptr T) { m_T = T; diff --git a/hoomd/mpcd/BlockForce.cc b/hoomd/mpcd/BlockForce.cc new file mode 100644 index 0000000000..6b5d93ac62 --- /dev/null +++ b/hoomd/mpcd/BlockForce.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BlockForce.cc + * \brief Export MPCD BlockForce. + */ + +#include "BlockForce.h" + +namespace hoomd + { +namespace mpcd + { +namespace detail + { + +void export_BlockForce(pybind11::module& m) + { + pybind11::class_>(m, "BlockForce") + .def(pybind11::init()) + .def_property("force", &BlockForce::getForce, &BlockForce::setForce) + .def_property("separation", &BlockForce::getSeparation, &BlockForce::setSeparation) + .def_property("width", &BlockForce::getWidth, &BlockForce::setWidth); + } + + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BlockForce.h b/hoomd/mpcd/BlockForce.h new file mode 100644 index 0000000000..043a49c955 --- /dev/null +++ b/hoomd/mpcd/BlockForce.h @@ -0,0 +1,143 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BlockForce.h + * \brief Definition of mpcd::BlockForce. + */ + +#ifndef MPCD_BLOCK_FORCE_H_ +#define MPCD_BLOCK_FORCE_H_ + +#include "hoomd/HOOMDMath.h" + +#ifdef __HIPCC__ +#define HOSTDEVICE __host__ __device__ +#define INLINE inline +#else +#define HOSTDEVICE +#define INLINE inline __attribute__((always_inline)) +#include +#include +#endif + +namespace hoomd + { +namespace mpcd + { + +//! Constant, opposite force applied to particles in a block +/*! + * Imposes a constant force in x as a function of position in y: + * + * \f{eqnarray*} + * \mathbf{F} &= +F \mathbf{e}_x & H-w \le y < H+w \\ + * &= -F \mathbf{e}_x & -H-w \le y < -H+w \\ + * &= \mathbf{0} & \mathrm{otherwise} + * \f} + * + * where \a F is the force magnitude, \a H is the half-width between the + * block centers, and \a w is the block half-width. + * + * This force field can be used to implement the double-parabola method for measuring + * viscosity by setting \f$H = L_y/4\f$ and \f$w=L_y/4\f$, or to mimick the reverse + * nonequilibrium shear flow profile by setting \f$H = L_y/4\f$ and \a w to a small value. + */ +class __attribute__((visibility("default"))) BlockForce + { + public: + //! Default constructor + HOSTDEVICE BlockForce() : BlockForce(0, 0, 0) { } + + //! Constructor + /*! + * \param F Force on all particles. + * \param separation Separation between centers of blocks. + * \param width Width of each block. + */ + HOSTDEVICE BlockForce(Scalar F, Scalar separation, Scalar width) : m_F(F) + { + m_H_plus_w = Scalar(0.5) * (separation + width); + m_H_minus_w = Scalar(0.5) * (separation - width); + } + + //! Force evaluation method + /*! + * \param r Particle position. + * \returns Force on the particle. + */ + HOSTDEVICE Scalar3 evaluate(const Scalar3& r) const + { + // sign = +1 if in top slab, -1 if in bottom slab, 0 if neither + const signed char sign = (char)((r.y >= m_H_minus_w && r.y < m_H_plus_w) + - (r.y >= -m_H_plus_w && r.y < -m_H_minus_w)); + return make_scalar3(sign * m_F, 0, 0); + } + + //! Get the force in the block + Scalar getForce() const + { + return m_F; + } + + //! Set the force in the block + void setForce(Scalar F) + { + m_F = F; + } + + //! Get the separation distance between block centers + Scalar getSeparation() const + { + return (m_H_plus_w + m_H_minus_w); + } + + //! Set the separation distance between block centers + void setSeparation(Scalar H) + { + const Scalar w = getWidth(); + m_H_plus_w = Scalar(0.5) * (H + w); + m_H_minus_w = Scalar(0.5) * (H - w); + } + + //! Get the block width + Scalar getWidth() const + { + return (m_H_plus_w - m_H_minus_w); + } + + //! Set the block width + void setWidth(Scalar w) + { + const Scalar H = getSeparation(); + m_H_plus_w = Scalar(0.5) * (H + w); + m_H_minus_w = Scalar(0.5) * (H - w); + } + +#ifndef __HIPCC__ + //! Get the unique name of this force + static std::string getName() + { + return std::string("BlockForce"); + } +#endif // __HIPCC__ + + private: + Scalar m_F; //!< Constant force + Scalar m_H_plus_w; //!< Upper bound on upper block + Scalar m_H_minus_w; //!< Lower bound on upper block + }; + +#ifndef __HIPCC__ +namespace detail + { +void export_BlockForce(pybind11::module& m); + } // end namespace detail +#endif // __HIPCC__ + + } // end namespace mpcd + } // end namespace hoomd +#undef HOSTDEVICE +#undef INLINE + +#endif // MPCD_BLOCK_FORCE_H_ diff --git a/hoomd/mpcd/BounceBackNVE.h b/hoomd/mpcd/BounceBackNVE.h index e4f5a8bd51..8ff487437f 100644 --- a/hoomd/mpcd/BounceBackNVE.h +++ b/hoomd/mpcd/BounceBackNVE.h @@ -40,7 +40,7 @@ class PYBIND11_EXPORT BounceBackNVE : public hoomd::md::IntegrationMethodTwoStep //! Constructor BounceBackNVE(std::shared_ptr sysdef, std::shared_ptr group, - std::shared_ptr geom); + std::shared_ptr geom); //! Destructor virtual ~BounceBackNVE(); @@ -52,71 +52,42 @@ class PYBIND11_EXPORT BounceBackNVE : public hoomd::md::IntegrationMethodTwoStep virtual void integrateStepTwo(uint64_t timestep); //! Get the streaming geometry - std::shared_ptr getGeometry() + std::shared_ptr getGeometry() { return m_geom; } //! Set the streaming geometry - void setGeometry(std::shared_ptr geom) + void setGeometry(std::shared_ptr geom) { - requestValidate(); m_geom = geom; } - protected: - std::shared_ptr m_geom; //!< Bounce-back geometry - bool m_validate_geom; //!< If true, run a validation check on the geometry - - //! Validate the system with the streaming geometry - void validate(); - - private: - void requestValidate() - { - m_validate_geom = true; - } - //! Check that particles lie inside the geometry - bool validateParticles(); + bool checkParticles(); + + protected: + std::shared_ptr m_geom; //!< Bounce-back geometry }; template BounceBackNVE::BounceBackNVE(std::shared_ptr sysdef, std::shared_ptr group, - std::shared_ptr geom) - : IntegrationMethodTwoStep(sysdef, group), m_geom(geom), m_validate_geom(true) + std::shared_ptr geom) + : IntegrationMethodTwoStep(sysdef, group), m_geom(geom) { m_exec_conf->msg->notice(5) << "Constructing BounceBackNVE + " << Geometry::getName() << std::endl; - - m_pdata->getBoxChangeSignal() - .template connect, &BounceBackNVE::requestValidate>(this); } template BounceBackNVE::~BounceBackNVE() { m_exec_conf->msg->notice(5) << "Destroying BounceBackNVE + " << Geometry::getName() << std::endl; - - m_pdata->getBoxChangeSignal() - .template disconnect, &BounceBackNVE::requestValidate>( - this); } template void BounceBackNVE::integrateStepOne(uint64_t timestep) { - if (m_aniso) - { - m_exec_conf->msg->error() << "mpcd.integrate: anisotropic particles are not supported with " - "bounce-back integrators." - << std::endl; - throw std::runtime_error("Anisotropic integration not supported with bounce-back"); - } - - if (m_validate_geom) - validate(); - // particle data ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, @@ -174,13 +145,6 @@ template void BounceBackNVE::integrateStepOne(uint64_t template void BounceBackNVE::integrateStepTwo(uint64_t timestep) { - if (m_aniso) - { - m_exec_conf->msg->error() << "mpcd.integrate: anisotropic particles are not supported with " - "bounce-back integrators." - << std::endl; - throw std::runtime_error("Anisotropic integration not supported with bounce-back"); - } ArrayHandle h_vel(m_pdata->getVelocities(), access_location::host, access_mode::readwrite); @@ -218,40 +182,11 @@ template void BounceBackNVE::integrateStepTwo(uint64_t } } -template void BounceBackNVE::validate() - { - // ensure that the global box is padded enough for periodic boundaries - const BoxDim box = m_pdata->getGlobalBox(); - if (!m_geom->validateBox(box, 0.)) - { - m_exec_conf->msg->error() << "BounceBackNVE: box too small for " << Geometry::getName() - << " geometry. Increase box size." << std::endl; - throw std::runtime_error("Simulation box too small for bounce back method"); - } - - // check that no particles are out of bounds - unsigned char error = !validateParticles(); -#ifdef ENABLE_MPI - if (m_exec_conf->getNRanks() > 1) - MPI_Allreduce(MPI_IN_PLACE, - &error, - 1, - MPI_UNSIGNED_CHAR, - MPI_LOR, - m_exec_conf->getMPICommunicator()); -#endif // ENABLE_MPI - if (error) - throw std::runtime_error("Invalid particle configuration for bounce back geometry"); - - // validation completed, unset flag - m_validate_geom = false; - } - /*! * Checks each particle position to determine if it lies within the geometry. If any particle is * out of bounds, an error is raised. */ -template bool BounceBackNVE::validateParticles() +template bool BounceBackNVE::checkParticles() { ArrayHandle h_pos(m_pdata->getPositions(), access_location::host, access_mode::read); ArrayHandle h_tag(m_pdata->getTags(), access_location::host, access_mode::read); @@ -261,6 +196,7 @@ template bool BounceBackNVE::validateParticles() access_mode::read); const unsigned int group_size = m_group->getNumMembers(); + bool out_of_bounds = false; for (unsigned int idx = 0; idx < group_size; ++idx) { const unsigned int pid = h_group.data[idx]; @@ -269,15 +205,24 @@ template bool BounceBackNVE::validateParticles() const Scalar3 pos = make_scalar3(postype.x, postype.y, postype.z); if (m_geom->isOutside(pos)) { - m_exec_conf->msg->errorAllRanks() - << "Particle with tag " << h_tag.data[pid] << " at (" << pos.x << "," << pos.y - << "," << pos.z << ") lies outside the " << Geometry::getName() - << " geometry. Fix configuration." << std::endl; - return false; + out_of_bounds = true; + break; } } - return true; +#ifdef ENABLE_MPI + if (m_exec_conf->getNRanks() > 1) + { + MPI_Allreduce(MPI_IN_PLACE, + &out_of_bounds, + 1, + MPI_CXX_BOOL, + MPI_LOR, + m_exec_conf->getMPICommunicator()); + } +#endif // ENABLE_MPI + + return !out_of_bounds; } namespace detail @@ -292,10 +237,11 @@ template void export_BounceBackNVE(pybind11::module& m) std::shared_ptr>>(m, name.c_str()) .def(pybind11::init, std::shared_ptr, - std::shared_ptr>()) + std::shared_ptr>()) .def_property("geometry", &BounceBackNVE::getGeometry, - &BounceBackNVE::setGeometry); + &BounceBackNVE::setGeometry) + .def("check_particles", &BounceBackNVE::checkParticles); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BounceBackNVEGPU.cu b/hoomd/mpcd/BounceBackNVEGPU.cu index bf074c4597..ca9756dccb 100644 --- a/hoomd/mpcd/BounceBackNVEGPU.cu +++ b/hoomd/mpcd/BounceBackNVEGPU.cu @@ -18,13 +18,13 @@ namespace gpu { //! Template instantiation of slit geometry streaming template cudaError_t -nve_bounce_step_one(const bounce_args_t& args, - const mpcd::detail::SlitGeometry& geom); +nve_bounce_step_one(const bounce_args_t& args, + const mpcd::ParallelPlateGeometry& geom); //! Template instantiation of slit pore geometry streaming template cudaError_t -nve_bounce_step_one(const bounce_args_t& args, - const mpcd::detail::SlitPoreGeometry& geom); +nve_bounce_step_one(const bounce_args_t& args, + const mpcd::PlanarPoreGeometry& geom); namespace kernel { diff --git a/hoomd/mpcd/BounceBackNVEGPU.h b/hoomd/mpcd/BounceBackNVEGPU.h index c67684afd0..b50f868284 100644 --- a/hoomd/mpcd/BounceBackNVEGPU.h +++ b/hoomd/mpcd/BounceBackNVEGPU.h @@ -31,7 +31,7 @@ template class PYBIND11_EXPORT BounceBackNVEGPU : public BounceB //! Constructor BounceBackNVEGPU(std::shared_ptr sysdef, std::shared_ptr group, - std::shared_ptr geom) + std::shared_ptr geom) : BounceBackNVE(sysdef, group, geom) { m_tuner_1.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(this->m_exec_conf)}, @@ -64,9 +64,6 @@ template void BounceBackNVEGPU::integrateStepOne(uint6 throw std::runtime_error("Anisotropic integration not supported with bounce-back"); } - if (this->m_validate_geom) - this->validate(); - // particle data ArrayHandle d_pos(this->m_pdata->getPositions(), access_location::device, @@ -155,7 +152,7 @@ template void export_BounceBackNVEGPU(pybind11::module& m) std::shared_ptr>>(m, name.c_str()) .def(pybind11::init, std::shared_ptr, - std::shared_ptr>()); + std::shared_ptr>()); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BounceBackStreamingMethod.cc.inc b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc new file mode 100644 index 0000000000..4fa6f83200 --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethod.cc.inc @@ -0,0 +1,30 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BounceBackStreamingMethod@_geometry@@_force@.cc + * \brief Template instantation for BounceBackStreamingMethod with @_geometry@ and @_force@ + * + * The geometry and force classes are filled in by CMake using configure_file(). See + * mpcd/CMakeLists.txt for list of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/@_geometry@.h" +#include "hoomd/mpcd/BounceBackStreamingMethod.h" + +#define GEOMETRY_CLASS @_geometry@ +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BounceBackStreamingMethod(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/ConfinedStreamingMethod.h b/hoomd/mpcd/BounceBackStreamingMethod.h similarity index 59% rename from hoomd/mpcd/ConfinedStreamingMethod.h rename to hoomd/mpcd/BounceBackStreamingMethod.h index 1934a4a497..b80092ec92 100644 --- a/hoomd/mpcd/ConfinedStreamingMethod.h +++ b/hoomd/mpcd/BounceBackStreamingMethod.h @@ -2,8 +2,8 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/ConfinedStreamingMethod.h - * \brief Declaration of mpcd::ConfinedStreamingMethod + * \file mpcd/BounceBackStreamingMethod.h + * \brief Declaration of mpcd::BounceBackStreamingMethod */ #ifndef MPCD_CONFINED_STREAMING_METHOD_H_ @@ -25,23 +25,22 @@ namespace mpcd * This method implements the base version of ballistic propagation of MPCD * particles in confined geometries. * - * \tparam Geometry The confining geometry (e.g., BulkGeometry, SlitGeometry). + * \tparam Geometry The confining geometry. + * \tparam Force The solvent force. * * The integration scheme is essentially Verlet with specular reflections. The particle is streamed * forward over the time interval. If it moves outside the Geometry, it is placed back on the * boundary and its velocity is updated according to the boundary conditions. Streaming then * continues until the timestep is completed. * - * To facilitate this, every Geometry must supply three methods: + * To facilitate this, every Geometry must supply two methods: * 1. detectCollision(): Determines when and where a collision occurs. If one does, this method * moves the particle back, reflects its velocity, and gives the time still remaining to integrate. * 2. isOutside(): Determines whether a particles lies outside the Geometry. - * 3. validateBox(): Checks whether the global simulation box is consistent with the streaming - * geometry. * */ -template -class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod +template +class PYBIND11_EXPORT BounceBackStreamingMethod : public mpcd::StreamingMethod { public: //! Constructor @@ -51,14 +50,15 @@ class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod * \param period Number of timesteps between collisions * \param phase Phase shift for periodic updates * \param geom Streaming geometry + * \param force Solvent force */ - ConfinedStreamingMethod(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period, - int phase, - std::shared_ptr geom) - : mpcd::StreamingMethod(sysdef, cur_timestep, period, phase), m_geom(geom), - m_validate_geom(true) + BounceBackStreamingMethod(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase, + std::shared_ptr geom, + std::shared_ptr force) + : mpcd::StreamingMethod(sysdef, cur_timestep, period, phase), m_geom(geom), m_force(force) { } @@ -66,33 +66,42 @@ class PYBIND11_EXPORT ConfinedStreamingMethod : public mpcd::StreamingMethod virtual void stream(uint64_t timestep); //! Get the streaming geometry - std::shared_ptr getGeometry() const + std::shared_ptr getGeometry() const { return m_geom; } //! Set the streaming geometry - void setGeometry(std::shared_ptr geom) + void setGeometry(std::shared_ptr geom) { - m_validate_geom = true; m_geom = geom; } - protected: - std::shared_ptr m_geom; //!< Streaming geometry - bool m_validate_geom; //!< If true, run a validation check on the geometry + //! Set the solvent force + std::shared_ptr getForce() const + { + return m_force; + } - //! Validate the system with the streaming geometry - void validate(); + //! Get the solvent force + void setForce(std::shared_ptr force) + { + m_force = force; + } //! Check that particles lie inside the geometry - virtual bool validateParticles(); + virtual bool checkParticles(); + + protected: + std::shared_ptr m_geom; //!< Streaming geometry + std::shared_ptr m_force; //!< Solvent force }; /*! * \param timestep Current time to stream */ -template void ConfinedStreamingMethod::stream(uint64_t timestep) +template +void BounceBackStreamingMethod::stream(uint64_t timestep) { if (!shouldStream(timestep)) return; @@ -102,12 +111,6 @@ template void ConfinedStreamingMethod::stream(uint64_t throw std::runtime_error("Cell list has not been set"); } - if (m_validate_geom) - { - validate(); - m_validate_geom = false; - } - const BoxDim box = m_cl->getCoverageBox(); ArrayHandle h_pos(m_mpcd_pdata->getPositions(), @@ -118,8 +121,8 @@ template void ConfinedStreamingMethod::stream(uint64_t access_mode::readwrite); const Scalar mass = m_mpcd_pdata->getMass(); - // acquire polymorphic pointer to the external field - const mpcd::ExternalField* field = (m_field) ? m_field->get(access_location::host) : nullptr; + // default construct a force if one is not set + const Force force = (m_force) ? *m_force : Force(); for (unsigned int cur_p = 0; cur_p < m_mpcd_pdata->getN(); ++cur_p) { @@ -130,10 +133,7 @@ template void ConfinedStreamingMethod::stream(uint64_t const Scalar4 vel_cell = h_vel.data[cur_p]; Scalar3 vel = make_scalar3(vel_cell.x, vel_cell.y, vel_cell.z); // estimate next velocity based on current acceleration - if (field) - { - vel += Scalar(0.5) * m_mpcd_dt * field->evaluate(pos) / mass; - } + vel += Scalar(0.5) * m_mpcd_dt * force.evaluate(pos) / mass; // propagate the particle to its new position ballistically Scalar dt_remain = m_mpcd_dt; @@ -144,10 +144,7 @@ template void ConfinedStreamingMethod::stream(uint64_t collide = m_geom->detectCollision(pos, vel, dt_remain); } while (dt_remain > 0 && collide); // finalize velocity update - if (field) - { - vel += Scalar(0.5) * m_mpcd_dt * field->evaluate(pos) / mass; - } + vel += Scalar(0.5) * m_mpcd_dt * force.evaluate(pos) / mass; // wrap and update the position int3 image = make_int3(0, 0, 0); @@ -162,39 +159,12 @@ template void ConfinedStreamingMethod::stream(uint64_t m_mpcd_pdata->invalidateCellCache(); } -template void ConfinedStreamingMethod::validate() - { - // ensure that the global box is padded enough for periodic boundaries - const BoxDim box = m_pdata->getGlobalBox(); - const Scalar cell_width = m_cl->getCellSize(); - if (!m_geom->validateBox(box, cell_width)) - { - m_exec_conf->msg->error() << "ConfinedStreamingMethod: box too small for " - << Geometry::getName() << " geometry. Increase box size." - << std::endl; - throw std::runtime_error("Simulation box too small for confined streaming method"); - } - - // check that no particles are out of bounds - unsigned char error = !validateParticles(); -#ifdef ENABLE_MPI - if (m_exec_conf->getNRanks() > 1) - MPI_Allreduce(MPI_IN_PLACE, - &error, - 1, - MPI_UNSIGNED_CHAR, - MPI_LOR, - m_exec_conf->getMPICommunicator()); -#endif // ENABLE_MPI - if (error) - throw std::runtime_error("Invalid MPCD particle configuration for confined geometry"); - } - /*! * Checks each MPCD particle position to determine if it lies within the geometry. If any particle * is out of bounds, an error is raised. */ -template bool ConfinedStreamingMethod::validateParticles() +template +bool BounceBackStreamingMethod::checkParticles() { ArrayHandle h_pos(m_mpcd_pdata->getPositions(), access_location::host, @@ -203,21 +173,31 @@ template bool ConfinedStreamingMethod::validateParticl access_location::host, access_mode::read); + bool out_of_bounds = false; for (unsigned int idx = 0; idx < m_mpcd_pdata->getN(); ++idx) { const Scalar4 postype = h_pos.data[idx]; const Scalar3 pos = make_scalar3(postype.x, postype.y, postype.z); if (m_geom->isOutside(pos)) { - m_exec_conf->msg->errorAllRanks() - << "MPCD particle with tag " << h_tag.data[idx] << " at (" << pos.x << "," << pos.y - << "," << pos.z << ") lies outside the " << Geometry::getName() - << " geometry. Fix configuration." << std::endl; - return false; + out_of_bounds = true; + break; } } - return true; +#ifdef ENABLE_MPI + if (m_exec_conf->getNRanks() > 1) + { + MPI_Allreduce(MPI_IN_PLACE, + &out_of_bounds, + 1, + MPI_CXX_BOOL, + MPI_LOR, + m_exec_conf->getMPICommunicator()); + } +#endif // ENABLE_MPI + + return !out_of_bounds; } namespace detail @@ -226,20 +206,25 @@ namespace detail /*! * \param m Python module to export to */ -template void export_ConfinedStreamingMethod(pybind11::module& m) +template void export_BounceBackStreamingMethod(pybind11::module& m) { - const std::string name = "ConfinedStreamingMethod" + Geometry::getName(); - pybind11::class_, + const std::string name = "BounceBackStreamingMethod" + Geometry::getName() + Force::getName(); + pybind11::class_, mpcd::StreamingMethod, - std::shared_ptr>>(m, name.c_str()) + std::shared_ptr>>( + m, + name.c_str()) .def(pybind11::init, unsigned int, unsigned int, int, - std::shared_ptr>()) - .def_property("geometry", - &mpcd::ConfinedStreamingMethod::getGeometry, - &mpcd::ConfinedStreamingMethod::setGeometry); + std::shared_ptr, + std::shared_ptr>()) + .def_property_readonly("geometry", + &mpcd::BounceBackStreamingMethod::getGeometry) + .def_property_readonly("mpcd_particle_force", + &mpcd::BounceBackStreamingMethod::getForce) + .def("check_mpcd_particles", &BounceBackStreamingMethod::checkParticles); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc new file mode 100644 index 0000000000..874b3c787f --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cc.inc @@ -0,0 +1,30 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BounceBackStreamingMethod@_geometry@@_force@GPU.cc + * \brief Template instantation for BounceBackStreamingMethodGPU with @_geometry@ and @_force@ + * + * The geometry and force classes are filled in by CMake using configure_file(). See + * mpcd/CMakeLists.txt for list of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/@_geometry@.h" +#include "hoomd/mpcd/BounceBackStreamingMethodGPU.h" + +#define GEOMETRY_CLASS @_geometry@ +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BounceBackStreamingMethodGPU(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc new file mode 100644 index 0000000000..a762f1f95e --- /dev/null +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cu.inc @@ -0,0 +1,35 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BounceBackStreamingMethod@_geometry@@_force@GPU.cu + * \brief Template instantation for BounceBackStreamingMethodGPU driver (and so kernel) with + * @_geometry@ and @_force@. + * + * The geometry and force classes are filled in by CMake using configure_file(). See + * mpcd/CMakeLists.txt for list of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/@_geometry@.h" +#include "hoomd/mpcd/BounceBackStreamingMethodGPU.cuh" + +#define GEOMETRY_CLASS @_geometry@ +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace gpu + { +//! Template instantiation of bulk geometry streaming +template cudaError_t __attribute__((visibility("default"))) +confined_stream(const stream_args_t& args, + const GEOMETRY_CLASS& geom, + const FORCE_CLASS& force); + } // end namespace gpu + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.cuh b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh similarity index 77% rename from hoomd/mpcd/ConfinedStreamingMethodGPU.cuh rename to hoomd/mpcd/BounceBackStreamingMethodGPU.cuh index 66774dfb46..3d05c0c7c4 100644 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.cuh +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.cuh @@ -5,11 +5,10 @@ #define MPCD_CONFINED_STREAMING_METHOD_GPU_CUH_ /*! - * \file mpcd/ConfinedStreamingMethodGPU.cuh - * \brief Declaration of CUDA kernels for mpcd::ConfinedStreamingMethodGPU + * \file mpcd/BounceBackStreamingMethodGPU.cuh + * \brief Declaration of CUDA kernels for mpcd::BounceBackStreamingMethodGPU */ -#include "ExternalField.h" #include "ParticleDataUtilities.h" #include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" @@ -27,29 +26,28 @@ struct stream_args_t stream_args_t(Scalar4* _d_pos, Scalar4* _d_vel, const Scalar _mass, - const mpcd::ExternalField* _field, const BoxDim& _box, const Scalar _dt, const unsigned int _N, const unsigned int _block_size) - : d_pos(_d_pos), d_vel(_d_vel), mass(_mass), field(_field), box(_box), dt(_dt), N(_N), + : d_pos(_d_pos), d_vel(_d_vel), mass(_mass), box(_box), dt(_dt), N(_N), block_size(_block_size) { } - Scalar4* d_pos; //!< Particle positions - Scalar4* d_vel; //!< Particle velocities - const Scalar mass; //!< Particle mass - const mpcd::ExternalField* field; //!< Applied external field on particles - const BoxDim box; //!< Simulation box - const Scalar dt; //!< Timestep - const unsigned int N; //!< Number of particles - const unsigned int block_size; //!< Number of threads per block + Scalar4* d_pos; //!< Particle positions + Scalar4* d_vel; //!< Particle velocities + const Scalar mass; //!< Particle mass + const BoxDim box; //!< Simulation box + const Scalar dt; //!< Timestep + const unsigned int N; //!< Number of particles + const unsigned int block_size; //!< Number of threads per block }; //! Kernel driver to stream particles ballistically -template -cudaError_t confined_stream(const stream_args_t& args, const Geometry& geom); +template +cudaError_t +confined_stream(const stream_args_t& args, const Geometry& geom, const Force& solvent_force); #ifdef __HIPCC__ namespace kernel @@ -79,15 +77,15 @@ namespace kernel * Particles are appropriately reflected from the boundaries defined by \a geom during the * position update step. The particle positions and velocities are updated accordingly. */ -template +template __global__ void confined_stream(Scalar4* d_pos, Scalar4* d_vel, const Scalar mass, - const mpcd::ExternalField* field, const BoxDim box, const Scalar dt, const unsigned int N, - const Geometry geom) + const Geometry geom, + const Force force) { // one thread per particle unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; @@ -101,10 +99,7 @@ __global__ void confined_stream(Scalar4* d_pos, const Scalar4 vel_cell = d_vel[idx]; Scalar3 vel = make_scalar3(vel_cell.x, vel_cell.y, vel_cell.z); // estimate next velocity based on current acceleration - if (field) - { - vel += Scalar(0.5) * dt * field->evaluate(pos) / mass; - } + vel += Scalar(0.5) * dt * force.evaluate(pos) / mass; // propagate the particle to its new position ballistically Scalar dt_remain = dt; @@ -115,10 +110,7 @@ __global__ void confined_stream(Scalar4* d_pos, collide = geom.detectCollision(pos, vel, dt_remain); } while (dt_remain > 0 && collide); // finalize velocity update - if (field) - { - vel += Scalar(0.5) * dt * field->evaluate(pos) / mass; - } + vel += Scalar(0.5) * dt * force.evaluate(pos) / mass; // wrap and update the position int3 image = make_int3(0, 0, 0); @@ -138,12 +130,12 @@ __global__ void confined_stream(Scalar4* d_pos, * * \sa mpcd::gpu::kernel::confined_stream */ -template -cudaError_t confined_stream(const stream_args_t& args, const Geometry& geom) +template +cudaError_t confined_stream(const stream_args_t& args, const Geometry& geom, const Force& force) { unsigned int max_block_size; cudaFuncAttributes attr; - cudaFuncGetAttributes(&attr, (const void*)mpcd::gpu::kernel::confined_stream); + cudaFuncGetAttributes(&attr, (const void*)mpcd::gpu::kernel::confined_stream); max_block_size = attr.maxThreadsPerBlock; unsigned int run_block_size = min(args.block_size, max_block_size); @@ -151,11 +143,11 @@ cudaError_t confined_stream(const stream_args_t& args, const Geometry& geom) mpcd::gpu::kernel::confined_stream<<>>(args.d_pos, args.d_vel, args.mass, - args.field, args.box, args.dt, args.N, - geom); + geom, + force); return cudaSuccess; } diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.h b/hoomd/mpcd/BounceBackStreamingMethodGPU.h similarity index 57% rename from hoomd/mpcd/ConfinedStreamingMethodGPU.h rename to hoomd/mpcd/BounceBackStreamingMethodGPU.h index c1dc60c849..735388d546 100644 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.h +++ b/hoomd/mpcd/BounceBackStreamingMethodGPU.h @@ -2,8 +2,8 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/ConfinedStreamingMethodGPU.h - * \brief Declaration of mpcd::ConfinedStreamingMethodGPU + * \file mpcd/BounceBackStreamingMethodGPU.h + * \brief Declaration of mpcd::BounceBackStreamingMethodGPU */ #ifndef MPCD_CONFINED_STREAMING_METHOD_GPU_H_ @@ -13,8 +13,8 @@ #error This header cannot be compiled by nvcc #endif -#include "ConfinedStreamingMethod.h" -#include "ConfinedStreamingMethodGPU.cuh" +#include "BounceBackStreamingMethod.h" +#include "BounceBackStreamingMethodGPU.cuh" #include "hoomd/Autotuner.h" namespace hoomd @@ -26,8 +26,9 @@ namespace mpcd * This method implements the GPU version of ballistic propagation of MPCD * particles in a confined geometry. */ -template -class PYBIND11_EXPORT ConfinedStreamingMethodGPU : public mpcd::ConfinedStreamingMethod +template +class PYBIND11_EXPORT BounceBackStreamingMethodGPU + : public mpcd::BounceBackStreamingMethod { public: //! Constructor @@ -37,13 +38,20 @@ class PYBIND11_EXPORT ConfinedStreamingMethodGPU : public mpcd::ConfinedStreamin * \param period Number of timesteps between collisions * \param phase Phase shift for periodic updates * \param geom Streaming geometry + * \param force Solvent force */ - ConfinedStreamingMethodGPU(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period, - int phase, - std::shared_ptr geom) - : mpcd::ConfinedStreamingMethod(sysdef, cur_timestep, period, phase, geom) + BounceBackStreamingMethodGPU(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase, + std::shared_ptr geom, + std::shared_ptr force) + : mpcd::BounceBackStreamingMethod(sysdef, + cur_timestep, + period, + phase, + geom, + force) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(this->m_exec_conf)}, this->m_exec_conf, @@ -61,17 +69,15 @@ class PYBIND11_EXPORT ConfinedStreamingMethodGPU : public mpcd::ConfinedStreamin /*! * \param timestep Current time to stream */ -template void ConfinedStreamingMethodGPU::stream(uint64_t timestep) +template +void BounceBackStreamingMethodGPU::stream(uint64_t timestep) { if (!this->shouldStream(timestep)) return; - // the validation step currently proceeds on the cpu because it is done infrequently. - // if it becomes a performance concern, it can be ported to the gpu - if (this->m_validate_geom) + if (!this->m_cl) { - this->validate(); - this->m_validate_geom = false; + throw std::runtime_error("Cell list has not been set"); } ArrayHandle d_pos(this->m_mpcd_pdata->getPositions(), @@ -83,15 +89,16 @@ template void ConfinedStreamingMethodGPU::stream(uint6 mpcd::gpu::stream_args_t args(d_pos.data, d_vel.data, this->m_mpcd_pdata->getMass(), - (this->m_field) ? this->m_field->get(access_location::device) - : nullptr, this->m_cl->getCoverageBox(), this->m_mpcd_dt, this->m_mpcd_pdata->getN(), m_tuner->getParam()[0]); + // default construct a force if one is not set + const Force force = (this->m_force) ? *(this->m_force) : Force(); + m_tuner->begin(); - mpcd::gpu::confined_stream(args, *(this->m_geom)); + mpcd::gpu::confined_stream(args, *(this->m_geom), force); if (this->m_exec_conf->isCUDAErrorCheckingEnabled()) CHECK_CUDA_ERROR(); m_tuner->end(); @@ -106,17 +113,21 @@ namespace detail /*! * \param m Python module to export to */ -template void export_ConfinedStreamingMethodGPU(pybind11::module& m) +template void export_BounceBackStreamingMethodGPU(pybind11::module& m) { - const std::string name = "ConfinedStreamingMethodGPU" + Geometry::getName(); - pybind11::class_, - mpcd::ConfinedStreamingMethod, - std::shared_ptr>>(m, name.c_str()) + const std::string name + = "BounceBackStreamingMethod" + Geometry::getName() + Force::getName() + "GPU"; + pybind11::class_, + mpcd::BounceBackStreamingMethod, + std::shared_ptr>>( + m, + name.c_str()) .def(pybind11::init, unsigned int, unsigned int, int, - std::shared_ptr>()); + std::shared_ptr, + std::shared_ptr>()); } } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/BoundaryCondition.h b/hoomd/mpcd/BoundaryCondition.h deleted file mode 100644 index 34a5909d9b..0000000000 --- a/hoomd/mpcd/BoundaryCondition.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/BoundaryCondition.h - * \brief Definition of valid MPCD boundary conditions. - */ - -#ifndef MPCD_BOUNDARY_CONDITION_H_ -#define MPCD_BOUNDARY_CONDITION_H_ - -namespace hoomd - { -namespace mpcd - { -namespace detail - { -//! Boundary conditions at the surface -/*! - * Boundaries are currently allowed to either be "no slip" or "slip". The tangential - * component of the fluid velocity is zero at a no-slip surface, while the shear stress - * is zero at a slip surface. Both boundaries are no-penetration, so the normal component - * of the fluid velocity is zero. - */ -enum struct boundary : unsigned char - { - no_slip = 0, - slip - }; - - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd -#endif // MPCD_BOUNDARY_CONDITION_H_ diff --git a/hoomd/mpcd/BulkGeometry.h b/hoomd/mpcd/BulkGeometry.h index 72763174ab..03ba2d4ca2 100644 --- a/hoomd/mpcd/BulkGeometry.h +++ b/hoomd/mpcd/BulkGeometry.h @@ -28,8 +28,8 @@ namespace detail //! Bulk (periodic) geometry /*! * This geometry is for a bulk fluid, and hence all of its methods simply indicate no collision - * occurs. It exists so that we can leverage the ConfinedStreamingMethod integrator to still stream - * in bulk. + * occurs. It exists so that we can leverage the BounceBackStreamingMethod integrator to still + * stream in bulk. */ class __attribute__((visibility("default"))) BulkGeometry { @@ -62,15 +62,6 @@ class __attribute__((visibility("default"))) BulkGeometry return false; } - //! Validate the simulation box - /*! - * \returns True because the simulation box is always big enough to hold a bulk geometry. - */ - HOSTDEVICE bool validateBox(const BoxDim& box, Scalar cell_size) const - { - return true; - } - #ifndef __HIPCC__ //! Get the unique name of this geometry static std::string getName() diff --git a/hoomd/mpcd/BulkStreamingMethod.cc.inc b/hoomd/mpcd/BulkStreamingMethod.cc.inc new file mode 100644 index 0000000000..3cb773ca0a --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethod.cc.inc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethod@_force@.cc + * \brief Template instantation for BulkStreamingMethod with @_force@ + * + * The force class is filled in by CMake using configure_file(). See mpcd/CMakeLists.txt for list + * of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/BulkStreamingMethod.h" + +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BulkStreamingMethod(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethod.h b/hoomd/mpcd/BulkStreamingMethod.h new file mode 100644 index 0000000000..be75cd2f8f --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethod.h @@ -0,0 +1,70 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethod.h + * \brief Declaration of mpcd::BulkStreamingMethod + */ + +#ifndef MPCD_BULK_STREAMING_METHOD_H_ +#define MPCD_BULK_STREAMING_METHOD_H_ + +#ifdef __HIPCC__ +#error This header cannot be compiled by nvcc +#endif + +#include "BounceBackStreamingMethod.h" +#include "BulkGeometry.h" +#include + +namespace hoomd + { +namespace mpcd + { + +template +class PYBIND11_EXPORT BulkStreamingMethod + : public BounceBackStreamingMethod + { + public: + BulkStreamingMethod(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase, + std::shared_ptr force) + : mpcd::BounceBackStreamingMethod( + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) + { + } + }; + +namespace detail + { +//! Export mpcd::BulkStreamingMethod to python +/*! + * \param m Python module to export to + */ +template void export_BulkStreamingMethod(pybind11::module& m) + { + const std::string name = "BulkStreamingMethod" + Force::getName(); + pybind11::class_, + mpcd::StreamingMethod, + std::shared_ptr>>(m, name.c_str()) + .def(pybind11::init, + unsigned int, + unsigned int, + int, + std::shared_ptr>()) + .def_property_readonly("mpcd_particle_force", &mpcd::BulkStreamingMethod::getForce); + } + + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd + +#endif // MPCD_BULK_STREAMING_METHOD_H_ diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc new file mode 100644 index 0000000000..05aac04603 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cc.inc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethod@_force@GPU.cc + * \brief Template instantation for BulkStreamingMethodGPU with @_force@ + * + * The force class is filled in by CMake using configure_file(). See mpcd/CMakeLists.txt for list + * of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/BulkStreamingMethodGPU.h" + +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace detail + { +template void export_BulkStreamingMethodGPU(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc new file mode 100644 index 0000000000..26e1edb739 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.cu.inc @@ -0,0 +1,33 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethod@_force@GPU.cu + * \brief Template instantation for BulkStreamingMethodGPU driver (and so kernel) with @_force@ + * + * The force class is filled in by CMake using configure_file(). See mpcd/CMakeLists.txt for list + * of values that are used. + */ + +// clang-format off +#include "hoomd/mpcd/@_force@.h" +#include "hoomd/mpcd/BulkGeometry.h" +#include "hoomd/mpcd/BounceBackStreamingMethodGPU.cuh" + +#define FORCE_CLASS @_force@ +// clang-format on + +namespace hoomd + { +namespace mpcd + { +namespace gpu + { +//! Template instantiation of bulk geometry streaming +template cudaError_t __attribute__((visibility("default"))) +confined_stream(const stream_args_t& args, + const detail::BulkGeometry& geom, + const FORCE_CLASS& force); + } // end namespace gpu + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/BulkStreamingMethodGPU.h b/hoomd/mpcd/BulkStreamingMethodGPU.h new file mode 100644 index 0000000000..4e7b8b6f37 --- /dev/null +++ b/hoomd/mpcd/BulkStreamingMethodGPU.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/BulkStreamingMethodGPU.h + * \brief Declaration of mpcd::BulkStreamingMethodGPU + */ + +#ifndef MPCD_BULK_STREAMING_METHOD_GPU_H_ +#define MPCD_BULK_STREAMING_METHOD_GPU_H_ + +#ifdef __HIPCC__ +#error This header cannot be compiled by nvcc +#endif + +#include "BounceBackStreamingMethodGPU.h" +#include "BulkGeometry.h" +#include + +namespace hoomd + { +namespace mpcd + { + +template +class PYBIND11_EXPORT BulkStreamingMethodGPU + : public BounceBackStreamingMethodGPU + { + public: + BulkStreamingMethodGPU(std::shared_ptr sysdef, + unsigned int cur_timestep, + unsigned int period, + int phase, + std::shared_ptr force) + : mpcd::BounceBackStreamingMethodGPU( + sysdef, + cur_timestep, + period, + phase, + std::make_shared(), + force) + { + } + }; + +namespace detail + { +//! Export mpcd::BulkStreamingMethodGPU to python +/*! + * \param m Python module to export to + */ +template void export_BulkStreamingMethodGPU(pybind11::module& m) + { + const std::string name = "BulkStreamingMethod" + Force::getName() + "GPU"; + pybind11::class_, + mpcd::StreamingMethod, + std::shared_ptr>>(m, name.c_str()) + .def(pybind11::init, + unsigned int, + unsigned int, + int, + std::shared_ptr>()) + .def_property_readonly("force", &mpcd::BulkStreamingMethodGPU::getForce); + } + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd + +#endif // MPCD_BULK_STREAMING_METHOD_GPU_H_ diff --git a/hoomd/mpcd/CMakeLists.txt b/hoomd/mpcd/CMakeLists.txt index 42c5be8431..64d3752659 100644 --- a/hoomd/mpcd/CMakeLists.txt +++ b/hoomd/mpcd/CMakeLists.txt @@ -1,9 +1,9 @@ -if (NOT BUILD_MD) +if(NOT BUILD_MD) message(FATAL_ERROR "MPCD package cannot be built without MD.") -endif(NOT BUILD_MD) +endif() -set(_mpcd_sources +set(_mpcd_cc_sources module.cc ATCollisionMethod.cc CellCommunicator.cc @@ -11,10 +11,10 @@ set(_mpcd_sources CellList.cc CollisionMethod.cc Communicator.cc - ExternalField.cc Integrator.cc - SlitGeometryFiller.cc - SlitPoreGeometryFiller.cc + ManualVirtualParticleFiller.cc + ParallelPlateGeometryFiller.cc + PlanarPoreGeometryFiller.cc Sorter.cc SRDCollisionMethod.cc StreamingGeometry.cc @@ -25,24 +25,24 @@ set(_mpcd_sources set(_mpcd_headers ATCollisionMethod.h BounceBackNVE.h - BoundaryCondition.h BulkGeometry.h + BulkStreamingMethod.h CellCommunicator.h CellThermoCompute.h CellList.h CollisionMethod.h - ConfinedStreamingMethod.h + BounceBackStreamingMethod.h Communicator.h CommunicatorUtilities.h - ExternalField.h Integrator.h + ManualVirtualParticleFiller.h ParticleData.h ParticleDataSnapshot.h ParticleDataUtilities.h - SlitGeometry.h - SlitGeometryFiller.h - SlitPoreGeometry.h - SlitPoreGeometryFiller.h + ParallelPlateGeometry.h + ParallelPlateGeometryFiller.h + PlanarPoreGeometry.h + PlanarPoreGeometryFiller.h Sorter.h SRDCollisionMethod.h StreamingGeometry.h @@ -50,115 +50,158 @@ set(_mpcd_headers VirtualParticleFiller.h ) -if (ENABLE_HIP) -list(APPEND _mpcd_sources - ATCollisionMethodGPU.cc - CellThermoComputeGPU.cc - CellListGPU.cc - CommunicatorGPU.cc - SlitGeometryFillerGPU.cc - SlitPoreGeometryFillerGPU.cc - SorterGPU.cc - SRDCollisionMethodGPU.cc - ) -list(APPEND _mpcd_headers - ATCollisionMethodGPU.cuh - ATCollisionMethodGPU.h - BounceBackNVEGPU.cuh - BounceBackNVEGPU.h - CellCommunicator.cuh - CellThermoComputeGPU.cuh - CellThermoComputeGPU.h - CellListGPU.cuh - CellListGPU.h - CommunicatorGPU.cuh - CommunicatorGPU.h - ConfinedStreamingMethodGPU.cuh - ConfinedStreamingMethodGPU.h - ParticleData.cuh - SlitGeometryFillerGPU.cuh - SlitGeometryFillerGPU.h - SlitPoreGeometryFillerGPU.cuh - SlitPoreGeometryFillerGPU.h - SorterGPU.cuh - SorterGPU.h - SRDCollisionMethodGPU.cuh - SRDCollisionMethodGPU.h - ) +set(_mpcd_cu_sources "") + +if(ENABLE_HIP) + list(APPEND _mpcd_cc_sources + ATCollisionMethodGPU.cc + CellThermoComputeGPU.cc + CellListGPU.cc + CommunicatorGPU.cc + ParallelPlateGeometryFillerGPU.cc + PlanarPoreGeometryFillerGPU.cc + SorterGPU.cc + SRDCollisionMethodGPU.cc + ) + list(APPEND _mpcd_headers + ATCollisionMethodGPU.cuh + ATCollisionMethodGPU.h + BounceBackNVEGPU.cuh + BounceBackNVEGPU.h + BulkStreamingMethodGPU.h + CellCommunicator.cuh + CellThermoComputeGPU.cuh + CellThermoComputeGPU.h + CellListGPU.cuh + CellListGPU.h + CommunicatorGPU.cuh + CommunicatorGPU.h + BounceBackStreamingMethodGPU.cuh + BounceBackStreamingMethodGPU.h + ParticleData.cuh + ParallelPlateGeometryFillerGPU.cuh + ParallelPlateGeometryFillerGPU.h + PlanarPoreGeometryFillerGPU.cuh + PlanarPoreGeometryFillerGPU.h + SorterGPU.cuh + SorterGPU.h + SRDCollisionMethodGPU.cuh + SRDCollisionMethodGPU.h + ) + set(_mpcd_cu_sources + ATCollisionMethodGPU.cu + BounceBackNVEGPU.cu + CellThermoComputeGPU.cu + CellListGPU.cu + CommunicatorGPU.cu + ParticleData.cu + ParallelPlateGeometryFillerGPU.cu + PlanarPoreGeometryFillerGPU.cu + SorterGPU.cu + SRDCollisionMethodGPU.cu + ) endif() -set(_mpcd_cu_sources - ATCollisionMethodGPU.cu - BounceBackNVEGPU.cu - CellThermoComputeGPU.cu - CellListGPU.cu - ConfinedStreamingMethodGPU.cu - CommunicatorGPU.cu - ExternalField.cu - ParticleData.cu - SlitGeometryFillerGPU.cu - SlitPoreGeometryFillerGPU.cu - SorterGPU.cu - SRDCollisionMethodGPU.cu +# generate cc and cu templates for forces and streaming geometries +set(_forces + BlockForce + ConstantForce + SineForce + NoForce ) - -if (ENABLE_HIP) - set(_cuda_sources ${_mpcd_cu_sources}) +set(_geometries + ParallelPlateGeometry + PlanarPoreGeometry + ) +foreach(_force ${_forces}) + # append force itself first + list(APPEND _mpcd_cc_sources ${_force}.cc) + list(APPEND _mpcd_headers ${_force}.h) + + # bulk geometry is special + configure_file( + BulkStreamingMethod.cc.inc + BulkStreamingMethod${_force}.cc + @ONLY + ) + list(APPEND _mpcd_cc_sources BulkStreamingMethod${_force}.cc) + if(ENABLE_HIP) + configure_file( + BulkStreamingMethodGPU.cc.inc + BulkStreamingMethod${_force}GPU.cc + @ONLY + ) + configure_file( + BulkStreamingMethodGPU.cu.inc + BulkStreamingMethod${_force}GPU.cu + @ONLY + ) + list(APPEND _mpcd_cc_sources BulkStreamingMethod${_force}GPU.cc) + list(APPEND _mpcd_cu_sources BulkStreamingMethod${_force}GPU.cu) + endif() + + # then the bounce back geometries + foreach(_geometry ${_geometries}) + configure_file( + BounceBackStreamingMethod.cc.inc + BounceBackStreamingMethod${_geometry}${_force}.cc + @ONLY + ) + list(APPEND _mpcd_cc_sources BounceBackStreamingMethod${_geometry}${_force}.cc) + if(ENABLE_HIP) + configure_file( + BounceBackStreamingMethodGPU.cc.inc + BounceBackStreamingMethod${_geometry}${_force}GPU.cc + @ONLY + ) + configure_file( + BounceBackStreamingMethodGPU.cu.inc + BounceBackStreamingMethod${_geometry}${_force}GPU.cu + @ONLY + ) + list(APPEND _mpcd_cc_sources BounceBackStreamingMethod${_geometry}${_force}GPU.cc) + list(APPEND _mpcd_cu_sources BounceBackStreamingMethod${_geometry}${_force}GPU.cu) + endif() + endforeach() +endforeach() + +if(ENABLE_HIP) + set_source_files_properties(${_mpcd_cu_sources} PROPERTIES LANGUAGE ${HOOMD_DEVICE_LANGUAGE}) endif (ENABLE_HIP) -hoomd_add_module(_mpcd SHARED ${_mpcd_sources} ${LINK_OBJ} ${_cuda_sources} ${_mpcd_headers} NO_EXTRAS) +hoomd_add_module(_mpcd SHARED ${_mpcd_cc_sources} ${_mpcd_cu_sources} ${_mpcd_headers} NO_EXTRAS) # alias into the HOOMD namespace so that plugins and symlinked components both work add_library(HOOMD::_mpcd ALIAS _mpcd) -if (APPLE) +if(APPLE) set_target_properties(_mpcd PROPERTIES INSTALL_RPATH "@loader_path/..;@loader_path/../md;@loader_path") else() set_target_properties(_mpcd PROPERTIES INSTALL_RPATH "\$ORIGIN/..;\$ORIGIN/../md;\$ORIGIN") endif() -if (ENABLE_HIP) - # Separable compilation is needed to support ExternalField.cu polymorphism. - # This likely breaks plugin support though. :-( - set_property(TARGET _mpcd PROPERTY CUDA_SEPARABLE_COMPILATION ON) -endif() - # link the library to its dependencies -target_link_libraries(_mpcd PUBLIC _md) -if (ENABLE_HIP) - target_link_libraries(_mpcd PUBLIC CUDA::cudadevrt) -endif (ENABLE_HIP) +target_link_libraries(_mpcd PUBLIC _hoomd _md) # install the library -install(TARGETS _mpcd EXPORT HOOMDTargets - LIBRARY DESTINATION ${PYTHON_SITE_INSTALL_DIR}/mpcd - ) +install(TARGETS _mpcd EXPORT HOOMDTargets LIBRARY DESTINATION ${PYTHON_SITE_INSTALL_DIR}/mpcd) +install(FILES ${_mpcd_headers} DESTINATION ${PYTHON_SITE_INSTALL_DIR}/include/hoomd/mpcd) -################ Python only modules -# copy python modules to the build directory to make it a working python package +# install and also copy python modules to the build directory to make it a working python package set(files __init__.py collide.py + fill.py force.py + geometry.py integrate.py + methods.py stream.py - update.py + tune.py ) - -install(FILES ${files} - DESTINATION ${PYTHON_SITE_INSTALL_DIR}/mpcd - ) - +install(FILES ${files} DESTINATION ${PYTHON_SITE_INSTALL_DIR}/mpcd) copy_files_to_build("${files}" "mpcd" "*.py") -# install headers in installation target -install(FILES ${_mpcd_headers} - DESTINATION ${PYTHON_SITE_INSTALL_DIR}/include/hoomd/mpcd - ) - -if (BUILD_TESTING) - add_subdirectory(test) +if(BUILD_TESTING) + add_subdirectory(test) endif() add_subdirectory(pytest) -if (BUILD_VALIDATION) - # add_subdirectory(validation) -endif (BUILD_VALIDATION) diff --git a/hoomd/mpcd/CellList.cc b/hoomd/mpcd/CellList.cc index 2e9100c22e..b40d13ffe8 100644 --- a/hoomd/mpcd/CellList.cc +++ b/hoomd/mpcd/CellList.cc @@ -7,6 +7,8 @@ #include "Communicator.h" #include "hoomd/Communicator.h" #endif // ENABLE_MPI +#include "hoomd/RNGIdentifiers.h" +#include "hoomd/RandomNumbers.h" /*! * \file mpcd/CellList.cc @@ -15,8 +17,8 @@ namespace hoomd { -mpcd::CellList::CellList(std::shared_ptr sysdef) - : Compute(sysdef), m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_cell_size(1.0), +mpcd::CellList::CellList(std::shared_ptr sysdef, Scalar cell_size, bool shift) + : Compute(sysdef), m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_cell_size(cell_size), m_cell_np_max(4), m_cell_np(m_exec_conf), m_cell_list(m_exec_conf), m_embed_cell_ids(m_exec_conf), m_conditions(m_exec_conf), m_needs_compute_dim(true), m_particles_sorted(false), m_virtual_change(false) @@ -28,6 +30,7 @@ mpcd::CellList::CellList(std::shared_ptr sysdef) m_cell_dim = make_uint3(0, 0, 0); m_global_cell_dim = make_uint3(0, 0, 0); + m_enable_grid_shift = shift; m_grid_shift = make_scalar3(0.0, 0.0, 0.0); m_max_grid_shift = 0.5 * m_cell_size; m_origin_idx = make_int3(0, 0, 0); @@ -81,6 +84,9 @@ void mpcd::CellList::compute(uint64_t timestep) if (peekCompute(timestep)) { + // ensure grid is shifted + drawGridShift(timestep); + #ifdef ENABLE_MPI // exchange embedded particles if necessary if (m_sysdef->isDomainDecomposed() && needsEmbedMigrate(timestep)) @@ -853,6 +859,36 @@ void mpcd::CellList::resetConditions() m_conditions.resetFlags(make_uint3(0, 0, 0)); } +/*! + * \param timestep Timestep to set shifting for + * + * \post The MPCD cell list has its grid shift set for \a timestep. + * + * If grid shifting is enabled, three uniform random numbers are drawn using + * the Mersenne twister generator. (In two dimensions, only two numbers are drawn.) + * + * If grid shifting is disabled, a zero vector is instead set. + */ +void mpcd::CellList::drawGridShift(uint64_t timestep) + { + if (m_enable_grid_shift) + { + uint16_t seed = m_sysdef->getSeed(); + + // PRNG using seed and timestep as seeds + hoomd::RandomGenerator rng(hoomd::Seed(hoomd::RNGIdentifier::MPCDCellList, timestep, seed), + hoomd::Counter()); + + // draw shift variables from uniform distribution + hoomd::UniformDistribution uniform(-m_max_grid_shift, m_max_grid_shift); + Scalar3 shift; + shift.x = uniform(rng); + shift.y = uniform(rng); + shift.z = (m_sysdef->getNDimensions() == 3) ? uniform(rng) : Scalar(0.0); + setGridShift(shift); + } + } + void mpcd::CellList::getCellStatistics() const { unsigned int min_np(0xffffffff), max_np(0); @@ -930,10 +966,11 @@ const int3 mpcd::CellList::wrapGlobalCell(const int3& cell) const void mpcd::detail::export_CellList(pybind11::module& m) { pybind11::class_>(m, "CellList") - .def(pybind11::init>()) + .def(pybind11::init, Scalar, bool>()) .def_property("cell_size", &mpcd::CellList::getCellSize, &mpcd::CellList::setCellSize) - .def("setEmbeddedGroup", &mpcd::CellList::setEmbeddedGroup) - .def("removeEmbeddedGroup", &mpcd::CellList::removeEmbeddedGroup); + .def_property("shift", + &mpcd::CellList::isGridShifting, + &mpcd::CellList::enableGridShifting); } } // end namespace hoomd diff --git a/hoomd/mpcd/CellList.h b/hoomd/mpcd/CellList.h index e20acc1fce..f87389c4f4 100644 --- a/hoomd/mpcd/CellList.h +++ b/hoomd/mpcd/CellList.h @@ -34,7 +34,7 @@ class PYBIND11_EXPORT CellList : public Compute { public: //! Constructor - CellList(std::shared_ptr sysdef); + CellList(std::shared_ptr sysdef, Scalar cell_size, bool shift); //! Destructor virtual ~CellList(); @@ -170,12 +170,37 @@ class PYBIND11_EXPORT CellList : public Compute bool isCommunicating(mpcd::detail::face dir); #endif // ENABLE_MPI + //! Get whether grid shifting is enabled + bool isGridShifting() const + { + return m_enable_grid_shift; + } + + //! Toggle the grid shifting on or off + /*! + * \param enable_grid_shift Flag to enable grid shifting if true + */ + void enableGridShifting(bool enable_grid_shift) + { + m_enable_grid_shift = enable_grid_shift; + if (!m_enable_grid_shift) + { + setGridShift(make_scalar3(0, 0, 0)); + } + } + //! Get the maximum permitted grid shift const Scalar getMaxGridShift() const { return m_max_grid_shift; } + // Get the grid shift vector + const Scalar3& getGridShift() const + { + return m_grid_shift; + } + //! Set the grid shift vector void setGridShift(const Scalar3& shift) { @@ -192,11 +217,8 @@ class PYBIND11_EXPORT CellList : public Compute m_grid_shift = shift; } - // Get the grid shift vector - const Scalar3& getGridShift() const - { - return m_grid_shift; - } + //! Generates the random grid shift vector + void drawGridShift(uint64_t timestep); //! Calculate current cell occupancy statistics virtual void getCellStatistics() const; @@ -217,12 +239,6 @@ class PYBIND11_EXPORT CellList : public Compute } } - //! Removes all embedded particles from collision coupling - void removeEmbeddedGroup() - { - setEmbeddedGroup(std::shared_ptr()); - } - //! Gets the cell id array for the embedded particles const GPUArray& getEmbeddedGroupCellIds() const { @@ -243,8 +259,9 @@ class PYBIND11_EXPORT CellList : public Compute std::shared_ptr m_mpcd_pdata; //!< MPCD particle data std::shared_ptr m_embed_group; //!< Embedded particles - Scalar3 m_grid_shift; //!< Amount to shift particle positions when computing cell list - Scalar m_max_grid_shift; //!< Maximum amount grid can be shifted in any direction + bool m_enable_grid_shift; //!< Flag to enable grid shifting + Scalar3 m_grid_shift; //!< Amount to shift particle positions when computing cell list + Scalar m_max_grid_shift; //!< Maximum amount grid can be shifted in any direction //! Allocates internal data arrays virtual void reallocate(); diff --git a/hoomd/mpcd/CellListGPU.cc b/hoomd/mpcd/CellListGPU.cc index e422f4efcf..be3f36fd78 100644 --- a/hoomd/mpcd/CellListGPU.cc +++ b/hoomd/mpcd/CellListGPU.cc @@ -11,7 +11,10 @@ namespace hoomd { -mpcd::CellListGPU::CellListGPU(std::shared_ptr sysdef) : mpcd::CellList(sysdef) +mpcd::CellListGPU::CellListGPU(std::shared_ptr sysdef, + Scalar cell_size, + bool shift) + : mpcd::CellList(sysdef, cell_size, shift) { m_tuner_cell.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -210,7 +213,7 @@ void mpcd::detail::export_CellListGPU(pybind11::module& m) pybind11::class_>( m, "CellListGPU") - .def(pybind11::init>()); + .def(pybind11::init, Scalar, bool>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/CellListGPU.h b/hoomd/mpcd/CellListGPU.h index 94936d1911..8f07cc2525 100644 --- a/hoomd/mpcd/CellListGPU.h +++ b/hoomd/mpcd/CellListGPU.h @@ -25,7 +25,7 @@ class PYBIND11_EXPORT CellListGPU : public mpcd::CellList { public: //! Constructor - CellListGPU(std::shared_ptr sysdef); + CellListGPU(std::shared_ptr sysdef, Scalar cell_size, bool shift); virtual ~CellListGPU(); diff --git a/hoomd/mpcd/CollisionMethod.cc b/hoomd/mpcd/CollisionMethod.cc index adf7cb14f3..56af41d5c5 100644 --- a/hoomd/mpcd/CollisionMethod.cc +++ b/hoomd/mpcd/CollisionMethod.cc @@ -7,8 +7,6 @@ */ #include "CollisionMethod.h" -#include "hoomd/RNGIdentifiers.h" -#include "hoomd/RandomNumbers.h" namespace hoomd { @@ -25,7 +23,7 @@ mpcd::CollisionMethod::CollisionMethod(std::shared_ptr sysdef, int phase) : m_sysdef(sysdef), m_pdata(m_sysdef->getParticleData()), m_mpcd_pdata(sysdef->getMPCDParticleData()), m_exec_conf(m_pdata->getExecConf()), - m_period(period), m_enable_grid_shift(true) + m_period(period) { // setup next timestep for collision m_next_timestep = cur_timestep; @@ -51,7 +49,7 @@ void mpcd::CollisionMethod::collide(uint64_t timestep) m_cl->setEmbeddedGroup(m_embed_group); // set random grid shift - drawGridShift(timestep); + m_cl->drawGridShift(timestep); // update cell list m_cl->compute(timestep); @@ -82,7 +80,7 @@ bool mpcd::CollisionMethod::peekCollide(uint64_t timestep) const * The collision method period is updated to \a period only if collision would occur at \a * cur_timestep. It is the caller's responsibility to ensure this condition is valid. */ -void mpcd::CollisionMethod::setPeriod(unsigned int cur_timestep, unsigned int period) +void mpcd::CollisionMethod::setPeriod(uint64_t cur_timestep, uint64_t period) { if (!peekCollide(cur_timestep)) { @@ -129,44 +127,6 @@ bool mpcd::CollisionMethod::shouldCollide(uint64_t timestep) } } -/*! - * \param timestep Timestep to set shifting for - * - * \post The MPCD cell list has its grid shift set for \a timestep. - * - * If grid shifting is enabled, three uniform random numbers are drawn using - * the Mersenne twister generator. (In two dimensions, only two numbers are drawn.) - * - * If grid shifting is disabled, a zero vector is instead set. - */ -void mpcd::CollisionMethod::drawGridShift(uint64_t timestep) - { - uint16_t seed = m_sysdef->getSeed(); - - // return zeros if shifting is off - if (!m_enable_grid_shift) - { - m_cl->setGridShift(make_scalar3(0.0, 0.0, 0.0)); - } - else - { - // PRNG using seed and timestep as seeds - hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::CollisionMethod, timestep, seed), - hoomd::Counter(m_instance)); - const Scalar max_shift = m_cl->getMaxGridShift(); - - // draw shift variables from uniform distribution - Scalar3 shift; - hoomd::UniformDistribution uniform(-max_shift, max_shift); - shift.x = uniform(rng); - shift.y = uniform(rng); - shift.z = (m_sysdef->getNDimensions() == 3) ? uniform(rng) : Scalar(0.0); - - m_cl->setGridShift(shift); - } - } - /*! * \param m Python module to export to */ @@ -176,12 +136,15 @@ void mpcd::detail::export_CollisionMethod(pybind11::module& m) m, "CollisionMethod") .def(pybind11::init, uint64_t, uint64_t, int>()) - .def("enableGridShifting", &mpcd::CollisionMethod::enableGridShifting) + .def_property_readonly("embedded_particles", + [](const std::shared_ptr method) + { + auto group = method->getEmbeddedGroup(); + return (group) ? group->getFilter() + : std::shared_ptr(); + }) .def("setEmbeddedGroup", &mpcd::CollisionMethod::setEmbeddedGroup) - .def("setPeriod", &mpcd::CollisionMethod::setPeriod) - .def_property("instance", - &mpcd::CollisionMethod::getInstance, - &mpcd::CollisionMethod::setInstance); + .def_property_readonly("period", &mpcd::CollisionMethod::getPeriod); } } // end namespace hoomd diff --git a/hoomd/mpcd/CollisionMethod.h b/hoomd/mpcd/CollisionMethod.h index 3573c895d8..0f1abe258e 100644 --- a/hoomd/mpcd/CollisionMethod.h +++ b/hoomd/mpcd/CollisionMethod.h @@ -47,18 +47,12 @@ class PYBIND11_EXPORT CollisionMethod : public Autotuned //! Peek if a collision will occur on this timestep virtual bool peekCollide(uint64_t timestep) const; - //! Toggle the grid shifting on or off - /*! - * \param enable_grid_shift Flag to enable grid shifting if true - */ - void enableGridShifting(bool enable_grid_shift) + //! Get the particle group that is coupled to the MPCD solvent through the collision step. + std::shared_ptr getEmbeddedGroup() { - m_enable_grid_shift = enable_grid_shift; + return m_embed_group; } - //! Generates the random grid shift vector - void drawGridShift(uint64_t timestep); - //! Sets a group of particles that is coupled to the MPCD solvent through the collision step /*! * \param embed_group Group to embed @@ -72,20 +66,14 @@ class PYBIND11_EXPORT CollisionMethod : public Autotuned } } - //! Set the period of the collision method - void setPeriod(unsigned int cur_timestep, unsigned int period); - - /// Set the RNG instance - void setInstance(unsigned int instance) + //! Get the period of the collision method + uint64_t getPeriod() const { - m_instance = instance; + return m_period; } - /// Get the RNG instance - unsigned int getInstance() - { - return m_instance; - } + //! Set the period of the collision method + void setPeriod(uint64_t cur_timestep, uint64_t period); //! Get the cell list used for collisions std::shared_ptr getCellList() const @@ -115,15 +103,11 @@ class PYBIND11_EXPORT CollisionMethod : public Autotuned uint64_t m_period; //!< Number of timesteps between collisions uint64_t m_next_timestep; //!< Timestep next collision should be performed - unsigned int m_instance = 0; //!< Unique ID for RNG seeding - //! Check if a collision should occur and advance the timestep counter virtual bool shouldCollide(uint64_t timestep); //! Call the collision rule virtual void rule(uint64_t timestep) { } - - bool m_enable_grid_shift; //!< Flag to enable grid shifting }; namespace detail diff --git a/hoomd/mpcd/ConfinedStreamingMethodGPU.cu b/hoomd/mpcd/ConfinedStreamingMethodGPU.cu deleted file mode 100644 index de5a6b5011..0000000000 --- a/hoomd/mpcd/ConfinedStreamingMethodGPU.cu +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/ConfinedStreamingMethodGPU.cu - * \brief Defines GPU functions and kernels used by mpcd::ConfinedStreamingMethodGPU - * - * \warning - * This file needs separable compilation with ExternalFields.cu. Any plugins extending - * the ConfinedStreamingGeometryGPU will also need to do separable compilation with - * ExternalFields.cu. - */ - -#include "ConfinedStreamingMethodGPU.cuh" -#include "StreamingGeometry.h" - -#include "ExternalField.h" -#include "hoomd/GPUPolymorph.cuh" - -namespace hoomd - { -namespace mpcd - { -namespace gpu - { -//! Template instantiation of bulk geometry streaming -template cudaError_t __attribute__((visibility("default"))) -confined_stream(const stream_args_t& args, - const mpcd::detail::BulkGeometry& geom); - -//! Template instantiation of slit geometry streaming -template cudaError_t __attribute__((visibility("default"))) -confined_stream(const stream_args_t& args, - const mpcd::detail::SlitGeometry& geom); - -//! Template instantiation of slit geometry streaming -template cudaError_t -confined_stream(const stream_args_t& args, - const mpcd::detail::SlitPoreGeometry& geom); - - } // end namespace gpu - } // end namespace mpcd - } // end namespace hoomd diff --git a/hoomd/mpcd/ConstantForce.cc b/hoomd/mpcd/ConstantForce.cc new file mode 100644 index 0000000000..3c4de8f6ac --- /dev/null +++ b/hoomd/mpcd/ConstantForce.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ConstantForce.cc + * \brief Export MPCD ConstantForce. + */ + +#include "ConstantForce.h" + +namespace hoomd + { +namespace mpcd + { +namespace detail + { + +void export_ConstantForce(pybind11::module& m) + { + pybind11::class_>(m, "ConstantForce") + .def(pybind11::init()) + .def_property( + "force", + [](const ConstantForce& force) + { + const auto F = force.getForce(); + return pybind11::make_tuple(F.x, F.y, F.z); + }, + [](ConstantForce& force, const pybind11::tuple& F) + { + force.setForce(make_scalar3(pybind11::cast(F[0]), + pybind11::cast(F[1]), + pybind11::cast(F[2]))); + }); + } + + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/ConstantForce.h b/hoomd/mpcd/ConstantForce.h new file mode 100644 index 0000000000..a1338eaf63 --- /dev/null +++ b/hoomd/mpcd/ConstantForce.h @@ -0,0 +1,88 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ConstantForce.h + * \brief Definition of mpcd::ConstantForce. + */ + +#ifndef MPCD_CONSTANT_FORCE_H_ +#define MPCD_CONSTANT_FORCE_H_ + +#include "hoomd/HOOMDMath.h" + +#ifdef __HIPCC__ +#define HOSTDEVICE __host__ __device__ +#define INLINE inline +#else +#define HOSTDEVICE +#define INLINE inline __attribute__((always_inline)) +#include +#include +#endif + +namespace hoomd + { +namespace mpcd + { +//! Constant force on all particles +class __attribute__((visibility("default"))) ConstantForce + { + public: + //! Default constructor + HOSTDEVICE ConstantForce() : m_F(make_scalar3(0, 0, 0)) { } + + //! Constructor + /*! + * \param F Force on all particles. + */ + HOSTDEVICE ConstantForce(Scalar3 F) : m_F(F) { } + + //! Force evaluation method + /*! + * \param r Particle position. + * \returns Force on the particle. + * + * Since the force is constant, just the constant value is returned. + * More complicated functional forms will need to operate on \a r. + */ + HOSTDEVICE INLINE Scalar3 evaluate(const Scalar3& r) const + { + return m_F; + } + + HOSTDEVICE Scalar3 getForce() const + { + return m_F; + } + + HOSTDEVICE void setForce(const Scalar3& F) + { + m_F = F; + } + +#ifndef __HIPCC__ + //! Get the unique name of this force + static std::string getName() + { + return std::string("ConstantForce"); + } +#endif // __HIPCC__ + + private: + Scalar3 m_F; //!< Constant force + }; + +#ifndef __HIPCC__ +namespace detail + { +void export_ConstantForce(pybind11::module& m); + } // end namespace detail +#endif // __HIPCC__ + + } // end namespace mpcd + } // end namespace hoomd +#undef HOSTDEVICE +#undef INLINE + +#endif // MPCD_CONSTANT_FORCE_H_ diff --git a/hoomd/mpcd/ExternalField.cc b/hoomd/mpcd/ExternalField.cc deleted file mode 100644 index b1409b53e1..0000000000 --- a/hoomd/mpcd/ExternalField.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/ExternalField.cc - * \brief Export functions for MPCD external fields. - */ - -#include "ExternalField.h" -#include "hoomd/GPUPolymorph.h" - -namespace hoomd - { -namespace mpcd - { -namespace detail - { -void export_ExternalFieldPolymorph(pybind11::module& m) - { - typedef hoomd::GPUPolymorph ExternalFieldPolymorph; - - pybind11::class_>( - m, - "ExternalField") - .def(pybind11::init>()) - // each field needs to get at least one (factory) method - .def("BlockForce", - (void(ExternalFieldPolymorph::*)(Scalar, Scalar, Scalar)) - & ExternalFieldPolymorph::reset) - .def("ConstantForce", - (void(ExternalFieldPolymorph::*)(Scalar3)) - & ExternalFieldPolymorph::reset) - .def("SineForce", - (void(ExternalFieldPolymorph::*)(Scalar, Scalar)) - & ExternalFieldPolymorph::reset); - } - - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd diff --git a/hoomd/mpcd/ExternalField.cu b/hoomd/mpcd/ExternalField.cu deleted file mode 100644 index cd443a58d5..0000000000 --- a/hoomd/mpcd/ExternalField.cu +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/ExternalField.cu - * \brief Template device_new methods for external fields. - * - * \warning - * Because __HIPCC__ seems to establish the virtual table with these device functions, - * other device functions using the ExternalField object need to be compiled with - * this bit using separable compilation. The consequence of all this is that - * new ExternalFields cannot be added through the plugin interface. - */ - -#include "ExternalField.h" -#include "hoomd/GPUPolymorph.cuh" - -namespace hoomd - { -template mpcd::BlockForce* hoomd::gpu::device_new(Scalar, Scalar, Scalar); -template mpcd::ConstantForce* hoomd::gpu::device_new(Scalar3); -template mpcd::SineForce* hoomd::gpu::device_new(Scalar, Scalar); -template void hoomd::gpu::device_delete(mpcd::ExternalField*); - - } // end namespace hoomd diff --git a/hoomd/mpcd/ExternalField.h b/hoomd/mpcd/ExternalField.h deleted file mode 100644 index c589021209..0000000000 --- a/hoomd/mpcd/ExternalField.h +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/ExternalField.h - * \brief Definition of mpcd::ExternalField. - */ - -#ifndef MPCD_EXTERNAL_FIELD_H_ -#define MPCD_EXTERNAL_FIELD_H_ - -#include "hoomd/HOOMDMath.h" - -#ifdef __HIPCC__ -#define HOSTDEVICE __host__ __device__ -#else -#define HOSTDEVICE -#include -#endif - -namespace hoomd - { -namespace mpcd - { -//! External force field on MPCD particles. -/*! - * The external field specifies a force that acts on the MPCD particles. - * It will be evaluated inside the streaming kernel to accelerate particles. - * - * This is the abstract base class. Deriving classes must implement their - * own evaluate() method, which takes a particle position and returns the force. - * - * You should explicitly instantiate device_new for your derived class in ExternalField.cu. - * You should then add an appropriate overloaded ::reset() method to ExternalField.cc to - * export the field to python. Then, add the python class to construct it. See ConstantForce - * as an example. - * - * \warning - * Because of the way __HIPCC__ handles compilation (see ExternalField.cu), new ExternalFields - * can only be implemented within HOOMD and \b NOT through the plugin interface. - */ -class ExternalField - { - public: - //! Virtual destructor (does nothing) - HOSTDEVICE virtual ~ExternalField() { } - - //! Force evaluation method - /*! - * \param r Particle position. - * \returns Force on the particle. - * - * Deriving classes must implement their own evaluate method. - */ - HOSTDEVICE virtual Scalar3 evaluate(const Scalar3& r) const = 0; - }; - -//! Constant, opposite force applied to particles in a block -/*! - * Imposes a constant force in x as a function of position in z: - * - * \f{eqnarray*} - * \mathbf{F} &= +F \mathbf{e}_x & H-w \le z < H+w \\ - * &= -F \mathbf{e}_x & -H-w \le z < -H+w \\ - * &= \mathbf{0} & \mathrm{otherwise} - * \f} - * - * where \a F is the force magnitude, \a H is the half-width between the - * block centers, and \a w is the block half-width. - * - * This force field can be used to implement the double-parabola method for measuring - * viscosity by setting \f$H = L_z/4\f$ and \f$w=L_z/4\f$, or to mimick the reverse - * nonequilibrium shear flow profile by setting \f$H = L_z/4\f$ and \a w to a small value. - */ -class BlockForce : public ExternalField - { - public: - //! Constructor - /*! - * \param F Force on all particles. - * \param H Half-width between block regions. - * \param w Half-width of blocks. - */ - HOSTDEVICE BlockForce(Scalar F, Scalar H, Scalar w) : m_F(F) - { - m_H_plus_w = H + w; - m_H_minus_w = H - w; - } - - //! Force evaluation method - /*! - * \param r Particle position. - * \returns Force on the particle. - */ - HOSTDEVICE virtual Scalar3 evaluate(const Scalar3& r) const override - { - // sign = +1 if in top slab, -1 if in bottom slab, 0 if neither - const signed char sign = (char)((r.z >= m_H_minus_w && r.z < m_H_plus_w) - - (r.z >= -m_H_plus_w && r.z < -m_H_minus_w)); - return make_scalar3(sign * m_F, 0, 0); - } - - private: - Scalar m_F; //!< Constant force - Scalar m_H_plus_w; //!< Upper bound on upper block, H + w - Scalar m_H_minus_w; //!< Lower bound on upper block, H - w - }; - -//! Constant force on all particles -class ConstantForce : public ExternalField - { - public: - //! Constructor - /*! - * \param F Force on all particles. - */ - HOSTDEVICE ConstantForce(Scalar3 F) : m_F(F) { } - - //! Force evaluation method - /*! - * \param r Particle position. - * \returns Force on the particle. - * - * Since the force is constant, just the constant value is returned. - * More complicated functional forms will need to operate on \a r. - */ - HOSTDEVICE virtual Scalar3 evaluate(const Scalar3& r) const override - { - return m_F; - } - - private: - Scalar3 m_F; //!< Constant force - }; - -//! Shearing sine force -/*! - * Imposes a sinusoidally varying force in x as a function of position in z. - * The shape of the force is controlled by the amplitude and the wavenumber. - * - * \f[ - * \mathbf{F}(\mathbf{r}) = F \sin (k r_z) \mathbf{e}_x - * \f] - */ -class SineForce : public ExternalField - { - public: - //! Constructor - /*! - * \param F Amplitude of the force. - * \param k Wavenumber for the force. - */ - HOSTDEVICE SineForce(Scalar F, Scalar k) : m_F(F), m_k(k) { } - - //! Force evaluation method - /*! - * \param r Particle position. - * \returns Force on the particle. - * - * Specifies the force to act in x as a function of z. Fast math - * routines are used since this is probably sufficiently accurate, - * given the other numerical errors already present. - */ - HOSTDEVICE virtual Scalar3 evaluate(const Scalar3& r) const override - { - return make_scalar3(m_F * fast::sin(m_k * r.z), 0, 0); - } - - private: - Scalar m_F; //!< Force constant - Scalar m_k; //!< Wavenumber for force in z - }; - -#ifndef __HIPCC__ -namespace detail - { -void export_ExternalFieldPolymorph(pybind11::module& m); - } // end namespace detail -#endif // __HIPCC__ - - } // end namespace mpcd - } // end namespace hoomd -#undef HOSTDEVICE - -#endif // MPCD_EXTERNAL_FIELD_H_ diff --git a/hoomd/mpcd/Integrator.cc b/hoomd/mpcd/Integrator.cc index 5ae0a037b3..a579b8acee 100644 --- a/hoomd/mpcd/Integrator.cc +++ b/hoomd/mpcd/Integrator.cc @@ -9,9 +9,15 @@ #include "Integrator.h" #ifdef ENABLE_MPI -#include "hoomd/Communicator.h" +#include "Communicator.h" +#ifdef ENABLE_HIP +#include "CommunicatorGPU.h" +#endif #endif +#include +PYBIND11_MAKE_OPAQUE(std::vector>); + namespace hoomd { /*! @@ -22,6 +28,25 @@ mpcd::Integrator::Integrator(std::shared_ptr sysdef, Scalar de : IntegratorTwoStep(sysdef, deltaT) { m_exec_conf->msg->notice(5) << "Constructing MPCD Integrator" << std::endl; + +#ifdef ENABLE_MPI + // automatically create MPCD communicator in MPI simulations + if (m_pdata->getDomainDecomposition()) + { + std::shared_ptr mpcd_comm; +#ifdef ENABLE_HIP + if (m_exec_conf->isCUDAEnabled()) + { + mpcd_comm = std::make_shared(sysdef); + } + else +#endif // ENABLE_HIP + { + mpcd_comm = std::make_shared(sysdef); + } + setMPCDCommunicator(mpcd_comm); + } +#endif // ENABLE_MPI } mpcd::Integrator::~Integrator() @@ -42,13 +67,10 @@ mpcd::Integrator::~Integrator() */ void mpcd::Integrator::update(uint64_t timestep) { - IntegratorTwoStep::update(timestep); - - // remove any leftover virtual particles + // remove leftover virtual particles, communicate MPCD particles, and refill if (checkCollide(timestep)) { m_sysdef->getMPCDParticleData()->removeVirtualParticles(); - m_collide->drawGridShift(timestep); } #ifdef ENABLE_MPI @@ -57,66 +79,26 @@ void mpcd::Integrator::update(uint64_t timestep) #endif // ENABLE_MPI // fill in any virtual particles - if (checkCollide(timestep) && !m_fillers.empty()) + if (checkCollide(timestep)) { - for (auto filler = m_fillers.begin(); filler != m_fillers.end(); ++filler) + for (auto& filler : m_fillers) { - (*filler)->fill(timestep); + filler->fill(timestep); } } - // optionally sort - if (m_sorter) + // optionally sort for performance + if (m_sorter && (*m_sorter->getTrigger())(timestep)) m_sorter->update(timestep); - // call the MPCD collision rule before the first MD step so that any embedded velocities are - // updated first + // perform the core MPCD steps of collision and streaming if (m_collide) m_collide->collide(timestep); - - // perform the first MD integration step - for (auto method = m_methods.begin(); method != m_methods.end(); ++method) - (*method)->integrateStepOne(timestep); - -// MD communication / rigid body updates -#ifdef ENABLE_MPI - if (m_sysdef->isDomainDecomposed()) - { - // Update the rigid body consituent particles before communicating so that any such - // particles that move from one domain to another are migrated. - updateRigidBodies(timestep + 1); - - m_comm->communicate(timestep + 1); - - // Communicator uses a compute callback to trigger updateRigidBodies again and ensure that - // all ghost constituent particle positions are set in accordance with any just communicated - // ghost and/or migrated rigid body centers. - } - else -#endif // ENABLE_MPI - { - // Update rigid body constituent particles in serial simulations. - updateRigidBodies(timestep + 1); - } - - // execute the MPCD streaming step now that MD particles are communicated onto their final - // domains if (m_stream) - { m_stream->stream(timestep); - } - // compute the net force on the MD particles -#ifdef ENABLE_HIP - if (m_exec_conf->isCUDAEnabled()) - computeNetForceGPU(timestep + 1); - else -#endif - computeNetForce(timestep + 1); - - // perform the second step of the MD integration - for (auto method = m_methods.begin(); method != m_methods.end(); ++method) - (*method)->integrateStepTwo(timestep); + // execute MD steps + IntegratorTwoStep::update(timestep); } /*! @@ -139,10 +121,11 @@ void mpcd::Integrator::prepRun(uint64_t timestep) { IntegratorTwoStep::prepRun(timestep); - // synchronize timestep in mpcd methods - if (m_collide) + // synchronize cell list in mpcd methods + syncCellList(); + if (m_cl) { - m_collide->drawGridShift(timestep); + m_cl->drawGridShift(timestep); } #ifdef ENABLE_MPI @@ -154,24 +137,30 @@ void mpcd::Integrator::prepRun(uint64_t timestep) #endif // ENABLE_MPI } -/*! - * \param filler Virtual particle filler to add to the integrator - * - * The \a filler is attached to the integrator exactly once. An error is raised if this filler has - * already been added. - */ -void mpcd::Integrator::addFiller(std::shared_ptr filler) +void mpcd::Integrator::syncCellList() { - auto it = std::find(m_fillers.begin(), m_fillers.end(), filler); - if (it != m_fillers.end()) + if (m_collide) { - m_exec_conf->msg->error() - << "Trying to add same MPCD virtual particle filler twice! Please report this bug." - << std::endl; - throw std::runtime_error("Duplicate attachment of MPCD virtual particle filler"); + m_collide->setCellList(m_cl); + } + if (m_stream) + { + m_stream->setCellList(m_cl); + } + if (m_sorter) + { + m_sorter->setCellList(m_cl); + } +#ifdef ENABLE_MPI + if (m_mpcd_comm) + { + m_mpcd_comm->setCellList(m_cl); + } +#endif + for (auto& filler : m_fillers) + { + filler->setCellList(m_cl); } - - m_fillers.push_back(filler); } /*! @@ -179,21 +168,24 @@ void mpcd::Integrator::addFiller(std::shared_ptr fi */ void mpcd::detail::export_Integrator(pybind11::module& m) { + pybind11::bind_vector>>( + m, + "VirtualParticleFillerList"); + pybind11::class_>(m, "Integrator") .def(pybind11::init, Scalar>()) - .def("setCollisionMethod", &mpcd::Integrator::setCollisionMethod) - .def("removeCollisionMethod", &mpcd::Integrator::removeCollisionMethod) - .def("setStreamingMethod", &mpcd::Integrator::setStreamingMethod) - .def("removeStreamingMethod", &mpcd::Integrator::removeStreamingMethod) - .def("setSorter", &mpcd::Integrator::setSorter) - .def("removeSorter", &mpcd::Integrator::removeSorter) - .def("addFiller", &mpcd::Integrator::addFiller) - .def("removeAllFillers", &mpcd::Integrator::removeAllFillers) -#ifdef ENABLE_MPI - .def("setMPCDCommunicator", &mpcd::Integrator::setMPCDCommunicator) -#endif // ENABLE_MPI - ; + .def_property("cell_list", &mpcd::Integrator::getCellList, &mpcd::Integrator::setCellList) + .def_property("collision_method", + &mpcd::Integrator::getCollisionMethod, + &mpcd::Integrator::setCollisionMethod) + .def_property("streaming_method", + &mpcd::Integrator::getStreamingMethod, + &mpcd::Integrator::setStreamingMethod) + .def_property("mpcd_particle_sorter", + &mpcd::Integrator::getSorter, + &mpcd::Integrator::setSorter) + .def_property_readonly("fillers", &mpcd::Integrator::getFillers); } } // end namespace hoomd diff --git a/hoomd/mpcd/Integrator.h b/hoomd/mpcd/Integrator.h index 535b20ef23..55e64dee6d 100644 --- a/hoomd/mpcd/Integrator.h +++ b/hoomd/mpcd/Integrator.h @@ -48,9 +48,22 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep //! Prepare for the run virtual void prepRun(uint64_t timestep); + //! Get the MPCD cell list shared by all methods + std::shared_ptr getCellList() const + { + return m_cl; + } + + //! Set the MPCD cell list shared by all methods + void setCellList(std::shared_ptr cl) + { + m_cl = cl; + syncCellList(); + } + #ifdef ENABLE_MPI //! Set the MPCD communicator to use - virtual void setMPCDCommunicator(std::shared_ptr comm) + void setMPCDCommunicator(std::shared_ptr comm) { // if the current communicator is set, first disable the migrate signal request if (m_mpcd_comm) @@ -79,15 +92,6 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep m_collide = collide; } - //! Remove the collision method - /*! - * \post The collision method is set to a null shared pointer. - */ - void removeCollisionMethod() - { - m_collide.reset(); - } - //! Get current streaming method std::shared_ptr getStreamingMethod() const { @@ -101,16 +105,16 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep void setStreamingMethod(std::shared_ptr stream) { m_stream = stream; - m_stream->setDeltaT(m_deltaT); + if (m_stream) + { + m_stream->setDeltaT(m_deltaT); + } } - //! Remove the streaming method - /*! - * \post The streaming method is set to a null shared pointer. - */ - void removeStreamingMethod() + //! Get the current sorting method + std::shared_ptr getSorter() const { - m_stream.reset(); + return m_sorter; } //! Set the sorting method @@ -122,47 +126,32 @@ class PYBIND11_EXPORT Integrator : public hoomd::md::IntegratorTwoStep m_sorter = sorter; } - //! Get the current sorting method - std::shared_ptr getSorter() const - { - return m_sorter; - } - - //! Remove the current sorting method - /*! - * \post The sorting method is set to a null shared pointer. - */ - void removeSorter() - { - m_sorter.reset(); - } - - //! Add a virtual particle filling method - void addFiller(std::shared_ptr filler); - - //! Remove all virtual particle fillers - void removeAllFillers() + //! Get the virtual particle fillers + std::vector>& getFillers() { - m_fillers.clear(); + return m_fillers; } protected: + std::shared_ptr m_cl; //!< MPCD cell list std::shared_ptr m_collide; //!< MPCD collision rule std::shared_ptr m_stream; //!< MPCD streaming rule std::shared_ptr m_sorter; //!< MPCD sorter - #ifdef ENABLE_MPI std::shared_ptr m_mpcd_comm; //!< MPCD communicator -#endif // ENABLE_MPI - +#endif std::vector> m_fillers; //!< MPCD virtual particle fillers + private: //! Check if a collision will occur at the current timestep bool checkCollide(uint64_t timestep) { return (m_collide && m_collide->peekCollide(timestep)); } + + //! Synchronize cell list to integrator dependencies + void syncCellList(); }; namespace detail diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.cc b/hoomd/mpcd/ManualVirtualParticleFiller.cc new file mode 100644 index 0000000000..0e08852f8d --- /dev/null +++ b/hoomd/mpcd/ManualVirtualParticleFiller.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ManualVirtualParticleFiller.cc + * \brief Definition of mpcd::ManualVirtualParticleFiller + */ + +#include "ManualVirtualParticleFiller.h" + +namespace hoomd + { +mpcd::ManualVirtualParticleFiller::ManualVirtualParticleFiller( + std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T) + : mpcd::VirtualParticleFiller(sysdef, type, density, T), m_N_fill(0), m_first_tag(0), + m_first_idx(0) + { + } + +void mpcd::ManualVirtualParticleFiller::fill(uint64_t timestep) + { + if (!m_cl) + { + throw std::runtime_error("Cell list has not been set"); + } + + // update the fill volume + computeNumFill(); + + // get the first tag from the fill number + m_first_tag = computeFirstTag(m_N_fill); + + // add the new virtual particles locally + m_first_idx = m_mpcd_pdata->addVirtualParticles(m_N_fill); + + // draw the particles consistent with those tags + drawParticles(timestep); + + m_mpcd_pdata->invalidateCellCache(); + } + +/*! + * \param m Python module to export to + */ +void mpcd::detail::export_ManualVirtualParticleFiller(pybind11::module& m) + { + pybind11::class_>( + m, + "ManualVirtualParticleFiller") + .def(pybind11::init, + const std::string&, + Scalar, + std::shared_ptr>()); + } + + } // end namespace hoomd diff --git a/hoomd/mpcd/ManualVirtualParticleFiller.h b/hoomd/mpcd/ManualVirtualParticleFiller.h new file mode 100644 index 0000000000..0edac792bb --- /dev/null +++ b/hoomd/mpcd/ManualVirtualParticleFiller.h @@ -0,0 +1,67 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ManualVirtualParticleFiller.h + * \brief Definition of class for manually backfilling solid boundaries with virtual particles. + */ + +#ifndef MPCD_MANUAL_VIRTUAL_PARTICLE_FILLER_H_ +#define MPCD_MANUAL_VIRTUAL_PARTICLE_FILLER_H_ + +#ifdef __HIPCC__ +#error This header cannot be compiled by nvcc +#endif + +#include "VirtualParticleFiller.h" + +#include + +#include + +namespace hoomd + { +namespace mpcd + { +//! Manually add virtual particles to the MPCD particle data +/*! + * The ManualVirtualParticleFiller base class defines an interface for adding virtual particles + * using a prescribed formula. The fill() method handles the basic tasks of appending a certain + * number of virtual particles to the particle data. Each deriving class must then implement two + * methods: + * 1. computeNumFill(), which is the number of virtual particles to add. + * 2. drawParticles(), which is the rule to determine where to put the particles. + */ +class PYBIND11_EXPORT ManualVirtualParticleFiller : public VirtualParticleFiller + { + public: + ManualVirtualParticleFiller(std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T); + + virtual ~ManualVirtualParticleFiller() { } + + //! Fill up virtual particles + void fill(uint64_t timestep); + + protected: + unsigned int m_N_fill; //!< Number of particles to fill locally + unsigned int m_first_tag; //!< First tag of locally held particles + unsigned int m_first_idx; //!< Particle index to start adding from + + //! Compute the total number of particles to fill + virtual void computeNumFill() { } + + //! Draw particles within the fill volume + virtual void drawParticles(uint64_t timestep) { } + }; + +namespace detail + { +//! Export the ManualVirtualParticleFiller to python +void export_ManualVirtualParticleFiller(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd +#endif // MPCD_MANUAL_VIRTUAL_PARTICLE_FILLER_H_ diff --git a/hoomd/mpcd/NoForce.cc b/hoomd/mpcd/NoForce.cc new file mode 100644 index 0000000000..3c6e45bf9d --- /dev/null +++ b/hoomd/mpcd/NoForce.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/NoForce.cc + * \brief Export MPCD NoForce. + */ + +#include "NoForce.h" + +namespace hoomd + { +namespace mpcd + { +namespace detail + { + +void export_NoForce(pybind11::module& m) + { + pybind11::class_>(m, "NoForce").def(pybind11::init<>()); + } + + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/NoForce.h b/hoomd/mpcd/NoForce.h new file mode 100644 index 0000000000..447823fe96 --- /dev/null +++ b/hoomd/mpcd/NoForce.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/NoForce.h + * \brief Definition of mpcd::NoForce. + */ + +#ifndef MPCD_NO_FORCE_H_ +#define MPCD_NO_FORCE_H_ + +#include "hoomd/HOOMDMath.h" + +#ifdef __HIPCC__ +#define HOSTDEVICE __host__ __device__ +#define INLINE inline +#else +#define HOSTDEVICE +#define INLINE inline __attribute__((always_inline)) +#include +#endif + +namespace hoomd + { +namespace mpcd + { + +//! No force on particles +class NoForce + { + public: + //! Force evaluation method + /*! + * \param r Particle position. + * \returns Force on the particle. + * + * This just returns zero, meaning no force. Hopefully the compiler will optimize this out! + */ + HOSTDEVICE INLINE Scalar3 evaluate(const Scalar3& r) const + { + return make_scalar3(0, 0, 0); + } + +#ifndef __HIPCC__ + //! Get the unique name of this force + static std::string getName() + { + return std::string("NoForce"); + } +#endif // __HIPCC__ + }; + +#ifndef __HIPCC__ +namespace detail + { +void export_NoForce(pybind11::module& m); + } // end namespace detail +#endif // __HIPCC__ + + } // end namespace mpcd + } // end namespace hoomd +#undef HOSTDEVICE +#undef INLINE + +#endif // MPCD_NO_FORCE_H_ diff --git a/hoomd/mpcd/SlitGeometry.h b/hoomd/mpcd/ParallelPlateGeometry.h similarity index 69% rename from hoomd/mpcd/SlitGeometry.h rename to hoomd/mpcd/ParallelPlateGeometry.h index 076f3de99c..b27ae75505 100644 --- a/hoomd/mpcd/SlitGeometry.h +++ b/hoomd/mpcd/ParallelPlateGeometry.h @@ -2,14 +2,12 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitGeometry.h + * \file mpcd/ParallelPlateGeometry.h * \brief Definition of the MPCD slit channel geometry */ -#ifndef MPCD_SLIT_GEOMETRY_H_ -#define MPCD_SLIT_GEOMETRY_H_ - -#include "BoundaryCondition.h" +#ifndef MPCD_PARALLEL_PLATE_GEOMETRY_H_ +#define MPCD_PARALLEL_PLATE_GEOMETRY_H_ #include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" @@ -25,8 +23,6 @@ namespace hoomd { namespace mpcd { -namespace detail - { //! Parallel plate (slit) geometry /*! * This class defines the geometry consistent with two infinite parallel plates. When the plates are @@ -36,16 +32,16 @@ namespace detail * * The channel geometry is defined by two parameters: the channel half-width \a H, and the velocity * of the plates \a V. The total distance between the plates is \f$2H\f$. The plates are stacked in - * the \a z direction, and are centered about the origin \f$z=0\f$. The upper plate moves in the + * the \a y direction, and are centered about the origin \f$y=0\f$. The upper plate moves in the * \f$+x\f$ direction with velocity \a V, and the lower plate moves in the \f$-x\f$ direction with * velocity \f$-V\f$. Hence, for no-slip boundary conditions there is a velocity profile: * * \f[ - * v_x(z) = \frac{Vz}{H} + * v_x(y) = \frac{Vy}{H} * \f] * * This gives an effective shear rate \f$\dot\gamma = V/H\f$, and the shear stress is - * $\sigma_{xz}\f$. + * $\sigma_{xy}\f$. * * The geometry enforces boundary conditions \b only on the MPCD solvent particles. Additional * interactions are required with any embedded particles using appropriate wall potentials. @@ -53,16 +49,19 @@ namespace detail * The wall boundary conditions can optionally be changed to slip conditions. For these BCs, the * previous discussion of the various flow profiles no longer applies. */ -class __attribute__((visibility("default"))) SlitGeometry +class __attribute__((visibility("default"))) ParallelPlateGeometry { public: //! Constructor /*! - * \param H Channel half-width - * \param V Velocity of the wall - * \param bc Boundary condition at the wall (slip or no-slip) + * \param separation Distance between plates + * \param speed Speed of the wall + * \param no_slip Boundary condition at the wall (slip or no-slip) */ - HOSTDEVICE SlitGeometry(Scalar H, Scalar V, boundary bc) : m_H(H), m_V(V), m_bc(bc) { } + HOSTDEVICE ParallelPlateGeometry(Scalar separation, Scalar speed, bool no_slip) + : m_H(Scalar(0.5) * separation), m_V(speed), m_no_slip(no_slip) + { + } //! Detect collision between the particle and the boundary /*! @@ -80,8 +79,8 @@ class __attribute__((visibility("default"))) SlitGeometry { /* * Detect if particle has left the box, and try to avoid branching or absolute value calls. - * The sign used in calculations is +1 if the particle is out-of-bounds in the +z direction, - * -1 if the particle is out-of-bounds in the -z direction, and 0 otherwise. + * The sign used in calculations is +1 if the particle is out-of-bounds in the +y direction, + * -1 if the particle is out-of-bounds in the -y direction, and 0 otherwise. * * We intentionally use > / < rather than >= / <= to make sure that spurious collisions do * not get detected when a particle is reset to the boundary location. A particle landing @@ -89,10 +88,10 @@ class __attribute__((visibility("default"))) SlitGeometry * step, and so the motion is essentially equivalent up to an epsilon of difference in the * channel width. */ - const signed char sign = (char)((pos.z > m_H) - (pos.z < -m_H)); + const signed char sign = (char)((pos.y > m_H) - (pos.y < -m_H)); // exit immediately if no collision is found or particle is not moving normal to the wall // (since no new collision could have occurred if there is no normal motion) - if (sign == 0 || vel.z == Scalar(0)) + if (sign == 0 || vel.y == Scalar(0)) { dt = Scalar(0); return false; @@ -100,27 +99,27 @@ class __attribute__((visibility("default"))) SlitGeometry /* * Remaining integration time dt is amount of time spent traveling distance out of bounds. - * If sign = +1, then pos.z > H. If sign = -1, then pos.z < -H, and we need difference in + * If sign = +1, then pos.y > H. If sign = -1, then pos.y < -H, and we need difference in * the opposite direction. * * TODO: if dt < 0, it is a spurious collision. How should it be treated? */ - dt = (pos.z - sign * m_H) / vel.z; + dt = (pos.y - sign * m_H) / vel.y; // backtrack the particle for dt to get to point of contact pos.x -= vel.x * dt; - pos.y -= vel.y * dt; - pos.z = sign * m_H; + pos.y = sign * m_H; + pos.z -= vel.z * dt; // update velocity according to boundary conditions // no-slip requires reflection of the tangential components - if (m_bc == boundary::no_slip) + if (m_no_slip) { vel.x = -vel.x + Scalar(sign * 2) * m_V; - vel.y = -vel.y; + vel.z = -vel.z; } // both slip and no-slip have no penetration of the surface - vel.z = -vel.z; + vel.y = -vel.y; return true; } @@ -132,39 +131,32 @@ class __attribute__((visibility("default"))) SlitGeometry */ HOSTDEVICE bool isOutside(const Scalar3& pos) const { - return (pos.z > m_H || pos.z < -m_H); + return (pos.y > m_H || pos.y < -m_H); } - //! Validate that the simulation box is large enough for the geometry + //! Get channel half width /*! - * \param box Global simulation box - * \param cell_size Size of MPCD cell - * - * The box is large enough for the slit if it is padded along the z direction so that - * the cells just outside the slit would not interact with each other through the boundary. + * \returns Channel half width */ - HOSTDEVICE bool validateBox(const BoxDim& box, Scalar cell_size) const + HOSTDEVICE Scalar getH() const { - const Scalar hi = box.getHi().z; - const Scalar lo = box.getLo().z; - - return ((hi - m_H) >= cell_size && ((-m_H - lo) >= cell_size)); + return m_H; } - //! Get channel half width + //! Get distance between plates /*! - * \returns Channel half width + * \returns Distance between plates */ - HOSTDEVICE Scalar getH() const + HOSTDEVICE Scalar getSeparation() const { - return m_H; + return 2 * m_H; } - //! Get the wall velocity + //! Get the wall speed /*! - * \returns Wall velocity + * \returns Wall speed */ - HOSTDEVICE Scalar getVelocity() const + HOSTDEVICE Scalar getSpeed() const { return m_V; } @@ -173,28 +165,27 @@ class __attribute__((visibility("default"))) SlitGeometry /*! * \returns Boundary condition at wall */ - HOSTDEVICE boundary getBoundaryCondition() const + HOSTDEVICE bool getNoSlip() const { - return m_bc; + return m_no_slip; } #ifndef __HIPCC__ //! Get the unique name of this geometry static std::string getName() { - return std::string("Slit"); + return std::string("ParallelPlates"); } #endif // __HIPCC__ private: - const Scalar m_H; //!< Half of the channel width - const Scalar m_V; //!< Velocity of the wall - const boundary m_bc; //!< Boundary condition + const Scalar m_H; //!< Half of the channel width + const Scalar m_V; //!< Velocity of the wall + const bool m_no_slip; //!< Boundary condition }; - } // end namespace detail } // end namespace mpcd } // end namespace hoomd #undef HOSTDEVICE -#endif // MPCD_SLIT_GEOMETRY_H_ +#endif // MPCD_PARALLEL_PLATE_GEOMETRY_H_ diff --git a/hoomd/mpcd/SlitGeometryFiller.cc b/hoomd/mpcd/ParallelPlateGeometryFiller.cc similarity index 58% rename from hoomd/mpcd/SlitGeometryFiller.cc rename to hoomd/mpcd/ParallelPlateGeometryFiller.cc index 47c384af1d..345f6bd9f0 100644 --- a/hoomd/mpcd/SlitGeometryFiller.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.cc @@ -2,42 +2,36 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitGeometryFiller.cc - * \brief Definition of mpcd::SlitGeometryFiller + * \file mpcd/ParallelPlateGeometryFiller.cc + * \brief Definition of mpcd::ParallelPlateGeometryFiller */ -#include "SlitGeometryFiller.h" +#include "ParallelPlateGeometryFiller.h" #include "hoomd/RNGIdentifiers.h" #include "hoomd/RandomNumbers.h" namespace hoomd { -mpcd::SlitGeometryFiller::SlitGeometryFiller(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - std::shared_ptr geom) - : mpcd::VirtualParticleFiller(sysdef, density, type, T), m_geom(geom) +mpcd::ParallelPlateGeometryFiller::ParallelPlateGeometryFiller( + std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T, + std::shared_ptr geom) + : mpcd::ManualVirtualParticleFiller(sysdef, type, density, T), m_geom(geom) { - m_exec_conf->msg->notice(5) << "Constructing MPCD SlitGeometryFiller" << std::endl; + m_exec_conf->msg->notice(5) << "Constructing MPCD ParallelPlateGeometryFiller" << std::endl; } -mpcd::SlitGeometryFiller::~SlitGeometryFiller() +mpcd::ParallelPlateGeometryFiller::~ParallelPlateGeometryFiller() { - m_exec_conf->msg->notice(5) << "Destroying MPCD SlitGeometryFiller" << std::endl; + m_exec_conf->msg->notice(5) << "Destroying MPCD ParallelPlateGeometryFiller" << std::endl; } -void mpcd::SlitGeometryFiller::computeNumFill() +void mpcd::ParallelPlateGeometryFiller::computeNumFill() { - // as a precaution, validate the global box with the current cell list const BoxDim& global_box = m_pdata->getGlobalBox(); const Scalar cell_size = m_cl->getCellSize(); - if (!m_geom->validateBox(global_box, cell_size)) - { - m_exec_conf->msg->error() - << "Invalid slit geometry for global box, cannot fill virtual particles." << std::endl; - throw std::runtime_error("Invalid slit geometry for global box"); - } // box and slit geometry const BoxDim& box = m_pdata->getBox(); @@ -46,8 +40,8 @@ void mpcd::SlitGeometryFiller::computeNumFill() const Scalar H = m_geom->getH(); // default is not to fill anything - m_z_min = -H; - m_z_max = H; + m_y_min = -H; + m_y_max = H; m_N_hi = m_N_lo = 0; /* @@ -56,17 +50,17 @@ void mpcd::SlitGeometryFiller::computeNumFill() * max shift of this cell edge. */ const Scalar max_shift = m_cl->getMaxGridShift(); - const Scalar global_lo = global_box.getLo().z; - if (box.getHi().z >= H) + const Scalar global_lo = global_box.getLo().y; + if (box.getHi().y >= H) { - m_z_max = cell_size * std::ceil((H - global_lo) / cell_size) + global_lo + max_shift; - m_N_hi = (unsigned int)std::round((m_z_max - H) * A * m_density); + m_y_max = cell_size * std::ceil((H - global_lo) / cell_size) + global_lo + max_shift; + m_N_hi = (unsigned int)std::round((m_y_max - H) * A * m_density); } - if (box.getLo().z <= -H) + if (box.getLo().y <= -H) { - m_z_min = cell_size * std::floor((-H - global_lo) / cell_size) + global_lo - max_shift; - m_N_lo = (unsigned int)std::round((-H - m_z_min) * A * m_density); + m_y_min = cell_size * std::floor((-H - global_lo) / cell_size) + global_lo - max_shift; + m_N_lo = (unsigned int)std::round((-H - m_y_min) * A * m_density); } // total number of fill particles @@ -76,7 +70,7 @@ void mpcd::SlitGeometryFiller::computeNumFill() /*! * \param timestep Current timestep to draw particles */ -void mpcd::SlitGeometryFiller::drawParticles(uint64_t timestep) +void mpcd::ParallelPlateGeometryFiller::drawParticles(uint64_t timestep) { ArrayHandle h_pos(m_mpcd_pdata->getPositions(), access_location::host, @@ -97,26 +91,25 @@ void mpcd::SlitGeometryFiller::drawParticles(uint64_t timestep) uint16_t seed = m_sysdef->getSeed(); // index to start filling from - const unsigned int first_idx = m_mpcd_pdata->getN() + m_mpcd_pdata->getNVirtual() - m_N_fill; for (unsigned int i = 0; i < m_N_fill; ++i) { const unsigned int tag = m_first_tag + i; hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitGeometryFiller, timestep, seed), - hoomd::Counter(tag)); + hoomd::Seed(hoomd::RNGIdentifier::ParallelPlateGeometryFiller, timestep, seed), + hoomd::Counter(tag, m_filler_id)); signed char sign = (char)((i >= m_N_lo) - (i < m_N_lo)); if (sign == -1) // bottom { - lo.z = m_z_min; - hi.z = -m_geom->getH(); + lo.y = m_y_min; + hi.y = -m_geom->getH(); } else // top { - lo.z = m_geom->getH(); - hi.z = m_z_max; + lo.y = m_geom->getH(); + hi.y = m_y_max; } - const unsigned int pidx = first_idx + i; + const unsigned int pidx = m_first_idx + i; h_pos.data[pidx] = make_scalar4(hoomd::UniformDistribution(lo.x, hi.x)(rng), hoomd::UniformDistribution(lo.y, hi.y)(rng), hoomd::UniformDistribution(lo.z, hi.z)(rng), @@ -128,7 +121,7 @@ void mpcd::SlitGeometryFiller::drawParticles(uint64_t timestep) vel.z = gen(rng); // TODO: should these be given zero net-momentum contribution (relative to the frame of // reference?) - h_vel.data[pidx] = make_scalar4(vel.x + sign * m_geom->getVelocity(), + h_vel.data[pidx] = make_scalar4(vel.x + sign * m_geom->getSpeed(), vel.y, vel.z, __int_as_scalar(mpcd::detail::NO_CELL)); @@ -139,17 +132,19 @@ void mpcd::SlitGeometryFiller::drawParticles(uint64_t timestep) /*! * \param m Python module to export to */ -void mpcd::detail::export_SlitGeometryFiller(pybind11::module& m) +void mpcd::detail::export_ParallelPlateGeometryFiller(pybind11::module& m) { - pybind11::class_>(m, "SlitGeometryFiller") + std::shared_ptr>( + m, + "ParallelPlateGeometryFiller") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, - std::shared_ptr>()) - .def("setGeometry", &mpcd::SlitGeometryFiller::setGeometry); + std::shared_ptr>()) + .def_property_readonly("geometry", &mpcd::ParallelPlateGeometryFiller::getGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/ParallelPlateGeometryFiller.h b/hoomd/mpcd/ParallelPlateGeometryFiller.h new file mode 100644 index 0000000000..8e91388ee3 --- /dev/null +++ b/hoomd/mpcd/ParallelPlateGeometryFiller.h @@ -0,0 +1,72 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ParallelPlateGeometryFiller.h + * \brief Definition of virtual particle filler for mpcd::ParallelPlateGeometry. + */ + +#ifndef MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_H_ +#define MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_H_ + +#ifdef __HIPCC__ +#error This header cannot be compiled by nvcc +#endif + +#include "ManualVirtualParticleFiller.h" +#include "ParallelPlateGeometry.h" + +#include + +namespace hoomd + { +namespace mpcd + { +//! Adds virtual particles to the MPCD particle data for ParallelPlateGeometry +/*! + * Particles are added to the volume that is overlapped by any of the cells that are also "inside" + * the channel, subject to the grid shift. + */ +class PYBIND11_EXPORT ParallelPlateGeometryFiller : public mpcd::ManualVirtualParticleFiller + { + public: + ParallelPlateGeometryFiller(std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T, + std::shared_ptr geom); + + virtual ~ParallelPlateGeometryFiller(); + + std::shared_ptr getGeometry() const + { + return m_geom; + } + + void setGeometry(std::shared_ptr geom) + { + m_geom = geom; + } + + protected: + std::shared_ptr m_geom; + Scalar m_y_min; //!< Min y coordinate for filling + Scalar m_y_max; //!< Max y coordinate for filling + unsigned int m_N_lo; //!< Number of particles to fill below channel + unsigned int m_N_hi; //!< number of particles to fill above channel + + //! Compute the total number of particles to fill + virtual void computeNumFill(); + + //! Draw particles within the fill volume + virtual void drawParticles(uint64_t timestep); + }; + +namespace detail + { +//! Export ParallelPlateGeometryFiller to python +void export_ParallelPlateGeometryFiller(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd +#endif // MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_H_ diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.cc b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc similarity index 66% rename from hoomd/mpcd/SlitGeometryFillerGPU.cc rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc index cba2ea0825..bbc6d2354d 100644 --- a/hoomd/mpcd/SlitGeometryFillerGPU.cc +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cc @@ -2,22 +2,22 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitGeometryFillerGPU.cc - * \brief Definition of mpcd::SlitGeometryFillerGPU + * \file mpcd/ParallelPlateGeometryFillerGPU.cc + * \brief Definition of mpcd::ParallelPlateGeometryFillerGPU */ -#include "SlitGeometryFillerGPU.h" -#include "SlitGeometryFillerGPU.cuh" +#include "ParallelPlateGeometryFillerGPU.cuh" +#include "ParallelPlateGeometryFillerGPU.h" namespace hoomd { -mpcd::SlitGeometryFillerGPU::SlitGeometryFillerGPU( +mpcd::ParallelPlateGeometryFillerGPU::ParallelPlateGeometryFillerGPU( std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T, - std::shared_ptr geom) - : mpcd::SlitGeometryFiller(sysdef, density, type, T, geom) + std::shared_ptr geom) + : mpcd::ParallelPlateGeometryFiller(sysdef, type, density, T, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -28,7 +28,7 @@ mpcd::SlitGeometryFillerGPU::SlitGeometryFillerGPU( /*! * \param timestep Current timestep */ -void mpcd::SlitGeometryFillerGPU::drawParticles(uint64_t timestep) +void mpcd::ParallelPlateGeometryFillerGPU::drawParticles(uint64_t timestep) { ArrayHandle d_pos(m_mpcd_pdata->getPositions(), access_location::device, @@ -40,8 +40,6 @@ void mpcd::SlitGeometryFillerGPU::drawParticles(uint64_t timestep) access_location::device, access_mode::readwrite); - const unsigned int first_idx = m_mpcd_pdata->getN() + m_mpcd_pdata->getNVirtual() - m_N_fill; - uint16_t seed = m_sysdef->getSeed(); m_tuner->begin(); @@ -49,15 +47,15 @@ void mpcd::SlitGeometryFillerGPU::drawParticles(uint64_t timestep) d_vel.data, d_tag.data, *m_geom, - m_z_min, - m_z_max, + m_y_min, + m_y_max, m_pdata->getBox(), m_mpcd_pdata->getMass(), m_type, m_N_lo, m_N_hi, m_first_tag, - first_idx, + m_first_idx, (*m_T)(timestep), timestep, seed, @@ -70,16 +68,18 @@ void mpcd::SlitGeometryFillerGPU::drawParticles(uint64_t timestep) /*! * \param m Python module to export to */ -void mpcd::detail::export_SlitGeometryFillerGPU(pybind11::module& m) +void mpcd::detail::export_ParallelPlateGeometryFillerGPU(pybind11::module& m) { - pybind11::class_>(m, "SlitGeometryFillerGPU") + pybind11::class_>( + m, + "ParallelPlateGeometryFillerGPU") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, - std::shared_ptr>()); + std::shared_ptr>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.cu b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu similarity index 85% rename from hoomd/mpcd/SlitGeometryFillerGPU.cu rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu index b9935e5e06..7db438616c 100644 --- a/hoomd/mpcd/SlitGeometryFillerGPU.cu +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cu @@ -2,12 +2,12 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitGeometryFillerGPU.cu - * \brief Defines GPU functions and kernels used by mpcd::SlitGeometryFillerGPU + * \file mpcd/ParallelPlateGeometryFillerGPU.cu + * \brief Defines GPU functions and kernels used by mpcd::ParallelPlateGeometryFillerGPU */ +#include "ParallelPlateGeometryFillerGPU.cuh" #include "ParticleDataUtilities.h" -#include "SlitGeometryFillerGPU.cuh" #include "hoomd/RNGIdentifiers.h" #include "hoomd/RandomNumbers.h" @@ -24,8 +24,8 @@ namespace kernel * \param d_vel Particle velocities * \param d_tag Particle tags * \param geom Slit geometry to fill - * \param z_min Lower bound to lower fill region - * \param z_max Upper bound to upper fill region + * \param y_min Lower bound to lower fill region + * \param y_max Upper bound to upper fill region * \param box Local simulation box * \param type Type of fill particles * \param N_lo Number of particles to fill in lower region @@ -45,9 +45,9 @@ namespace kernel __global__ void slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, - const mpcd::detail::SlitGeometry geom, - const Scalar z_min, - const Scalar z_max, + const mpcd::ParallelPlateGeometry geom, + const Scalar y_min, + const Scalar y_max, const BoxDim box, const unsigned int type, const unsigned int N_lo, @@ -69,13 +69,13 @@ __global__ void slit_draw_particles(Scalar4* d_pos, Scalar3 hi = box.getHi(); if (sign == -1) // bottom { - lo.z = z_min; - hi.z = -geom.getH(); + lo.y = y_min; + hi.y = -geom.getH(); } else // top { - lo.z = geom.getH(); - hi.z = z_max; + lo.y = geom.getH(); + hi.y = y_max; } // particle tag and index @@ -85,7 +85,7 @@ __global__ void slit_draw_particles(Scalar4* d_pos, // initialize random number generator for positions and velocity hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitGeometryFiller, timestep, seed), + hoomd::Seed(hoomd::RNGIdentifier::ParallelPlateGeometryFiller, timestep, seed), hoomd::Counter(tag)); d_pos[pidx] = make_scalar4(hoomd::UniformDistribution(lo.x, hi.x)(rng), hoomd::UniformDistribution(lo.y, hi.y)(rng), @@ -98,7 +98,7 @@ __global__ void slit_draw_particles(Scalar4* d_pos, vel.z = gen(rng); // TODO: should these be given zero net-momentum contribution (relative to the frame of // reference?) - d_vel[pidx] = make_scalar4(vel.x + sign * geom.getVelocity(), + d_vel[pidx] = make_scalar4(vel.x + sign * geom.getSpeed(), vel.y, vel.z, __int_as_scalar(mpcd::detail::NO_CELL)); @@ -110,8 +110,8 @@ __global__ void slit_draw_particles(Scalar4* d_pos, * \param d_vel Particle velocities * \param d_tag Particle tags * \param geom Slit geometry to fill - * \param z_min Lower bound to lower fill region - * \param z_max Upper bound to upper fill region + * \param y_min Lower bound to lower fill region + * \param y_max Upper bound to upper fill region * \param box Local simulation box * \param mass Mass of fill particles * \param type Type of fill particles @@ -129,9 +129,9 @@ __global__ void slit_draw_particles(Scalar4* d_pos, cudaError_t slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, - const mpcd::detail::SlitGeometry& geom, - const Scalar z_min, - const Scalar z_max, + const mpcd::ParallelPlateGeometry& geom, + const Scalar y_min, + const Scalar y_max, const BoxDim& box, const Scalar mass, const unsigned int type, @@ -162,8 +162,8 @@ cudaError_t slit_draw_particles(Scalar4* d_pos, d_vel, d_tag, geom, - z_min, - z_max, + y_min, + y_max, box, type, N_lo, diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.cuh b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh similarity index 76% rename from hoomd/mpcd/SlitGeometryFillerGPU.cuh rename to hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh index 1bbfa589a4..c9b751d07c 100644 --- a/hoomd/mpcd/SlitGeometryFillerGPU.cuh +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.cuh @@ -5,13 +5,13 @@ #define MPCD_SLIT_GEOMETRY_FILLER_GPU_CUH_ /*! - * \file mpcd/SlitGeometryFillerGPU.cuh - * \brief Declaration of CUDA kernels for mpcd::SlitGeometryFillerGPU + * \file mpcd/ParallelPlateGeometryFillerGPU.cuh + * \brief Declaration of CUDA kernels for mpcd::ParallelPlateGeometryFillerGPU */ #include -#include "SlitGeometry.h" +#include "ParallelPlateGeometry.h" #include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" @@ -21,13 +21,13 @@ namespace mpcd { namespace gpu { -//! Draw virtual particles in the SlitGeometry +//! Draw virtual particles in the ParallelPlateGeometry cudaError_t slit_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, - const mpcd::detail::SlitGeometry& geom, - const Scalar z_min, - const Scalar z_max, + const mpcd::ParallelPlateGeometry& geom, + const Scalar y_min, + const Scalar y_max, const BoxDim& box, const Scalar mass, const unsigned int type, diff --git a/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h new file mode 100644 index 0000000000..929e547f18 --- /dev/null +++ b/hoomd/mpcd/ParallelPlateGeometryFillerGPU.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/ParallelPlateGeometryFillerGPU.h + * \brief Definition of virtual particle filler for mpcd::ParallelPlateGeometry on the GPU. + */ + +#ifndef MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_GPU_H_ +#define MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_GPU_H_ + +#ifdef __HIPCC__ +#error This header cannot be compiled by nvcc +#endif + +#include "ParallelPlateGeometryFiller.h" +#include "hoomd/Autotuner.h" +#include + +namespace hoomd + { +namespace mpcd + { +//! Adds virtual particles to the MPCD particle data for ParallelPlateGeometry using the GPU +class PYBIND11_EXPORT ParallelPlateGeometryFillerGPU : public mpcd::ParallelPlateGeometryFiller + { + public: + //! Constructor + ParallelPlateGeometryFillerGPU(std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T, + std::shared_ptr geom); + + protected: + //! Draw particles within the fill volume on the GPU + virtual void drawParticles(uint64_t timestep); + + private: + std::shared_ptr> m_tuner; //!< Autotuner for drawing particles + }; + +namespace detail + { +//! Export ParallelPlateGeometryFillerGPU to python +void export_ParallelPlateGeometryFillerGPU(pybind11::module& m); + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd +#endif // MPCD_PARALLEL_PLATE_GEOMETRY_FILLER_GPU_H_ diff --git a/hoomd/mpcd/ParticleData.cc b/hoomd/mpcd/ParticleData.cc index f694f0c910..f46c2cf53b 100644 --- a/hoomd/mpcd/ParticleData.cc +++ b/hoomd/mpcd/ParticleData.cc @@ -876,12 +876,16 @@ unsigned int mpcd::ParticleData::getTag(unsigned int idx) const } /*! - * \param N Allocate space for \a N additional virtualq particles in the particle data arrays + * \param N Allocate space for \a N additional virtual particles in the particle data arrays + * \returns The first index of the new virtual particles in the arrays. */ -void mpcd::ParticleData::addVirtualParticles(unsigned int N) +unsigned int mpcd::ParticleData::addVirtualParticles(unsigned int N) { + const unsigned int first_idx = m_N + m_N_virtual; if (N == 0) - return; + { + return first_idx; + } // increase number of virtual particles m_N_virtual += N; @@ -901,6 +905,8 @@ void mpcd::ParticleData::addVirtualParticles(unsigned int N) } notifyNumVirtual(); + + return first_idx; } #ifdef ENABLE_MPI diff --git a/hoomd/mpcd/ParticleData.h b/hoomd/mpcd/ParticleData.h index 108705ad85..42d9c631a4 100644 --- a/hoomd/mpcd/ParticleData.h +++ b/hoomd/mpcd/ParticleData.h @@ -336,7 +336,7 @@ class PYBIND11_EXPORT ParticleData : public Autotuned } //! Allocate memory for virtual particles - void addVirtualParticles(unsigned int N); + unsigned int addVirtualParticles(unsigned int N); //! Remove all virtual particles /*! diff --git a/hoomd/mpcd/SlitPoreGeometry.h b/hoomd/mpcd/PlanarPoreGeometry.h similarity index 72% rename from hoomd/mpcd/SlitPoreGeometry.h rename to hoomd/mpcd/PlanarPoreGeometry.h index 288aa46c8b..c0a821d196 100644 --- a/hoomd/mpcd/SlitPoreGeometry.h +++ b/hoomd/mpcd/PlanarPoreGeometry.h @@ -2,14 +2,12 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitPoreGeometry.h + * \file mpcd/PlanarPoreGeometry.h * \brief Definition of the MPCD slit pore geometry */ -#ifndef MPCD_SLIT_PORE_GEOMETRY_H_ -#define MPCD_SLIT_PORE_GEOMETRY_H_ - -#include "BoundaryCondition.h" +#ifndef MPCD_PLANAR_PORE_GEOMETRY_H_ +#define MPCD_PLANAR_PORE_GEOMETRY_H_ #include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" @@ -25,34 +23,36 @@ namespace hoomd { namespace mpcd { -namespace detail - { + //! Parallel plate (slit) geometry with pore boundaries /*! * This class defines the geometry consistent with two finite-length parallel plates. The plates - * are finite in \a x, infinite in \a y, and stacked in \a z. If a uniform body force is applied + * are finite in \a x, infinite in \a z, and stacked in \a y. If a uniform body force is applied * to the fluid and there are no-slip boundary conditions, parabolic Poiseuille flow profile is * created subject to entrance/exit effects. * - * The channel geometry is defined by two parameters: the channel half-width \a H in \a z and the + * The channel geometry is defined by two parameters: the channel half-width \a H in \a y and the * pore half-length \a L in \a x. The total distance between the plates is \f$2H\f$, and their total - * length is \f$2L\f$. The plates are centered about the origin \f$(x,z)=(0,0)\f$. + * length is \f$2L\f$. The plates are centered about the origin \f$(x,y)=(0,0)\f$. * * There is an infinite bounding wall with normal in \a x at the edges of the pore. * There are no bounding walls away from the pore (PBCs apply). * - * \sa mpcd::detail::SlitGeometry for additional discussion of the boundary conditions, etc. + * \sa mpcd::ParallelPlateGeometry for additional discussion of the boundary conditions, etc. */ -class __attribute__((visibility("default"))) SlitPoreGeometry +class __attribute__((visibility("default"))) PlanarPoreGeometry { public: //! Constructor /*! - * \param H Channel half-width - * \param L Pore half-length - * \param bc Boundary condition at the wall (slip or no-slip) + * \param separation Distance between pore walls + * \param length Pore length + * \param no_slip Boundary condition at the wall (slip or no-slip) */ - HOSTDEVICE SlitPoreGeometry(Scalar H, Scalar L, boundary bc) : m_H(H), m_L(L), m_bc(bc) { } + HOSTDEVICE PlanarPoreGeometry(Scalar separation, Scalar length, bool no_slip) + : m_H(Scalar(0.5) * separation), m_L(Scalar(0.5) * length), m_no_slip(no_slip) + { + } //! Detect collision between the particle and the boundary /*! @@ -75,7 +75,7 @@ class __attribute__((visibility("default"))) SlitPoreGeometry * sign.x is +1 if outside pore in +x, -1 if outside pore in -x, and 0 otherwise. * sign.y is +1 if outside walls in +z, -1 if outside walls in -z, and 0 otherwise. */ const char2 sign = make_char2((char)((pos.x >= m_L) - (pos.x <= -m_L)), - (char)((pos.z > m_H) - (pos.z < -m_H))); + (char)((pos.y > m_H) - (pos.y < -m_H))); // exit early if collision didn't happen if (sign.x != 0 || sign.y == 0) { @@ -83,7 +83,7 @@ class __attribute__((visibility("default"))) SlitPoreGeometry return false; } - // times to either hit the pore in x, or the wall in z + // times to either hit the pore in x, or the wall in y Scalar2 dts; if (vel.x != Scalar(0)) { @@ -96,9 +96,9 @@ class __attribute__((visibility("default"))) SlitPoreGeometry // no collision dts.x = Scalar(-1); } - if (vel.z != Scalar(0)) + if (vel.y != Scalar(0)) { - dts.y = (pos.z - sign.y * m_H) / vel.z; + dts.y = (pos.y - sign.y * m_H) / vel.y; } else { @@ -106,7 +106,7 @@ class __attribute__((visibility("default"))) SlitPoreGeometry dts.y = Scalar(-1); } - // determine if collisions happend with the x or z walls. + // determine if collisions happend with the x or y walls. // if both occur, use the one that happened first (leaves the most time) // this neglects coming through the corner exactly, but that's OK to an approx. uchar2 hits = make_uchar2(dts.x > 0 && dts.x < dt, dts.y > 0 && dts.y < dt); @@ -133,7 +133,7 @@ class __attribute__((visibility("default"))) SlitPoreGeometry else if (!hits.x && hits.y) { dt = dts.y; - n.z = -sign.y; + n.y = -sign.y; } else { @@ -147,7 +147,7 @@ class __attribute__((visibility("default"))) SlitPoreGeometry // update velocity according to boundary conditions // no-slip requires reflection of the tangential components const Scalar3 vn = dot(n, vel) * n; - if (m_bc == boundary::no_slip) + if (m_no_slip) { const Scalar3 vt = vel - vn; vel += Scalar(-2) * vt; @@ -165,33 +165,25 @@ class __attribute__((visibility("default"))) SlitPoreGeometry */ HOSTDEVICE bool isOutside(const Scalar3& pos) const { - return ((pos.x > -m_L && pos.x < m_L) && (pos.z > m_H || pos.z < -m_H)); + return ((pos.x > -m_L && pos.x < m_L) && (pos.y > m_H || pos.y < -m_H)); } - //! Validate that the simulation box is large enough for the geometry + //! Get pore half width /*! - * \param box Global simulation box - * \param cell_size Size of MPCD cell - * - * The box is large enough for the pore if it is padded along the x and z direction so that - * the cells just outside the pore would not interact with each other through the boundary. + * \returns Pore half width */ - HOSTDEVICE bool validateBox(const BoxDim& box, Scalar cell_size) const + HOSTDEVICE Scalar getH() const { - const Scalar3 hi = box.getHi(); - const Scalar3 lo = box.getLo(); - - return ((hi.x - m_L) >= cell_size && (-m_L - lo.x) >= cell_size && (hi.z - m_H) >= cell_size - && (-m_H - lo.z) >= cell_size); + return m_H; } - //! Get pore half width + //! Get distance between pore walls /*! - * \returns Pore half width + * \returns Distance between pore walls */ - HOSTDEVICE Scalar getH() const + HOSTDEVICE Scalar getSeparation() const { - return m_H; + return Scalar(2.0) * m_H; } //! Get pore half length @@ -203,32 +195,40 @@ class __attribute__((visibility("default"))) SlitPoreGeometry return m_L; } + //! Get pore length + /*! + * \returns Pore length + */ + HOSTDEVICE Scalar getLength() const + { + return Scalar(2.0) * m_L; + } + //! Get the wall boundary condition /*! * \returns Boundary condition at wall */ - HOSTDEVICE boundary getBoundaryCondition() const + HOSTDEVICE bool getNoSlip() const { - return m_bc; + return m_no_slip; } #ifndef __HIPCC__ //! Get the unique name of this geometry static std::string getName() { - return std::string("SlitPore"); + return std::string("PlanarPore"); } #endif // __HIPCC__ private: - const Scalar m_H; //!< Half of the channel width - const Scalar m_L; //!< Half of the pore length - const boundary m_bc; //!< Boundary condition + const Scalar m_H; //!< Half of the channel width + const Scalar m_L; //!< Half of the pore length + const bool m_no_slip; //!< Boundary condition }; - } // end namespace detail } // end namespace mpcd } // end namespace hoomd #undef HOSTDEVICE -#endif // MPCD_SLIT_PORE_GEOMETRY_H_ +#endif // MPCD_PLANAR_PORE_GEOMETRY_H_ diff --git a/hoomd/mpcd/SlitPoreGeometryFiller.cc b/hoomd/mpcd/PlanarPoreGeometryFiller.cc similarity index 68% rename from hoomd/mpcd/SlitPoreGeometryFiller.cc rename to hoomd/mpcd/PlanarPoreGeometryFiller.cc index 28aed36b6f..21face4679 100644 --- a/hoomd/mpcd/SlitPoreGeometryFiller.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.cc @@ -2,11 +2,11 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitPoreGeometryFiller.cc - * \brief Definition of mpcd::SlitPoreGeometryFiller + * \file mpcd/PlanarPoreGeometryFiller.cc + * \brief Definition of mpcd::PlanarPoreGeometryFiller */ -#include "SlitPoreGeometryFiller.h" +#include "PlanarPoreGeometryFiller.h" #include "hoomd/RNGIdentifiers.h" #include "hoomd/RandomNumbers.h" @@ -14,17 +14,16 @@ namespace hoomd { -mpcd::SlitPoreGeometryFiller::SlitPoreGeometryFiller( +mpcd::PlanarPoreGeometryFiller::PlanarPoreGeometryFiller( std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T, - uint16_t seed, - std::shared_ptr geom) - : mpcd::VirtualParticleFiller(sysdef, density, type, T), m_num_boxes(0), + std::shared_ptr geom) + : mpcd::ManualVirtualParticleFiller(sysdef, type, density, T), m_num_boxes(0), m_boxes(MAX_BOXES, m_exec_conf), m_ranges(MAX_BOXES, m_exec_conf) { - m_exec_conf->msg->notice(5) << "Constructing MPCD SlitPoreGeometryFiller" << std::endl; + m_exec_conf->msg->notice(5) << "Constructing MPCD PlanarPoreGeometryFiller" << std::endl; setGeometry(geom); @@ -32,19 +31,19 @@ mpcd::SlitPoreGeometryFiller::SlitPoreGeometryFiller( m_needs_recompute = true; m_recompute_cache = make_scalar3(-1, -1, -1); m_pdata->getBoxChangeSignal() - .connect( + .connect( this); } -mpcd::SlitPoreGeometryFiller::~SlitPoreGeometryFiller() +mpcd::PlanarPoreGeometryFiller::~PlanarPoreGeometryFiller() { - m_exec_conf->msg->notice(5) << "Destroying MPCD SlitPoreGeometryFiller" << std::endl; + m_exec_conf->msg->notice(5) << "Destroying MPCD PlanarPoreGeometryFiller" << std::endl; m_pdata->getBoxChangeSignal() - .disconnect( - this); + .disconnect(this); } -void mpcd::SlitPoreGeometryFiller::computeNumFill() +void mpcd::PlanarPoreGeometryFiller::computeNumFill() { const Scalar cell_size = m_cl->getCellSize(); const Scalar max_shift = m_cl->getMaxGridShift(); @@ -60,13 +59,6 @@ void mpcd::SlitPoreGeometryFiller::computeNumFill() // as a precaution, validate the global box with the current cell list const BoxDim& global_box = m_pdata->getGlobalBox(); - if (!m_geom->validateBox(global_box, cell_size)) - { - m_exec_conf->msg->error() - << "Invalid slit pore geometry for global box, cannot fill virtual particles." - << std::endl; - throw std::runtime_error("Invalid slit pore geometry for global box"); - } // box and slit geometry const BoxDim& box = m_pdata->getBox(); @@ -74,7 +66,7 @@ void mpcd::SlitPoreGeometryFiller::computeNumFill() const Scalar3 hi = box.getHi(); const Scalar H = m_geom->getH(); const Scalar L = m_geom->getL(); - const Scalar Ly = box.getL().y; + const Scalar Lz = box.getL().z; /* * Determine the lowest / highest extent of a cells overlapping the boundaries. @@ -83,25 +75,25 @@ void mpcd::SlitPoreGeometryFiller::computeNumFill() */ const Scalar3 global_lo = global_box.getLo(); const Scalar3 global_hi = global_box.getHi(); - Scalar2 x_bounds, z_bounds; + Scalar2 x_bounds, y_bounds; // upper bound on lower wall in x x_bounds.x = cell_size * std::ceil((-L - global_lo.x) / cell_size) + global_lo.x + max_shift; // lower bound on upper wall in x x_bounds.y = cell_size * std::floor((L - global_lo.x) / cell_size) + global_lo.x - max_shift; - // lower bound on lower wall in z - z_bounds.x = cell_size * std::floor((-H - global_lo.z) / cell_size) + global_lo.z - max_shift; - // upper bound on upper wall in z - z_bounds.y = cell_size * std::ceil((H - global_lo.z) / cell_size) + global_lo.z + max_shift; + // lower bound on lower wall in y + y_bounds.x = cell_size * std::floor((-H - global_lo.y) / cell_size) + global_lo.y - max_shift; + // upper bound on upper wall in y + y_bounds.y = cell_size * std::ceil((H - global_lo.y) / cell_size) + global_lo.y + max_shift; - // define the 6 2D bounding boxes (lo.x,hi.x,lo.z,hi.z) for filling in this geometry (y is + // define the 6 2D bounding boxes (lo.x,hi.x,lo.y,hi.y) for filling in this geometry (z is // infinite) (this is essentially a U shape inside the pore). std::array allboxes; - allboxes[0] = make_scalar4(-L, x_bounds.x, z_bounds.y, global_hi.z); - allboxes[1] = make_scalar4(x_bounds.y, L, z_bounds.y, global_hi.z); - allboxes[2] = make_scalar4(-L, x_bounds.x, global_lo.z, z_bounds.x); - allboxes[3] = make_scalar4(x_bounds.y, L, global_lo.z, z_bounds.x); - allboxes[4] = make_scalar4(-L, L, H, z_bounds.y); - allboxes[5] = make_scalar4(-L, L, z_bounds.x, -H); + allboxes[0] = make_scalar4(-L, x_bounds.x, y_bounds.y, global_hi.y); + allboxes[1] = make_scalar4(x_bounds.y, L, y_bounds.y, global_hi.y); + allboxes[2] = make_scalar4(-L, x_bounds.x, global_lo.y, y_bounds.x); + allboxes[3] = make_scalar4(x_bounds.y, L, global_lo.y, y_bounds.x); + allboxes[4] = make_scalar4(-L, L, H, y_bounds.y); + allboxes[5] = make_scalar4(-L, L, y_bounds.x, -H); // find all boxes that overlap the domain ArrayHandle h_boxes(m_boxes, access_location::host, access_mode::overwrite); @@ -112,16 +104,16 @@ void mpcd::SlitPoreGeometryFiller::computeNumFill() { const Scalar4 fillbox = allboxes[i]; // test for bounding box overlap between the domain and the fill box - if (!(hi.x < fillbox.x || lo.x > fillbox.y || hi.z < fillbox.z || lo.z > fillbox.w)) + if (!(hi.x < fillbox.x || lo.x > fillbox.y || hi.y < fillbox.z || lo.y > fillbox.w)) { // some overlap, so clamp the box to the local domain const Scalar4 clampbox = make_scalar4(std::max(fillbox.x, lo.x), std::min(fillbox.y, hi.x), - std::max(fillbox.z, lo.z), - std::min(fillbox.w, hi.z)); + std::max(fillbox.z, lo.y), + std::min(fillbox.w, hi.y)); // determine volume (# of particles) for filling - const Scalar volume = (clampbox.y - clampbox.x) * Ly * (clampbox.w - clampbox.z); + const Scalar volume = (clampbox.y - clampbox.x) * (clampbox.w - clampbox.z) * Lz; const unsigned int N_box = (unsigned int)std::round(volume * m_density); // only add box if it isn't empty @@ -144,7 +136,7 @@ void mpcd::SlitPoreGeometryFiller::computeNumFill() /*! * \param timestep Current timestep to draw particles */ -void mpcd::SlitPoreGeometryFiller::drawParticles(uint64_t timestep) +void mpcd::PlanarPoreGeometryFiller::drawParticles(uint64_t timestep) { // quit early if not filling to ensure we don't access any memory that hasn't been set if (m_N_fill == 0) @@ -175,13 +167,12 @@ void mpcd::SlitPoreGeometryFiller::drawParticles(uint64_t timestep) uint16_t seed = m_sysdef->getSeed(); // index to start filling from - const unsigned int first_idx = m_mpcd_pdata->getN() + m_mpcd_pdata->getNVirtual() - m_N_fill; for (unsigned int i = 0; i < m_N_fill; ++i) { const unsigned int tag = m_first_tag + i; hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitPoreGeometryFiller, timestep, seed), - hoomd::Counter(tag)); + hoomd::Seed(hoomd::RNGIdentifier::PlanarPoreGeometryFiller, timestep, seed), + hoomd::Counter(tag, m_filler_id)); // advanced past end of this box range, take the next if (i >= boxlast) @@ -191,11 +182,11 @@ void mpcd::SlitPoreGeometryFiller::drawParticles(uint64_t timestep) const Scalar4 fillbox = h_boxes.data[boxid]; lo.x = fillbox.x; hi.x = fillbox.y; - lo.z = fillbox.z; - hi.z = fillbox.w; + lo.y = fillbox.z; + hi.y = fillbox.w; } - const unsigned int pidx = first_idx + i; + const unsigned int pidx = m_first_idx + i; h_pos.data[pidx] = make_scalar4(hoomd::UniformDistribution(lo.x, hi.x)(rng), hoomd::UniformDistribution(lo.y, hi.y)(rng), hoomd::UniformDistribution(lo.z, hi.z)(rng), @@ -216,18 +207,17 @@ void mpcd::SlitPoreGeometryFiller::drawParticles(uint64_t timestep) /*! * \param m Python module to export to */ -void mpcd::detail::export_SlitPoreGeometryFiller(pybind11::module& m) +void mpcd::detail::export_PlanarPoreGeometryFiller(pybind11::module& m) { - pybind11::class_>(m, "SlitPoreGeometryFiller") + std::shared_ptr>(m, "PlanarPoreGeometryFiller") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, - unsigned int, - std::shared_ptr>()) - .def("setGeometry", &mpcd::SlitPoreGeometryFiller::setGeometry); + std::shared_ptr>()) + .def_property_readonly("geometry", &mpcd::PlanarPoreGeometryFiller::getGeometry); } } // end namespace hoomd diff --git a/hoomd/mpcd/SlitPoreGeometryFiller.h b/hoomd/mpcd/PlanarPoreGeometryFiller.h similarity index 57% rename from hoomd/mpcd/SlitPoreGeometryFiller.h rename to hoomd/mpcd/PlanarPoreGeometryFiller.h index d41dbdb41d..22fca93734 100644 --- a/hoomd/mpcd/SlitPoreGeometryFiller.h +++ b/hoomd/mpcd/PlanarPoreGeometryFiller.h @@ -2,8 +2,8 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitPoreGeometryFiller.h - * \brief Definition of virtual particle filler for mpcd::detail::SlitPoreGeometry. + * \file mpcd/PlanarPoreGeometryFiller.h + * \brief Definition of virtual particle filler for mpcd::PlanarPoreGeometry. */ #ifndef MPCD_SLIT_PORE_GEOMETRY_FILLER_H_ @@ -13,8 +13,8 @@ #error This header cannot be compiled by nvcc #endif -#include "SlitPoreGeometry.h" -#include "VirtualParticleFiller.h" +#include "ManualVirtualParticleFiller.h" +#include "PlanarPoreGeometry.h" #include @@ -22,31 +22,35 @@ namespace hoomd { namespace mpcd { -//! Adds virtual particles to the MPCD particle data for SlitPoreGeometry +//! Adds virtual particles to the MPCD particle data for PlanarPoreGeometry /*! * Particles are added to the volume that is overlapped by any of the cells that are also "inside" * the channel, subject to the grid shift. */ -class PYBIND11_EXPORT SlitPoreGeometryFiller : public mpcd::VirtualParticleFiller +class PYBIND11_EXPORT PlanarPoreGeometryFiller : public mpcd::ManualVirtualParticleFiller { public: - SlitPoreGeometryFiller(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - uint16_t seed, - std::shared_ptr geom); + PlanarPoreGeometryFiller(std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T, + std::shared_ptr geom); - virtual ~SlitPoreGeometryFiller(); + virtual ~PlanarPoreGeometryFiller(); - void setGeometry(std::shared_ptr geom) + std::shared_ptr getGeometry() const + { + return m_geom; + } + + void setGeometry(std::shared_ptr geom) { m_geom = geom; notifyRecompute(); } protected: - std::shared_ptr m_geom; + std::shared_ptr m_geom; const static unsigned int MAX_BOXES = 6; //!< Maximum number of boxes to fill unsigned int m_num_boxes; //!< Number of boxes to use in filling @@ -70,8 +74,8 @@ class PYBIND11_EXPORT SlitPoreGeometryFiller : public mpcd::VirtualParticleFille namespace detail { -//! Export SlitPoreGeometryFiller to python -void export_SlitPoreGeometryFiller(pybind11::module& m); +//! Export PlanarPoreGeometryFiller to python +void export_PlanarPoreGeometryFiller(pybind11::module& m); } // end namespace detail } // end namespace mpcd } // end namespace hoomd diff --git a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cc b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc similarity index 68% rename from hoomd/mpcd/SlitPoreGeometryFillerGPU.cc rename to hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc index 08ee95ff09..8216ffcaa3 100644 --- a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cc +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cc @@ -2,23 +2,22 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitPoreGeometryFillerGPU.cc - * \brief Definition of mpcd::SlitPoreGeometryFillerGPU + * \file mpcd/PlanarPoreGeometryFillerGPU.cc + * \brief Definition of mpcd::PlanarPoreGeometryFillerGPU */ -#include "SlitPoreGeometryFillerGPU.h" -#include "SlitPoreGeometryFillerGPU.cuh" +#include "PlanarPoreGeometryFillerGPU.h" +#include "PlanarPoreGeometryFillerGPU.cuh" namespace hoomd { -mpcd::SlitPoreGeometryFillerGPU::SlitPoreGeometryFillerGPU( +mpcd::PlanarPoreGeometryFillerGPU::PlanarPoreGeometryFillerGPU( std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T, - uint16_t seed, - std::shared_ptr geom) - : mpcd::SlitPoreGeometryFiller(sysdef, density, type, T, seed, geom) + std::shared_ptr geom) + : mpcd::PlanarPoreGeometryFiller(sysdef, type, density, T, geom) { m_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -29,7 +28,7 @@ mpcd::SlitPoreGeometryFillerGPU::SlitPoreGeometryFillerGPU( /*! * \param timestep Current timestep */ -void mpcd::SlitPoreGeometryFillerGPU::drawParticles(uint64_t timestep) +void mpcd::PlanarPoreGeometryFillerGPU::drawParticles(uint64_t timestep) { ArrayHandle d_pos(m_mpcd_pdata->getPositions(), access_location::device, @@ -45,8 +44,6 @@ void mpcd::SlitPoreGeometryFillerGPU::drawParticles(uint64_t timestep) ArrayHandle d_boxes(m_boxes, access_location::device, access_mode::read); ArrayHandle d_ranges(m_ranges, access_location::device, access_mode::read); - const unsigned int first_idx = m_mpcd_pdata->getN() + m_mpcd_pdata->getNVirtual() - m_N_fill; - uint16_t seed = m_sysdef->getSeed(); m_tuner->begin(); @@ -61,7 +58,7 @@ void mpcd::SlitPoreGeometryFillerGPU::drawParticles(uint64_t timestep) m_mpcd_pdata->getMass(), m_type, m_first_tag, - first_idx, + m_first_idx, (*m_T)(timestep), timestep, seed, @@ -74,18 +71,18 @@ void mpcd::SlitPoreGeometryFillerGPU::drawParticles(uint64_t timestep) /*! * \param m Python module to export to */ -void mpcd::detail::export_SlitPoreGeometryFillerGPU(pybind11::module& m) +void mpcd::detail::export_PlanarPoreGeometryFillerGPU(pybind11::module& m) { - pybind11::class_>(m, - "SlitPoreGeometryFillerGPU") + pybind11::class_>( + m, + "PlanarPoreGeometryFillerGPU") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr, - unsigned int, - std::shared_ptr>()); + std::shared_ptr>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cu b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu similarity index 96% rename from hoomd/mpcd/SlitPoreGeometryFillerGPU.cu rename to hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu index ce52bbbddd..0f9313a177 100644 --- a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cu +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cu @@ -2,12 +2,12 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitGeometryFillerGPU.cu - * \brief Defines GPU functions and kernels used by mpcd::SlitGeometryFillerGPU + * \file mpcd/ParallelPlateGeometryFillerGPU.cu + * \brief Defines GPU functions and kernels used by mpcd::ParallelPlateGeometryFillerGPU */ #include "ParticleDataUtilities.h" -#include "SlitPoreGeometryFillerGPU.cuh" +#include "PlanarPoreGeometryFillerGPU.cuh" #include "hoomd/RNGIdentifiers.h" #include "hoomd/RandomNumbers.h" @@ -87,8 +87,8 @@ __global__ void slit_pore_draw_particles(Scalar4* d_pos, const Scalar4 fillbox = s_boxes[boxid]; lo.x = fillbox.x; hi.x = fillbox.y; - lo.z = fillbox.z; - hi.z = fillbox.w; + lo.y = fillbox.z; + hi.y = fillbox.w; break; } } @@ -100,7 +100,7 @@ __global__ void slit_pore_draw_particles(Scalar4* d_pos, // initialize random number generator for positions and velocity hoomd::RandomGenerator rng( - hoomd::Seed(hoomd::RNGIdentifier::SlitPoreGeometryFiller, timestep, seed), + hoomd::Seed(hoomd::RNGIdentifier::PlanarPoreGeometryFiller, timestep, seed), hoomd::Counter(tag)); d_pos[pidx] = make_scalar4(hoomd::UniformDistribution(lo.x, hi.x)(rng), hoomd::UniformDistribution(lo.y, hi.y)(rng), diff --git a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cuh b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cuh similarity index 87% rename from hoomd/mpcd/SlitPoreGeometryFillerGPU.cuh rename to hoomd/mpcd/PlanarPoreGeometryFillerGPU.cuh index f391f6e1ff..9814ad90c8 100644 --- a/hoomd/mpcd/SlitPoreGeometryFillerGPU.cuh +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.cuh @@ -5,13 +5,13 @@ #define MPCD_SLIT_PORE_GEOMETRY_FILLER_GPU_CUH_ /*! - * \file mpcd/SlitGeometryFillerGPU.cuh - * \brief Declaration of CUDA kernels for mpcd::SlitPoreGeometryFillerGPU + * \file mpcd/PlanarPoreGeometryFillerGPU.cuh + * \brief Declaration of CUDA kernels for mpcd::PlanarPoreGeometryFillerGPU */ #include -#include "SlitPoreGeometry.h" +#include "PlanarPoreGeometry.h" #include "hoomd/BoxDim.h" #include "hoomd/HOOMDMath.h" @@ -21,7 +21,7 @@ namespace mpcd { namespace gpu { -//! Draw virtual particles in the SlitPoreGeometry +//! Draw virtual particles in the PlanarPoreGeometry cudaError_t slit_pore_draw_particles(Scalar4* d_pos, Scalar4* d_vel, unsigned int* d_tag, diff --git a/hoomd/mpcd/SlitPoreGeometryFillerGPU.h b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h similarity index 50% rename from hoomd/mpcd/SlitPoreGeometryFillerGPU.h rename to hoomd/mpcd/PlanarPoreGeometryFillerGPU.h index 7378bf2b2e..0084f4a779 100644 --- a/hoomd/mpcd/SlitPoreGeometryFillerGPU.h +++ b/hoomd/mpcd/PlanarPoreGeometryFillerGPU.h @@ -2,8 +2,8 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. /*! - * \file mpcd/SlitPoreGeometryFillerGPU.h - * \brief Definition of virtual particle filler for mpcd::detail::SlitPoreGeometry on the GPU. + * \file mpcd/PlanarPoreGeometryFillerGPU.h + * \brief Definition of virtual particle filler for mpcd::PlanarPoreGeometry on the GPU. */ #ifndef MPCD_SLIT_PORE_GEOMETRY_FILLER_GPU_H_ @@ -13,7 +13,7 @@ #error This header cannot be compiled by nvcc #endif -#include "SlitPoreGeometryFiller.h" +#include "PlanarPoreGeometryFiller.h" #include "hoomd/Autotuner.h" #include @@ -21,17 +21,16 @@ namespace hoomd { namespace mpcd { -//! Adds virtual particles to the MPCD particle data for SlitPoreGeometry using the GPU -class PYBIND11_EXPORT SlitPoreGeometryFillerGPU : public mpcd::SlitPoreGeometryFiller +//! Adds virtual particles to the MPCD particle data for PlanarPoreGeometry using the GPU +class PYBIND11_EXPORT PlanarPoreGeometryFillerGPU : public mpcd::PlanarPoreGeometryFiller { public: //! Constructor - SlitPoreGeometryFillerGPU(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - uint16_t seed, - std::shared_ptr geom); + PlanarPoreGeometryFillerGPU(std::shared_ptr sysdef, + const std::string& type, + Scalar density, + std::shared_ptr T, + std::shared_ptr geom); protected: //! Draw particles within the fill volume on the GPU @@ -43,8 +42,8 @@ class PYBIND11_EXPORT SlitPoreGeometryFillerGPU : public mpcd::SlitPoreGeometryF namespace detail { -//! Export SlitPoreGeometryFillerGPU to python -void export_SlitPoreGeometryFillerGPU(pybind11::module& m); +//! Export PlanarPoreGeometryFillerGPU to python +void export_PlanarPoreGeometryFillerGPU(pybind11::module& m); } // end namespace detail } // end namespace mpcd } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethod.cc b/hoomd/mpcd/SRDCollisionMethod.cc index d403547803..c2589830d5 100644 --- a/hoomd/mpcd/SRDCollisionMethod.cc +++ b/hoomd/mpcd/SRDCollisionMethod.cc @@ -16,9 +16,9 @@ mpcd::SRDCollisionMethod::SRDCollisionMethod(std::shared_ptr s unsigned int cur_timestep, unsigned int period, int phase, - uint16_t seed) + Scalar angle) : mpcd::CollisionMethod(sysdef, cur_timestep, period, phase), m_rotvec(m_exec_conf), - m_angle(0.0), m_factors(m_exec_conf) + m_angle(angle), m_factors(m_exec_conf) { m_exec_conf->msg->notice(5) << "Constructing MPCD SRD collision method" << std::endl; } @@ -153,9 +153,10 @@ void mpcd::SRDCollisionMethod::rotate(uint64_t timestep) // load rotation vector and precompute functions for rotation matrix ArrayHandle h_rotvec(m_rotvec, access_location::host, access_mode::read); - const double cos_a = slow::cos(m_angle); + const double angle_rad = m_angle * M_PI / 180.0; + const double cos_a = slow::cos(angle_rad); const double one_minus_cos_a = 1.0 - cos_a; - const double sin_a = slow::sin(m_angle); + const double sin_a = slow::sin(angle_rad); // load scale factors if required const bool use_thermostat = (m_T) ? true : false; @@ -284,14 +285,14 @@ void mpcd::detail::export_SRDCollisionMethod(pybind11::module& m) pybind11::class_>(m, "SRDCollisionMethod") - .def(pybind11::init, - unsigned int, - unsigned int, - int, - unsigned int>()) - .def("setRotationAngle", &mpcd::SRDCollisionMethod::setRotationAngle) - .def("setTemperature", &mpcd::SRDCollisionMethod::setTemperature) - .def("unsetTemperature", &mpcd::SRDCollisionMethod::unsetTemperature); + .def(pybind11:: + init, unsigned int, unsigned int, int, Scalar>()) + .def_property("angle", + &mpcd::SRDCollisionMethod::getRotationAngle, + &mpcd::SRDCollisionMethod::setRotationAngle) + .def_property("kT", + &mpcd::SRDCollisionMethod::getTemperature, + &mpcd::SRDCollisionMethod::setTemperature); } } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethod.h b/hoomd/mpcd/SRDCollisionMethod.h index 70d1bc1b88..1858f1b95d 100644 --- a/hoomd/mpcd/SRDCollisionMethod.h +++ b/hoomd/mpcd/SRDCollisionMethod.h @@ -30,24 +30,24 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod unsigned int cur_timestep, unsigned int period, int phase, - uint16_t seed); + Scalar angle); //! Destructor virtual ~SRDCollisionMethod(); void setCellList(std::shared_ptr cl); - //! Get the MPCD rotation angle - double getRotationAngle() const + //! Get the MPCD rotation angles + Scalar getRotationAngle() const { return m_angle; } //! Set the MPCD rotation angle /*! - * \param angle MPCD rotation angle in radians + * \param angle MPCD rotation angle in degrees */ - void setRotationAngle(double angle) + void setRotationAngle(Scalar angle) { m_angle = angle; } @@ -64,16 +64,16 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod return m_factors; } - //! Set the temperature and enable the thermostat - void setTemperature(std::shared_ptr T) + //! Get the temperature + std::shared_ptr getTemperature() const { - m_T = T; + return m_T; } - //! Unset the temperature - void unsetTemperature() + //! Set the temperature + void setTemperature(std::shared_ptr T) { - m_T = std::shared_ptr(); + m_T = T; } //! Get the requested thermo flags @@ -89,7 +89,7 @@ class PYBIND11_EXPORT SRDCollisionMethod : public mpcd::CollisionMethod protected: std::shared_ptr m_thermo; //!< Cell thermo GPUVector m_rotvec; //!< MPCD rotation vectors - double m_angle; //!< MPCD rotation angle (radians) + Scalar m_angle; //!< MPCD rotation angle (degrees) std::shared_ptr m_T; //!< Temperature for thermostat GPUVector m_factors; //!< Cell-level rescale factors diff --git a/hoomd/mpcd/SRDCollisionMethodGPU.cc b/hoomd/mpcd/SRDCollisionMethodGPU.cc index 80903ee98d..5e59bb5a13 100644 --- a/hoomd/mpcd/SRDCollisionMethodGPU.cc +++ b/hoomd/mpcd/SRDCollisionMethodGPU.cc @@ -16,8 +16,8 @@ mpcd::SRDCollisionMethodGPU::SRDCollisionMethodGPU(std::shared_ptr({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -102,6 +102,7 @@ void mpcd::SRDCollisionMethodGPU::rotate(uint64_t timestep) new ArrayHandle(m_factors, access_location::device, access_mode::read)); } + const double angle_rad = m_angle * M_PI / 180.0; if (m_embed_group) { ArrayHandle d_embed_group(m_embed_group->getIndexArray(), @@ -123,7 +124,7 @@ void mpcd::SRDCollisionMethodGPU::rotate(uint64_t timestep) d_embed_cell_ids.data, d_cell_vel.data, d_rotvec.data, - m_angle, + angle_rad, (m_T) ? d_factors->data : NULL, N_mpcd, N_tot, @@ -141,7 +142,7 @@ void mpcd::SRDCollisionMethodGPU::rotate(uint64_t timestep) NULL, d_cell_vel.data, d_rotvec.data, - m_angle, + angle_rad, (m_T) ? d_factors->data : NULL, N_mpcd, N_tot, @@ -179,11 +180,9 @@ void mpcd::detail::export_SRDCollisionMethodGPU(pybind11::module& m) pybind11::class_>(m, "SRDCollisionMethodGPU") - .def(pybind11::init, - unsigned int, - unsigned int, - int, - unsigned int>()); + .def( + pybind11:: + init, unsigned int, unsigned int, int, Scalar>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SRDCollisionMethodGPU.h b/hoomd/mpcd/SRDCollisionMethodGPU.h index 14a2041b03..03c1d2f429 100644 --- a/hoomd/mpcd/SRDCollisionMethodGPU.h +++ b/hoomd/mpcd/SRDCollisionMethodGPU.h @@ -28,7 +28,7 @@ class PYBIND11_EXPORT SRDCollisionMethodGPU : public mpcd::SRDCollisionMethod unsigned int cur_timestep, unsigned int period, int phase, - uint16_t seed); + Scalar angle); void setCellList(std::shared_ptr cl); diff --git a/hoomd/mpcd/SineForce.cc b/hoomd/mpcd/SineForce.cc new file mode 100644 index 0000000000..9be2631d49 --- /dev/null +++ b/hoomd/mpcd/SineForce.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/SineForce.cc + * \brief Export MPCD SineForce. + */ + +#include "SineForce.h" + +namespace hoomd + { +namespace mpcd + { +namespace detail + { + +void export_SineForce(pybind11::module& m) + { + pybind11::class_>(m, "SineForce") + .def(pybind11::init()) + .def_property("amplitude", &SineForce::getAmplitude, &SineForce::setAmplitude) + .def_property("wavenumber", &SineForce::getWavenumber, &SineForce::setWavenumber); + } + + } // end namespace detail + } // end namespace mpcd + } // end namespace hoomd diff --git a/hoomd/mpcd/SineForce.h b/hoomd/mpcd/SineForce.h new file mode 100644 index 0000000000..db4ff8aa48 --- /dev/null +++ b/hoomd/mpcd/SineForce.h @@ -0,0 +1,114 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +/*! + * \file mpcd/SineForce.h + * \brief Definition of mpcd::SineForce. + */ + +#ifndef MPCD_SINE_FORCE_H_ +#define MPCD_SINE_FORCE_H_ + +#include "hoomd/HOOMDMath.h" + +#ifdef __HIPCC__ +#define HOSTDEVICE __host__ __device__ +#define INLINE inline +#else +#define HOSTDEVICE +#define INLINE inline __attribute__((always_inline)) +#include +#include +#endif + +namespace hoomd + { +namespace mpcd + { + +//! Shearing sine force +/*! + * Imposes a sinusoidally varying force in x as a function of position in y. + * The shape of the force is controlled by the amplitude and the wavenumber. + * + * \f[ + * \mathbf{F}(\mathbf{r}) = F \sin (k r_y) \mathbf{e}_x + * \f] + */ +class __attribute__((visibility("default"))) SineForce + { + public: + //! Default constructor + HOSTDEVICE SineForce() : SineForce(0, 0) { } + + //! Constructor + /*! + * \param F Amplitude of the force. + * \param k Wavenumber for the force. + */ + HOSTDEVICE SineForce(Scalar F, Scalar k) : m_F(F), m_k(k) { } + + //! Force evaluation method + /*! + * \param r Particle position. + * \returns Force on the particle. + * + * Specifies the force to act in x as a function of y. Fast math + * routines are used since this is probably sufficiently accurate, + * given the other numerical errors already present. + */ + HOSTDEVICE Scalar3 evaluate(const Scalar3& r) const + { + return make_scalar3(m_F * fast::sin(m_k * r.y), 0, 0); + } + + //! Get the sine amplitude + Scalar getAmplitude() const + { + return m_F; + } + + //! Set the sine amplitude + void setAmplitude(Scalar F) + { + m_F = F; + } + + //! Get the sine wavenumber + Scalar getWavenumber() const + { + return m_k; + } + + //! Set the sine wavenumber + void setWavenumber(Scalar k) + { + m_k = k; + } + +#ifndef __HIPCC__ + //! Get the unique name of this force + static std::string getName() + { + return std::string("SineForce"); + } +#endif // __HIPCC__ + + private: + Scalar m_F; //!< Force constant + Scalar m_k; //!< Wavenumber for force in y + }; + +#ifndef __HIPCC__ +namespace detail + { +void export_SineForce(pybind11::module& m); + } // end namespace detail +#endif // __HIPCC__ + + } // end namespace mpcd + } // end namespace hoomd +#undef HOSTDEVICE +#undef INLINE + +#endif // MPCD_SINE_FORCE_H_ diff --git a/hoomd/mpcd/SlitGeometryFiller.h b/hoomd/mpcd/SlitGeometryFiller.h deleted file mode 100644 index 53eae42855..0000000000 --- a/hoomd/mpcd/SlitGeometryFiller.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/SlitGeometryFiller.h - * \brief Definition of virtual particle filler for mpcd::detail::SlitGeometry. - */ - -#ifndef MPCD_SLIT_GEOMETRY_FILLER_H_ -#define MPCD_SLIT_GEOMETRY_FILLER_H_ - -#ifdef __HIPCC__ -#error This header cannot be compiled by nvcc -#endif - -#include "SlitGeometry.h" -#include "VirtualParticleFiller.h" - -#include - -namespace hoomd - { -namespace mpcd - { -//! Adds virtual particles to the MPCD particle data for SlitGeometry -/*! - * Particles are added to the volume that is overlapped by any of the cells that are also "inside" - * the channel, subject to the grid shift. - */ -class PYBIND11_EXPORT SlitGeometryFiller : public mpcd::VirtualParticleFiller - { - public: - SlitGeometryFiller(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - std::shared_ptr geom); - - virtual ~SlitGeometryFiller(); - - void setGeometry(std::shared_ptr geom) - { - m_geom = geom; - } - - protected: - std::shared_ptr m_geom; - Scalar m_z_min; //!< Min z coordinate for filling - Scalar m_z_max; //!< Max z coordinate for filling - unsigned int m_N_lo; //!< Number of particles to fill below channel - unsigned int m_N_hi; //!< number of particles to fill above channel - - //! Compute the total number of particles to fill - virtual void computeNumFill(); - - //! Draw particles within the fill volume - virtual void drawParticles(uint64_t timestep); - }; - -namespace detail - { -//! Export SlitGeometryFiller to python -void export_SlitGeometryFiller(pybind11::module& m); - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd -#endif // MPCD_SLIT_GEOMETRY_FILLER_H_ diff --git a/hoomd/mpcd/SlitGeometryFillerGPU.h b/hoomd/mpcd/SlitGeometryFillerGPU.h deleted file mode 100644 index 47aa8cc403..0000000000 --- a/hoomd/mpcd/SlitGeometryFillerGPU.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file mpcd/SlitGeometryFillerGPU.h - * \brief Definition of virtual particle filler for mpcd::detail::SlitGeometry on the GPU. - */ - -#ifndef MPCD_SLIT_GEOMETRY_FILLER_GPU_H_ -#define MPCD_SLIT_GEOMETRY_FILLER_GPU_H_ - -#ifdef __HIPCC__ -#error This header cannot be compiled by nvcc -#endif - -#include "SlitGeometryFiller.h" -#include "hoomd/Autotuner.h" -#include - -namespace hoomd - { -namespace mpcd - { -//! Adds virtual particles to the MPCD particle data for SlitGeometry using the GPU -class PYBIND11_EXPORT SlitGeometryFillerGPU : public mpcd::SlitGeometryFiller - { - public: - //! Constructor - SlitGeometryFillerGPU(std::shared_ptr sysdef, - Scalar density, - unsigned int type, - std::shared_ptr T, - std::shared_ptr geom); - - protected: - //! Draw particles within the fill volume on the GPU - virtual void drawParticles(uint64_t timestep); - - private: - std::shared_ptr> m_tuner; //!< Autotuner for drawing particles - }; - -namespace detail - { -//! Export SlitGeometryFillerGPU to python -void export_SlitGeometryFillerGPU(pybind11::module& m); - } // end namespace detail - } // end namespace mpcd - } // end namespace hoomd -#endif // MPCD_SLIT_GEOMETRY_FILLER_GPU_H_ diff --git a/hoomd/mpcd/Sorter.cc b/hoomd/mpcd/Sorter.cc index 545d704e0f..0adcdc876e 100644 --- a/hoomd/mpcd/Sorter.cc +++ b/hoomd/mpcd/Sorter.cc @@ -13,16 +13,11 @@ namespace hoomd /*! * \param sysdef System definition */ -mpcd::Sorter::Sorter(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period) - : m_sysdef(sysdef), m_pdata(m_sysdef->getParticleData()), m_exec_conf(m_pdata->getExecConf()), - m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_order(m_exec_conf), m_rorder(m_exec_conf), - m_period(period) +mpcd::Sorter::Sorter(std::shared_ptr sysdef, std::shared_ptr trigger) + : Tuner(sysdef, trigger), m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_order(m_exec_conf), + m_rorder(m_exec_conf) { m_exec_conf->msg->notice(5) << "Constructing MPCD Sorter" << std::endl; - - setPeriod(cur_timestep, period); } mpcd::Sorter::~Sorter() @@ -37,9 +32,6 @@ mpcd::Sorter::~Sorter() */ void mpcd::Sorter::update(uint64_t timestep) { - if (!shouldSort(timestep)) - return; - if (!m_cl) { throw std::runtime_error("Cell list has not been set"); @@ -159,33 +151,13 @@ void mpcd::Sorter::applyOrder() const m_mpcd_pdata->swapTags(); } -bool mpcd::Sorter::peekSort(uint64_t timestep) const - { - if (timestep < m_next_timestep) - return false; - else - return ((timestep - m_next_timestep) % m_period == 0); - } - -bool mpcd::Sorter::shouldSort(uint64_t timestep) - { - if (peekSort(timestep)) - { - m_next_timestep = timestep + m_period; - return true; - } - else - return false; - } - /*! * \param m Python module to export to */ void mpcd::detail::export_Sorter(pybind11::module& m) { - pybind11::class_>(m, "Sorter") - .def(pybind11::init, unsigned int, unsigned int>()) - .def("setPeriod", &mpcd::Sorter::setPeriod); + pybind11::class_>(m, "Sorter") + .def(pybind11::init, std::shared_ptr>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/Sorter.h b/hoomd/mpcd/Sorter.h index 0ad19ff11a..b6a1633b2e 100644 --- a/hoomd/mpcd/Sorter.h +++ b/hoomd/mpcd/Sorter.h @@ -15,8 +15,9 @@ #include "CellList.h" -#include "hoomd/Autotuned.h" #include "hoomd/SystemDefinition.h" +#include "hoomd/Tuner.h" + #include namespace hoomd @@ -38,13 +39,11 @@ namespace mpcd * because they cannot be removed easily if they are sorted with the rest of the particles, * and the performance gains from doing a separate (segmented) sort on them is probably small. */ -class PYBIND11_EXPORT Sorter : public Autotuned +class PYBIND11_EXPORT Sorter : public Tuner { public: //! Constructor - Sorter(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period); + Sorter(std::shared_ptr sysdef, std::shared_ptr trigger); //! Destructor virtual ~Sorter(); @@ -52,16 +51,6 @@ class PYBIND11_EXPORT Sorter : public Autotuned //! Update the particle data order virtual void update(uint64_t timestep); - bool peekSort(uint64_t timestep) const; - - //! Change the period - void setPeriod(unsigned int cur_timestep, unsigned int period) - { - m_period = period; - const unsigned int multiple = cur_timestep / m_period + (cur_timestep % m_period != 0); - m_next_timestep = multiple * m_period; - } - //! Set the cell list used for sorting virtual void setCellList(std::shared_ptr cl) { @@ -72,27 +61,17 @@ class PYBIND11_EXPORT Sorter : public Autotuned } protected: - std::shared_ptr m_sysdef; //!< HOOMD system definition - std::shared_ptr m_pdata; //!< HOOMD particle data - std::shared_ptr m_exec_conf; //!< Execution configuration - std::shared_ptr m_mpcd_pdata; //!< MPCD particle data std::shared_ptr m_cl; //!< MPCD cell list GPUVector m_order; //!< Maps new sorted index onto old particle indexes GPUVector m_rorder; //!< Maps old particle indexes onto new sorted indexes - unsigned int m_period; //!< Sorting period - uint64_t m_next_timestep; //!< Next step to apply sorting - //! Compute the sorting order at the current timestep virtual void computeOrder(uint64_t timestep); //! Apply the sorting order virtual void applyOrder() const; - - private: - bool shouldSort(uint64_t timestep); }; namespace detail diff --git a/hoomd/mpcd/SorterGPU.cc b/hoomd/mpcd/SorterGPU.cc index 2cd4319f70..e906f725b6 100644 --- a/hoomd/mpcd/SorterGPU.cc +++ b/hoomd/mpcd/SorterGPU.cc @@ -15,9 +15,8 @@ namespace hoomd * \param sysdef System definition */ mpcd::SorterGPU::SorterGPU(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period) - : mpcd::Sorter(sysdef, cur_timestep, period) + std::shared_ptr trigger) + : mpcd::Sorter(sysdef, trigger) { m_sentinel_tuner.reset(new Autotuner<1>({AutotunerBase::makeBlockSizeRange(m_exec_conf)}, m_exec_conf, @@ -180,7 +179,7 @@ void mpcd::detail::export_SorterGPU(pybind11::module& m) { pybind11::class_>(m, "SorterGPU") - .def(pybind11::init, unsigned int, unsigned int>()); + .def(pybind11::init, std::shared_ptr>()); } } // end namespace hoomd diff --git a/hoomd/mpcd/SorterGPU.h b/hoomd/mpcd/SorterGPU.h index b91e2906e5..80515140f3 100644 --- a/hoomd/mpcd/SorterGPU.h +++ b/hoomd/mpcd/SorterGPU.h @@ -29,9 +29,7 @@ class PYBIND11_EXPORT SorterGPU : public mpcd::Sorter { public: //! Constructor - SorterGPU(std::shared_ptr sysdef, - unsigned int cur_timestep, - unsigned int period); + SorterGPU(std::shared_ptr sysdef, std::shared_ptr trigger); protected: /// Kernel tuner for filling sentinels in cell list. diff --git a/hoomd/mpcd/StreamingGeometry.cc b/hoomd/mpcd/StreamingGeometry.cc index 384f40c372..12f2de22ac 100644 --- a/hoomd/mpcd/StreamingGeometry.cc +++ b/hoomd/mpcd/StreamingGeometry.cc @@ -14,35 +14,27 @@ namespace mpcd { namespace detail { -void export_boundary(pybind11::module& m) - { - pybind11::enum_(m, "boundary") - .value("no_slip", boundary::no_slip) - .value("slip", boundary::slip); - } - -void export_BulkGeometry(pybind11::module& m) - { - pybind11::class_>(m, "BulkGeometry") - .def(pybind11::init<>()); - } -void export_SlitGeometry(pybind11::module& m) +void export_ParallelPlateGeometry(pybind11::module& m) { - pybind11::class_>(m, "SlitGeometry") - .def(pybind11::init()) - .def("getH", &SlitGeometry::getH) - .def("getVelocity", &SlitGeometry::getVelocity) - .def("getBoundaryCondition", &SlitGeometry::getBoundaryCondition); + pybind11::class_>( + m, + ParallelPlateGeometry::getName().c_str()) + .def(pybind11::init()) + .def_property_readonly("separation", &ParallelPlateGeometry::getSeparation) + .def_property_readonly("speed", &ParallelPlateGeometry::getSpeed) + .def_property_readonly("no_slip", &ParallelPlateGeometry::getNoSlip); } -void export_SlitPoreGeometry(pybind11::module& m) +void export_PlanarPoreGeometry(pybind11::module& m) { - pybind11::class_>(m, "SlitPoreGeometry") - .def(pybind11::init()) - .def("getH", &SlitPoreGeometry::getH) - .def("getL", &SlitPoreGeometry::getL) - .def("getBoundaryCondition", &SlitPoreGeometry::getBoundaryCondition); + pybind11::class_>( + m, + PlanarPoreGeometry::getName().c_str()) + .def(pybind11::init()) + .def_property_readonly("separation", &PlanarPoreGeometry::getSeparation) + .def_property_readonly("length", &PlanarPoreGeometry::getLength) + .def_property_readonly("no_slip", &PlanarPoreGeometry::getNoSlip); } } // end namespace detail diff --git a/hoomd/mpcd/StreamingGeometry.h b/hoomd/mpcd/StreamingGeometry.h index 6c21378723..fa088e8083 100644 --- a/hoomd/mpcd/StreamingGeometry.h +++ b/hoomd/mpcd/StreamingGeometry.h @@ -9,10 +9,8 @@ #ifndef MPCD_STREAMING_GEOMETRY_H_ #define MPCD_STREAMING_GEOMETRY_H_ -#include "BoundaryCondition.h" -#include "BulkGeometry.h" -#include "SlitGeometry.h" -#include "SlitPoreGeometry.h" +#include "ParallelPlateGeometry.h" +#include "PlanarPoreGeometry.h" #ifndef __HIPCC__ #include @@ -23,17 +21,12 @@ namespace mpcd { namespace detail { -//! Export boundary enum to python -void export_boundary(pybind11::module& m); -//! Export BulkGeometry to python -void export_BulkGeometry(pybind11::module& m); +//! Export ParallelPlateGeometry to python +void export_ParallelPlateGeometry(pybind11::module& m); -//! Export SlitGeometry to python -void export_SlitGeometry(pybind11::module& m); - -//! Export SlitPoreGeometry to python -void export_SlitPoreGeometry(pybind11::module& m); +//! Export PlanarPoreGeometry to python +void export_PlanarPoreGeometry(pybind11::module& m); } // end namespace detail } // end namespace mpcd diff --git a/hoomd/mpcd/StreamingMethod.cc b/hoomd/mpcd/StreamingMethod.cc index 7769a5a775..819f113453 100644 --- a/hoomd/mpcd/StreamingMethod.cc +++ b/hoomd/mpcd/StreamingMethod.cc @@ -120,9 +120,7 @@ void mpcd::detail::export_StreamingMethod(pybind11::module& m) m, "StreamingMethod") .def(pybind11::init, unsigned int, unsigned int, int>()) - .def("setPeriod", &mpcd::StreamingMethod::setPeriod) - .def("setField", &mpcd::StreamingMethod::setField) - .def("removeField", &mpcd::StreamingMethod::removeField); + .def_property_readonly("period", &mpcd::StreamingMethod::getPeriod); } } // end namespace hoomd diff --git a/hoomd/mpcd/StreamingMethod.h b/hoomd/mpcd/StreamingMethod.h index 61466b553b..4f8d5d19ba 100644 --- a/hoomd/mpcd/StreamingMethod.h +++ b/hoomd/mpcd/StreamingMethod.h @@ -14,9 +14,7 @@ #endif #include "CellList.h" -#include "ExternalField.h" #include "hoomd/Autotuned.h" -#include "hoomd/GPUPolymorph.h" #include "hoomd/SystemDefinition.h" #include @@ -66,19 +64,13 @@ class PYBIND11_EXPORT StreamingMethod : public Autotuned return m_mpcd_dt; } - //! Set the external field - void setField(std::shared_ptr> field) + //! Get the streaming period + unsigned int getPeriod() const { - m_field = field; + return m_period; } - //! Remove the external field - void removeField() - { - m_field.reset(); - } - - //! Set the period of the streaming method + //! Set the streaming period void setPeriod(unsigned int cur_timestep, unsigned int period); //! Set the cell list used for collisions @@ -98,8 +90,6 @@ class PYBIND11_EXPORT StreamingMethod : public Autotuned unsigned int m_period; //!< Number of MD timesteps between streaming steps uint64_t m_next_timestep; //!< Timestep next streaming step should be performed - std::shared_ptr> m_field; //!< External field - //! Check if streaming should occur virtual bool shouldStream(uint64_t timestep); }; diff --git a/hoomd/mpcd/VirtualParticleFiller.cc b/hoomd/mpcd/VirtualParticleFiller.cc index e57a75ee00..3b2ee99026 100644 --- a/hoomd/mpcd/VirtualParticleFiller.cc +++ b/hoomd/mpcd/VirtualParticleFiller.cc @@ -10,51 +10,25 @@ namespace hoomd { +unsigned int mpcd::VirtualParticleFiller::s_filler_count = 0; + mpcd::VirtualParticleFiller::VirtualParticleFiller(std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T) : m_sysdef(sysdef), m_pdata(m_sysdef->getParticleData()), m_exec_conf(m_pdata->getExecConf()), - m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_density(density), m_type(type), m_T(T), - m_N_fill(0), m_first_tag(0) - { - } - -void mpcd::VirtualParticleFiller::fill(uint64_t timestep) + m_mpcd_pdata(m_sysdef->getMPCDParticleData()), m_density(density), m_T(T) { - if (!m_cl) - { - throw std::runtime_error("Cell list has not been set"); - } + setType(type); - // update the fill volume - computeNumFill(); - - // in mpi, do a prefix scan on the tag offset in this range - // then shift the first tag by the current number of particles, which ensures a compact tag - // array - m_first_tag = 0; + // assign ID from count, but synchronize with root in case count got off somewhere + m_filler_id = s_filler_count++; #ifdef ENABLE_MPI if (m_exec_conf->getNRanks() > 1) { - // scan the number to fill to get the tag range I own - MPI_Exscan(&m_N_fill, - &m_first_tag, - 1, - MPI_UNSIGNED, - MPI_SUM, - m_exec_conf->getMPICommunicator()); + bcast(m_filler_id, 0, m_exec_conf->getMPICommunicator()); } #endif // ENABLE_MPI - m_first_tag += m_mpcd_pdata->getNGlobal() + m_mpcd_pdata->getNVirtualGlobal(); - - // add the new virtual particles locally - m_mpcd_pdata->addVirtualParticles(m_N_fill); - - // draw the particles consistent with those tags - drawParticles(timestep); - - m_mpcd_pdata->invalidateCellCache(); } void mpcd::VirtualParticleFiller::setDensity(Scalar density) @@ -67,15 +41,40 @@ void mpcd::VirtualParticleFiller::setDensity(Scalar density) m_density = density; } -void mpcd::VirtualParticleFiller::setType(unsigned int type) +std::string mpcd::VirtualParticleFiller::getType() const { - if (type >= m_mpcd_pdata->getNTypes()) + return m_mpcd_pdata->getNameByType(m_type); + } + +void mpcd::VirtualParticleFiller::setType(const std::string& type) + { + m_type = m_mpcd_pdata->getTypeByName(type); + } + +/*! + * \param N_fill Number of virtual particles to add on each rank + * \returns First tag to assign to a virtual particle on each rank. + */ +unsigned int mpcd::VirtualParticleFiller::computeFirstTag(unsigned int N_fill) const + { + // exclusive scan of number on each rank in MPI + unsigned int first_tag = 0; +#ifdef ENABLE_MPI + if (m_exec_conf->getNRanks() > 1) { - m_exec_conf->msg->error() << "Invalid type id specified for MPCD virtual particle filler" - << std::endl; - throw std::runtime_error("Invalid type id"); + MPI_Exscan(&N_fill, + &first_tag, + 1, + MPI_UNSIGNED, + MPI_SUM, + m_exec_conf->getMPICommunicator()); } - m_type = type; +#endif // ENABLE_MPI + + // shift the first tag based on the number of particles (real and virtual) already used + first_tag += m_mpcd_pdata->getNGlobal() + m_mpcd_pdata->getNVirtualGlobal(); + + return first_tag; } /*! @@ -87,12 +86,18 @@ void mpcd::detail::export_VirtualParticleFiller(pybind11::module& m) m, "VirtualParticleFiller") .def(pybind11::init, + const std::string&, Scalar, - unsigned int, std::shared_ptr>()) - .def("setDensity", &mpcd::VirtualParticleFiller::setDensity) - .def("setType", &mpcd::VirtualParticleFiller::setType) - .def("setTemperature", &mpcd::VirtualParticleFiller::setTemperature); + .def_property("density", + &mpcd::VirtualParticleFiller::getDensity, + &mpcd::VirtualParticleFiller::setDensity) + .def_property("type", + &mpcd::VirtualParticleFiller::getType, + &mpcd::VirtualParticleFiller::setType) + .def_property("kT", + &mpcd::VirtualParticleFiller::getTemperature, + &mpcd::VirtualParticleFiller::setTemperature); } } // end namespace hoomd diff --git a/hoomd/mpcd/VirtualParticleFiller.h b/hoomd/mpcd/VirtualParticleFiller.h index 0f247762b2..4fb8f57ceb 100644 --- a/hoomd/mpcd/VirtualParticleFiller.h +++ b/hoomd/mpcd/VirtualParticleFiller.h @@ -3,7 +3,7 @@ /*! * \file mpcd/VirtualParticleFiller.h - * \brief Definition of class for backfilling solid boundaries with virtual particles. + * \brief Definition of abstract base class for backfilling solid boundaries with virtual particles. */ #ifndef MPCD_VIRTUAL_PARTICLE_FILLER_H_ @@ -20,6 +20,8 @@ #include "hoomd/Variant.h" #include +#include + namespace hoomd { namespace mpcd @@ -28,30 +30,41 @@ namespace mpcd /*! * Virtual particles are used to pad cells sliced by solid boundaries so that their viscosity does * not get too low. The VirtualParticleFiller base class defines an interface for adding these - * particles. The base VirtualParticleFiller implements a fill() method, which handles the basic - * tasks of appending a certain number of virtual particles to the particle data. Each deriving - * class must then implement two methods: - * 1. computeNumFill(), which is the number of virtual particles to add. - * 2. drawParticles(), which is the rule to determine where to put the particles. + * particles. Deriving classes must implement a fill() method that adds the particles. */ class PYBIND11_EXPORT VirtualParticleFiller : public Autotuned { public: VirtualParticleFiller(std::shared_ptr sysdef, + const std::string& type, Scalar density, - unsigned int type, std::shared_ptr T); virtual ~VirtualParticleFiller() { } //! Fill up virtual particles - void fill(uint64_t timestep); + virtual void fill(uint64_t timestep) { } + + //! Get the fill particle density + Scalar getDensity() const + { + return m_density; + } //! Set the fill particle density void setDensity(Scalar density); + //! Get the filler particle type + std::string getType() const; + //! Set the fill particle type - void setType(unsigned int type); + void setType(const std::string& type); + + //! Get the fill particle temperature + std::shared_ptr getTemperature() const + { + return m_T; + } //! Set the fill particle temperature void setTemperature(std::shared_ptr T) @@ -75,15 +88,12 @@ class PYBIND11_EXPORT VirtualParticleFiller : public Autotuned Scalar m_density; //!< Fill density unsigned int m_type; //!< Fill type std::shared_ptr m_T; //!< Temperature for filled particles + unsigned int m_filler_id; //!< Unique ID of this filler - unsigned int m_N_fill; //!< Number of particles to fill locally - unsigned int m_first_tag; //!< First tag of locally held particles - - //! Compute the total number of particles to fill - virtual void computeNumFill() { } + unsigned int computeFirstTag(unsigned int N_fill) const; - //! Draw particles within the fill volume - virtual void drawParticles(uint64_t timestep) { } + private: + static unsigned int s_filler_count; //!< Total count of fillers, used to assign unique ID }; namespace detail diff --git a/hoomd/mpcd/__init__.py b/hoomd/mpcd/__init__.py index 5428a031c5..c46a133ffb 100644 --- a/hoomd/mpcd/__init__.py +++ b/hoomd/mpcd/__init__.py @@ -1,254 +1,70 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" Multiparticle collision dynamics. +"""Multiparticle collision dynamics. Simulating complex fluids and soft matter using conventional molecular dynamics -methods (:py:mod:`hoomd.md`) can be computationally demanding due to large -disparities in the relevant length and time scales between molecular-scale -solvents and mesoscale solutes such as polymers, colloids, and deformable -materials like cells. One way to overcome this challenge is to simplify the model -for the solvent while retaining its most important interactions with the solute. -MPCD is a particle-based simulation method for resolving solvent-mediated -fluctuating hydrodynamic interactions with a microscopically detailed solute -model. This method has been successfully applied to a simulate a broad class -of problems, including polymer solutions and colloidal suspensions both in and -out of equilibrium. +methods (`hoomd.md`) can be computationally demanding due to large disparities +in the relevant length and time scales between molecular-scale solvents and +mesoscale solutes such as polymers, colloids, or cells. One way to overcome this +challenge is to simplify the model for the solvent while retaining its most +important interactions with the solute. MPCD is a particle-based simulation +method for resolving solvent-mediated fluctuating hydrodynamic interactions with +a microscopically detailed solute model. .. rubric:: Algorithm -In MPCD, the solvent is represented by point particles having continuous -positions and velocities. The solvent particles propagate in alternating +In MPCD, a fluid is represented by point particles having continuous +positions and velocities. The MPCD particles propagate in alternating streaming and collision steps. During the streaming step, particles evolve -according to Newton's equations of motion. Typically, no external forces are -applied to the solvent, and streaming is straightforward with a large time step. -Particles are then binned into local cells and undergo a stochastic multiparticle -collision within the cell. Collisions lead to the build up of hydrodynamic -interactions, and the frequency and nature of the collisions, along with the -solvent properties, determine the transport coefficients. All standard collision -rules conserve linear momentum within the cell and can optionally be made to -enforce angular-momentum conservation. Currently, we have implemented -the following collision rules with linear-momentum conservation only: - - * :py:obj:`~hoomd.mpcd.collide.srd` -- Stochastic rotation dynamics - * :py:obj:`~hoomd.mpcd.collide.at` -- Andersen thermostat - -Solute particles can be coupled to the solvent during the collision step. This -is particularly useful for soft materials like polymers. Standard molecular -dynamics integration can be applied to the solute. Coupling to the MPCD -solvent introduces both hydrodynamic interactions and a heat bath that acts as -a thermostat. In the future, fluid-solid coupling will also be introduced during -the streaming step to couple hard particles and boundaries. - -Details of this implementation of the MPCD algorithm for HOOMD-blue can be found +according to Newton's equations of motion. Particles are then binned into local +cells and undergo a stochastic collision within the cell. Collisions lead to the +build up of hydrodynamic interactions, and the frequency and nature of the +collisions, along with the fluid properties, determine the transport +coefficients. All standard collision rules conserve linear momentum within the +cell and can optionally be made to enforce angular-momentum conservation. +Currently, we have implemented the following collision rules with +linear-momentum conservation only: + +* :class:`~hoomd.mpcd.collide.StochasticRotationDynamics` +* :class:`~hoomd.mpcd.collide.AndersenThermostat` + +Solute particles can be coupled to the MPCD particles during the collision step. +This is particularly useful for soft materials like polymers. Standard molecular +dynamics methods can be applied to the solute. Coupling to the MPCD particles +introduces both hydrodynamic interactions and a heat bath that acts as a +thermostat. + +The MPCD particles can additionally be coupled to solid boundaries (with no-slip +or slip boundary conditions) during the streaming step. + +Details of HOOMD-blue's implementation of the MPCD algorithm can be found in `M. P. Howard et al. (2018) `_. +Note, though, that continued improvements to the code may cause some deviations. .. rubric:: Getting started MPCD is intended to be used as an add-on to the standard MD methods in -:py:mod:`hoomd.md`. To get started, take the following steps: - - 1. Initialize any solute particles using standard methods (`Simulation`). - 2. Initialize the MPCD solvent particles using one of the methods in - :py:mod:`.mpcd.init`. Additional details on how to manipulate the solvent - particle data can be found in :py:mod:`.mpcd.data`. - 3. Create an MPCD :py:obj:`~hoomd.mpcd.integrator`. - 4. Choose the appropriate streaming method from :py:mod:`.mpcd.stream`. - 5. Choose the appropriate collision rule from :py:mod:`.mpcd.collide`, and set - the collision rule parameters. - 6. Setup an MD integrator and any interactions between solute particles. - 7. Optionally, configure the sorting frequency to improve performance (see - :py:obj:`update.sort`). - 8. Run your simulation! - -Example script for a pure bulk SRD fluid:: - - import hoomd - hoomd.context.initialize() - from hoomd import mpcd - - # Initialize (empty) solute in box. - box = hoomd.data.boxdim(L=100.) - hoomd.init.read_snapshot(hoomd.data.make_snapshot(N=0, box=box)) - - # Initialize MPCD particles and set sorting period. - s = mpcd.init.make_random(N=int(10*box.get_volume()), kT=1.0, seed=7) - s.sorter.set_period(period=25) - - # Create MPCD integrator with streaming and collision methods. - mpcd.integrator(dt=0.1) - mpcd.stream.bulk(period=1) - mpcd.collide.srd(seed=42, period=1, angle=130., kT=1.0) - - hoomd.run(2000) - -.. rubric:: Stability +`hoomd.md`. Getting started can look like: -:py:mod:`hoomd.mpcd` is currently **stable**, but remains under development. -When upgrading versions, existing job scripts may need to be need to be updated. -Such modifications will be noted in the change log. +1. Initialize the MPCD particles through `Snapshot.mpcd`. You can include any + solute particles in the snapshot as well. +2. Create the MPCD `Integrator`. Setup solute particle integration methods + and interactions as you normally would to use `hoomd.md`. +3. Choose the streaming method from :mod:`.mpcd.stream`. +4. Choose the collision rule from :mod:`.mpcd.collide`. Couple the solute to the + collision step. +5. Run your simulation! """ -# these imports are necessary in order to link derived types between modules -import hoomd -from hoomd import _hoomd -from hoomd.md import _md - from hoomd.mpcd import collide +from hoomd.mpcd import fill from hoomd.mpcd import force +from hoomd.mpcd import geometry from hoomd.mpcd import integrate +from hoomd.mpcd.integrate import Integrator +from hoomd.mpcd import methods from hoomd.mpcd import stream -from hoomd.mpcd import update - - -class integrator(): - """ MPCD integrator - - Args: - dt (float): Each time step of the simulation ```hoomd.run``` will - advance the real time of the system forward by *dt* (in time units). - aniso (bool): Whether to integrate rotational degrees of freedom (bool), - default None (autodetect). - - The MPCD integrator enables the MPCD algorithm concurrently with standard - MD :py:mod:`~hoomd.md.methods` methods. An integrator must be created - in order for :py:mod:`~hoomd.mpcd.stream` and :py:mod:`~hoomd.mpcd.collide` - methods to take effect. Embedded MD particles require the creation of an - appropriate integration method. Typically, this will just be - :py:class:`~hoomd.md.methods.NVE`. - - In MPCD simulations, *dt* defines the amount of time that the system is advanced - forward every time step. MPCD streaming and collision steps can be defined to - occur in multiples of *dt*. In these cases, any MD particle data will be updated - every *dt*, while the MPCD particle data is updated asynchronously for performance. - For example, if MPCD streaming happens every 5 steps, then the particle data will be - updated as follows:: - - 0 1 2 3 4 5 - MD: |---->|---->|---->|---->|---->| - MPCD: |---------------------------->| - - If the MPCD particle data is accessed via the snapshot interface at time - step 3, it will actually contain the MPCD particle data for time step 5. - The MD particles can be read at any time step because their positions - are updated every step. - - Examples:: - - mpcd.integrator(dt=0.1) - mpcd.integrator(dt=0.01, aniso=True) - - """ - - def __init__(self, dt, aniso=None): - # check system is initialized - if hoomd.context.current.mpcd is None: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.integrate: an MPCD system must be initialized before the integrator\n' - ) - raise RuntimeError('MPCD system not initialized') - - # hoomd.integrate._integrator.__init__(self) - - self.supports_methods = True - self.dt = dt - self.aniso = aniso - self.metadata_fields = ['dt', 'aniso'] - - # configure C++ integrator - self.cpp_integrator = _mpcd.Integrator(hoomd.context.current.mpcd.data, - self.dt) - if hoomd.context.current.mpcd.comm is not None: - self.cpp_integrator.setMPCDCommunicator( - hoomd.context.current.mpcd.comm) - hoomd.context.current.system.setIntegrator(self.cpp_integrator) - - if self.aniso is not None: - self.set_params(aniso=aniso) - - _aniso_modes = {} - - def set_params(self, dt=None, aniso=None): - """ Changes parameters of an existing integration mode. - - Args: - dt (float): New time step delta (if set) (in time units). - aniso (bool): Anisotropic integration mode (bool), default None (autodetect). - - Examples:: - - integrator.set_params(dt=0.007) - integrator.set_params(dt=0.005, aniso=False) - - """ - self.check_initialization() - - # change the parameters - if dt is not None: - self.dt = dt - self.cpp_integrator.setDeltaT(dt) - - if aniso is not None: - if aniso in self._aniso_modes: - anisoMode = self._aniso_modes[aniso] - else: - hoomd.context.current.device.cpp_msg.error( - "mpcd.integrate: unknown anisotropic mode {}.\n".format( - aniso)) - raise RuntimeError( - "Error setting anisotropic integration mode.") - self.aniso = aniso - self.cpp_integrator.setAnisotropicMode(anisoMode) - - def update_methods(self): - self.check_initialization() - - # update the integration methods that are set - self.cpp_integrator.removeAllIntegrationMethods() - for m in hoomd.context.current.integration_methods: - self.cpp_integrator.addIntegrationMethod(m.cpp_method) - - # remove all virtual particle fillers before readding them - self.cpp_integrator.removeAllFillers() - - # ensure that the streaming and collision methods are up to date - stream = hoomd.context.current.mpcd._stream - if stream is not None: - self.cpp_integrator.setStreamingMethod(stream._cpp) - if stream._filler is not None: - self.cpp_integrator.addFiller(stream._filler) - else: - hoomd.context.current.device.cpp_msg.warning( - "Running mpcd without a streaming method!\n") - self.cpp_integrator.removeStreamingMethod() - - collide = hoomd.context.current.mpcd._collide - if collide is not None: - if stream is not None and (collide.period < stream.period - or collide.period % stream.period != 0): - hoomd.context.current.device.cpp_msg.error( - 'mpcd.integrate: collision period must be multiple of integration period\n' - ) - raise ValueError( - 'Collision period must be multiple of integration period') - - self.cpp_integrator.setCollisionMethod(collide._cpp) - else: - hoomd.context.current.device.cpp_msg.warning( - "Running mpcd without a collision method!\n") - self.cpp_integrator.removeCollisionMethod() - - sorter = hoomd.context.current.mpcd.sorter - if sorter is not None and sorter.enabled: - if collide is not None and (sorter.period < collide.period - or sorter.period % collide.period != 0): - hoomd.context.current.device.cpp_msg.error( - 'mpcd.integrate: sorting period should be a multiple of collision period\n' - ) - raise ValueError( - 'Sorting period must be multiple of collision period') - self.cpp_integrator.setSorter(sorter._cpp) - else: - self.cpp_integrator.removeSorter() +from hoomd.mpcd import tune diff --git a/hoomd/mpcd/collide.py b/hoomd/mpcd/collide.py index 95fa838f60..32fc2c5662 100644 --- a/hoomd/mpcd/collide.py +++ b/hoomd/mpcd/collide.py @@ -1,367 +1,355 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD collision methods +"""MPCD collision methods. -An MPCD collision method is required to update the particle velocities over time. -It is meant to be used in conjunction with an :py:class:`~hoomd.mpcd.integrator` -and streaming method (see :py:mod:`~hoomd.mpcd.stream`). Particles are binned into -cells based on their positions, and all particles in a cell undergo a stochastic -collision that updates their velocities while conserving linear momentum. Collision -rules can optionally be extended to also enforce angular-momentum conservation. -The stochastic collision lead to a build up of hydrodynamic interactions, and the -choice of collision rule and solvent properties determine the transport coefficients. +An MPCD collision method is required to update the particle velocities over +time. Particles are binned into cells based on their positions, and all +particles in a cell undergo a stochastic collision that updates their velocities +while conserving linear momentum. Collision rules can optionally be extended to +also conserve angular momentum The stochastic collisions lead to a build up of +hydrodynamic interactions, and the choice of collision rule determines the +transport coefficients. + +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) """ import hoomd -from hoomd.md import _md +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes, variant_preprocessing +from hoomd.mpcd import _mpcd +from hoomd.operation import Compute, Operation -from . import _mpcd -import numpy as np - -class _collision_method(): - """ Base collision method +class CellList(Compute): + """Collision cell list. Args: - seed (int): Seed to the collision method random number generator (must be positive) - period (int): Number of integration steps between collisions + cell_size (float): Size of a collision cell. + shift (bool): When True, randomly shift underlying collision cells. - This class is not intended to be initialized directly by the user. Instead, - initialize a specific collision method directly. It is included in the documentation - to supply signatures for common methods. + The MPCD `CellList` bins particles into cubic cells of edge length + `cell_size`. Currently, the simulation box must be orthorhombic, and its + edges must be a multiple of `cell_size`. - """ - - def __init__(self, seed, period): - # check for hoomd initialization - if not hoomd.init.is_initialized(): - raise RuntimeError( - 'mpcd.collide: system must be initialized before collision method\n' - ) + When the total mean-free path of the MPCD particles is small, the cells + should be randomly shifted in order to ensure Galilean invariance of the + algorithm. This random shift is drawn from a uniform distribution, and it + can shift the grid by up to half a cell in each direction. The performance + penalty from grid shifting is small, so it is recommended to enable it in + all simulations. - # check for mpcd initialization - if hoomd.context.current.mpcd is None: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.collide: an MPCD system must be initialized before the collision method\n' - ) - raise RuntimeError('MPCD system not initialized') + .. rubric:: Example: - # check for multiple collision rule initializations - if hoomd.context.current.mpcd._collide is not None: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.collide: only one collision method can be created.\n') - raise RuntimeError('Multiple initialization of collision method') + Access default cell list from integrator. - self.period = period - self.seed = seed - self.group = None - self.shift = True - self.enabled = True - self._cpp = None + .. code-block:: python - self.enable() + cell_list = simulation.operations.integrator.cell_list - def embed(self, group): - """ Embed a particle group into the MPCD collision + Attributes: + cell_size (float): Edge length of a collision cell. - Args: - group (``hoomd.group``): Group of particles to embed + .. rubric:: Example: - The *group* is embedded into the MPCD collision step and cell properties. - During collisions, the embedded particles are included in determining - per-cell quantities, and the collisions are applied to the embedded - particles. + .. code-block:: python - No integrator is generated for *group*. Usually, you will need to create - a separate method to integrate the embedded particles. The recommended - (and most common) integrator to use is :py:class:`~hoomd.md.methods.NVE`. - It is generally **not** a good idea to use a thermostatting integrator for - the embedded particles, since the MPCD particles themselves already act - as a heat bath that will thermalize the embedded particles. + cell_list.cell_size = 1.0 - Examples:: + shift (bool): When True, randomly shift underlying collision cells. - polymer = hoomd.group.type('P') - md.integrate.nve(group=polymer) - method.embed(polymer) + .. rubric:: Example: - """ + .. code-block:: python - self.group = group - self._cpp.setEmbeddedGroup(group.cpp_group) + cell_list.shift = True - def enable(self): - """ Enable the collision method + """ - Examples:: + def __init__(self, cell_size, shift=True): + super().__init__() - method.enable() + param_dict = ParameterDict( + cell_size=float(cell_size), + shift=bool(shift), + ) + self._param_dict.update(param_dict) - Enabling the collision method adds it to the current MPCD system definition. - Only one collision method can be attached to the system at any time. - If another method is already set, ``disable`` must be called - first before switching. + def _attach_hook(self): + sim = self._simulation + sim._warn_if_seed_unset() - """ + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _mpcd.CellListGPU + else: + cpp_class = _mpcd.CellList - self.enabled = True - hoomd.context.current.mpcd._collide = self + self._cpp_obj = cpp_class(sim.state._cpp_sys_def, self.cell_size, + self.shift) - def disable(self): - """ Disable the collision method + super()._attach_hook() - Examples:: - method.disable() +class CollisionMethod(Operation): + """Base collision method. - Disabling the collision method removes it from the current MPCD system definition. - Only one collision method can be attached to the system at any time, so - use this method to remove the current collision method before adding another. + Args: + period (int): Number of integration steps between collisions. + embedded_particles (hoomd.filter.filter_like): HOOMD particles to + include in collision. - """ + Attributes: + embedded_particles (hoomd.filter.filter_like): HOOMD particles to + include in collision (*read only*). - self.enabled = False - hoomd.context.current.mpcd._collide = None + These particles are included in per-cell quantities and have their + velocities updated along with the MPCD particles. - def set_period(self, period): - """ Set the collision period. + You will need to create an appropriate method to integrate the + positions of these particles. The recommended integrator is + :class:`~hoomd.md.methods.ConstantVolume` with no thermostat (NVE). + It is generally **not** a good idea to use a thermostat because the + MPCD particles themselves already act as a heat bath for the + embedded particles. - Args: - period (int): New collision period. + Warning: + Do not embed particles that are part of a rigid body. Momentum + will not be correctly transferred to the body. Support for this + is planned in future. - The MPCD collision period can only be changed to a new value on a - simulation timestep that is a multiple of both the previous *period* - and the new *period*. An error will be raised if it is not. + period (int): Number of integration steps between collisions + (*read only*). - Examples:: + A collision is executed each time the + :attr:`~hoomd.Simulation.timestep` is a multiple of `period`. It + must be a multiple of `period` for the + :class:`~hoomd.mpcd.stream.StreamingMethod` if one is attached to + the :class:`~hoomd.mpcd.Integrator`. - # The initial period is 5. - # The period can be updated to 2 on step 10. - hoomd.run_upto(10) - method.set_period(period=2) + """ - # The period can be updated to 4 on step 12. - hoomd.run_upto(12) - hoomd.set_period(period=4) + def __init__(self, period, embedded_particles=None): + super().__init__() - """ + param_dict = ParameterDict( + period=int(period), + embedded_particles=OnlyTypes(hoomd.filter.ParticleFilter, + allow_none=True), + ) + param_dict["embedded_particles"] = embedded_particles + self._param_dict.update(param_dict) - cur_tstep = hoomd.context.current.system.getCurrentTimeStep() - if cur_tstep % self.period != 0 or cur_tstep % period != 0: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.collide: collision period can only be changed on multiple of current and new period.\n' - ) - raise RuntimeError( - 'collision period can only be changed on multiple of current and new period' - ) - self._cpp.setPeriod(cur_tstep, period) - self.period = period +class AndersenThermostat(CollisionMethod): + r"""Andersen thermostat collision method. + Args: + period (int): Number of integration steps between collisions. + kT (hoomd.variant.variant_like): Temperature of the thermostat + :math:`[\mathrm{energy}]`. + embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to + include in collision. -class at(_collision_method): - r""" Andersen thermostat method - Args: - seed (int): Seed to the collision method random number generator (must be positive) - period (int): Number of integration steps between collisions - kT (:py:mod:`hoomd.variant` or :py:obj:`float`): Temperature set - point for the thermostat (in energy units). - group (``hoomd.group``): Group of particles to embed in collisions - - This class implements the Andersen thermostat collision rule for MPCD, as described - by `Allahyarov and Gompper `_. - Every *period* steps, the particles are binned into cells. The size of the cell - can be selected as a property of the MPCD system (see :py:meth:`.data.system.set_params`). + This class implements the Andersen thermostat collision rule for MPCD, as + described by `Allahyarov and Gompper + `_. Every + :attr:`~CollisionMethod.period` steps, the particles are binned into cells. New particle velocities are then randomly drawn from a Gaussian distribution - (using *seed*) relative to the center-of-mass velocity for the cell. The random - velocities are given zero-mean so that the cell momentum is conserved. This - collision rule naturally imparts the canonical (NVT) ensemble consistent - with *kT*. The properties of the AT fluid are tuned using *period*, *kT*, the - underlying size of the MPCD cell list, and the particle density. + relative to the center-of-mass velocity for the cell. The random velocities + are given zero mean so that the cell linear momentum is conserved. This + collision rule naturally imparts the constant-temperature ensemble + consistent with `kT`. - Note: - The *period* must be chosen as a multiple of the MPCD - :py:mod:`~hoomd.mpcd.stream` period. Other values will result in an - error when ```hoomd.run``` is called. + .. rubric:: Examples: - When the total mean-free path of the MPCD particles is small, the underlying - MPCD cell list must be randomly shifted in order to ensure Galilean - invariance. Because the performance penalty from grid shifting is small, - shifting is enabled by default in all simulations. Disable it using - :py:meth:`set_params()` if you are sure that you do not want to use it. + Collision for only MPCD particles. - HOOMD particles in *group* can be embedded into the collision step (see - ``embed``). A separate integration method (:py:mod:`~hoomd.md.methods`) - must be specified in order to integrate the positions of particles in *group*. - The recommended integrator is :py:class:`~hoomd.md.methods.NVE`. + .. code-block:: python - Examples:: + andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat( + period=1, + kT=1.0) + simulation.operations.integrator.collision_method = andersen_thermostat - collide.at(seed=42, period=1, kT=1.0) - collide.at(seed=77, period=50, kT=1.5, group=hoomd.group.all()) + Collision including embedded particles. - """ + .. code-block:: python - def __init__(self, seed, period, kT, group=None): + andersen_thermostat = hoomd.mpcd.collide.AndersenThermostat( + period=20, + kT=1.0, + embedded_particles=hoomd.filter.All()) + simulation.operations.integrator.collision_method = andersen_thermostat - _collision_method.__init__(self, seed, period) - self.kT = hoomd.variant._setup_variant_input(kT) + Attributes: + kT (hoomd.variant.variant_like): Temperature of the thermostat + :math:`[\mathrm{energy}]`. - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - collide_class = _mpcd.ATCollisionMethod - thermo_class = _mpcd.CellThermoCompute - else: - collide_class = _mpcd.ATCollisionMethodGPU - thermo_class = _mpcd.CellThermoComputeGPU + This temperature determines the distribution used to generate the + random numbers. - # create an auxiliary thermo compute and disable logging on it - if hoomd.context.current.mpcd._at_thermo is None: - rand_thermo = thermo_class(hoomd.context.current.mpcd.data) - hoomd.context.current.system.addCompute(rand_thermo, - "mpcd_at_thermo") - hoomd.context.current.mpcd._at_thermo = rand_thermo + .. rubric:: Examples: - self._cpp = collide_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), self.period, 0, - self.seed, hoomd.context.current.mpcd._thermo, - hoomd.context.current.mpcd._at_thermo, self.kT.cpp_variant) + Constant temperature. - if group is not None: - self.embed(group) + .. code-block:: python - def set_params(self, shift=None, kT=None): - """ Set parameters for the SRD collision method + andersen_thermostat.kT = 1.0 - Args: - shift (bool): If True, perform a random shift of the underlying cell list. - kT (:py:mod:`hoomd.variant` or :py:obj:`float`): Temperature - set point for the thermostat (in energy units). + Variable temperature. - Examples:: + .. code-block:: python - srd.set_params(shift=False) - srd.set_params(shift=True, kT=1.0) - srd.set_params(kT=hoomd.data.variant.linear_interp([[0,1.0],[100,5.0]])) + andersen_thermostat.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) - """ + """ - if shift is not None: - self.shift = shift - self._cpp.enableGridShifting(shift) - if kT is not None: - self.kT = hoomd.variant._setup_variant_input(kT) - self._cpp.setTemperature(self.kT.cpp_variant) + def __init__(self, period, kT, embedded_particles=None): + super().__init__(period, embedded_particles) + param_dict = ParameterDict(kT=hoomd.variant.Variant) + param_dict["kT"] = kT + self._param_dict.update(param_dict) + + def _attach_hook(self): + sim = self._simulation + sim._warn_if_seed_unset() + + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _mpcd.ATCollisionMethodGPU + else: + cpp_class = _mpcd.ATCollisionMethod -class srd(_collision_method): - r""" Stochastic rotation dynamics method + self._cpp_obj = cpp_class(sim.state._cpp_sys_def, sim.timestep, + self.period, 0, self.kT) + + if self.embedded_particles is not None: + self._cpp_obj.setEmbeddedGroup( + sim.state._get_group(self.embedded_particles)) + + super()._attach_hook() + + +class StochasticRotationDynamics(CollisionMethod): + r"""Stochastic rotation dynamics collision method. Args: - seed (int): Seed to the collision method random number generator (must be positive) - period (int): Number of integration steps between collisions - angle (float): SRD rotation angle (degrees) - kT (:py:mod:`hoomd.variant` or :py:obj:`float` or bool): Temperature set - point for the thermostat (in energy units). If False (default), no - thermostat is applied and an NVE simulation is run. - group (``hoomd.group``): Group of particles to embed in collisions - - This class implements the classic stochastic rotation dynamics collision - rule for MPCD as first proposed by `Malevanets and Kapral `_. - Every *period* steps, the particles are binned into cells. The size of the cell - can be selected as a property of the MPCD system (see :py:meth:`.data.system.set_params`). - The particle velocities are then rotated by *angle* around an - axis randomly drawn from the unit sphere. The rotation is done relative to - the average velocity, so this rotation rule conserves momentum and energy - within each cell, and so also globally. The properties of the SRD fluid - are tuned using *period*, *angle*, *kT*, the underlying size of the MPCD - cell list, and the particle density. - - Note: - The *period* must be chosen as a multiple of the MPCD - :py:mod:`~hoomd.mpcd.stream` period. Other values will - result in an error when ```hoomd.run``` is called. - - When the total mean-free path of the MPCD particles is small, the underlying - MPCD cell list must be randomly shifted in order to ensure Galilean - invariance. Because the performance penalty from grid shifting is small, - shifting is enabled by default in all simulations. Disable it using - :py:meth:`set_params()` if you are sure that you do not want to use it. - - HOOMD particles in *group* can be embedded into the collision step (see - ``embed()``). A separate integration method (:py:mod:`~hoomd.md.methods`) - must be specified in order to integrate the positions of particles in *group*. - The recommended integrator is :py:class:`~hoomd.md.methods.NVE`. + period (int): Number of integration steps between collisions. + angle (float): Rotation angle (in degrees). + kT (hoomd.variant.variant_like): Temperature for the collision + thermostat :math:`[\mathrm{energy}]`. If None, no thermostat is + used. + embedded_particles (hoomd.filter.ParticleFilter): HOOMD particles to + include in collision. + + This class implements the stochastic rotation dynamics (SRD) collision + rule for MPCD proposed by `Malevanets and Kapral + `_. Every :attr:`~CollisionMethod.period` + steps, the particles are binned into cells. The particle velocities are then + rotated by `angle` around an axis randomly drawn from the unit sphere. The + rotation is done relative to the average velocity, so this rotation rule + conserves linear momentum and kinetic energy within each cell. The SRD method naturally imparts the NVE ensemble to the system comprising - the MPCD particles and *group*. Accordingly, the system must be properly - initialized to the correct temperature. (SRD has an H theorem, and so - particles exchange momentum to reach an equilibrium temperature.) A thermostat - can be applied in conjunction with the SRD method through the *kT* parameter. - SRD employs a `Maxwell-Boltzmann thermostat `_ - on the cell level, which generates the (correct) isothermal ensemble. The - temperature is defined relative to the cell-average velocity, and so can be - used to dissipate heat in nonequilibrium simulations. Under this thermostat, the - SRD algorithm still conserves momentum, but energy is of course no longer conserved. - - Examples:: - - collide.srd(seed=42, period=1, angle=130.) - collide.srd(seed=77, period=50, angle=130., group=hoomd.group.all()) - collide.srd(seed=1991, period=10, angle=90., kT=1.5) + the MPCD particles and the `embedded_particles`. Accordingly, the system + must be properly initialized to the correct temperature. A thermostat can be + applied in conjunction with the SRD method through the `kT` parameter. SRD + employs a `Maxwell-Boltzmann thermostat + `_ on the cell level, which + generates a constant-temperature ensemble. The temperature is defined + relative to the cell-average velocity, and so can be used to dissipate heat + in nonequilibrium simulations. Under this thermostat, the SRD algorithm + still conserves linear momentum, but kinetic energy is no longer conserved. + + .. rubric:: Examples: + + Collision of MPCD particles. + + .. code-block:: python + + srd = hoomd.mpcd.collide.StochasticRotationDynamics(period=1, angle=130) + simulation.operations.integrator.collision_method = srd + + Collision of MPCD particles with thermostat. + + .. code-block:: python + + srd = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, + angle=130, + kT=1.0) + simulation.operations.integrator.collision_method = srd + + Collision including embedded particles. + + .. code-block:: python + + srd = hoomd.mpcd.collide.StochasticRotationDynamics( + period=20, + angle=130, + kT=1.0, + embedded_particles=hoomd.filter.All()) + simulation.operations.integrator.collision_method = srd + + Attributes: + angle (float): Rotation angle (in degrees) + + .. rubric:: Example: + + .. code-block:: python + + srd.angle = 130 + + kT (hoomd.variant.variant_like): Temperature for the collision + thermostat :math:`[\mathrm{energy}]`. + + .. rubric:: Examples: + + Constant temperature. + + .. code-block:: python + + srd.kT = 1.0 + + Variable temperature. + + .. code-block:: python + + srd.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) """ - def __init__(self, seed, period, angle, kT=False, group=None): + def __init__(self, period, angle, kT=None, embedded_particles=None): + super().__init__(period, embedded_particles) + + param_dict = ParameterDict( + angle=float(angle), + kT=OnlyTypes(hoomd.variant.Variant, + allow_none=True, + preprocess=variant_preprocessing), + ) + param_dict["kT"] = kT + self._param_dict.update(param_dict) - _collision_method.__init__(self, seed, period) + def _attach_hook(self): + sim = self._simulation + sim._warn_if_seed_unset() - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - collide_class = _mpcd.SRDCollisionMethod + if isinstance(sim.device, hoomd.device.GPU): + cpp_class = _mpcd.SRDCollisionMethodGPU else: - collide_class = _mpcd.SRDCollisionMethodGPU - self._cpp = collide_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), self.period, 0, - self.seed, hoomd.context.current.mpcd._thermo) - - self.set_params(angle=angle, kT=kT) - if group is not None: - self.embed(group) - - def set_params(self, angle=None, shift=None, kT=None): - """ Set parameters for the SRD collision method - - Args: - angle (float): SRD rotation angle (degrees) - shift (bool): If True, perform a random shift of the underlying cell list - kT (:py:mod:`hoomd.variant` or :py:obj:`float` or bool): Temperature - set point for the thermostat (in energy units). If False, any - set thermostat is removed and an NVE simulation is run. - - Examples:: - - srd.set_params(angle=90.) - srd.set_params(shift=False) - srd.set_params(angle=130., shift=True, kT=1.0) - srd.set_params(kT=hoomd.data.variant.linear_interp([[0,1.0],[100,5.0]])) - srd.set_params(kT=False) - - """ - - if angle is not None: - self.angle = angle - self._cpp.setRotationAngle(angle * np.pi / 180.) - if shift is not None: - self.shift = shift - self._cpp.enableGridShifting(shift) - if kT is not None: - if kT is False: - self._cpp.unsetTemperature() - self.kT = kT - else: - self.kT = hoomd.variant._setup_variant_input(kT) - self._cpp.setTemperature(self.kT.cpp_variant) + cpp_class = _mpcd.SRDCollisionMethod + + self._cpp_obj = cpp_class(sim.state._cpp_sys_def, sim.timestep, + self.period, 0, self.angle) + + if self.embedded_particles is not None: + self._cpp_obj.setEmbeddedGroup( + sim.state._get_group(self.embedded_particles)) + + super()._attach_hook() diff --git a/hoomd/mpcd/fill.py b/hoomd/mpcd/fill.py new file mode 100644 index 0000000000..512d016bfa --- /dev/null +++ b/hoomd/mpcd/fill.py @@ -0,0 +1,175 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r"""MPCD virtual-particle fillers. + +Virtual particles are MPCD particles that are added to ensure MPCD +collision cells that are sliced by solid boundaries do not become "underfilled". +From the perspective of the MPCD algorithm, the number density of particles in +these sliced cells is lower than the average density, and so the transport +properties may differ. In practice, this usually means that the boundary +conditions do not appear to be properly enforced. + +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + +""" + +import hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.mpcd import _mpcd +from hoomd.mpcd.geometry import Geometry, ParallelPlates +from hoomd.operation import Operation + + +class VirtualParticleFiller(Operation): + """Base virtual-particle filler. + + Args: + type (str): Type of particles to fill. + density (float): Particle number density. + kT (hoomd.variant.variant_like): Temperature of particles. + + Virtual particles will be added with the specified `type` and `density`. + Their velocities will be drawn from a Maxwell--Boltzmann distribution + consistent with `kT`. + + .. invisible-code-block: python + + filler = hoomd.mpcd.fill.VirtualParticleFiller( + type="A", + density=5.0, + kT=1.0) + simulation.operations.integrator.virtual_particle_fillers = [filler] + + Attributes: + type (str): Type of particles to fill. + + .. rubric:: Example: + + .. code-block:: python + + filler.type = "A" + + density (float): Particle number density. + + .. rubric:: Example: + + .. code-block:: python + + filler.density = 5.0 + + kT (hoomd.variant.variant_like): Temperature of particles. + + .. rubric:: Examples: + + Constant temperature. + + .. code-block:: python + + filler.kT = 1.0 + + Variable temperature. + + .. code-block:: python + + filler.kT = hoomd.variant.Ramp(1.0, 2.0, 0, 100) + + """ + + def __init__(self, type, density, kT): + super().__init__() + + param_dict = ParameterDict( + type=str(type), + density=float(density), + kT=hoomd.variant.Variant, + ) + param_dict["kT"] = kT + self._param_dict.update(param_dict) + + +class GeometryFiller(VirtualParticleFiller): + """Virtual-particle filler for a bounce-back geometry. + + Args: + type (str): Type of particles to fill. + density (float): Particle number density. + kT (hoomd.variant.variant_like): Temperature of particles. + geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around. + + Virtual particles are inserted in cells whose volume is sliced by the + specified `geometry`. The algorithm for doing the filling depends on the + specific `geometry`. + + .. rubric:: Example: + + Filler for parallel plate geometry. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) + filler = hoomd.mpcd.fill.GeometryFiller( + type="A", + density=5.0, + kT=1.0, + geometry=plates) + simulation.operations.integrator.virtual_particle_fillers = [filler] + + Attributes: + geometry (hoomd.mpcd.geometry.Geometry): Surface to fill around + (*read only*). + + """ + + _cpp_class_map = {} + + def __init__(self, type, density, kT, geometry): + super().__init__(type, density, kT) + + param_dict = ParameterDict(geometry=Geometry,) + param_dict["geometry"] = geometry + self._param_dict.update(param_dict) + + def _attach_hook(self): + sim = self._simulation + sim._warn_if_seed_unset() + + self.geometry._attach(sim) + + # try to find class in map, otherwise default to internal MPCD module + geom_type = type(self.geometry) + try: + class_info = self._cpp_class_map[geom_type] + except KeyError: + class_info = (_mpcd, geom_type.__name__ + "GeometryFiller") + class_info = list(class_info) + if isinstance(sim.device, hoomd.device.GPU): + class_info[1] += "GPU" + class_ = getattr(*class_info, None) + assert class_ is not None, ("Virtual particle filler for geometry " + "not found") + + self._cpp_obj = class_( + sim.state._cpp_sys_def, + self.type, + self.density, + self.kT, + self.geometry._cpp_obj, + ) + + super()._attach_hook() + + def _detach_hook(self): + self.geometry._detach() + super()._detach_hook() + + @classmethod + def _register_cpp_class(cls, geometry, module, cpp_class_name): + cls._cpp_class_map[geometry] = (module, cpp_class_name) + + +GeometryFiller._register_cpp_class(ParallelPlates, _mpcd, + "ParallelPlateGeometryFiller") diff --git a/hoomd/mpcd/force.py b/hoomd/mpcd/force.py index 638f1a2cf7..0ab1842a4e 100644 --- a/hoomd/mpcd/force.py +++ b/hoomd/mpcd/force.py @@ -1,273 +1,233 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD external force fields. +r"""MPCD particle body forces. -An external field specifies the force to be applied per MPCD particle in -the equations of motion (see :py:mod:`.mpcd.stream`). The external force should -be compatible with the chosen streaming geometry. Global momentum conservation is -typically broken by adding an external force field; care should be chosen that -the force field does not cause the system to net accelerate (i.e., it must maintain -*average* momentum conservation). Additionally, a thermostat will likely be -required to maintain temperature control in the driven system (see -:py:mod:`.mpcd.collide`). +MPCD can apply a body force to each MPCD particle as a function of position. +The external force should be compatible with the chosen +:class:`~hoomd.mpcd.geometry.Geometry`. Global momentum conservation can be +broken by adding a body force, so care should be chosen that the entire model +is designed so that the system does not have net acceleration. For example, +solid boundaries can be used to dissipate momentum, or a balancing force can be +applied to particles that are embedded in the MPCD particles through the +collision step. Additionally, a thermostat will likely be required to maintain +temperature control in the driven system. -.. note:: +.. invisible-code-block: python - The external force **must** be attached to a streaming method - (see :py:mod:`.mpcd.stream`) using ``set_force`` to take effect. - On its own, the force object will not affect the system. + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) """ -import hoomd from hoomd import _hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.mpcd import _mpcd +from hoomd.operation import _HOOMDBaseObject -from . import _mpcd +class BodyForce(_HOOMDBaseObject): + """Body force. -class _force(): - r""" Base external force field. - - This base class does some basic initialization tests, and then constructs the - polymorphic external field base class in C++. This base class is essentially a - factory that can initialize other derived classes. New classes need to be exported - in C++ with the appropriate template parameters, and then can be constructed at - the python level by a deriving type. Use :py:class:`constant` as an example. + The `BodyForce` is a body force applied to each MPCD particle. This + class should not be instantiated directly. It exists for type checking. """ - def __init__(self): - # check for hoomd initialization - if not hoomd.init.is_initialized(): - raise RuntimeError( - 'mpcd.force: system must be initialized before the external force.\n' - ) - - # check for mpcd initialization - if hoomd.context.current.mpcd is None: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.force: an MPCD system must be initialized before the external force.\n' - ) - raise RuntimeError('MPCD system not initialized') - - self._cpp = _mpcd.ExternalField( - hoomd.context.current.device.cpp_exec_conf) + pass -class block(_force): - r""" Block force. +class BlockForce(BodyForce): + r"""Block force. Args: - F (float): Magnitude of the force in *x* per particle. - H (float or None): Half-width between centers of block regions. - w (float or None): Half-width of blocks. + force (float): Magnitude of the force in *x* per particle. + separation (float): Distance between the centers of the blocks. + width (float): Width of each block. - Imposes a constant force in *x* as a function of position in *z*: + The `force` magnitude *F* is applied in the *x* direction on the MPCD + particles in blocks defined along the *y* direction by the `separation` + :math:`2H` and the `width` :math:`2w`. The force in *x* is :math:`+F` in the + upper block, :math:`-F` in the lower block, and zero otherwise. .. math:: :nowrap: \begin{equation} \mathbf{F} = \begin{cases} - +F \mathbf{e}_x & |r_z - H| < w \\ - -F \mathbf{e}_x & |r_z + H| < w \\ + +F \mathbf{e}_x & |r_y - H| < w \\ + -F \mathbf{e}_x & |r_y + H| < w \\ \mathbf{0} & \mathrm{otherwise} \end{cases} \end{equation} - The force is applied in blocks defined by *H* and *w* so that the force in *x* - is :math:`+F` in the upper block, :math:`-F` in the lower block, and zero otherwise. - The blocks must lie fully within the simulation box or an error will be raised. - The blocks also should not overlap (the force will be zero in any overlapping - regions), and a warning will be issued if the blocks overlap. + The `BlockForce` can be used to implement the double-parabola method for + measuring viscosity using separation :math:`L_y/2` and width :math:`L_y/2`, + where :math:`L_y` is the size of the simulation box in *y*. + + Warning: + You should define the blocks to lie fully within the simulation box and + to not overlap each other. + + .. rubric:: Example: + + Block force for double-parabola method. + + .. code-block:: python + + Ly = simulation.state.box.Ly + force = hoomd.mpcd.force.BlockForce( + force=1.0, separation=Ly/2, width=Ly/2) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) + simulation.operations.integrator.streaming_method = stream + + Attributes: + force (float): Magnitude of the force in *x* per particle. + + .. rubric:: Example: + + .. code-block:: python + + force.force = 1.0 - This force field can be used to implement the double-parabola method for measuring - viscosity by setting :math:`H = L_z/4` and :math:`w = L_z/4`, where :math:`L_z` is - the size of the simulation box in *z*. If *H* or *w* is None, it will default to this - value based on the current simulation box. + separation (float): Distance between the centers of the blocks. - Examples:: + .. rubric:: Example: - # fully specified blocks - force.block(F=1.0, H=5.0, w=5.0) + .. code-block:: python - # default blocks to full box - force.block(F=0.5) + Ly = simulation.state.box.Ly + force.separation = Ly / 2 - .. note:: + width (float): Width of each block. - The external force **must** be attached to a streaming method - (see :py:mod:`.mpcd.stream`) using ``set_force`` to take effect. - On its own, the force object will not affect the system. + .. rubric:: Example: - .. versionadded:: 2.6 + .. code-block:: python + + Ly = simulation.state.box.Ly + force.width = Ly / 2 """ - def __init__(self, F, H=None, w=None): - - # current box size - Lz = hoomd.context.current.system_definition.getParticleData( - ).getGlobalBox().getL().z - - # setup default blocks if needed - if H is None: - H = Lz / 4 - if w is None: - w = Lz / 4 - - # validate block positions - if H <= 0 or H > Lz / 2: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.force.block: H = {} should be nonzero and inside box.\n' - .format(H)) - raise ValueError('Invalid block spacing') - if w <= 0 or w > (Lz / 2 - H): - hoomd.context.current.device.cpp_msg.error( - 'mpcd.force.block: w = {} should be nonzero and keep block in box (H = {}).\n' - .format(w, H)) - raise ValueError('Invalid block width') - if w > H: - hoomd.context.current.device.cpp_msg.warning( - 'mpcd.force.block: blocks overlap with H = {} < w = {}.\n' - .format(H, w)) - - # initialize python level - _force.__init__(self) - self._F = F - self._H = H - self._w = w - - # initialize c++ - self._cpp.BlockForce(self.F, self.H, self.w) - - @property - def F(self): - return self._F - - @property - def H(self): - return self._H - - @property - def w(self): - return self._w - - -class constant(_force): - r""" Constant force. + def __init__(self, force, separation=None, width=None): + super().__init__() - Args: - F (tuple): 3d vector specifying the force per particle. + param_dict = ParameterDict( + force=float(force), + separation=float(separation), + width=float(width), + ) + self._param_dict.update(param_dict) + + def _attach_hook(self): + self._cpp_obj = _mpcd.BlockForce(self.force, self.separation, + self.width) + super()._attach_hook() - The same constant-force is applied to all particles, independently of time - and their positions. This force is useful for simulating pressure-driven - flow in conjunction with a confined geometry (e.g., :py:class:`~.stream.slit`) - having no-slip boundary conditions. - Examples:: +class ConstantForce(BodyForce): + r"""Constant force. - # tuple - force.constant((1.,0.,0.)) + Args: + force (`tuple` [`float`, `float`, `float`]): Force vector per particle. - # list - force.constant([1.,2.,3.]) + The same constant force is applied to all MPCD particles, independently + of time and position. This force is useful for simulating pressure-driven + flow in conjunction with a confined geometry having no-slip boundary + conditions. It is also useful for measuring diffusion coefficients with + nonequilibrium methods. - # NumPy array - g = np.array([0.,0.,-1.]) - force.constant(g) + .. rubric:: Example: - .. note:: + .. code-block:: python - The external force **must** be attached to a streaming method - (see :py:mod:`.mpcd.stream`) using ``set_force`` to take effect. - On its own, the force object will not affect the system. + force = hoomd.mpcd.force.ConstantForce((1.0, 0, 0)) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) + simulation.operations.integrator.streaming_method = stream - .. versionadded:: 2.6 + Attributes: + force (`tuple` [`float`, `float`, `float`]): Force vector per particle. - """ + .. rubric:: Example: + + .. code-block:: python - def __init__(self, F): + force.force = (1.0, 0.0, 0.0) - try: - if len(F) != 3: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.force.constant: field must be a 3-component vector.\n' - ) - raise ValueError('External field must be a 3-component vector') - except TypeError: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.force.constant: field must be a 3-component vector.\n') - raise ValueError('External field must be a 3-component vector') + """ - # initialize python level - _force.__init__(self) - self._F = F + def __init__(self, force): + super().__init__() - # initialize c++ - self._cpp.ConstantForce( - _hoomd.make_scalar3(self.F[0], self.F[1], self.F[2])) + param_dict = ParameterDict(force=(float, float, float)) + param_dict["force"] = force + self._param_dict.update(param_dict) - @property - def F(self): - return self._F + def _attach_hook(self): + self._cpp_obj = _mpcd.ConstantForce( + _hoomd.make_scalar3(self.force[0], self.force[1], self.force[2])) + super()._attach_hook() -class sine(_force): - r""" Sine force. +class SineForce(BodyForce): + r"""Sine force. Args: - F (float): Magnitude of the force in *x* per particle. - k (float): Wavenumber for the force. + amplitude (float): Amplitude of the sinusoid. + wavenumber (float): Wavenumber for the sinusoid. - Applies a force in *x* that is sinusoidally varying in *z*. + `SineForce` applies a force with amplitude *F* in *x* that is sinusoidally + varying in *y* with wavenumber *k* to all MPCD particles: .. math:: - \mathbf{F}(\mathbf{r}) = F \sin (k r_z) \mathbf{e}_x + \mathbf{F}(\mathbf{r}) = F \sin (k r_y) \mathbf{e}_x Typically, the wavenumber should be something that is commensurate - with the simulation box. For example, :math:`k = 2\pi/L_z` will generate - one period of the sine in :py:class:`~.stream.bulk` geometry. + with the simulation box. For example, :math:`k = 2\pi/L_y` will generate + one period of the sine. - Examples:: + .. rubric:: Example: - # one period - k0 = 2.*np.pi/box.Lz - force.sine(F=1.0, k=k0) + Sine force with one period. - # two periods - force.sine(F=0.5, k=2*k0) + .. code-block:: python - The user will need to determine what value of *k* makes sense for their - problem, as it is too difficult to validate all values of *k* for all - streaming geometries. + Ly = simulation.state.box.Ly + force = hoomd.mpcd.force.SineForce( + amplitude=1.0, + wavenumber=2 * numpy.pi / Ly) + stream = hoomd.mpcd.stream.Bulk(period=1, mpcd_particle_force=force) + simulation.operations.integrator.streaming_method = stream - .. note:: + Attributes: + amplitude (float): Amplitude of the sinusoid. - The external force **must** be attached to a streaming method - (see :py:mod:`.mpcd.stream`) using ``set_force`` to take effect. - On its own, the force object will not affect the system. + .. rubric:: Example: - .. versionadded:: 2.6 + .. code-block:: python - """ + force.amplitude = 1.0 + + wavenumber (float): Wavenumber for the sinusoid. - def __init__(self, F, k): + .. rubric:: Example: - # initialize python level - _force.__init__(self) - self._F = F - self._k = k + .. code-block:: python + + Ly = simulation.state.box.Ly + force.wavenumber = 2 * numpy.pi / Ly + + """ - # initialize c++ - self._cpp.SineForce(self.F, self.k) + def __init__(self, amplitude, wavenumber): + super().__init__() - @property - def F(self): - return self._F + param_dict = ParameterDict(amplitude=float(amplitude), + wavenumber=float(wavenumber)) + self._param_dict.update(param_dict) - @property - def k(self): - return self._k + def _attach_hook(self): + self._cpp_obj = _mpcd.SineForce(self.amplitude, self.wavenumber) + super()._attach_hook() diff --git a/hoomd/mpcd/geometry.py b/hoomd/mpcd/geometry.py new file mode 100644 index 0000000000..5ff12d30b8 --- /dev/null +++ b/hoomd/mpcd/geometry.py @@ -0,0 +1,171 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r"""MPCD geometries. + +A geometry defines solid boundaries that cannot be penetrated. These +geometries are used for various operations in the MPCD algorithm including: + +* Bounce-back streaming for MPCD particles + (:class:`hoomd.mpcd.stream.BounceBack`) +* Bounce-back integration for MD particles + (:class:`hoomd.mpcd.methods.BounceBack`) +* Virtual particle filling (:class:`hoomd.mpcd.fill.GeometryFiller`) + +Each geometry may put constraints on the size of the simulation and where +particles are allowed. These constraints will be documented by each object. + +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + +""" + +from hoomd.data.parameterdicts import ParameterDict +from hoomd.mpcd import _mpcd +from hoomd.operation import _HOOMDBaseObject + + +class Geometry(_HOOMDBaseObject): + r"""Geometry. + + Args: + no_slip (bool): If True, surfaces have a no-slip boundary condition. + Otherwise, they have a slip boundary condition. + + .. invisible-code-block: python + + geometry = hoomd.mpcd.geometry.Geometry(no_slip=True) + + Attributes: + no_slip (bool): If True, plates have a no-slip boundary condition. + Otherwise, they have a slip boundary condition (*read only*). + + A no-slip boundary condition means that the average velocity is + zero at the surface. A slip boundary condition means that the + average *normal* velocity is zero at the surface, but there + is no friction against the *tangential* velocity. + + """ + + def __init__(self, no_slip): + super().__init__() + + param_dict = ParameterDict(no_slip=bool(no_slip),) + self._param_dict.update(param_dict) + + +class ParallelPlates(Geometry): + r"""Parallel-plate channel. + + Args: + separation (float): Distance between plates. + speed (float): Wall speed. + no_slip (bool): If True, surfaces have no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + `ParallelPlates` confines particles between two infinite parallel plates + centered around the origin. The plates are placed at :math:`y=-H` and + :math:`y=+H`, where the total `separation` is :math:`2H`. The plates may be + put into motion with `speed` *V*, having velocity :math:`-V` and :math:`+V` + in the *x* direction, respectively. If combined with a no-slip boundary + condition, this motion can be used to generate simple shear flow. + + .. rubric:: Examples: + + Stationary parallel plates with no-slip boundary condition. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) + simulation.operations.integrator.streaming_method = stream + + Stationary parallel plates with slip boundary condition. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates( + separation=6.0, no_slip=False) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) + simulation.operations.integrator.streaming_method = stream + + Moving parallel plates. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates( + separation=6.0, speed=1.0, no_slip=True) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) + simulation.operations.integrator.streaming_method = stream + + Attributes: + separation (float): Distance between plates (*read only*). + + speed (float): Wall speed (*read only*). + + `speed` will have no effect if `no_slip` is False because the slip + surface cannot generate shear stress. + + """ + + def __init__(self, separation, speed=0.0, no_slip=True): + super().__init__(no_slip) + param_dict = ParameterDict( + separation=float(separation), + speed=float(speed), + ) + self._param_dict.update(param_dict) + + def _attach_hook(self): + self._cpp_obj = _mpcd.ParallelPlates(self.separation, self.speed, + self.no_slip) + super()._attach_hook() + + +class PlanarPore(Geometry): + r"""Pore with parallel plate opening. + + Args: + separation (float): Distance between pore walls. + length (float): Pore length. + no_slip (bool): If True, surfaces have no-slip boundary condition. + Otherwise, they have the slip boundary condition. + + `PlanarPore` is a finite-length version of `ParallelPlates`. The + geometry is similar, except that the plates extend from :math:`x=-L` to + :math:`x=+L` (total `length` *2L*). Additional solid walls + with normals in *x* prevent penetration into the regions above / below the + plates. The plates are infinite in *z*. Outside the pore, the simulation box + has full periodic boundaries; it is not confined by any walls. This model + hence mimics a narrow pore in, e.g., a membrane. + + .. rubric:: Example: + + .. code-block:: python + + pore = hoomd.mpcd.geometry.PlanarPore(separation=6.0, length=4.0) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=pore) + simulation.operations.integrator.streaming_method = stream + + Attributes: + separation (float): Distance between pore walls (*read only*). + + length (float): Pore length (*read only*). + + """ + + def __init__(self, separation, length, no_slip=True): + super().__init__(no_slip) + + param_dict = ParameterDict( + separation=float(separation), + length=float(length), + ) + self._param_dict.update(param_dict) + + def _attach_hook(self): + self._cpp_obj = _mpcd.PlanarPore(self.separation, self.length, + self.no_slip) + super()._attach_hook() diff --git a/hoomd/mpcd/integrate.py b/hoomd/mpcd/integrate.py index a884e45b09..5c8413bdc5 100644 --- a/hoomd/mpcd/integrate.py +++ b/hoomd/mpcd/integrate.py @@ -1,241 +1,278 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD integration methods - -Defines bounce-back methods for integrating solutes (MD particles) embedded in an MPCD -solvent. The integration scheme is velocity Verlet (NVE) with bounce-back performed at -the solid boundaries defined by a geometry, as in :py:mod:`.mpcd.stream`. This gives a -simple approximation of the interactions required to keep a solute bounded in a geometry, -and more complex interactions can be specified, for example, by writing custom external fields. - -Similar caveats apply to these methods as for the :py:mod:`.mpcd.stream` methods. In particular: - - 1. The simulation box is periodic, but the geometry imposes inherently non-periodic boundary - conditions. You must ensure that the box is sufficiently large to enclose the geometry - and that all particles lie inside it, or an error will be raised at runtime. - 2. You must also ensure that particles do not self-interact through the periodic boundaries. - This is usually achieved for simple pair potentials by padding the box size by the largest - cutoff radius. Failure to do so may result in unphysical interactions. - 3. Bounce-back rules do not always enforce no-slip conditions at surfaces properly. It - may still be necessary to add additional 'ghost' MD particles in the surface - to achieve the right boundary conditions and reduce density fluctuations. - -The integration methods defined here are not restricted to only MPCD simulations: they can be -used with both ``md.integrate.mode_standard`` and :py:class:`.mpcd.integrator`. For -example, the same integration methods might be used to run DPD simulations with surfaces. - -These bounce-back methods do not support anisotropic integration because torques are currently -not computed for collisions with the boundary. Similarly, rigid bodies will also not be treated -correctly because the integrators are not aware of the extent of the particles; the surface -reflections are treated as point particles. An error will be raised if an anisotropic integration -mode is specified. - -""" +"""Implement MPCD Integrator.""" import hoomd -from hoomd import _hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data import syncedlist +from hoomd.data.typeconverter import OnlyTypes +from hoomd.md.integrate import Integrator as _MDIntegrator, _set_synced_list +from hoomd.mpcd import _mpcd +from hoomd.mpcd.collide import CellList, CollisionMethod +from hoomd.mpcd.fill import VirtualParticleFiller +from hoomd.mpcd.stream import StreamingMethod +from hoomd.mpcd.tune import ParticleSorter -from . import _mpcd - -class _bounce_back(): - """ NVE integration with bounce-back rules. - - Args: - group (``hoomd.group``): Group of particles on which to apply this method. - - :py:class:`_bounce_back` is a base class integration method. It must be used with - ``md.integrate.mode_standard`` or :py:class:`.mpcd.integrator`. - Deriving classes implement the specific geometry and valid parameters for those geometries. - Currently, there is no mechanism to share geometries between multiple instances of the same - integration method. - - A :py:class:`hoomd.md.compute.ThermodynamicQuantities` is automatically specified and associated with *group*. - - """ - - def __init__(self, group): - # initialize base class - # hoomd.integrate._integration_method.__init__(self) - - # create the compute thermo - hoomd.compute._get_unique_thermo(group=group) - - # store metadata - self.group = group - self.boundary = None - self.metadata_fields = ['group', 'boundary'] - - def _process_boundary(self, bc): - """ Process boundary condition string into enum - - Args: - bc (str): Boundary condition, either "no_slip" or "slip" - - Returns: - A valid boundary condition enum. - - The enum interface is still fairly clunky for the user since the boundary - condition is buried too deep in the package structure. This is a convenience - method for interpreting. - - """ - if bc == "no_slip": - return _mpcd.boundary.no_slip - elif bc == "slip": - return _mpcd.boundary.slip - else: - hoomd.context.current.device.cpp_msg.error( - "mpcd.integrate: boundary condition " + bc - + " not recognized.\n") - raise ValueError("Unrecognized streaming boundary condition") - return None - - -class slit(_bounce_back): - """ NVE integration with bounce-back rules in a slit channel. +@hoomd.logging.modify_namespace(("mpcd", "Integrator")) +class Integrator(_MDIntegrator): + r"""MPCD integrator. Args: - group (``hoomd.group``): Group of particles on which to apply this method. - H (float): channel half-width - V (float): wall speed (default: 0) - boundary : 'slip' or 'no_slip' boundary condition at wall (default: 'no_slip') + dt (float): Integrator time step size :math:`[\mathrm{time}]`. - This integration method applies to particles in *group* in the parallel-plate channel geometry. - This method is the MD analog of :py:class:`.stream.slit`, which documents additional details - about the geometry. + methods (Sequence[hoomd.md.methods.Method]): Sequence of integration + methods. The default value of ``None`` initializes an empty list. - Examples:: + forces (Sequence[hoomd.md.force.Force]): Sequence of forces applied to + the particles in the system. The default value of ``None`` initializes + an empty list. - all = group.all() - slit = mpcd.integrate.slit(group=all, H=5.0) - slit = mpcd.integrate.slit(group=all, H=10.0, V=1.0) + integrate_rotational_dof (bool): When True, integrate rotational degrees + of freedom. - .. versionadded:: 2.7 + constraints (Sequence[hoomd.md.constrain.Constraint]): Sequence of + constraint forces applied to the particles in the system. + The default value of ``None`` initializes an empty list. Rigid body + objects (i.e. `hoomd.md.constrain.Rigid`) are not allowed in the + list. + + rigid (hoomd.md.constrain.Rigid): An object defining the rigid bodies in + the simulation. + + half_step_hook (hoomd.md.HalfStepHook): Enables the user to perform + arbitrary computations during the half-step of the integration. + + streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method + for the MPCD particles. + + collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method + for the MPCD particles and any embedded particles. + + virtual_particle_fillers + (Sequence[hoomd.mpcd.fill.VirtualParticleFiller]): MPCD + virtual-particle filler(s). + + mpcd_particle_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner that sorts + the MPCD particles. + + The MPCD `Integrator` enables the MPCD algorithm concurrently with standard + MD methods. + + In MPCD simulations, :attr:`~hoomd.md.Integrator.dt` defines the amount of + time that the system is advanced forward every time step. MPCD streaming and + collision steps can be defined to occur in multiples of + :attr:`~hoomd.md.Integrator.dt`. In these cases, any MD particle data will + be updated every :attr:`~hoomd.md.Integrator.dt`, while the MPCD particle + data is updated asynchronously for performance. For example, if MPCD + streaming happens every 5 steps, then the particle data will be updated as + follows:: + + 0 1 2 3 4 5 + MD: |---->|---->|---->|---->|---->| + MPCD: |---------------------------->| + + If the MPCD particle data is accessed via the snapshot interface at time + step 3, it will actually contain the MPCD particle data for time step 5. + The MD particles can be read at any time step because their positions + are updated every step. + + .. rubric:: Examples: + + .. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + + Integrator for only MPCD particles. + + .. code-block:: python + + stream = hoomd.mpcd.stream.Bulk(period=1) + collide = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, + angle=130) + integrator = hoomd.mpcd.Integrator( + dt=0.1, + streaming_method=stream, + collision_method=collide, + mpcd_particle_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + simulation.operations.integrator = integrator + + MPCD integrator with solutes. + + .. code-block:: python + + dt_md = 0.005 + md_steps_per_collision = 20 # collision time = 0.1 + + stream = hoomd.mpcd.stream.Bulk(period=md_steps_per_collision) + collide = hoomd.mpcd.collide.StochasticRotationDynamics( + period=md_steps_per_collision, + angle=130, + embedded_particles=hoomd.filter.All()) + solute_method = hoomd.md.methods.ConstantVolume( + filter=collide.embedded_particles) + + integrator = hoomd.mpcd.Integrator( + dt=dt_md, + methods=[solute_method], + streaming_method=stream, + collision_method=collide, + mpcd_particle_sorter=hoomd.mpcd.tune.ParticleSorter( + trigger=20*md_steps_per_collision) + ) + simulation.operations.integrator = integrator + + MPCD integrator with virtual particle filler. + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) + stream = hoomd.mpcd.stream.BounceBack(period=1, geometry=plates) + collide = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, + angle=130, + kT=1.0) + filler = hoomd.mpcd.fill.GeometryFiller( + type="A", + density=5.0, + kT=1.0, + geometry=plates) + + integrator = hoomd.mpcd.Integrator( + dt=0.1, + streaming_method=stream, + collision_method=collide, + virtual_particle_fillers=[filler], + mpcd_particle_sorter=hoomd.mpcd.tune.ParticleSorter(trigger=20)) + simulation.operations.integrator = integrator + + Attributes: + collision_method (hoomd.mpcd.collide.CollisionMethod): Collision method + for the MPCD particles and any embedded particles. + + mpcd_particle_sorter (hoomd.mpcd.tune.ParticleSorter): Tuner that sorts + the MPCD particles (recommended). + + streaming_method (hoomd.mpcd.stream.StreamingMethod): Streaming method + for the MPCD particles. """ - def __init__(self, group, H, V=0.0, boundary="no_slip"): - # initialize base class - _bounce_back.__init__(self, group) - self.metadata_fields += ['H', 'V'] - - # initialize the c++ class - if not hoomd.context.current.device.mode == 'gpu': - cpp_class = _mpcd.BounceBackNVESlit - else: - cpp_class = _mpcd.BounceBackNVESlitGPU - - self.H = H - self.V = V - self.boundary = boundary - - bc = self._process_boundary(boundary) - geom = _mpcd.SlitGeometry(H, V, bc) - - self.cpp_method = cpp_class(hoomd.context.current.system_definition, - group.cpp_group, geom) - self.cpp_method.validateGroup() - - def set_params(self, H=None, V=None, boundary=None): - """ Set parameters for the slit geometry. - - Args: - H (float): channel half-width - V (float): wall speed (default: 0) - boundary : 'slip' or 'no_slip' boundary condition at wall (default: 'no_slip') - - Examples:: - - slit.set_params(H=8.) - slit.set_params(V=2.0) - slit.set_params(boundary='slip') - slit.set_params(H=5, V=0., boundary='no_slip') + def __init__( + self, + dt, + integrate_rotational_dof=False, + forces=None, + constraints=None, + methods=None, + rigid=None, + half_step_hook=None, + streaming_method=None, + collision_method=None, + virtual_particle_fillers=None, + mpcd_particle_sorter=None, + ): + super().__init__( + dt, + integrate_rotational_dof, + forces, + constraints, + methods, + rigid, + half_step_hook, + ) + + self._cell_list = CellList(cell_size=1.0, shift=True) + + virtual_particle_fillers = ([] if virtual_particle_fillers is None else + virtual_particle_fillers) + self._virtual_particle_fillers = syncedlist.SyncedList( + VirtualParticleFiller, + syncedlist._PartialGetAttr("_cpp_obj"), + iterable=virtual_particle_fillers, + ) + + param_dict = ParameterDict( + streaming_method=OnlyTypes(StreamingMethod, allow_none=True), + collision_method=OnlyTypes(CollisionMethod, allow_none=True), + mpcd_particle_sorter=OnlyTypes(ParticleSorter, allow_none=True), + ) + param_dict.update( + dict( + streaming_method=streaming_method, + collision_method=collision_method, + mpcd_particle_sorter=mpcd_particle_sorter, + )) + self._param_dict.update(param_dict) + + @property + def cell_list(self): + """hoomd.mpcd.collide.CellList: Collision cell list. + + A `CellList` is automatically created with each `Integrator` + using typical defaults of cell size 1 and random grid shifting enabled. + You can change this parameter configuration if desired. """ - - if H is not None: - self.H = H - - if V is not None: - self.V = V - - if boundary is not None: - self.boundary = boundary - - bc = self._process_boundary(self.boundary) - self.cpp_method.geometry = _mpcd.SlitGeometry(self.H, self.V, bc) - - -class slit_pore(_bounce_back): - """ NVE integration with bounce-back rules in a slit pore channel. - - Args: - group (``hoomd.group``): Group of particles on which to apply this method. - H (float): channel half-width. - L (float): pore half-length. - boundary : 'slip' or 'no_slip' boundary condition at wall (default: 'no_slip') - - This integration method applies to particles in *group* in the parallel-plate (slit) pore geometry. - This method is the MD analog of :py:class:`.stream.slit_pore`, which documents additional details - about the geometry. - - Examples:: - - all = group.all() - slit_pore = mpcd.integrate.slit_pore(group=all, H=10.0, L=10.) - - .. versionadded:: 2.7 - - """ - - def __init__(self, group, H, L, boundary="no_slip"): - # initialize base class - _bounce_back.__init__(self, group) - self.metadata_fields += ['H', 'L'] - - # initialize the c++ class - if not hoomd.context.current.device.mode == 'gpu': - cpp_class = _mpcd.BounceBackNVESlitPore + return self._cell_list + + @property + def virtual_particle_fillers(self): + """Sequence[hoomd.mpcd.fill.VirtualParticleFiller]: MPCD \ + virtual-particle fillers.""" + return self._virtual_particle_fillers + + @virtual_particle_fillers.setter + def virtual_particle_fillers(self, value): + _set_synced_list(self._virtual_particle_fillers, value) + + def _attach_hook(self): + self._cell_list._attach(self._simulation) + if self.streaming_method is not None: + self.streaming_method._attach(self._simulation) + if self.collision_method is not None: + self.collision_method._attach(self._simulation) + if self.mpcd_particle_sorter is not None: + self.mpcd_particle_sorter._attach(self._simulation) + + self._cpp_obj = _mpcd.Integrator(self._simulation.state._cpp_sys_def, + self.dt) + self._virtual_particle_fillers._sync(self._simulation, + self._cpp_obj.fillers) + self._cpp_obj.cell_list = self._cell_list._cpp_obj + + super(_MDIntegrator, self)._attach_hook() + + def _detach_hook(self): + self._cell_list._detach() + self._virtual_particle_fillers._unsync() + if self.streaming_method is not None: + self.streaming_method._detach() + if self.collision_method is not None: + self.collision_method._detach() + if self.mpcd_particle_sorter is not None: + self.mpcd_particle_sorter._detach() + + super()._detach_hook() + + def _setattr_param(self, attr, value): + if attr in ("streaming_method", "collision_method", + "mpcd_particle_sorter"): + cur_value = getattr(self, attr) + if value is cur_value: + return + + if value is not None and value._attached: + raise ValueError("Cannot attach to multiple integrators.") + + # if already attached, change out and set parameter + if self._attached: + if cur_value is not None: + cur_value._detach() + if value is not None: + value._attach(self._simulation) + self._param_dict[attr] = value else: - cpp_class = _mpcd.BounceBackNVESlitPoreGPU - - self.H = H - self.L = L - self.boundary = boundary - - bc = self._process_boundary(boundary) - geom = _mpcd.SlitPoreGeometry(H, L, bc) - - self.cpp_method = cpp_class(hoomd.context.current.system_definition, - group.cpp_group, geom) - self.cpp_method.validateGroup() - - def set_params(self, H=None, L=None, boundary=None): - """ Set parameters for the slit pore geometry. - - Args: - H (float): channel half-width. - L (float): pore half-length. - boundary : 'slip' or 'no_slip' boundary condition at wall (default: 'no_slip') - - Examples:: - - slit_pore.set_params(H=8.) - slit_pore.set_params(L=2.0) - slit_pore.set_params(boundary='slip') - slit_pore.set_params(H=5, L=4., boundary='no_slip') - - """ - if H is not None: - self.H = H - - if L is not None: - self.L = L - - if boundary is not None: - self.boundary = boundary - - bc = self._process_boundary(self.boundary) - self.cpp_method.geometry = _mpcd.SlitPoreGeometry(self.H, self.L, bc) + super()._setattr_param(attr, value) diff --git a/hoomd/mpcd/methods.py b/hoomd/mpcd/methods.py new file mode 100644 index 0000000000..8f8708b22e --- /dev/null +++ b/hoomd/mpcd/methods.py @@ -0,0 +1,140 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r"""MPCD integration methods. + +Extra integration methods for solutes (MD particles) embedded in an MPCD +fluid. These methods are not restricted to MPCD simulations: they can be used +as methods of `hoomd.md.Integrator`. For example, `BounceBack` might be used to +run DPD simulations with surfaces. + +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + +""" + +import hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.filter import ParticleFilter +from hoomd.md.methods import Method +from hoomd.mpcd import _mpcd +from hoomd.mpcd.geometry import Geometry + + +class BounceBack(Method): + r"""Velocity Verlet integration method with bounce-back from surfaces. + + Args: + filter (hoomd.filter.filter_like): Subset of particles on which to + apply this method. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + + A bounce-back method for integrating solutes (MD particles) embedded in an + MPCD fluid. The integration scheme is velocity Verlet with bounce-back + performed at the solid boundaries defined by a geometry, as in + `hoomd.mpcd.stream.BounceBack`. This gives a simple approximation of the + interactions required to keep a solute bounded in a geometry, and more + complex interactions can be specified, for example, by writing custom + external fields. + + Similar caveats apply to these methods as for + `hoomd.mpcd.stream.BounceBack`. In particular: + + 1. The simulation box is periodic, but the `geometry` may impose + non-periodic boundary conditions. You must ensure that the box is + sufficiently large to enclose the `geometry` and that all particles lie + inside it + 2. You must also ensure that particles do not self-interact through the + periodic boundaries. This is usually achieved for simple pair potentials + by padding the box size by the largest cutoff radius. Failure to do so + may result in unphysical interactions. + 3. Bounce-back rules do not always enforce no-slip conditions at surfaces + properly. It may still be necessary to add additional "ghost" MD + particles in the surface to achieve the right boundary conditions and + reduce density fluctuations. + + Warning: + + This method does not support anisotropic integration because torques are + not computed for collisions with the boundary. Rigid bodies will also + not be treated correctly because the integrator is not aware of the + extent of the particles. The surface reflections are treated as point + particles. These conditions are too complicated to validate easily, so + it is the user's responsibility to choose the `filter` correctly. + + .. rubric:: Example: + + .. code-block:: python + + plates = hoomd.mpcd.geometry.ParallelPlates(separation=6.0) + nve = hoomd.mpcd.methods.BounceBack( + filter=hoomd.filter.All(), geometry=plates) + simulation.operations.integrator.methods.append(nve) + + Attributes: + filter (hoomd.filter.filter_like): Subset of particles on which to apply + this method (*read only*). + + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from + (*read only*). + + """ + + _cpp_class_map = {} + + def __init__(self, filter, geometry): + super().__init__() + + param_dict = ParameterDict(filter=ParticleFilter, geometry=Geometry) + param_dict.update(dict(filter=filter, geometry=geometry)) + self._param_dict.update(param_dict) + + def check_particles(self): + """Check if particles are inside `geometry`. + + This method can only be called after this object is attached to a + simulation. + + Returns: + True if all particles are inside `geometry`. + + .. rubric:: Example: + + .. code-block:: python + + assert nve.check_particles() + + """ + return self._cpp_obj.check_particles() + + def _attach_hook(self): + sim = self._simulation + + self.geometry._attach(sim) + + # try to find class in map, otherwise default to internal MPCD module + geom_type = type(self.geometry) + try: + class_info = self._cpp_class_map[geom_type] + except KeyError: + class_info = (_mpcd, "BounceBackNVE" + geom_type.__name__) + class_info = list(class_info) + if isinstance(sim.device, hoomd.device.GPU): + class_info[1] += "GPU" + class_ = getattr(*class_info, None) + assert class_ is not None, "Bounce back method for geometry not found" + + group = sim.state._get_group(self.filter) + self._cpp_obj = class_(sim.state._cpp_sys_def, group, + self.geometry._cpp_obj) + super()._attach_hook() + + def _detach_hook(self): + self.geometry._detach() + super()._detach_hook() + + @classmethod + def _register_cpp_class(cls, geometry, module, cpp_class_name): + cls._cpp_class_map[geometry] = (module, cpp_class_name) diff --git a/hoomd/mpcd/module.cc b/hoomd/mpcd/module.cc index b522ad742b..60c44a10c9 100644 --- a/hoomd/mpcd/module.cc +++ b/hoomd/mpcd/module.cc @@ -15,6 +15,12 @@ #include "CellThermoComputeGPU.h" #endif // ENABLE_HIP +// forces +#include "BlockForce.h" +#include "ConstantForce.h" +#include "NoForce.h" +#include "SineForce.h" + // integration #include "Integrator.h" @@ -28,11 +34,13 @@ #endif // ENABLE_HIP // Streaming methods -#include "ConfinedStreamingMethod.h" +#include "BounceBackStreamingMethod.h" +#include "BulkStreamingMethod.h" #include "StreamingGeometry.h" #include "StreamingMethod.h" #ifdef ENABLE_HIP -#include "ConfinedStreamingMethodGPU.h" +#include "BounceBackStreamingMethodGPU.h" +#include "BulkStreamingMethodGPU.h" #endif // ENABLE_HIP // integration methods @@ -42,12 +50,13 @@ #endif // virtual particle fillers -#include "SlitGeometryFiller.h" -#include "SlitPoreGeometryFiller.h" +#include "ManualVirtualParticleFiller.h" +#include "ParallelPlateGeometryFiller.h" +#include "PlanarPoreGeometryFiller.h" #include "VirtualParticleFiller.h" #ifdef ENABLE_HIP -#include "SlitGeometryFillerGPU.h" -#include "SlitPoreGeometryFillerGPU.h" +#include "ParallelPlateGeometryFillerGPU.h" +#include "PlanarPoreGeometryFillerGPU.h" #endif // ENABLE_HIP // communicator @@ -118,6 +127,11 @@ PYBIND11_MODULE(_mpcd, m) mpcd::detail::export_CellThermoComputeGPU(m); #endif // ENABLE_HIP + mpcd::detail::export_BlockForce(m); + mpcd::detail::export_ConstantForce(m); + mpcd::detail::export_NoForce(m); + mpcd::detail::export_SineForce(m); + mpcd::detail::export_Integrator(m); mpcd::detail::export_CollisionMethod(m); @@ -128,35 +142,67 @@ PYBIND11_MODULE(_mpcd, m) mpcd::detail::export_SRDCollisionMethodGPU(m); #endif // ENABLE_HIP - mpcd::detail::export_boundary(m); - mpcd::detail::export_BulkGeometry(m); - mpcd::detail::export_SlitGeometry(m); - mpcd::detail::export_SlitPoreGeometry(m); + mpcd::detail::export_ParallelPlateGeometry(m); + mpcd::detail::export_PlanarPoreGeometry(m); mpcd::detail::export_StreamingMethod(m); - mpcd::detail::export_ExternalFieldPolymorph(m); - mpcd::detail::export_ConfinedStreamingMethod(m); - mpcd::detail::export_ConfinedStreamingMethod(m); - mpcd::detail::export_ConfinedStreamingMethod(m); -#ifdef ENABLE_HIP - mpcd::detail::export_ConfinedStreamingMethodGPU(m); - mpcd::detail::export_ConfinedStreamingMethodGPU(m); - mpcd::detail::export_ConfinedStreamingMethodGPU(m); + // bulk + mpcd::detail::export_BulkStreamingMethod(m); + mpcd::detail::export_BulkStreamingMethod(m); + mpcd::detail::export_BulkStreamingMethod(m); + mpcd::detail::export_BulkStreamingMethod(m); +#ifdef ENABLE_HIP + mpcd::detail::export_BulkStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(m); + mpcd::detail::export_BulkStreamingMethodGPU(m); +#endif // ENABLE_HIP + // parallel plate + mpcd::detail::export_BounceBackStreamingMethod( + m); + mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod(m); +#ifdef ENABLE_HIP + mpcd::detail::export_BounceBackStreamingMethodGPU(m); + mpcd::detail::export_BounceBackStreamingMethodGPU(m); + mpcd::detail::export_BounceBackStreamingMethodGPU( + m); + mpcd::detail::export_BounceBackStreamingMethodGPU( + m); +#endif // ENABLE_HIP + // planar pore + mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod( + m); + mpcd::detail::export_BounceBackStreamingMethod(m); + mpcd::detail::export_BounceBackStreamingMethod(m); +#ifdef ENABLE_HIP + mpcd::detail::export_BounceBackStreamingMethodGPU( + m); + mpcd::detail::export_BounceBackStreamingMethodGPU(m); + mpcd::detail::export_BounceBackStreamingMethodGPU(m); + mpcd::detail::export_BounceBackStreamingMethodGPU(m); #endif // ENABLE_HIP - mpcd::detail::export_BounceBackNVE(m); - mpcd::detail::export_BounceBackNVE(m); + mpcd::detail::export_BounceBackNVE(m); + mpcd::detail::export_BounceBackNVE(m); #ifdef ENABLE_HIP - mpcd::detail::export_BounceBackNVEGPU(m); - mpcd::detail::export_BounceBackNVEGPU(m); + mpcd::detail::export_BounceBackNVEGPU(m); + mpcd::detail::export_BounceBackNVEGPU(m); #endif // ENABLE_HIP mpcd::detail::export_VirtualParticleFiller(m); - mpcd::detail::export_SlitGeometryFiller(m); - mpcd::detail::export_SlitPoreGeometryFiller(m); + mpcd::detail::export_ManualVirtualParticleFiller(m); + mpcd::detail::export_ParallelPlateGeometryFiller(m); + mpcd::detail::export_PlanarPoreGeometryFiller(m); #ifdef ENABLE_HIP - mpcd::detail::export_SlitGeometryFillerGPU(m); - mpcd::detail::export_SlitPoreGeometryFillerGPU(m); + mpcd::detail::export_ParallelPlateGeometryFillerGPU(m); + mpcd::detail::export_PlanarPoreGeometryFillerGPU(m); #endif // ENABLE_HIP #ifdef ENABLE_MPI diff --git a/hoomd/mpcd/pytest/CMakeLists.txt b/hoomd/mpcd/pytest/CMakeLists.txt index b3fac64ba7..cbdddfcaae 100644 --- a/hoomd/mpcd/pytest/CMakeLists.txt +++ b/hoomd/mpcd/pytest/CMakeLists.txt @@ -1,6 +1,14 @@ # copy python modules to the build directory to make it a working python package set(files __init__.py + test_collide.py + test_fill.py + test_force.py + test_geometry.py + test_integrator.py + test_methods.py test_snapshot.py + test_stream.py + test_tune.py ) install(FILES ${files} diff --git a/hoomd/mpcd/pytest/test_collide.py b/hoomd/mpcd/pytest/test_collide.py new file mode 100644 index 0000000000..0db7c21266 --- /dev/null +++ b/hoomd/mpcd/pytest/test_collide.py @@ -0,0 +1,125 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def small_snap(): + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 1 + snap.particles.types = ["A"] + snap.mpcd.N = 1 + snap.mpcd.types = ["A"] + return snap + + +@pytest.mark.parametrize( + "cls, init_args", + [ + ( + hoomd.mpcd.collide.AndersenThermostat, + { + "kT": 1.0, + }, + ), + ( + hoomd.mpcd.collide.StochasticRotationDynamics, + { + "angle": 90, + }, + ), + ], + ids=["AndersenThermostat", "StochasticRotationDynamics"], +) +class TestCollisionMethod: + + def test_create(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + cm = cls(period=5, **init_args) + ig = hoomd.mpcd.Integrator(dt=0.02, collision_method=cm) + sim.operations.integrator = ig + + assert ig.collision_method is cm + assert cm.embedded_particles is None + assert cm.period == 5 + if "kT" in init_args: + assert isinstance(cm.kT, hoomd.variant.Constant) + assert cm.kT(0) == init_args["kT"] + + sim.run(0) + assert ig.collision_method is cm + assert cm.embedded_particles is None + assert cm.period == 5 + if "kT" in init_args: + assert isinstance(cm.kT, hoomd.variant.Constant) + assert cm.kT(0) == init_args["kT"] + + def test_pickling(self, small_snap, simulation_factory, cls, init_args): + cm = cls(period=1, **init_args) + pickling_check(cm) + + sim = simulation_factory(small_snap) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + sim.run(0) + pickling_check(cm) + + def test_embed(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + cm = cls(period=1, embedded_particles=hoomd.filter.All(), **init_args) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + assert isinstance(cm.embedded_particles, hoomd.filter.All) + sim.run(0) + assert isinstance(cm.embedded_particles, hoomd.filter.All) + + def test_temperature(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + if "kT" not in init_args: + init_args["kT"] = 1.0 + kT_required = False + else: + kT_required = True + cm = cls(period=1, **init_args) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + assert isinstance(cm.kT, hoomd.variant.Constant) + assert cm.kT(0) == 1.0 + sim.run(0) + assert isinstance(cm.kT, hoomd.variant.Constant) + + ramp = hoomd.variant.Ramp(1.0, 2.0, 0, 10) + cm.kT = ramp + assert cm.kT is ramp + sim.run(0) + assert cm.kT is ramp + + if not kT_required: + cm.kT = None + assert cm.kT is None + sim.run(0) + assert cm.kT is None + + def test_run(self, small_snap, simulation_factory, cls, init_args): + sim = simulation_factory(small_snap) + cm = cls(period=1, **init_args) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + collision_method=cm) + + # test that one step can run without error with only solvent + sim.run(1) + + # test that one step can run without error with embedded particles + if "kT" not in init_args: + init_args["kT"] = 1.0 + sim.operations.integrator.collision_method = cls( + period=1, embedded_particles=hoomd.filter.All(), **init_args) + sim.run(1) diff --git a/hoomd/mpcd/pytest/test_fill.py b/hoomd/mpcd/pytest/test_fill.py new file mode 100644 index 0000000000..c968f34a70 --- /dev/null +++ b/hoomd/mpcd/pytest/test_fill.py @@ -0,0 +1,87 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def snap(): + snap_ = hoomd.Snapshot() + if snap_.communicator.rank == 0: + snap_.configuration.box = [10, 10, 10, 0, 0, 0] + snap_.particles.N = 0 + snap_.particles.types = ["A"] + snap_.mpcd.N = 1 + snap_.mpcd.types = ["A"] + return snap_ + + +@pytest.mark.parametrize( + "cls, init_args", + [ + (hoomd.mpcd.geometry.ParallelPlates, { + "separation": 8.0 + }), + (hoomd.mpcd.geometry.PlanarPore, { + "separation": 8.0, + "length": 10.0 + }), + ], + ids=["ParallelPlates", "PlanarPore"], +) +class TestGeometryFiller: + + def test_create_and_attributes(self, simulation_factory, snap, cls, + init_args): + geom = cls(**init_args) + filler = hoomd.mpcd.fill.GeometryFiller(type="A", + density=5.0, + kT=1.0, + geometry=geom) + + assert filler.geometry is geom + assert filler.type == "A" + assert filler.density == 5.0 + assert isinstance(filler.kT, hoomd.variant.Constant) + assert filler.kT(0) == 1.0 + + sim = simulation_factory(snap) + filler._attach(sim) + assert filler.geometry is geom + assert filler.type == "A" + assert filler.density == 5.0 + assert isinstance(filler.kT, hoomd.variant.Constant) + assert filler.kT(0) == 1.0 + + filler.density = 3.0 + filler.kT = hoomd.variant.Ramp(2.0, 1.0, 0, 10) + assert filler.geometry is geom + assert filler.type == "A" + assert filler.density == 3.0 + assert isinstance(filler.kT, hoomd.variant.Ramp) + assert filler.kT(0) == 2.0 + + def test_run(self, simulation_factory, snap, cls, init_args): + filler = hoomd.mpcd.fill.GeometryFiller(type="A", + density=5.0, + kT=1.0, + geometry=cls(**init_args)) + sim = simulation_factory(snap) + ig = hoomd.mpcd.Integrator(dt=0.1, virtual_particle_fillers=[filler]) + sim.operations.integrator = ig + sim.run(1) + + def test_pickling(self, simulation_factory, snap, cls, init_args): + geom = cls(**init_args) + filler = hoomd.mpcd.fill.GeometryFiller(type="A", + density=5.0, + kT=1.0, + geometry=geom) + pickling_check(filler) + + sim = simulation_factory(snap) + filler._attach(sim) + pickling_check(filler) diff --git a/hoomd/mpcd/pytest/test_force.py b/hoomd/mpcd/pytest/test_force.py new file mode 100644 index 0000000000..ecb5dfe75d --- /dev/null +++ b/hoomd/mpcd/pytest/test_force.py @@ -0,0 +1,88 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import numpy as np + +import hoomd +from hoomd.conftest import pickling_check + + +def test_block_force(simulation_factory): + # make the force + force = hoomd.mpcd.force.BlockForce(force=2.0, separation=6.0, width=1.0) + assert force.force == 2.0 + assert force.separation == 6.0 + assert force.width == 1.0 + pickling_check(force) + + # try changing the values + force.force = 1.0 + force.separation = 4.0 + force.width = 0.5 + assert force.force == 1.0 + assert force.separation == 4.0 + assert force.width == 0.5 + + # now attach and check again + sim = simulation_factory() + force._attach(sim) + assert force.force == 1.0 + assert force.separation == 4.0 + assert force.width == 0.5 + + # change values while attached + force.force = 2.0 + force.separation = 6.0 + force.width = 1.0 + assert force.force == 2.0 + assert force.separation == 6.0 + assert force.width == 1.0 + pickling_check(force) + + +def test_constant_force(simulation_factory): + # make the force + force = hoomd.mpcd.force.ConstantForce(force=(1, -2, 3)) + np.testing.assert_array_almost_equal(force.force, (1, -2, 3)) + pickling_check(force) + + # try changing the force vector + force.force = (-1, 2, -3) + np.testing.assert_array_almost_equal(force.force, (-1, 2, -3)) + + # now attach and check again + sim = simulation_factory() + force._attach(sim) + np.testing.assert_array_almost_equal(force.force, (-1, 2, -3)) + + # change values while attached + force.force = (1, -2, 3) + np.testing.assert_array_almost_equal(force.force, (1, -2, 3)) + pickling_check(force) + + +def test_sine_force(simulation_factory): + # make the force + force = hoomd.mpcd.force.SineForce(amplitude=2.0, wavenumber=1) + assert force.amplitude == 2.0 + assert force.wavenumber == 1.0 + pickling_check(force) + + # try changing the values + force.amplitude = 1.0 + force.wavenumber = 2.0 + assert force.amplitude == 1.0 + assert force.wavenumber == 2.0 + + # now attach and check again + sim = simulation_factory() + force._attach(sim) + assert force.amplitude == 1.0 + assert force.wavenumber == 2.0 + + # change values while attached + force.amplitude = 2.0 + force.wavenumber = 1.0 + assert force.amplitude == 2.0 + assert force.wavenumber == 1.0 + pickling_check(force) diff --git a/hoomd/mpcd/pytest/test_geometry.py b/hoomd/mpcd/pytest/test_geometry.py new file mode 100644 index 0000000000..4982def9a8 --- /dev/null +++ b/hoomd/mpcd/pytest/test_geometry.py @@ -0,0 +1,93 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def snap(): + snap_ = hoomd.Snapshot() + if snap_.communicator.rank == 0: + snap_.configuration.box = [10, 10, 10, 0, 0, 0] + snap_.particles.N = 0 + snap_.particles.types = ["A"] + snap_.mpcd.N = 1 + snap_.mpcd.types = ["A"] + return snap_ + + +class TestParallelPlates: + + def test_default_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.ParallelPlates(separation=8.0) + assert geom.separation == 8.0 + assert geom.speed == 0.0 + assert geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.separation == 8.0 + assert geom.speed == 0.0 + assert geom.no_slip + + def test_nondefault_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.ParallelPlates(separation=10.0, + speed=1.0, + no_slip=False) + assert geom.separation == 10.0 + assert geom.speed == 1.0 + assert not geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.separation == 10.0 + assert geom.speed == 1.0 + assert not geom.no_slip + + def test_pickling(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.ParallelPlates(separation=8.0) + pickling_check(geom) + + sim = simulation_factory(snap) + geom._attach(sim) + pickling_check(geom) + + +class TestPlanarPore: + + def test_default_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=10.0) + assert geom.separation == 8.0 + assert geom.length == 10.0 + assert geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.separation == 8.0 + assert geom.length == 10.0 + assert geom.no_slip + + def test_nondefault_init(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.PlanarPore(separation=10.0, + length=8.0, + no_slip=False) + assert geom.separation == 10.0 + assert geom.length == 8.0 + assert not geom.no_slip + + sim = simulation_factory(snap) + geom._attach(sim) + assert geom.separation == 10.0 + assert geom.length == 8.0 + assert not geom.no_slip + + def test_pickling(self, simulation_factory, snap): + geom = hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=10.0) + pickling_check(geom) + + sim = simulation_factory(snap) + geom._attach(sim) + pickling_check(geom) diff --git a/hoomd/mpcd/pytest/test_integrator.py b/hoomd/mpcd/pytest/test_integrator.py new file mode 100644 index 0000000000..c760a3b743 --- /dev/null +++ b/hoomd/mpcd/pytest/test_integrator.py @@ -0,0 +1,196 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def make_simulation(simulation_factory): + + def _make_simulation(): + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 1 + snap.particles.types = ["A"] + snap.mpcd.N = 1 + snap.mpcd.types = ["A"] + return simulation_factory(snap) + + return _make_simulation + + +def test_create(make_simulation): + sim = make_simulation() + ig = hoomd.mpcd.Integrator(dt=0.1) + sim.operations.integrator = ig + + sim.run(0) + assert ig._attached + assert ig.cell_list is not None + assert ig.streaming_method is None + assert ig.collision_method is None + + +def test_collision_method(make_simulation): + sim = make_simulation() + collide = hoomd.mpcd.collide.StochasticRotationDynamics(period=1, angle=130) + + # check that constructor assigns right + ig = hoomd.mpcd.Integrator(dt=0.1, collision_method=collide) + sim.operations.integrator = ig + assert ig.collision_method is collide + sim.run(0) + assert ig.collision_method is collide + + # clear out by setter + ig.collision_method = None + assert ig.collision_method is None + sim.run(0) + assert ig.collision_method is None + + # assign by setter + ig.collision_method = collide + assert ig.collision_method is collide + sim.run(0) + assert ig.collision_method is collide + + +def test_streaming_method(make_simulation): + sim = make_simulation() + stream = hoomd.mpcd.stream.Bulk(period=1) + + # check that constructor assigns right + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=stream) + sim.operations.integrator = ig + assert ig.streaming_method is stream + sim.run(0) + assert ig.streaming_method is stream + + # clear out by setter + ig.streaming_method = None + assert ig.streaming_method is None + sim.run(0) + assert ig.streaming_method is None + + # assign by setter + ig.streaming_method = stream + assert ig.streaming_method is stream + sim.run(0) + assert ig.streaming_method is stream + + +def test_virtual_particle_fillers(make_simulation): + sim = make_simulation() + geom = hoomd.mpcd.geometry.ParallelPlates(separation=8.0) + filler = hoomd.mpcd.fill.GeometryFiller( + type="A", + density=5.0, + kT=1.0, + geometry=geom, + ) + + # check that constructor assigns right + ig = hoomd.mpcd.Integrator(dt=0.1, virtual_particle_fillers=[filler]) + sim.operations.integrator = ig + assert len(ig.virtual_particle_fillers) == 1 + assert filler in ig.virtual_particle_fillers + sim.run(0) + assert len(ig.virtual_particle_fillers) == 1 + assert filler in ig.virtual_particle_fillers + + # attach a second filler + filler2 = hoomd.mpcd.fill.GeometryFiller( + type="A", + density=1.0, + kT=1.0, + geometry=geom, + ) + ig.virtual_particle_fillers.append(filler2) + assert len(ig.virtual_particle_fillers) == 2 + assert filler in ig.virtual_particle_fillers + assert filler2 in ig.virtual_particle_fillers + sim.run(0) + assert len(ig.virtual_particle_fillers) == 2 + assert filler in ig.virtual_particle_fillers + assert filler2 in ig.virtual_particle_fillers + + # make sure synced list is working right with non-empty list assignment + ig.virtual_particle_fillers = [filler] + assert len(ig.virtual_particle_fillers) == 1 + assert len(ig._cpp_obj.fillers) == 1 + + ig.virtual_particle_fillers = [] + assert len(ig.virtual_particle_fillers) == 0 + sim.run(0) + assert len(ig.virtual_particle_fillers) == 0 + + +def test_mpcd_particle_sorter(make_simulation): + sim = make_simulation() + sorter = hoomd.mpcd.tune.ParticleSorter(trigger=1) + + # check that constructor assigns right + ig = hoomd.mpcd.Integrator(dt=0.1, mpcd_particle_sorter=sorter) + sim.operations.integrator = ig + assert ig.mpcd_particle_sorter is sorter + sim.run(0) + assert ig.mpcd_particle_sorter is sorter + + # clear out by setter + ig.mpcd_particle_sorter = None + assert ig.mpcd_particle_sorter is None + sim.run(0) + assert ig.mpcd_particle_sorter is None + + # assign by setter + ig.mpcd_particle_sorter = sorter + assert ig.mpcd_particle_sorter is sorter + sim.run(0) + assert ig.mpcd_particle_sorter is sorter + + +def test_attach_and_detach(make_simulation): + sim = make_simulation() + ig = hoomd.mpcd.Integrator(dt=0.1) + sim.operations.integrator = ig + + # make sure attach works even without collision and streaming methods + sim.run(0) + assert ig._attached + assert ig.cell_list._attached + assert ig.streaming_method is None + assert ig.collision_method is None + assert len(ig.virtual_particle_fillers) == 0 + assert ig.mpcd_particle_sorter is None + + # attach with both methods + ig.streaming_method = hoomd.mpcd.stream.Bulk(period=1) + ig.collision_method = hoomd.mpcd.collide.StochasticRotationDynamics( + period=1, angle=130) + ig.mpcd_particle_sorter = hoomd.mpcd.tune.ParticleSorter(trigger=1) + sim.run(0) + assert ig.streaming_method._attached + assert ig.collision_method._attached + assert ig.mpcd_particle_sorter._attached + + # detach with everything + sim.operations._unschedule() + assert not ig._attached + assert not ig.cell_list._attached + assert not ig.streaming_method._attached + assert not ig.collision_method._attached + assert not ig.mpcd_particle_sorter._attached + + +def test_pickling(make_simulation): + ig = hoomd.mpcd.Integrator(dt=0.1) + pickling_check(ig) + + sim = make_simulation() + sim.operations.integrator = ig + sim.run(0) + pickling_check(ig) diff --git a/hoomd/mpcd/pytest/test_methods.py b/hoomd/mpcd/pytest/test_methods.py new file mode 100644 index 0000000000..1cf4373f8b --- /dev/null +++ b/hoomd/mpcd/pytest/test_methods.py @@ -0,0 +1,185 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import numpy as np +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def snap(): + snap_ = hoomd.Snapshot() + if snap_.communicator.rank == 0: + snap_.configuration.box = [10, 10, 10, 0, 0, 0] + snap_.particles.N = 2 + snap_.particles.types = ["A"] + snap_.particles.position[:] = [[4.95, 3.85, -4.95], [0.0, -3.8, 0.0]] + snap_.particles.velocity[:] = [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]] + snap_.particles.mass[:] = [1.0, 2.0] + return snap_ + + +@pytest.fixture +def integrator(): + bb = hoomd.mpcd.methods.BounceBack( + filter=hoomd.filter.All(), + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) + ig = hoomd.mpcd.Integrator(dt=0.1, methods=[bb]) + return ig + + +class TestBounceBack: + + def test_pickling(self, simulation_factory, snap, integrator): + pickling_check(integrator.methods[0]) + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + sim.run(0) + pickling_check(integrator.methods[0]) + + def test_step_noslip(self, simulation_factory, snap, integrator): + """Test step with no-slip boundary conditions.""" + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + # take one step + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.95, 3.95, 4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step where one particle will now hit the wall + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.95, 3.95, 4.95], [-0.2, -4.0, -0.2]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, + [[-1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]]) + + # take another step, reflecting the second particle + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[4.95, 3.85, -4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[-1.0, -1.0, 1.0], [1.0, 1.0, 1.0]]) + + def test_step_slip(self, simulation_factory, snap, integrator): + """Test step with slip boundary conditions.""" + integrator.methods[0].geometry.no_slip = False + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + # take one step + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.95, 3.95, 4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step where one particle will now hit the wall + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.85, 3.95, 4.85], [-0.2, -4.0, -0.2]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, + [[1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step, reflecting perpendicular motion of second particle + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.75, 3.85, 4.75], [-0.3, -3.9, -0.3]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]]) + + def test_step_moving_wall(self, simulation_factory, snap, integrator): + integrator.dt = 0.3 + integrator.methods[0].geometry.speed = 1.0 + + if snap.communicator.rank == 0: + snap.particles.velocity[1] = [-2.0, -1.0, -1.0] + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + # run one step and check bounce back of particles + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.75, 3.85, -4.95], [-0.4, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.0, -1.0, 1.0], [0.0, 1.0, 1.0]]) + + def test_accel(self, simulation_factory, snap, integrator): + force = hoomd.md.force.Constant(filter=hoomd.filter.All()) + force.constant_force["A"] = (2, 4, -2) + integrator.forces.append(force) + + if snap.communicator.rank == 0: + snap.particles.position[:] = [[0, 0, 0], [0, 0, 0]] + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[0.11, 0.12, -0.11], [-0.095, -0.09, -0.105]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.2, 1.4, -1.2], [-0.9, -0.8, -1.1]]) + + @pytest.mark.parametrize("H,expected_result", [(4.0, True), (3.8, False)]) + def test_check_particles(self, simulation_factory, snap, integrator, H, + expected_result): + """Test box validation raises an error on run.""" + integrator.methods[0].geometry.separation = 2 * H + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + sim.run(0) + assert integrator.methods[0].check_particles() is expected_result + + def test_md_integrator(self, simulation_factory, snap): + """Test we can also attach to a normal MD integrator.""" + bb = hoomd.mpcd.methods.BounceBack( + filter=hoomd.filter.All(), + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) + integrator = hoomd.md.Integrator(dt=0.1, methods=[bb]) + + sim = simulation_factory(snap) + sim.operations.integrator = integrator + + # verify one step works right + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.particles.position, + [[-4.95, 3.95, 4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.particles.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) diff --git a/hoomd/mpcd/pytest/test_snapshot.py b/hoomd/mpcd/pytest/test_snapshot.py index a53b4da0dc..b300b797be 100644 --- a/hoomd/mpcd/pytest/test_snapshot.py +++ b/hoomd/mpcd/pytest/test_snapshot.py @@ -1,10 +1,10 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. +import pytest + import hoomd -from hoomd import Simulation import numpy as np -import pytest # default testing configuration, used in setting values below test_positions = np.array([[1, 2, 3], [-9, -6, -3], [7, 8, 9]]) @@ -67,7 +67,8 @@ def test_resize(snap): np.testing.assert_array_equal(snap.mpcd.velocity, [test_velocities[0]]) np.testing.assert_array_equal(snap.mpcd.typeid, [test_typeids[0]]) - # grow the snapshot by one, and make sure first entry is retained, and it is padded by zeros + # grow the snapshot by one, and make sure first entry is retained, and + # it is padded by zeros snap.mpcd.N = 2 np.testing.assert_array_equal(snap.mpcd.position, [test_positions[0], [0, 0, 0]]) @@ -135,7 +136,7 @@ def test_replicate(snap): def test_create_and_restore_from_snap(snap, simulation_factory): - """Test that simulation can be created and restored with MPCD data in snapshot.""" + """Test simulation can be created and restored with MPCD data.""" if snap.communicator.num_ranks > 2: pytest.skip("Test must be run on 1 or 2 ranks") diff --git a/hoomd/mpcd/pytest/test_stream.py b/hoomd/mpcd/pytest/test_stream.py new file mode 100644 index 0000000000..55b09b9990 --- /dev/null +++ b/hoomd/mpcd/pytest/test_stream.py @@ -0,0 +1,519 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import numpy as np +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def snap(): + snap_ = hoomd.Snapshot() + if snap_.communicator.rank == 0: + snap_.configuration.box = [10, 10, 10, 0, 0, 0] + snap_.particles.N = 0 + snap_.particles.types = ["A"] + snap_.mpcd.N = 1 + snap_.mpcd.types = ["A"] + return snap_ + + +@pytest.mark.parametrize( + "cls, init_args", + [ + (hoomd.mpcd.stream.Bulk, {}), + ( + hoomd.mpcd.stream.BounceBack, + { + "geometry": + hoomd.mpcd.geometry.ParallelPlates( + separation=8.0, speed=0.0, no_slip=True), + }, + ), + ( + hoomd.mpcd.stream.BounceBack, + { + "geometry": + hoomd.mpcd.geometry.PlanarPore( + separation=8.0, length=6.0, no_slip=True) + }, + ), + ], + ids=["Bulk", "ParallelPlates", "PlanarPore"], +) +class TestStreamingMethod: + + def test_create(self, simulation_factory, snap, cls, init_args): + sim = simulation_factory(snap) + sm = cls(period=5, **init_args) + ig = hoomd.mpcd.Integrator(dt=0.02, streaming_method=sm) + sim.operations.integrator = ig + + assert ig.streaming_method is sm + assert sm.period == 5 + sim.run(0) + assert ig.streaming_method is sm + assert sm.period == 5 + + def test_pickling(self, simulation_factory, snap, cls, init_args): + sm = cls(period=5, **init_args) + pickling_check(sm) + + sim = simulation_factory(snap) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + streaming_method=sm) + sim.run(0) + pickling_check(sm) + + @pytest.mark.parametrize( + "force", + [ + None, + hoomd.mpcd.force.BlockForce(force=2.0, separation=3.0, width=0.5), + hoomd.mpcd.force.ConstantForce(force=(1, -2, 3)), + hoomd.mpcd.force.SineForce(amplitude=2.0, wavenumber=1), + ], + ids=["NoForce", "BlockForce", "ConstantForce", "SineForce"], + ) + def test_force_attach(self, simulation_factory, snap, cls, init_args, + force): + """Test that force can be attached with various forces.""" + sm = cls(period=5, **init_args, mpcd_particle_force=force) + assert sm.mpcd_particle_force is force + pickling_check(sm) + + sim = simulation_factory(snap) + sim.operations.integrator = hoomd.mpcd.Integrator(dt=0.02, + streaming_method=sm) + sim.run(0) + + assert sm.mpcd_particle_force is force + pickling_check(sm) + + def test_forced_step(self, simulation_factory, snap, cls, init_args): + """Test a forced step. + + The particle starts in the middle, and there is a constant force in +x + and -z. + + This test should be skipped or adapted if geometries are added for which + this point is / will be out of bounds, but is legal for all the ones we + have now. + """ + if snap.communicator.rank == 0: + snap.mpcd.position[0] = [0, -1, 1] + snap.mpcd.velocity[0] = [1, -2, 3] + + sim = simulation_factory(snap) + sm = cls(period=1, + **init_args, + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, -1))) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take 1 step and check updated velocity and position + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal(snap.mpcd.velocity, + [[1.1, -2.0, 2.9]]) + np.testing.assert_array_almost_equal(snap.mpcd.position, + [[0.105, -1.2, 1.295]]) + + +class TestBulk: + + def test_bulk_step(self, simulation_factory, snap): + if snap.communicator.rank == 0: + snap.mpcd.N = 2 + snap.mpcd.position[:] = [[1.0, 4.85, 3.0], [-3.0, -4.75, -1.0]] + snap.mpcd.velocity[:] = [[1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]] + + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.Bulk(period=1) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take one step + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.1, 4.95, 3.1], [-3.1, -4.85, -1.1]]) + + # take another step, wrapping the first particle through the boundary + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.2, -4.95, 3.2], [-3.2, -4.95, -1.2]]) + + # take another step, wrapping the second particle through the boundary + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.3, -4.85, 3.3], [-3.3, 4.95, -1.3]]) + + # change streaming method to use a different period, and change + # integrator step running again should not move the particles since we + # haven't hit next period + ig.dt = 0.05 + ig.streaming_method = hoomd.mpcd.stream.Bulk(period=4) + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.3, -4.85, 3.3], [-3.3, 4.95, -1.3]]) + + # but running one more should move them + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.5, -4.65, 3.5], [-3.5, 4.75, -1.5]]) + + # then 3 more should do nothing + sim.run(3) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[1.5, -4.65, 3.5], [-3.5, 4.75, -1.5]]) + + +class TestParallelPlates: + + def test_step_noslip(self, simulation_factory, snap): + """Test step with no-slip boundary conditions.""" + if snap.communicator.rank == 0: + snap.mpcd.N = 2 + snap.mpcd.position[:] = [[4.95, 3.85, -4.95], [0.0, -3.8, 0.0]] + snap.mpcd.velocity[:] = [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0)) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take one step + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.95, 3.95, 4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step where one particle will now hit the wall + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.95, 3.95, 4.95], [-0.2, -4.0, -0.2]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[-1.0, -1.0, 1.0], [-1.0, -1.0, -1.0]]) + + # take another step, reflecting the second particle + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[4.95, 3.85, -4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[-1.0, -1.0, 1.0], [1.0, 1.0, 1.0]]) + + def test_step_slip(self, simulation_factory, snap): + """Test step with slip boundary conditions.""" + if snap.communicator.rank == 0: + snap.mpcd.N = 2 + snap.mpcd.position[:] = [[4.95, 3.85, -4.95], [0.0, -3.8, 0.0]] + snap.mpcd.velocity[:] = [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0, + no_slip=False), + ) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take one step + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.95, 3.95, 4.95], [-0.1, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, 1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step where one particle will now hit the wall + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.85, 3.95, 4.85], [-0.2, -4.0, -0.2]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]) + + # take another step, reflecting perpendicular motion of second particle + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.75, 3.85, 4.75], [-0.3, -3.9, -0.3]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]]) + + def test_step_moving_wall(self, simulation_factory, snap): + """Test step with moving wall. + + The first particle is matched exactly to the wall speed, and so it will + translate at same velocity along +x for 0.3 tau. It will bounce back in + y and z to where it started. (vx stays the same, and vy and vz flip.) + + The second particle has y and z velocities flip again, and since it + started closer, it moves relative to original position. + """ + if snap.communicator.rank == 0: + snap.mpcd.N = 2 + snap.mpcd.position[:] = [[4.95, 3.85, -4.95], [0.0, -3.8, 0.0]] + snap.mpcd.velocity[:] = [[1.0, 1.0, -1.0], [-2.0, -1.0, -1.0]] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=8.0, + speed=1, + no_slip=True), + ) + ig = hoomd.mpcd.Integrator(dt=0.3, streaming_method=sm) + sim.operations.integrator = ig + + # run one step and check bounce back of particles + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal( + snap.mpcd.position, [[-4.75, 3.85, -4.95], [-0.4, -3.9, -0.1]]) + np.testing.assert_array_almost_equal( + snap.mpcd.velocity, [[1.0, -1.0, 1.0], [0.0, 1.0, 1.0]]) + + @pytest.mark.parametrize("H,expected_result", [(4.0, True), (3.8, False)]) + def test_check_mpcd_particles(self, simulation_factory, snap, H, + expected_result): + if snap.communicator.rank == 0: + snap.mpcd.position[0] = [0, 3.85, 0] + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates(separation=2 * H)) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + sim.run(0) + assert sm.check_mpcd_particles() is expected_result + + +class TestPlanarPore: + + def _make_particles(self, snap): + if snap.communicator.rank == 0: + snap.mpcd.N = 8 + snap.mpcd.position[:] = [ + [-3.05, -4.11, -4], + [3.05, 4.11, 4], + [-3.05, 4.11, -2], + [3.05, -4.11, 2], + [0, 3.95, 0], + [0, -3.95, 0], + [3.03, -3.98, 0], + [3.02, -3.97, 0], + ] + snap.mpcd.velocity[:] = [ + [1.0, 1.0, -1.0], + [-1.0, -1.0, 1.0], + [1.0, -1.0, 0.0], + [-1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, -1.0, 0.0], + [-1.0, -1.0, 0.0], + [-1.0, -1.0, 0.0], + ] + return snap + + def test_step_noslip(self, simulation_factory, snap): + snap = self._make_particles(snap) + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, + length=6.0, + no_slip=True), + ) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take one step, and everything should collide and bounce back + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal(snap.mpcd.position[0], + [-3.05, -4.11, -4]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[0], + [-1.0, -1.0, 1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.05, 4.11, 4]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[1], + [1.0, 1.0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.05, 4.11, -2]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], + [-1.0, 1.0, 0.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.05, -4.11, 2]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], + [1.0, -1.0, 0.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 3.95, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], + [0, -1.0, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, -3.95, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], + [0, 1.0, 0]) + # hits y = -4 after 0.02, then reverses. + # x is 3.01, so reverses to 3.09 + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [3.09, -3.92, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], + [1, 1, 0]) + # hits x = 3 after 0.02, then reverses. + # y is -3.99, so reverses to -3.91 + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.08, -3.91, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], + [1, 1, 0]) + + # take another step where nothing hits now + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal(snap.mpcd.position[0], + [-3.15, -4.21, -3.9]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.15, 4.21, 3.9]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.15, 4.21, -2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.15, -4.21, 2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 3.85, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, -3.85, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [3.19, -3.82, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.18, -3.81, 0]) + + def test_step_slip(self, simulation_factory, snap): + snap = self._make_particles(snap) + sim = simulation_factory(snap) + sm = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, + length=6.0, + no_slip=False), + ) + ig = hoomd.mpcd.Integrator(dt=0.1, streaming_method=sm) + sim.operations.integrator = ig + + # take one step, and everything should collide and bounce back + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal(snap.mpcd.position[0], + [-3.05, -4.01, -4.1]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[0], + [-1.0, 1.0, -1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.05, 4.01, 4.1]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[1], + [1.0, -1.0, 1.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.05, 4.01, -2]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[2], + [-1.0, -1.0, 0.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.05, -4.01, 2]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[3], + [1.0, 1.0, 0.0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 3.95, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[4], + [0, -1.0, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, -3.95, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[5], + [0, 1.0, 0]) + # hits y = -4 after 0.02, then reverses. + # x is not touched because slip + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [2.93, -3.92, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[6], + [-1, 1, 0]) + # hits x = 3 after 0.02, then reverses. + # y is not touched because slip + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.08, -4.07, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.velocity[7], + [1, -1, 0]) + + # take another step where nothing hits now + sim.run(1) + snap = sim.state.get_snapshot() + if snap.communicator.rank == 0: + np.testing.assert_array_almost_equal(snap.mpcd.position[0], + [-3.15, -3.91, -4.2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[1], + [3.15, 3.91, 4.2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[2], + [-3.15, 3.91, -2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[3], + [3.15, -3.91, 2]) + np.testing.assert_array_almost_equal(snap.mpcd.position[4], + [0, 3.85, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[5], + [0, -3.85, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[6], + [2.83, -3.82, 0]) + np.testing.assert_array_almost_equal(snap.mpcd.position[7], + [3.18, -4.17, 0]) + + def test_check_mpcd_particles(self, simulation_factory, snap): + """Test box validation raises an error on run.""" + snap = self._make_particles(snap) + sim = simulation_factory(snap) + ig = hoomd.mpcd.Integrator(dt=0.1) + sim.operations.integrator = ig + + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=6.0), + ) + sim.run(0) + assert ig.streaming_method.check_mpcd_particles() + + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=7.6, length=6.0), + ) + sim.run(0) + assert not ig.streaming_method.check_mpcd_particles() + + ig.streaming_method = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.PlanarPore(separation=8.0, length=7.0), + ) + assert not ig.streaming_method.check_mpcd_particles() diff --git a/hoomd/mpcd/pytest/test_tune.py b/hoomd/mpcd/pytest/test_tune.py new file mode 100644 index 0000000000..ddec40026d --- /dev/null +++ b/hoomd/mpcd/pytest/test_tune.py @@ -0,0 +1,50 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +import pytest + +import hoomd +from hoomd.conftest import pickling_check + + +@pytest.fixture +def snap(): + snap = hoomd.Snapshot() + if snap.communicator.rank == 0: + snap.configuration.box = [20, 20, 20, 0, 0, 0] + snap.particles.N = 0 + snap.particles.types = ["A"] + snap.mpcd.N = 1 + snap.mpcd.types = ["A"] + return snap + + +class TestParticleSorter: + + def test_create(self, simulation_factory, snap): + sim = simulation_factory(snap) + + sorter = hoomd.mpcd.tune.ParticleSorter(trigger=5) + assert isinstance(sorter.trigger, hoomd.trigger.Trigger) + + trigger = hoomd.trigger.Periodic(50) + sorter.trigger = trigger + assert sorter.trigger is trigger + + ig = hoomd.mpcd.Integrator(dt=0.02, mpcd_particle_sorter=sorter) + sim.operations.integrator = ig + sim.run(0) + assert sorter.trigger is trigger + + def test_pickling(self, simulation_factory, snap): + sorter = hoomd.mpcd.tune.ParticleSorter(trigger=5) + pickling_check(sorter) + + sorter.trigger = hoomd.trigger.Periodic(50) + pickling_check(sorter) + + sim = simulation_factory(snap) + sim.operations.integrator = hoomd.mpcd.Integrator( + dt=0.02, mpcd_particle_sorter=sorter) + sim.run(0) + pickling_check(sorter) diff --git a/hoomd/mpcd/stream.py b/hoomd/mpcd/stream.py index 338d289750..f5e58b68bf 100644 --- a/hoomd/mpcd/stream.py +++ b/hoomd/mpcd/stream.py @@ -1,13 +1,13 @@ # Copyright (c) 2009-2024 The Regents of the University of Michigan. # Part of HOOMD-blue, released under the BSD 3-Clause License. -r""" MPCD streaming methods +r"""MPCD streaming methods. An MPCD streaming method is required to update the particle positions over time. -It is meant to be used in conjunction with an :py:class:`~hoomd.mpcd.integrator` -and collision method (see :py:mod:`~hoomd.mpcd.collide`). Particle positions are -propagated ballistically according to Newton's equations using a velocity-Verlet -scheme for a time :math:`\Delta t`: +It is meant to be used in conjunction with an :class:`.mpcd.Integrator` and +:class:`~hoomd.mpcd.collide.CollisionMethod`. Particle positions are propagated +ballistically according to Newton's equations using a velocity-Verlet scheme for +a time :math:`\Delta t`: .. math:: @@ -15,554 +15,291 @@ \mathbf{r}(t+\Delta t) &= \mathbf{r}(t) + \mathbf{v}(t+\Delta t/2) \Delta t - \mathbf{v}(t + \Delta t) &= \mathbf{v}(t + \Delta t/2) + (\mathbf{f}/m)(\Delta t / 2) - -where **r** and **v** are the particle position and velocity, respectively, and **f** -is the external force acting on the particles of mass *m*. For a list of forces -that can be applied, see :py:mod:`.mpcd.force`. - -Since one of the main strengths of the MPCD algorithm is that it can be coupled to -complex boundaries, the streaming geometry can be configured. MPCD solvent particles -will be reflected from boundary surfaces using specular reflections (bounce-back) -rules consistent with either "slip" or "no-slip" hydrodynamic boundary conditions. -(The external force is only applied to the particles at the beginning and the end -of this process.) To help fully enforce the boundary conditions, "virtual" MPCD -particles can be inserted near the boundary walls. - -Although a streaming geometry is enforced on the MPCD solvent particles, there are -a few important caveats: - - 1. Embedded particles are not coupled to the boundary walls. They must be confined - by an appropriate method, e.g., an external potential, an explicit particle wall, - or a bounce-back method (see :py:mod:`.mpcd.integrate`). - 2. The confined geometry exists inside a fully periodic simulation box. Hence, the - box must be padded large enough that the MPCD cells do not interact through the - periodic boundary. Usually, this means adding at least one extra layer of cells - in the confined dimensions. Your periodic simulation box will be validated by - the confined geometry. - 3. It is an error for MPCD particles to lie "outside" the confined geometry. You - must initialize your system carefully using the snapshot interface to ensure - all particles are "inside" the geometry. An error will be raised otherwise. + \mathbf{v}(t + \Delta t) &= \mathbf{v}(t + \Delta t/2) + + (\mathbf{f}/m)(\Delta t / 2) -""" - -import hoomd -from hoomd import _hoomd - -from . import _mpcd - - -class _streaming_method: - """Base streaming method - - Args: - period (int): Number of integration steps between streaming step - - This class is not intended to be initialized directly by the user. Instead, - initialize a specific streaming method directly. It is included in the documentation - to supply signatures for common methods. - - """ - - def __init__(self, period): - # check for hoomd initialization - if not hoomd.init.is_initialized(): - raise RuntimeError( - "mpcd.stream: system must be initialized before streaming method\n" - ) - - # check for mpcd initialization - if hoomd.context.current.mpcd is None: - hoomd.context.current.device.cpp_msg.error( - "mpcd.stream: an MPCD system must be initialized before the streaming method\n" - ) - raise RuntimeError("MPCD system not initialized") - - # check for multiple collision rule initializations - if hoomd.context.current.mpcd._stream is not None: - hoomd.context.current.device.cpp_msg.error( - "mpcd.stream: only one streaming method can be created.\n") - raise RuntimeError("Multiple initialization of streaming method") - - self.period = period - self.enabled = True - self.force = None - self._cpp = None - self._filler = None - - # attach the streaming method to the system - self.enable() - - def enable(self): - """Enable the streaming method - - Examples:: - - method.enable() - - Enabling the streaming method adds it to the current MPCD system definition. - Only one streaming method can be attached to the system at any time. - If another method is already set, ``disable`` must be called - first before switching. Streaming will occur when the timestep is the next - multiple of *period*. - - """ - - self.enabled = True - hoomd.context.current.mpcd._stream = self - - def disable(self): - """Disable the streaming method - - Examples:: - - method.disable() - - Disabling the streaming method removes it from the current MPCD system definition. - Only one streaming method can be attached to the system at any time, so - use this method to remove the current streaming method before adding another. - - """ - - self.enabled = False - hoomd.context.current.mpcd._stream = None - - def set_period(self, period): - """Set the streaming period. +where **r** and **v** are the particle position and velocity, respectively, and +**f** is the external force acting on the particles of mass *m*. For a list of +forces that can be applied, see :mod:`.mpcd.force`. - Args: - period (int): New streaming period. +.. invisible-code-block: python - The MPCD streaming period can only be changed to a new value on a - simulation timestep that is a multiple of both the previous *period* - and the new *period*. An error will be raised if it is not. + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) - Examples:: - - # The initial period is 5. - # The period can be updated to 2 on step 10. - hoomd.run_upto(10) - method.set_period(period=2) - - # The period can be updated to 4 on step 12. - hoomd.run_upto(12) - hoomd.set_period(period=4) - - """ - - cur_tstep = hoomd.context.current.system.getCurrentTimeStep() - if cur_tstep % self.period != 0 or cur_tstep % period != 0: - hoomd.context.current.device.cpp_msg.error( - "mpcd.stream: streaming period can only be changed on multiple of current and new period.\n" - ) - raise RuntimeError( - "Streaming period can only be changed on multiple of current and new period" - ) - - self._cpp.setPeriod(cur_tstep, period) - self.period = period - - def set_force(self, force): - """Set the external force field for streaming. - - Args: - force (:py:mod:`.mpcd.force`): External force field to apply to MPCD particles. - - Setting an external *force* will generate a flow of the MPCD particles subject to the - boundary conditions of the streaming geometry. Note that the force field should be - chosen in a way that makes sense for the geometry (e.g., so that the box is not - continually accelerating). - - Warning: - The *force* applies only to the MPCD particles. If you have embedded - particles, you should usually additionally specify a force from :py:mod:`.md.force` - for that particle group. - - Examples:: - - f = mpcd.force.constant(field=(1.0,0.0,0.0)) - streamer.set_force(f) - - """ - self.force = force - self._cpp.setField(self.force._cpp) - - def remove_force(self): - """Remove the external force field for streaming. - - Warning: - This only removes the force on the MPCD particles. If you have embedded - particles, you must separately disable any corresponding external force. - - Example:: - - streamer.remove_force() - - """ - self.force = None - self._cpp.removeField() - - def _process_boundary(self, bc): - """Process boundary condition string into enum - - Args: - bc (str): Boundary condition, either "no_slip" or "slip" - - Returns: - A valid boundary condition enum. - - The enum interface is still fairly clunky for the user since the boundary - condition is buried too deep in the package structure. This is a convenience - method for interpreting. +""" - """ - if bc == "no_slip": - return _mpcd.boundary.no_slip - elif bc == "slip": - return _mpcd.boundary.slip - else: - hoomd.context.current.device.cpp_msg.error( - "mpcd.stream: boundary condition " + bc + " not recognized.\n") - raise ValueError("Unrecognized streaming boundary condition") +import hoomd +from hoomd.data.parameterdicts import ParameterDict +from hoomd.data.typeconverter import OnlyTypes +from hoomd.mpcd import _mpcd +from hoomd.mpcd.force import BodyForce +from hoomd.mpcd.geometry import Geometry +from hoomd.operation import Operation -class bulk(_streaming_method): - """Bulk fluid streaming geometry. +class StreamingMethod(Operation): + """Base streaming method. Args: - period (int): Number of integration steps between collisions. + period (int): Number of integration steps covered by streaming step. + mpcd_particle_force (BodyForce): Force on MPCD particles. - :py:class:`bulk` performs the streaming step for MPCD particles in a fully - periodic geometry (2D or 3D). This geometry is appropriate for modeling - bulk fluids. The streaming time :math:`\\Delta t` is equal to *period* steps - of the :py:class:`~hoomd.mpcd.integrator`. For a pure MPCD fluid, - typically *period* should be 1. When particles are embedded in the MPCD fluid - through the collision step, *period* should be equal to the MPCD collision - *period* for best performance. The MPCD particle positions will be updated - every time the simulation timestep is a multiple of *period*. This is - equivalent to setting a *phase* of 0 using the terminology of other - periodic :py:mod:`~hoomd.update` methods. + Attributes: + period (int): Number of integration steps covered by streaming step + (*read only*). - Example for pure MPCD fluid:: + The MPCD particles will be streamed every time the + :attr:`~hoomd.Simulation.timestep` is a multiple of `period`. The + streaming time is hence equal to `period` steps of the + :class:`~hoomd.mpcd.Integrator`. Typically `period` should be equal + to the :attr:`~hoomd.mpcd.collide.CollisionMethod.period` for the + corresponding collision method. A smaller fraction of this may be + used if an external force is applied, and more faithful numerical + integration is needed. - mpcd.integrator(dt=0.1) - mpcd.collide.srd(seed=42, period=1, angle=130.) - mpcd.stream.bulk(period=1) + mpcd_particle_force (BodyForce): Body force on MPCD particles. - Example for embedded particles:: - - mpcd.integrator(dt=0.01) - mpcd.collide.srd(seed=42, period=10, angle=130., group=hoomd.group.all()) - mpcd.stream.bulk(period=10) + The `mpcd_particle_force` cannot be changed after the + `StreamingMethod` is constructed, but its attributes can be + modified. """ - def __init__(self, period=1): - _streaming_method.__init__(self, period) + def __init__(self, period, mpcd_particle_force=None): + super().__init__() - # create the base streaming class - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - stream_class = _mpcd.ConfinedStreamingMethodBulk - else: - stream_class = _mpcd.ConfinedStreamingMethodGPUBulk - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), - self.period, - 0, - _mpcd.BulkGeometry(), + param_dict = ParameterDict( + period=int(period), + mpcd_particle_force=OnlyTypes(BodyForce, allow_none=True), ) + param_dict["mpcd_particle_force"] = mpcd_particle_force + self._param_dict.update(param_dict) -class slit(_streaming_method): - r"""Parallel plate (slit) streaming geometry. +class Bulk(StreamingMethod): + """Bulk fluid. Args: - H (float): channel half-width - V (float): wall speed (default: 0) - boundary (str): boundary condition at wall ("slip" or "no_slip"") - period (int): Number of integration steps between collisions + period (int): Number of integration steps covered by streaming step. + mpcd_particle_force (BodyForce): Body force on MPCD particles. - The slit geometry represents a fluid confined between two infinite parallel - plates. The slit is centered around the origin, and the walls are placed - at :math:`z=-H` and :math:`z=+H`, so the total channel width is *2H*. - The walls may be put into motion, moving with speeds :math:`-V` and - :math:`+V` in the *x* direction, respectively. If combined with a - no-slip boundary condition, this motion can be used to generate simple - shear flow. + `Bulk` streams the MPCD particles in a fully periodic geometry (2D or 3D). + This geometry is appropriate for modeling bulk fluids, i.e., those that + are not confined by any surfaces. - The "inside" of the :py:class:`slit` is the space where :math:`|z| < H`. + .. rubric:: Examples: - Examples:: + Bulk streaming. - stream.slit(period=10, H=30.) - stream.slit(period=1, H=25., V=0.1) + .. code-block:: python - .. versionadded:: 2.6 + stream = hoomd.mpcd.stream.Bulk(period=1) + simulation.operations.integrator.streaming_method = stream - """ + Bulk streaming with applied force. - def __init__(self, H, V=0.0, boundary="no_slip", period=1): - _streaming_method.__init__(self, period) + .. code-block:: python - self.H = H - self.V = V - self.boundary = boundary + stream = hoomd.mpcd.stream.Bulk( + period=1, + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) + simulation.operations.integrator.streaming_method = stream + + """ - bc = self._process_boundary(boundary) + def _attach_hook(self): + sim = self._simulation - # create the base streaming class - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - stream_class = _mpcd.ConfinedStreamingMethodSlit + # attach and use body force if present + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._attach(sim) + mpcd_particle_force = self.mpcd_particle_force._cpp_obj else: - stream_class = _mpcd.ConfinedStreamingMethodGPUSlit - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), + mpcd_particle_force = None + + # try to find force in map, otherwise use default + force_type = type(self.mpcd_particle_force) + try: + class_info = self._cpp_class_map[force_type] + except KeyError: + if self.mpcd_particle_force is not None: + force_name = force_type.__name__ + else: + force_name = "NoForce" + class_info = ( + _mpcd, + "BulkStreamingMethod" + force_name, + ) + class_info = list(class_info) + if isinstance(sim.device, hoomd.device.GPU): + class_info[1] += "GPU" + class_ = getattr(*class_info, None) + assert class_ is not None, ("C++ streaming method could not be " + "determined") + + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, self.period, 0, - _mpcd.SlitGeometry(H, V, bc), + mpcd_particle_force, ) - def set_filler(self, density, kT, seed, type="A"): - r"""Add virtual particles to slit channel. + super()._attach_hook() - Args: - density (float): Density of virtual particles. - kT (float): Temperature of virtual particles. - seed (int): Seed to pseudo-random number generator for virtual particles. - type (str): Type of the MPCD particles to fill with. + def _detach_hook(self): + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._detach() + super()._detach_hook() - The virtual particle filler draws particles within the volume *outside* the - slit walls that could be overlapped by any cell that is partially *inside* - the slit channel (between the parallel plates). The particles are drawn from - the velocity distribution consistent with *kT* and with the given *density*. - The mean of the distribution is zero in *y* and *z*, but is equal to the wall - speed in *x*. Typically, the virtual particle density and temperature are set - to the same conditions as the solvent. + _cpp_class_map = {} - The virtual particles will act as a weak thermostat on the fluid, and so energy - is no longer conserved. Momentum will also be sunk into the walls. + @classmethod + def _register_cpp_class(cls, force, module, class_name): + cls._cpp_class_map[force] = (module, class_name) - Example:: - slit.set_filler(density=5.0, kT=1.0, seed=42) +class BounceBack(StreamingMethod): + """Streaming with bounce-back rule for surfaces. - .. versionadded:: 2.6 - - """ - - type_id = hoomd.context.current.mpcd.particles.getTypeByName(type) - T = hoomd.variant._setup_variant_input(kT) - - if self._filler is None: - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - fill_class = _mpcd.SlitGeometryFiller - else: - fill_class = _mpcd.SlitGeometryFillerGPU - self._filler = fill_class( - hoomd.context.current.mpcd.data, - density, - type_id, - T.cpp_variant, - seed, - self._cpp.geometry, - ) - else: - self._filler.setDensity(density) - self._filler.setType(type_id) - self._filler.setTemperature(T.cpp_variant) - self._filler.setSeed(seed) - - def remove_filler(self): - """Remove the virtual particle filler. - - Example:: + Args: + period (int): Number of integration steps covered by streaming step. + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from. + mpcd_particle_force (BodyForce): Body force on MPCD particles. + + One of the main strengths of the MPCD algorithm is that it can be coupled to + complex boundaries, defined by a `geometry`. This `StreamingMethod` reflects + the MPCD particles from boundary surfaces using specular reflections + (bounce-back) rules consistent with either "slip" or "no-slip" hydrodynamic + boundary conditions. The external force is only applied to the particles at + the beginning and the end of this process. + + Although a streaming geometry is enforced on the MPCD particles, + there are a few important caveats: + + 1. Embedded particles are not coupled to the boundary walls. They must be + confined by an appropriate method, e.g., an external potential, an + explicit particle wall, or a bounce-back method + (`hoomd.mpcd.methods.BounceBack`). + 2. The `geometry` exists inside a fully periodic simulation box. + Hence, the box must be padded large enough that the MPCD cells do not + interact through the periodic boundary. Usually, this means adding at + least one extra layer of cells in the confined dimensions. Your periodic + simulation box will be validated by the `geometry`. + 3. It is an error for MPCD particles to lie "outside" the `geometry`. + You must initialize your system carefully to ensure all particles are + "inside" the geometry. + + .. rubric:: Examples: + + Shear flow between moving parallel plates. + + .. code-block:: python + + stream = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates( + separation=6.0, speed=1.0, no_slip=True)) + simulation.operations.integrator.streaming_method = stream + + Pressure driven flow between parallel plates. + + .. code-block:: python + + stream = hoomd.mpcd.stream.BounceBack( + period=1, + geometry=hoomd.mpcd.geometry.ParallelPlates( + separation=6.0, no_slip=True), + mpcd_particle_force=hoomd.mpcd.force.ConstantForce((1, 0, 0))) + simulation.operations.integrator.streaming_method = stream + + Attributes: + geometry (hoomd.mpcd.geometry.Geometry): Surface to bounce back from + (*read only*). - slit.remove_filler() + """ - .. versionadded:: 2.6 + _cpp_class_map = {} - """ + def __init__(self, period, geometry, mpcd_particle_force=None): + super().__init__(period, mpcd_particle_force) - self._filler = None + param_dict = ParameterDict(geometry=Geometry) + param_dict["geometry"] = geometry + self._param_dict.update(param_dict) - def set_params(self, H=None, V=None, boundary=None): - """Set parameters for the slit geometry. + def check_mpcd_particles(self): + """Check if MPCD particles are inside `geometry`. - Args: - H (float): channel half-width - V (float): wall speed (default: 0) - boundary (str): boundary condition at wall ("slip" or "no_slip"") + This method can only be called after this object is attached to a + simulation. - Changing any of these parameters will require the geometry to be - constructed and validated, so do not change these too often. + Returns: + True if all MPCD particles are inside `geometry`. - Examples:: + .. rubric:: Examples: - slit.set_params(H=15.0) - slit.set_params(V=0.2, boundary="no_slip") + .. code-block:: python - .. versionadded:: 2.6 + assert stream.check_mpcd_particles() """ + return self._cpp_obj.check_mpcd_particles() - if H is not None: - self.H = H - - if V is not None: - self.V = V - - if boundary is not None: - self.boundary = boundary - - bc = self._process_boundary(self.boundary) - self._cpp.geometry = _mpcd.SlitGeometry(self.H, self.V, bc) - if self._filler is not None: - self._filler.setGeometry(self._cpp.geometry) - - -class slit_pore(_streaming_method): - r"""Parallel plate (slit) pore streaming geometry. - - Args: - H (float): channel half-width - L (float): pore half-length - boundary (str): boundary condition at wall ("slip" or "no_slip"") - period (int): Number of integration steps between collisions - - The slit pore geometry represents a fluid partially confined between two - parallel plates that have finite length in *x*. The slit pore is centered - around the origin, and the walls are placed at :math:`z=-H` and - :math:`z=+H`, so the total channel width is *2H*. They extend from - :math:`x=-L` to :math:`x=+L` (total length *2L*), where additional solid walls - with normals in *x* prevent penetration into the regions above / below the - plates. The plates are infinite in *y*. Outside the pore, the simulation box - has full periodic boundaries; it is not confined by any walls. This model - hence mimics a narrow pore in, e.g., a membrane. - - .. image:: mpcd_slit_pore.png - - The "inside" of the :py:class:`slit_pore` is the space where - :math:`|z| < H` for :math:`|x| < L`, and the entire space where - :math:`|x| \ge L`. - - Examples:: - - stream.slit_pore(period=10, H=30., L=10.) - stream.slit_pore(period=1, H=25., L=25.) - - .. versionadded:: 2.7 + def _attach_hook(self): + sim = self._simulation - """ - - def __init__(self, H, L, boundary="no_slip", period=1): - _streaming_method.__init__(self, period) - - self.H = H - self.L = L - self.boundary = boundary + self.geometry._attach(sim) - bc = self._process_boundary(boundary) - - # create the base streaming class - if not hoomd.context.current.device.mode == "gpu": - stream_class = _mpcd.ConfinedStreamingMethodSlitPore + # attach and use body force if present + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._attach(sim) + mpcd_particle_force = self.mpcd_particle_force._cpp_obj else: - stream_class = _mpcd.ConfinedStreamingMethodGPUSlitPore - self._cpp = stream_class( - hoomd.context.current.mpcd.data, - hoomd.context.current.system.getCurrentTimeStep(), + mpcd_particle_force = None + + # try to find force in map, otherwise use default + geom_type = type(self.geometry) + force_type = type(self.mpcd_particle_force) + try: + class_info = self._cpp_class_map[geom_type, force_type] + except KeyError: + if self.mpcd_particle_force is not None: + force_name = force_type.__name__ + else: + force_name = "NoForce" + + class_info = ( + _mpcd, + "BounceBackStreamingMethod" + geom_type.__name__ + force_name, + ) + class_info = list(class_info) + if isinstance(sim.device, hoomd.device.GPU): + class_info[1] += "GPU" + class_ = getattr(*class_info, None) + assert class_ is not None, "Streaming method for geometry not found" + + self._cpp_obj = class_( + sim.state._cpp_sys_def, + sim.timestep, self.period, 0, - _mpcd.SlitPoreGeometry(H, L, bc), + self.geometry._cpp_obj, + mpcd_particle_force, ) - def set_filler(self, density, kT, seed, type="A"): - r"""Add virtual particles to slit pore. - - Args: - density (float): Density of virtual particles. - kT (float): Temperature of virtual particles. - seed (int): Seed to pseudo-random number generator for virtual particles. - type (str): Type of the MPCD particles to fill with. - - The virtual particle filler draws particles within the volume *outside* the - slit pore boundaries that could be overlapped by any cell that is partially *inside* - the slit pore. The particles are drawn from the velocity distribution consistent - with *kT* and with the given *density*. The mean of the distribution is zero in - *x*, *y*, and *z*. Typically, the virtual particle density and temperature are set - to the same conditions as the solvent. - - The virtual particles will act as a weak thermostat on the fluid, and so energy - is no longer conserved. Momentum will also be sunk into the walls. - - Example:: - - slit_pore.set_filler(density=5.0, kT=1.0, seed=42) - - """ - type_id = hoomd.context.current.mpcd.particles.getTypeByName(type) - T = hoomd.variant._setup_variant_input(kT) - - if self._filler is None: - if not hoomd.context.current.device.mode == "gpu": - fill_class = _mpcd.SlitPoreGeometryFiller - else: - fill_class = _mpcd.SlitPoreGeometryFillerGPU - self._filler = fill_class( - hoomd.context.current.mpcd.data, - density, - type_id, - T.cpp_variant, - seed, - self._cpp.geometry, - ) - else: - self._filler.setDensity(density) - self._filler.setType(type_id) - self._filler.setTemperature(T.cpp_variant) - self._filler.setSeed(seed) - - def remove_filler(self): - """Remove the virtual particle filler. - - Example:: - - slit_pore.remove_filler() - - """ - - self._filler = None - - def set_params(self, H=None, L=None, boundary=None): - """Set parameters for the slit geometry. - - Args: - H (float): channel half-width - L (float): pore half-length - boundary (str): boundary condition at wall ("slip" or "no_slip"") - - Changing any of these parameters will require the geometry to be - constructed and validated, so do not change these too often. - - Examples:: - - slit_pore.set_params(H=15.0) - slit_pore.set_params(L=10.0, boundary="no_slip") - - """ - - if H is not None: - self.H = H - - if L is not None: - self.L = L + super()._attach_hook() - if boundary is not None: - self.boundary = boundary + def _detach_hook(self): + self.geometry._detach() + if self.mpcd_particle_force is not None: + self.mpcd_particle_force._detach() + super()._detach_hook() - bc = self._process_boundary(self.boundary) - self._cpp.geometry = _mpcd.SlitPoreGeometry(self.H, self.L, bc) - if self._filler is not None: - self._filler.setGeometry(self._cpp.geometry) + @classmethod + def _register_cpp_class(cls, geometry, force, module, cpp_class_name): + # we will allow None for the force, but we need its class type not value + if force is None: + force = type(None) + cls._cpp_cpp_class_map[geometry, force] = (module, cpp_class_name) diff --git a/hoomd/mpcd/test/CMakeLists.txt b/hoomd/mpcd/test/CMakeLists.txt index 2ad97739b8..3d082bae59 100644 --- a/hoomd/mpcd/test/CMakeLists.txt +++ b/hoomd/mpcd/test/CMakeLists.txt @@ -3,9 +3,9 @@ set(TEST_LIST at_collision_method cell_list cell_thermo_compute - #external_field - slit_geometry_filler - slit_pore_geometry_filler + force + parallel_plate_geometry_filler + planar_pore_geometry_filler sorter srd_collision_method streaming_method @@ -22,14 +22,13 @@ if(ENABLE_MPI) ADD_TO_MPI_TESTS(cell_list 8) ADD_TO_MPI_TESTS(cell_thermo_compute 8) ADD_TO_MPI_TESTS(communicator 8) - ADD_TO_MPI_TESTS(slit_geometry_filler 8) - ADD_TO_MPI_TESTS(slit_pore_geometry_filler 8) + ADD_TO_MPI_TESTS(parallel_plate_geometry_filler 8) + ADD_TO_MPI_TESTS(planar_pore_geometry_filler 8) endif() macro(compile_test TEST_EXE TEST_SRC) # check for extra cuda files if(ENABLE_HIP AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_EXE}.cu) - set(CUDA_SEPARABLE_COMPILATION OFF) set(_cuda_sources ${TEST_EXE}.cu) else() set(_cuda_sources "") @@ -49,7 +48,7 @@ endmacro(compile_test) foreach (CUR_TEST ${TEST_LIST}) set(CUR_TEST_SRC ${CUR_TEST}_test.cc) set(CUR_TEST_EXE ${CUR_TEST}_test) - set(CUR_TEST_NAME mpcd-core-${CUR_TEST}) + set(CUR_TEST_NAME mpcd-${CUR_TEST}) compile_test(${CUR_TEST_EXE} ${CUR_TEST_SRC}) if (ENABLE_MPI) @@ -63,7 +62,7 @@ endforeach(CUR_TEST) foreach (CUR_TEST ${MPI_TEST_LIST}) set(CUR_TEST_SRC ${CUR_TEST}_mpi_test.cc) set(CUR_TEST_EXE ${CUR_TEST}_mpi_test) - set(CUR_TEST_NAME mpcd-core-${CUR_TEST}-mpi) + set(CUR_TEST_NAME mpcd-${CUR_TEST}-mpi) compile_test(${CUR_TEST_EXE} ${CUR_TEST_SRC}) add_test(NAME ${CUR_TEST_NAME} COMMAND diff --git a/hoomd/mpcd/test/at_collision_method_test.cc b/hoomd/mpcd/test/at_collision_method_test.cc index c778548108..e665d00863 100644 --- a/hoomd/mpcd/test/at_collision_method_test.cc +++ b/hoomd/mpcd/test/at_collision_method_test.cc @@ -42,11 +42,10 @@ void at_collision_method_basic_test(std::shared_ptr exec // initialize system and collision method std::shared_ptr pdata_4 = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr T = std::make_shared(1.5); std::shared_ptr collide = std::make_shared(sysdef, 0, 2, 1, T); collide->setCellList(cl); - collide->enableGridShifting(false); // thermo to test properties auto thermo = std::make_shared(sysdef, cl); @@ -127,11 +126,10 @@ void at_collision_method_embed_test(std::shared_ptr exec // initialize system and collision method std::shared_ptr pdata_4 = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr T = std::make_shared(1.5); std::shared_ptr collide = std::make_shared(sysdef, 0, 1, -1, T); collide->setCellList(cl); - collide->enableGridShifting(false); // thermo to check properties auto thermo = std::make_shared(sysdef, cl); diff --git a/hoomd/mpcd/test/cell_communicator_mpi_test.cc b/hoomd/mpcd/test/cell_communicator_mpi_test.cc index ccf7fc7901..f7e105ad4a 100644 --- a/hoomd/mpcd/test/cell_communicator_mpi_test.cc +++ b/hoomd/mpcd/test/cell_communicator_mpi_test.cc @@ -54,7 +54,7 @@ void cell_communicator_reduce_test(std::shared_ptr exec_ std::shared_ptr pdata_comm(new Communicator(sysdef, decomposition)); sysdef->setCommunicator(pdata_comm); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); cl->computeDimensions(); // Fill in a dummy cell property array, which is just the global index of each cell @@ -155,7 +155,7 @@ void cell_communicator_overdecompose_test(std::shared_ptr pdata_comm(new Communicator(sysdef, decomposition)); sysdef->setCommunicator(pdata_comm); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); cl->computeDimensions(); // Don't really care what's in this array, just want to make sure errors get thrown diff --git a/hoomd/mpcd/test/cell_list_mpi_test.cc b/hoomd/mpcd/test/cell_list_mpi_test.cc index a4967fefd9..910e1b8ca9 100644 --- a/hoomd/mpcd/test/cell_list_mpi_test.cc +++ b/hoomd/mpcd/test/cell_list_mpi_test.cc @@ -74,7 +74,7 @@ void celllist_dimension_test(std::shared_ptr exec_conf, // initialize mpcd system auto pdata_1 = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); // compute the cell list cl->computeDimensions(); @@ -763,7 +763,7 @@ template void celllist_basic_test(std::shared_ptr pdata = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); cl->compute(0); const unsigned int my_rank = exec_conf->getRank(); { @@ -986,7 +986,7 @@ template void celllist_edge_test(std::shared_ptrsetCommunicator(pdata_comm); std::shared_ptr pdata = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); // move particles to edges of domains for testing const unsigned int my_rank = exec_conf->getRank(); diff --git a/hoomd/mpcd/test/cell_list_test.cc b/hoomd/mpcd/test/cell_list_test.cc index 87930148e9..b0c3e22017 100644 --- a/hoomd/mpcd/test/cell_list_test.cc +++ b/hoomd/mpcd/test/cell_list_test.cc @@ -28,7 +28,7 @@ template void celllist_dimension_test(std::shared_ptrgetMPCDParticleData(); // define a system of different edge lengths - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); // compute the cell list dimensions cl->computeDimensions(); @@ -115,7 +115,7 @@ template void celllist_small_test(std::shared_ptr pdata_9 = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); cl->compute(0); // check that each particle is in the proper bin (cell list and velocity) @@ -285,7 +285,7 @@ template void celllist_grid_shift_test(std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); std::shared_ptr pdata_1 = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); cl->compute(0); { ArrayHandle h_cell_np(cl->getCellSizeArray(), @@ -405,7 +405,7 @@ template void celllist_embed_test(std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); std::shared_ptr pdata_8 = sysdef->getMPCDParticleData(); - std::shared_ptr cl(new CL(sysdef)); + std::shared_ptr cl(new CL(sysdef, 1.0, false)); cl->compute(0); // at first, there is no embedded particle, so everything should just look like the test diff --git a/hoomd/mpcd/test/cell_thermo_compute_mpi_test.cc b/hoomd/mpcd/test/cell_thermo_compute_mpi_test.cc index a14671274d..d8f8d764a9 100644 --- a/hoomd/mpcd/test/cell_thermo_compute_mpi_test.cc +++ b/hoomd/mpcd/test/cell_thermo_compute_mpi_test.cc @@ -56,7 +56,7 @@ template void cell_thermo_basic_test(std::shared_ptrsetCommunicator(pdata_comm); std::shared_ptr pdata = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr thermo = std::make_shared(sysdef, cl); AllThermoRequest thermo_req(thermo); diff --git a/hoomd/mpcd/test/cell_thermo_compute_test.cc b/hoomd/mpcd/test/cell_thermo_compute_test.cc index c3f2f4531e..f9f063a5f1 100644 --- a/hoomd/mpcd/test/cell_thermo_compute_test.cc +++ b/hoomd/mpcd/test/cell_thermo_compute_test.cc @@ -39,7 +39,7 @@ template void cell_thermo_basic_test(std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); std::shared_ptr pdata_5 = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr thermo = std::make_shared(sysdef, cl); AllThermoRequest thermo_req(thermo); thermo->compute(0); @@ -258,7 +258,7 @@ template void cell_thermo_embed_test(std::shared_ptr selector(new ParticleFilterAll()); std::shared_ptr group(new ParticleGroup(sysdef, selector)); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); cl->setEmbeddedGroup(group); std::shared_ptr thermo = std::make_shared(sysdef, cl); AllThermoRequest thermo_req(thermo); diff --git a/hoomd/mpcd/test/communicator_mpi_test.cc b/hoomd/mpcd/test/communicator_mpi_test.cc index b992166570..118f1862e9 100644 --- a/hoomd/mpcd/test/communicator_mpi_test.cc +++ b/hoomd/mpcd/test/communicator_mpi_test.cc @@ -88,8 +88,7 @@ void test_communicator_migrate(communicator_creator comm_creator, snap->mpcd_data.type[7] = 7; std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf, decomposition)); // set a small cell size so that nothing will lie in the diffusion layer - auto cl = std::make_shared(sysdef); - cl->setCellSize(0.05); + auto cl = std::make_shared(sysdef, 0.05, false); // initialize the communicator std::shared_ptr comm = comm_creator(sysdef, nstages); @@ -726,8 +725,7 @@ void test_communicator_migrate_ortho(communicator_creator comm_creator, snap->mpcd_data.position[7] = vec3(1.5, 0.5, 0.0); std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf, decomposition)); // set a small cell size so that nothing will lie in the diffusion layer - auto cl = std::make_shared(sysdef); - cl->setCellSize(0.05); + auto cl = std::make_shared(sysdef, 0.05, false); // initialize the communicator std::shared_ptr comm = comm_creator(sysdef, nstages); @@ -919,7 +917,7 @@ void test_communicator_overdecompose(std::shared_ptr exe auto sysdef = std::make_shared(snap, exec_conf, decomposition); // initialize the communicator - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); auto comm = std::make_shared(sysdef); comm->setCellList(cl); if (should_fail) diff --git a/hoomd/mpcd/test/external_field_test.cc b/hoomd/mpcd/test/external_field_test.cc deleted file mode 100644 index 0a818e2808..0000000000 --- a/hoomd/mpcd/test/external_field_test.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file external_field_test.cc - * \brief Tests for ExternalField functors. - */ - -#include "hoomd/ExecutionConfiguration.h" -#include "hoomd/GPUArray.h" - -#include "hoomd/GPUPolymorph.h" -#include "hoomd/mpcd/ExternalField.h" -#ifdef ENABLE_HIP -#include "external_field_test.cuh" -#endif // ENABLE_HIP - -#include - -#include "hoomd/test/upp11_config.h" -HOOMD_UP_MAIN(); - -using namespace hoomd; - -void test_external_field(std::shared_ptr exec_conf, - std::shared_ptr> field, - const std::vector& ref_pos, - const std::vector& ref_force) - { - const unsigned int N = ref_pos.size(); - - // test points - GPUArray pos(N, exec_conf); - { - ArrayHandle h_pos(pos, access_location::host, access_mode::overwrite); - for (unsigned int i = 0; i < N; ++i) - h_pos.data[i] = ref_pos[i]; - } - - // check host evaluation - UP_ASSERT(field->get(access_location::host) != nullptr); - { - ArrayHandle h_pos(pos, access_location::host, access_mode::read); - for (unsigned int i = 0; i < N; ++i) - { - const Scalar3 r = h_pos.data[i]; - const Scalar3 f = field->get(access_location::host)->evaluate(r); - UP_ASSERT_CLOSE(f.x, ref_force[i].x, tol_small); - UP_ASSERT_CLOSE(f.y, ref_force[i].y, tol_small); - UP_ASSERT_CLOSE(f.z, ref_force[i].z, tol_small); - } - } - -#ifdef ENABLE_HIP - if (exec_conf->isCUDAEnabled()) - { - UP_ASSERT(field->get(access_location::device) != nullptr); - GPUArray out(N, exec_conf); - { - ArrayHandle d_out(out, access_location::device, access_mode::overwrite); - ArrayHandle d_pos(pos, access_location::device, access_mode::read); - gpu::test_external_field(d_out.data, - field->get(access_location::device), - d_pos.data, - N); - } - { - ArrayHandle h_out(out, access_location::host, access_mode::read); - for (unsigned int i = 0; i < N; ++i) - { - const Scalar3 f = h_out.data[i]; - UP_ASSERT_CLOSE(f.x, ref_force[i].x, tol_small); - UP_ASSERT_CLOSE(f.y, ref_force[i].y, tol_small); - UP_ASSERT_CLOSE(f.z, ref_force[i].z, tol_small); - } - } - } - else - { - UP_ASSERT(field->get(access_location::device) == nullptr); - } -#endif // ENABLE_HIP - } - -//! Test constant force on CPU -UP_TEST(constant_force_cpu) - { - auto exec_conf = std::make_shared(ExecutionConfiguration::CPU); - - auto field = std::make_shared>(exec_conf); - field->reset(make_scalar3(6, 7, 8)); - - std::vector ref_pos = {make_scalar3(1, 2, 3), make_scalar3(-1, 0, -2)}; - std::vector ref_force = {make_scalar3(6, 7, 8), make_scalar3(6, 7, 8)}; - - test_external_field(exec_conf, field, ref_pos, ref_force); - } - -//! Test sine force on CPU -UP_TEST(sine_force_cpu) - { - auto exec_conf = std::make_shared(ExecutionConfiguration::CPU); - - auto field = std::make_shared>(exec_conf); - field->reset(Scalar(2.0), Scalar(M_PI)); - - std::vector ref_pos = {make_scalar3(1, 2, 0.5), make_scalar3(-1, 0, -1. / 6.)}; - std::vector ref_force = {make_scalar3(2.0, 0, 0), make_scalar3(-1., 0, 0)}; - - test_external_field(exec_conf, field, ref_pos, ref_force); - } - -#ifdef ENABLE_HIP -//! Test constant force on GPU -UP_TEST(constant_force_gpu) - { - auto exec_conf = std::make_shared(ExecutionConfiguration::GPU); - - auto field = std::make_shared>(exec_conf); - field->reset(make_scalar3(6, 7, 8)); - - std::vector ref_pos = {make_scalar3(1, 2, 3), make_scalar3(-1, 0, -2)}; - std::vector ref_force = {make_scalar3(6, 7, 8), make_scalar3(6, 7, 8)}; - - test_external_field(exec_conf, field, ref_pos, ref_force); - } - -//! Test sine force on GPU -UP_TEST(sine_force_gpu) - { - auto exec_conf = std::make_shared(ExecutionConfiguration::GPU); - - auto field = std::make_shared>(exec_conf); - field->reset(Scalar(2.0), Scalar(M_PI)); - - std::vector ref_pos = {make_scalar3(1, 2, 0.5), make_scalar3(-1, 0, -1. / 6.)}; - std::vector ref_force = {make_scalar3(2.0, 0, 0), make_scalar3(-1., 0, 0)}; - - test_external_field(exec_conf, field, ref_pos, ref_force); - } -#endif // ENABLE_HIP diff --git a/hoomd/mpcd/test/external_field_test.cu b/hoomd/mpcd/test/external_field_test.cu deleted file mode 100644 index 4fc20f8ea4..0000000000 --- a/hoomd/mpcd/test/external_field_test.cu +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#include "external_field_test.cuh" -#include "hoomd/GPUPolymorph.cuh" - -namespace gpu - { -namespace kernel - { -__global__ void test_external_field(Scalar3* out, - const mpcd::ExternalField* field, - const Scalar3* pos, - const unsigned int N) - { - const int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) - return; - out[idx] = field->evaluate(pos[idx]); - } - } // end namespace kernel - -cudaError_t test_external_field(Scalar3* out, - const mpcd::ExternalField* field, - const Scalar3* pos, - const unsigned int N) - { - const unsigned int block_size = 32; - const unsigned int num_blocks = (N + block_size - 1) / block_size; - kernel::test_external_field<<>>(out, field, pos, N); - return cudaSuccess; - } - } // end namespace gpu diff --git a/hoomd/mpcd/test/external_field_test.cuh b/hoomd/mpcd/test/external_field_test.cuh deleted file mode 100644 index 344b0c23cc..0000000000 --- a/hoomd/mpcd/test/external_field_test.cuh +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#ifndef HOOMD_MPCD_TEST_EXTERNAL_FIELD_TEST_CUH_ -#define HOOMD_MPCD_TEST_EXTERNAL_FIELD_TEST_CUH_ - -#include "hoomd/HOOMDMath.h" -#include "hoomd/mpcd/ExternalField.h" - -namespace gpu - { -cudaError_t test_external_field(Scalar3* out, - const mpcd::ExternalField* field, - const Scalar3* pos, - const unsigned int N); - } // end namespace gpu - -#endif // HOOMD_MPCD_TEST_EXTERNAL_FIELD_TEST_CUH_ diff --git a/hoomd/mpcd/test/force_test.cc b/hoomd/mpcd/test/force_test.cc new file mode 100644 index 0000000000..4c98fc9645 --- /dev/null +++ b/hoomd/mpcd/test/force_test.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2009-2024 The Regents of the University of Michigan. +// Part of HOOMD-blue, released under the BSD 3-Clause License. + +#include + +#include "hoomd/mpcd/BlockForce.h" +#include "hoomd/mpcd/ConstantForce.h" +#include "hoomd/mpcd/NoForce.h" +#include "hoomd/mpcd/SineForce.h" + +#include "hoomd/test/upp11_config.h" + +HOOMD_UP_MAIN(); +using namespace hoomd; + +template +void test_force(std::shared_ptr force, + const std::vector& ref_pos, + const std::vector& ref_force) + { + UP_ASSERT_EQUAL(ref_pos.size(), ref_force.size()); + for (unsigned int i = 0; i < ref_pos.size(); ++i) + { + const Scalar3 r = ref_pos[i]; + const Scalar3 F = force->evaluate(r); + const Scalar3 F_ref = ref_force[i]; + UP_ASSERT_CLOSE(F.x, F_ref.x, tol_small); + UP_ASSERT_CLOSE(F.y, F_ref.y, tol_small); + UP_ASSERT_CLOSE(F.z, F_ref.z, tol_small); + } + } + +//! Test block force +UP_TEST(block_force) + { + auto force = std::make_shared(2, 6, 0.4); + std::vector ref_pos = {make_scalar3(0, 2.7, 0), + make_scalar3(1, 2.9, 2), + make_scalar3(2, 3.3, 1), + make_scalar3(-1, -2, 0), + make_scalar3(4, -3, 5), + make_scalar3(4, -4, 5)}; + std::vector ref_force = {make_scalar3(0, 0, 0), + make_scalar3(2, 0, 0), + make_scalar3(0, 0, 0), + make_scalar3(0, 0, 0), + make_scalar3(-2, 0, 0), + make_scalar3(0, 0, 0)}; + test_force(force, ref_pos, ref_force); + } + +//! Test constant force +UP_TEST(constant_force) + { + auto force = std::make_shared(make_scalar3(6, 7, 8)); + std::vector ref_pos = {make_scalar3(1, 2, 3), make_scalar3(-1, 0, -2)}; + std::vector ref_force = {make_scalar3(6, 7, 8), make_scalar3(6, 7, 8)}; + test_force(force, ref_pos, ref_force); + } + +//! Test no force (zeros) +UP_TEST(no_force) + { + auto force = std::make_shared(); + std::vector ref_pos = {make_scalar3(1, 2, 3), make_scalar3(-1, 0, -2)}; + std::vector ref_force = {make_scalar3(0, 0, 0), make_scalar3(0, 0, 0)}; + test_force(force, ref_pos, ref_force); + } + +//! Test sine force +UP_TEST(sine_force) + { + auto force = std::make_shared(Scalar(2.0), Scalar(M_PI)); + std::vector ref_pos = {make_scalar3(1, 0.5, 2), make_scalar3(-1, -1. / 6., 0)}; + std::vector ref_force = {make_scalar3(2.0, 0, 0), make_scalar3(-1., 0, 0)}; + test_force(force, ref_pos, ref_force); + } diff --git a/hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc similarity index 81% rename from hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc rename to hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc index 4891bedd18..06fea35d73 100644 --- a/hoomd/mpcd/test/slit_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_mpi_test.cc @@ -2,9 +2,9 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. #include "hoomd/Communicator.h" -#include "hoomd/mpcd/SlitGeometryFiller.h" +#include "hoomd/mpcd/ParallelPlateGeometryFiller.h" #ifdef ENABLE_HIP -#include "hoomd/mpcd/SlitGeometryFillerGPU.h" +#include "hoomd/mpcd/ParallelPlateGeometryFillerGPU.h" #endif // ENABLE_HIP #include "hoomd/SnapshotSystemData.h" @@ -14,7 +14,8 @@ HOOMD_UP_MAIN() using namespace hoomd; -template void slit_fill_mpi_test(std::shared_ptr exec_conf) +template +void parallel_plate_fill_mpi_test(std::shared_ptr exec_conf) { UP_ASSERT_EQUAL(exec_conf->getNRanks(), 8); @@ -23,6 +24,7 @@ template void slit_fill_mpi_test(std::shared_ptrparticle_data.type_mapping.push_back("A"); snap->mpcd_data.resize(1); snap->mpcd_data.type_mapping.push_back("A"); + snap->mpcd_data.type_mapping.push_back("B"); snap->mpcd_data.position[0] = vec3(1, 1, 1); snap->mpcd_data.velocity[0] = vec3(123, 456, 789); @@ -33,17 +35,15 @@ template void slit_fill_mpi_test(std::shared_ptrsetCommunicator(pdata_comm); auto pdata = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); UP_ASSERT_EQUAL(pdata->getNVirtual(), 0); UP_ASSERT_EQUAL(pdata->getNVirtualGlobal(), 0); // create slit channel with half width 5 - auto slit = std::make_shared(5.0, - 0.0, - mpcd::detail::boundary::no_slip); + auto slit = std::make_shared(10.0, 0.0, true); std::shared_ptr kT = std::make_shared(1.0); - std::shared_ptr filler - = std::make_shared(sysdef, 2.0, 0, kT, slit); + std::shared_ptr filler + = std::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* @@ -80,15 +80,15 @@ template void slit_fill_mpi_test(std::shared_ptrgetNVirtualGlobal(), 0); } -UP_TEST(slit_fill_mpi) +UP_TEST(parallel_plate_fill_mpi) { - slit_fill_mpi_test( + parallel_plate_fill_mpi_test( std::make_shared(ExecutionConfiguration::CPU)); } #ifdef ENABLE_HIP -UP_TEST(slit_fill_mpi_gpu) +UP_TEST(parallel_plate_fill_mpi_gpu) { - slit_fill_mpi_test( + parallel_plate_fill_mpi_test( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/mpcd/test/slit_geometry_filler_test.cc b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc similarity index 82% rename from hoomd/mpcd/test/slit_geometry_filler_test.cc rename to hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc index 5c318af68a..42b687c618 100644 --- a/hoomd/mpcd/test/slit_geometry_filler_test.cc +++ b/hoomd/mpcd/test/parallel_plate_geometry_filler_test.cc @@ -1,9 +1,9 @@ // Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. -#include "hoomd/mpcd/SlitGeometryFiller.h" +#include "hoomd/mpcd/ParallelPlateGeometryFiller.h" #ifdef ENABLE_HIP -#include "hoomd/mpcd/SlitGeometryFillerGPU.h" +#include "hoomd/mpcd/ParallelPlateGeometryFillerGPU.h" #endif // ENABLE_HIP #include "hoomd/SnapshotSystemData.h" @@ -13,29 +13,28 @@ HOOMD_UP_MAIN() using namespace hoomd; -template void slit_fill_basic_test(std::shared_ptr exec_conf) +template +void parallel_plate_fill_basic_test(std::shared_ptr exec_conf) { std::shared_ptr> snap(new SnapshotSystemData()); snap->global_box = std::make_shared(20.0); snap->particle_data.type_mapping.push_back("A"); snap->mpcd_data.resize(1); snap->mpcd_data.type_mapping.push_back("A"); + snap->mpcd_data.type_mapping.push_back("B"); snap->mpcd_data.position[0] = vec3(1, -2, 3); snap->mpcd_data.velocity[0] = vec3(123, 456, 789); std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); auto pdata = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); - cl->setCellSize(2.0); + auto cl = std::make_shared(sysdef, 2.0, false); UP_ASSERT_EQUAL(pdata->getNVirtual(), 0); // create slit channel with half width 5 - auto slit = std::make_shared(5.0, - 1.0, - mpcd::detail::boundary::no_slip); + auto slit = std::make_shared(10.0, 1.0, true); std::shared_ptr kT = std::make_shared(1.5); - std::shared_ptr filler - = std::make_shared(sysdef, 2.0, 1, kT, slit); + std::shared_ptr filler + = std::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* @@ -69,10 +68,10 @@ template void slit_fill_basic_test(std::shared_ptr= Scalar(-7.0) && y < Scalar(-5.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0) && y < Scalar(7.0)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * (2 * 20 * 20)); @@ -98,10 +97,10 @@ template void slit_fill_basic_test(std::shared_ptr= Scalar(-7.0) && y < Scalar(-5.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0) && y < Scalar(7.0)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * 2 * (2 * 20 * 20)); @@ -122,10 +121,10 @@ template void slit_fill_basic_test(std::shared_ptrgetN(); i < pdata->getN() + pdata->getNVirtual(); ++i) { - const Scalar z = h_pos.data[i].z; - if (z < Scalar(-5.0)) + const Scalar y = h_pos.data[i].y; + if (y >= Scalar(-5.5) && y < Scalar(-5.0)) ++N_lo; - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0) && y < Scalar(5.5)) ++N_hi; } UP_ASSERT_EQUAL(N_lo, 2 * (20 * 20 / 2)); @@ -152,16 +151,16 @@ template void slit_fill_basic_test(std::shared_ptrgetN(); i < pdata->getN() + pdata->getNVirtual(); ++i) { - const Scalar z = h_pos.data[i].z; + const Scalar y = h_pos.data[i].y; const Scalar4 vel_cell = h_vel.data[i]; const Scalar3 vel = make_scalar3(vel_cell.x, vel_cell.y, vel_cell.z); - if (z < Scalar(-5.0)) + if (y >= Scalar(-7.0) && y < Scalar(-5.0)) { v_lo += vel; T_avg += dot(vel - make_scalar3(-1.0, 0, 0), vel - make_scalar3(-1.0, 0, 0)); ++N_lo; } - else if (z >= Scalar(5.0)) + else if (y >= Scalar(5.0) && y < Scalar(7.0)) { v_hi += vel; T_avg += dot(vel - make_scalar3(1.0, 0, 0), vel - make_scalar3(1.0, 0, 0)); @@ -185,15 +184,15 @@ template void slit_fill_basic_test(std::shared_ptr( + parallel_plate_fill_basic_test( std::make_shared(ExecutionConfiguration::CPU)); } #ifdef ENABLE_HIP -UP_TEST(slit_fill_basic_gpu) +UP_TEST(parallel_plate_fill_basic_gpu) { - slit_fill_basic_test( + parallel_plate_fill_basic_test( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc similarity index 81% rename from hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc rename to hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc index 5558c6028d..3e22e297a8 100644 --- a/hoomd/mpcd/test/slit_pore_geometry_filler_mpi_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_mpi_test.cc @@ -2,9 +2,9 @@ // Part of HOOMD-blue, released under the BSD 3-Clause License. #include "hoomd/Communicator.h" -#include "hoomd/mpcd/SlitPoreGeometryFiller.h" +#include "hoomd/mpcd/PlanarPoreGeometryFiller.h" #ifdef ENABLE_HIP -#include "hoomd/mpcd/SlitPoreGeometryFillerGPU.h" +#include "hoomd/mpcd/PlanarPoreGeometryFillerGPU.h" #endif // ENABLE_HIP #include "hoomd/SnapshotSystemData.h" @@ -14,7 +14,7 @@ HOOMD_UP_MAIN() using namespace hoomd; -template void slit_pore_fill_mpi_test(std::shared_ptr exec_conf) +template void planar_pore_fill_mpi_test(std::shared_ptr exec_conf) { UP_ASSERT_EQUAL(exec_conf->getNRanks(), 8); @@ -23,6 +23,7 @@ template void slit_pore_fill_mpi_test(std::shared_ptrparticle_data.type_mapping.push_back("A"); snap->mpcd_data.resize(1); snap->mpcd_data.type_mapping.push_back("A"); + snap->mpcd_data.type_mapping.push_back("B"); snap->mpcd_data.position[0] = vec3(1, 1, 1); snap->mpcd_data.velocity[0] = vec3(123, 456, 789); @@ -33,19 +34,15 @@ template void slit_pore_fill_mpi_test(std::shared_ptrsetCommunicator(pdata_comm); auto pdata = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); - cl->setCellSize(2.0); + auto cl = std::make_shared(sysdef, 2.0, false); UP_ASSERT_EQUAL(pdata->getNVirtual(), 0); UP_ASSERT_EQUAL(pdata->getNVirtualGlobal(), 0); // create slit channel with half width 5 and half length 8 - auto slit - = std::make_shared(5.0, - 8.0, - mpcd::detail::boundary::no_slip); + auto slit = std::make_shared(10.0, 16.0, true); std::shared_ptr kT = std::make_shared(1.0); - std::shared_ptr filler - = std::make_shared(sysdef, 2.0, 0, kT, 42, slit); + std::shared_ptr filler + = std::make_shared(sysdef, "A", 2.0, kT, slit); filler->setCellList(cl); /* @@ -88,15 +85,15 @@ template void slit_pore_fill_mpi_test(std::shared_ptrgetNVirtualGlobal(), 0); } -UP_TEST(slit_pore_fill_mpi) +UP_TEST(planar_pore_fill_mpi) { - slit_pore_fill_mpi_test( + planar_pore_fill_mpi_test( std::make_shared(ExecutionConfiguration::CPU)); } #ifdef ENABLE_HIP -UP_TEST(slit_pore_fill_mpi_gpu) +UP_TEST(planar_pore_fill_mpi_gpu) { - slit_pore_fill_mpi_test( + planar_pore_fill_mpi_test( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc similarity index 86% rename from hoomd/mpcd/test/slit_pore_geometry_filler_test.cc rename to hoomd/mpcd/test/planar_pore_geometry_filler_test.cc index 307783d04a..bb3b9a6aa9 100644 --- a/hoomd/mpcd/test/slit_pore_geometry_filler_test.cc +++ b/hoomd/mpcd/test/planar_pore_geometry_filler_test.cc @@ -1,9 +1,9 @@ // Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. -#include "hoomd/mpcd/SlitPoreGeometryFiller.h" +#include "hoomd/mpcd/PlanarPoreGeometryFiller.h" #ifdef ENABLE_HIP -#include "hoomd/mpcd/SlitPoreGeometryFillerGPU.h" +#include "hoomd/mpcd/PlanarPoreGeometryFillerGPU.h" #endif // ENABLE_HIP #include "hoomd/SnapshotSystemData.h" @@ -13,31 +13,29 @@ HOOMD_UP_MAIN() using namespace hoomd; -template void slit_pore_fill_basic_test(std::shared_ptr exec_conf) +template +void planar_pore_fill_basic_test(std::shared_ptr exec_conf) { std::shared_ptr> snap(new SnapshotSystemData()); snap->global_box = std::make_shared(20.0); snap->particle_data.type_mapping.push_back("A"); snap->mpcd_data.resize(1); snap->mpcd_data.type_mapping.push_back("A"); + snap->mpcd_data.type_mapping.push_back("B"); snap->mpcd_data.position[0] = vec3(1, -2, 3); snap->mpcd_data.velocity[0] = vec3(123, 456, 789); std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); auto pdata = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); - cl->setCellSize(2.0); + auto cl = std::make_shared(sysdef, 2.0, false); UP_ASSERT_EQUAL(pdata->getNVirtual(), 0); // create slit pore channel with half width 5, half length 8 - auto slit - = std::make_shared(5.0, - 8.0, - mpcd::detail::boundary::no_slip); + auto slit = std::make_shared(10.0, 16.0, true); // fill density 2, temperature 1.5 std::shared_ptr kT = std::make_shared(1.5); - std::shared_ptr filler - = std::make_shared(sysdef, 2.0, 1, kT, 42, slit); + std::shared_ptr filler + = std::make_shared(sysdef, "B", 2.0, kT, slit); filler->setCellList(cl); /* @@ -77,9 +75,9 @@ template void slit_pore_fill_basic_test(std::shared_ptr= Scalar(-8.0) && r.x <= Scalar(8.0)) { - if (r.z < Scalar(-5.0)) + if (r.y < Scalar(-5.0)) ++N_lo; - else if (r.z >= Scalar(5.0)) + else if (r.y >= Scalar(5.0)) ++N_hi; } } @@ -111,9 +109,9 @@ template void slit_pore_fill_basic_test(std::shared_ptr= Scalar(-8.0) && r.x <= Scalar(8.0)) { - if (r.z < Scalar(-5.0)) + if (r.y < Scalar(-5.0)) ++N_lo; - else if (r.z >= Scalar(5.0)) + else if (r.y >= Scalar(5.0)) ++N_hi; } } @@ -140,9 +138,9 @@ template void slit_pore_fill_basic_test(std::shared_ptr= Scalar(-8.0) && r.x <= Scalar(8.0)) { - if (r.z < Scalar(-5.0)) + if (r.y < Scalar(-5.0)) ++N_lo; - else if (r.z >= Scalar(5.0)) + else if (r.y >= Scalar(5.0)) ++N_hi; } } @@ -186,15 +184,15 @@ template void slit_pore_fill_basic_test(std::shared_ptr( + planar_pore_fill_basic_test( std::make_shared(ExecutionConfiguration::CPU)); } #ifdef ENABLE_HIP -UP_TEST(slit_pore_fill_basic_gpu) +UP_TEST(planar_pore_fill_basic_gpu) { - slit_pore_fill_basic_test( + planar_pore_fill_basic_test( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/mpcd/test/sorter_test.cc b/hoomd/mpcd/test/sorter_test.cc index dd26c223d2..78ee098b1a 100644 --- a/hoomd/mpcd/test/sorter_test.cc +++ b/hoomd/mpcd/test/sorter_test.cc @@ -66,7 +66,7 @@ template void sorter_test(std::shared_ptr exec_ snap->mpcd_data.type[0] = 7; std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); // add an embedded group std::shared_ptr embed_pdata = sysdef->getParticleData(); @@ -75,7 +75,7 @@ template void sorter_test(std::shared_ptr exec_ cl->setEmbeddedGroup(group); // run the sorter - std::shared_ptr sorter = std::make_shared(sysdef, 0, 1); + std::shared_ptr sorter = std::make_shared(sysdef, nullptr); sorter->setCellList(cl); sorter->update(0); @@ -245,7 +245,7 @@ template void sorter_virtual_test(std::shared_ptrmpcd_data.type[0] = 7; std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); // add 2 virtual particles to fill in the rest of the cells auto pdata = sysdef->getMPCDParticleData(); @@ -273,7 +273,7 @@ template void sorter_virtual_test(std::shared_ptr sorter = std::make_shared(sysdef, 0, 1); + std::shared_ptr sorter = std::make_shared(sysdef, nullptr); sorter->setCellList(cl); sorter->update(0); diff --git a/hoomd/mpcd/test/srd_collision_method_test.cc b/hoomd/mpcd/test/srd_collision_method_test.cc index 6bf74e5e35..32c1983c0c 100644 --- a/hoomd/mpcd/test/srd_collision_method_test.cc +++ b/hoomd/mpcd/test/srd_collision_method_test.cc @@ -53,13 +53,9 @@ void srd_collision_method_basic_test(std::shared_ptr exe // initialize system and collision method std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); std::shared_ptr pdata_4 = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); - std::shared_ptr collide = std::make_shared(sysdef, 0, 2, 1, 42); + auto cl = std::make_shared(sysdef, 1.0, false); + std::shared_ptr collide = std::make_shared(sysdef, 0, 2, 1, 130.); collide->setCellList(cl); - collide->enableGridShifting(false); - // 130 degrees, forces all components of the rotation matrix to act - const double rot_angle = 2.2689280275926285; - collide->setRotationAngle(rot_angle); // create a thermo, and use it to check the current auto thermo = std::make_shared(sysdef, cl); @@ -148,7 +144,7 @@ void srd_collision_method_basic_test(std::shared_ptr exe Scalar3 q1 = v1 - dot(v1, rot_vec) * rot_vec; Scalar3 q2 = v2 - dot(v2, rot_vec) * rot_vec; Scalar cos_angle = dot(q1, q2) / (sqrt(dot(q1, q1)) * sqrt(dot(q2, q2))); - CHECK_CLOSE(cos_angle, slow::cos(rot_angle), tol_small); + CHECK_CLOSE(cos_angle, slow::cos(collide->getRotationAngle() * M_PI / 180.), tol_small); } } @@ -175,7 +171,7 @@ void srd_collision_method_rotvec_test(std::shared_ptr ex snap->global_box = std::make_shared(50.0); snap->particle_data.type_mapping.push_back("A"); std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr collide = std::make_shared(sysdef, 0, 1, -1, 42); collide->setCellList(cl); @@ -282,13 +278,10 @@ void srd_collision_method_embed_test(std::shared_ptr exe // initialize system and collision method std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); std::shared_ptr pdata_4 = sysdef->getMPCDParticleData(); - auto cl = std::make_shared(sysdef); - std::shared_ptr collide = std::make_shared(sysdef, 0, 1, -1, 827); + auto cl = std::make_shared(sysdef, 1.0, false); + std::shared_ptr collide + = std::make_shared(sysdef, 0, 1, -1, 130.); collide->setCellList(cl); - collide->enableGridShifting(false); - // 130 degrees, forces all components of the rotation matrix to act - const double rot_angle = 2.2689280275926285; - collide->setRotationAngle(rot_angle); // create a thermo, and use it to check the current auto thermo = std::make_shared(sysdef, cl); @@ -339,7 +332,7 @@ void srd_collision_method_thermostat_test(std::shared_ptr(10000, box, 1.0, 42, 3, exec_conf); sysdef->setMPCDParticleData(pdata); - auto cl = std::make_shared(sysdef); + auto cl = std::make_shared(sysdef, 1.0, false); std::shared_ptr collide = std::make_shared(sysdef, 0, 1, -1, 827); collide->setCellList(cl); auto thermo = std::make_shared(sysdef, cl); diff --git a/hoomd/mpcd/test/streaming_method_test.cc b/hoomd/mpcd/test/streaming_method_test.cc index 7d0a8901ba..88ab348e3f 100644 --- a/hoomd/mpcd/test/streaming_method_test.cc +++ b/hoomd/mpcd/test/streaming_method_test.cc @@ -1,11 +1,11 @@ // Copyright (c) 2009-2024 The Regents of the University of Michigan. // Part of HOOMD-blue, released under the BSD 3-Clause License. -#include "hoomd/mpcd/ConfinedStreamingMethod.h" -#include "hoomd/mpcd/StreamingGeometry.h" +#include "hoomd/mpcd/BulkStreamingMethod.h" #ifdef ENABLE_HIP -#include "hoomd/mpcd/ConfinedStreamingMethodGPU.h" +#include "hoomd/mpcd/BulkStreamingMethodGPU.h" #endif // ENABLE_HIP +#include "hoomd/mpcd/NoForce.h" #include "hoomd/SnapshotSystemData.h" #include "hoomd/test/upp11_config.h" @@ -33,9 +33,8 @@ void streaming_method_basic_test(std::shared_ptr exec_co std::shared_ptr sysdef(new SystemDefinition(snap, exec_conf)); // setup a streaming method at timestep 2 with period 2 and phase 1 - auto geom = std::make_shared(); - std::shared_ptr stream = std::make_shared(sysdef, 2, 2, 1, geom); - auto cl = std::make_shared(sysdef); + std::shared_ptr stream = std::make_shared(sysdef, 2, 2, 1, nullptr); + auto cl = std::make_shared(sysdef, 1.0, false); stream->setCellList(cl); // set timestep to 0.05, so the MPCD step is 2 x 0.05 = 0.1 @@ -113,16 +112,14 @@ void streaming_method_basic_test(std::shared_ptr exec_co //! basic test case for MPCD StreamingMethod class UP_TEST(mpcd_streaming_method_basic) { - typedef mpcd::ConfinedStreamingMethod method; - streaming_method_basic_test( + streaming_method_basic_test>( std::make_shared(ExecutionConfiguration::CPU)); } #ifdef ENABLE_HIP //! basic test case for MPCD StreamingMethod class UP_TEST(mpcd_streaming_method_setup) { - typedef mpcd::ConfinedStreamingMethodGPU method; - streaming_method_basic_test( + streaming_method_basic_test>( std::make_shared(ExecutionConfiguration::GPU)); } #endif // ENABLE_HIP diff --git a/hoomd/mpcd/tune.py b/hoomd/mpcd/tune.py new file mode 100644 index 0000000000..74d5e84d7f --- /dev/null +++ b/hoomd/mpcd/tune.py @@ -0,0 +1,76 @@ +# Copyright (c) 2009-2024 The Regents of the University of Michigan. +# Part of HOOMD-blue, released under the BSD 3-Clause License. + +r"""MPCD tuning operations. + +These operations will affect the performance of MPCD simulations but not +their correctness. + +.. invisible-code-block: python + + simulation = hoomd.util.make_example_simulation(mpcd_types=["A"]) + simulation.operations.integrator = hoomd.mpcd.Integrator(dt=0.1) + +""" + +import hoomd +from hoomd.mpcd import _mpcd +from hoomd.operation import TriggeredOperation + + +class ParticleSorter(TriggeredOperation): + r"""MPCD particle sorter. + + Args: + trigger (hoomd.trigger.trigger_like): Select the time steps on which to + sort. + + This tuner sorts the MPCD particles into cell order. To perform the sort, + the cell list is first computed with the current particle order. Particles + are then reordered in memory as they are stored in the cell list, which can + significantly improve performance of all subsequent cell-based steps of the + MPCD algorithm due to improved cache coherency. + + The optimal frequency for sorting depends on the number of particles, so the + `trigger` itself should be tuned to give the maximum performance. The + trigger's period should be a multiple of + `hoomd.mpcd.collide.CollisionMethod.period` to avoid unnecessary cell list + builds. Typically, using a small multiple (tens) of the collision period + works best. + + To achieve the best performance, the `ParticleSorter` is not added to + `hoomd.Operations.tuners`. Instead, set it in + `hoomd.mpcd.Integrator.mpcd_particle_sorter`. + + Essentially all MPCD systems benefit from sorting, so it is recommended + to use one for all simulations! + + .. rubric:: Example: + + .. code-block:: python + + sorter = hoomd.mpcd.tune.ParticleSorter(trigger=20) + simulation.operations.integrator.mpcd_particle_sorter = sorter + + Attributes: + trigger (hoomd.trigger.Trigger): Number of integration steps + between sorting. + + .. rubric:: Example: + + .. code-block:: python + + sorter.trigger = 20 + + """ + + def __init__(self, trigger): + super().__init__(trigger) + + def _attach_hook(self): + if isinstance(self._simulation.device, hoomd.device.GPU): + class_ = _mpcd.SorterGPU + else: + class_ = _mpcd.Sorter + self._cpp_obj = class_(self._simulation.state._cpp_sys_def, + self.trigger) diff --git a/hoomd/mpcd/update.py b/hoomd/mpcd/update.py deleted file mode 100644 index 5d86cdcdfc..0000000000 --- a/hoomd/mpcd/update.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2009-2024 The Regents of the University of Michigan. -# Part of HOOMD-blue, released under the BSD 3-Clause License. - -r""" MPCD particle updaters - -Updates properties of MPCD particles. - -""" - -import hoomd -from hoomd.md import _md - -from . import _mpcd - - -class sort(): - r""" Sorts MPCD particles in memory to improve cache coherency. - - Args: - system (:py:class:`hoomd.mpcd.data.system`): MPCD system to create sorter for - period (int): Sort whenever the timestep is a multiple of *period*. - .. versionadded:: 2.6 - - Warning: - Do not create :py:class:`hoomd.mpcd.update.sort` explicitly in your script. - HOOMD creates a sorter by default. - - Every *period* time steps, particles are reordered in memory based on - the cell list generated at the current timestep. Sorting can significantly improve - performance of all other cell-based steps of the MPCD algorithm. The efficiency of - the sort operation obviously depends on the number of particles, and so the *period* - should be tuned to give the maximum performance. - - Note: - The *period* should be no smaller than the MPCD collision period, or unnecessary - cell list builds will occur. - - Essentially all MPCD systems benefit from sorting, and so a sorter is created by - default with the MPCD system. To disable it or modify parameters, save the system - and access the sorter through it:: - - s = mpcd.init.read_snapshot(snap) - # the sorter is only available after initialization - s.sorter.set_period(period=5) - s.sorter.disable() - - """ - - def __init__(self, system, period=50): - - # check for mpcd initialization - if system.sorter is not None: - hoomd.context.current.device.cpp_msg.error( - 'mpcd.update: system already has a sorter created!\n') - raise RuntimeError('MPCD sorter already created') - - # create the c++ mirror class - if not hoomd.context.current.device.cpp_exec_conf.isCUDAEnabled(): - cpp_class = _mpcd.Sorter - else: - cpp_class = _mpcd.SorterGPU - self._cpp = cpp_class(system.data, - hoomd.context.current.system.getCurrentTimeStep(), - period) - - self.period = period - self.enabled = True - - def disable(self): - self.enabled = False - - def enable(self): - self.enabled = True - - def set_period(self, period): - """ Change the sorting period. - - Args: - period (int): New period to set. - - Examples:: - - sorter.set_period(100) - sorter.set_period(1) - - While the simulation is running, the action of each updater - is executed every *period* time steps. Changing the period does - not change the phase set when the analyzer was first created. - - """ - - self.period = period - self._cpp.setPeriod(hoomd.context.current.system.getCurrentTimeStep(), - self.period) - - def tune(self, start, stop, step, tsteps, quiet=False): - """ Tune the sorting period. - - Args: - start (int): Start of tuning interval to scan (inclusive). - stop (int): End of tuning interval to scan (inclusive). - step (int): Spacing between tuning points. - tsteps (int): Number of timesteps to run at each tuning point. - quiet (bool): Quiet the individual run calls. - - Returns: - int: The optimal sorting period from the scanned range. - - The optimal sorting period for the MPCD particles is determined from - a sequence of short runs. The sorting period is first set to *start*. - The TPS value is determined for a run of length *tsteps*. This run is - repeated 3 times, and the median TPS of the runs is saved. The sorting - period is then incremented by *step*, and the process is repeated until - *stop* is reached. The period giving the fastest TPS is determined, and - the sorter period is updated to this value. The results of the scan - are also reported as output, and the fastest sorting period is also - returned. - - Note: - A short warmup run is **required** before calling :py:meth:`tune()` - in order to ensure the runtime autotuners have found optimal - kernel launch parameters. - - Examples:: - - # warmup run - hoomd.run(5000) - - # tune sorting period - sorter.tune(start=5, stop=50, step=5, tsteps=1000) - - """ - - # scan through range of sorting periods and log TPS - periods = range(start, stop + 1, step) - tps = [] - for p in periods: - cur_tps = [] - self.set_period(period=p) - for i in range(0, 3): - hoomd.run(tsteps, quiet=quiet) - cur_tps.append(hoomd.context.current.system.getLastTPS()) - - # save the median tps - cur_tps.sort() - tps.append(cur_tps[1]) - - # determine fastest period and set it on the sorter - fastest = tps.index(max(tps)) - opt_period = periods[fastest] - self.set_period(period=opt_period) - - # output results - hoomd.context.current.device.cpp_msg.notice( - 2, '--- sort.tune() statistics\n') - hoomd.context.current.device.cpp_msg.notice( - 2, 'Optimal period = {0}\n'.format(opt_period)) - hoomd.context.current.device.cpp_msg.notice( - 2, ' period = ' + str(periods) + '\n') - hoomd.context.current.device.cpp_msg.notice( - 2, ' TPS = ' + str(tps) + '\n') - - return opt_period diff --git a/hoomd/mpcd/validation/CMakeLists.txt b/hoomd/mpcd/validation/CMakeLists.txt deleted file mode 100644 index 03ca3f0c62..0000000000 --- a/hoomd/mpcd/validation/CMakeLists.txt +++ /dev/null @@ -1,75 +0,0 @@ - - -set(TEST_LIST - validate_at - validate_srd - ) -SET(EXCLUDE_FROM_MPI - ) - -############################# -# macro for adding hoomd script tests -macro(add_hoomd_script_test test) - -set(TEST_NAME mpcd-${test}) -set(TEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${test}_test.py) - -# use mpirun -n 1 in MPI builds, otherwise, just run hoomd -if (ENABLE_MPI) - add_test(NAME ${TEST_NAME}-cpu - COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 1 - ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=cpu" LABELS "validation") - set_tests_properties(${TEST_NAME}-cpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") - if (ENABLE_HIP) - add_test(NAME ${TEST_NAME}-gpu - COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 1 - ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=gpu" LABELS "validation") - set_tests_properties(${TEST_NAME}-gpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") - endif (ENABLE_HIP) -else () - add_test(NAME ${TEST_NAME}-cpu COMMAND ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=cpu" LABELS "validation") - set_tests_properties(${TEST_NAME}-cpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") - if (ENABLE_HIP) - add_test(NAME ${TEST_NAME}-gpu COMMAND ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=gpu" LABELS "validation") - set_tests_properties(${TEST_NAME}-gpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") - endif (ENABLE_HIP) -endif() - -endmacro(add_hoomd_script_test) -############################### - -############################# -# macro for adding hoomd script tests (MPI version) -macro(add_hoomd_script_test_mpi test nproc) - -set(TEST_NAME mpcd-${test}) -set(TEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${test}_test.py) - -add_test(NAME ${TEST_NAME}-mpi-cpu - COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${nproc} - ${MPIEXEC_POSTFLAGS} ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=cpu" LABELS "validation") -set_tests_properties(${TEST_NAME}-mpi-cpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") -if (ENABLE_HIP) -add_test(NAME ${TEST_NAME}-mpi-gpu - COMMAND ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${nproc} - ${MPIEXEC_POSTFLAGS} ${PYTHON_EXECUTABLE} ${TEST_FILE} "--mode=gpu" LABELS "validation") -set_tests_properties(${TEST_NAME}-mpi-gpu PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}") -endif (ENABLE_HIP) -endmacro(add_hoomd_script_test_mpi) -############################### - -# setup regular tests -foreach(test ${TEST_LIST}) -add_hoomd_script_test(${test}) -endforeach(test) - -# setup MPI tests -if (ENABLE_MPI) - foreach(test ${TEST_LIST}) - GET_FILENAME_COMPONENT(test_name ${test} NAME_WE) - if(NOT "${EXCLUDE_FROM_MPI}" MATCHES ${test_name}) - # execute on two processors - add_hoomd_script_test_mpi(${test} 2) - endif() - endforeach(test) -endif(ENABLE_MPI) diff --git a/hoomd/test/CMakeLists.txt b/hoomd/test/CMakeLists.txt index fda663ff2f..2cfdb99261 100644 --- a/hoomd/test/CMakeLists.txt +++ b/hoomd/test/CMakeLists.txt @@ -20,10 +20,6 @@ set(TEST_LIST random_numbers_test ) -if (HIP_PLATFORM STREQUAL "nvcc") - set(add TEST_LIST test_gpu_polymorph) -endif() - if (ENABLE_HIP) list(APPEND TEST_LIST test_warp_tools diff --git a/hoomd/test/test_gpu_polymorph.cc b/hoomd/test/test_gpu_polymorph.cc deleted file mode 100644 index f256b5a901..0000000000 --- a/hoomd/test/test_gpu_polymorph.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -/*! - * \file test_gpu_polymorph.cc - * \brief Tests for GPUPolymorph wrapper. - */ - -#include "hoomd/ExecutionConfiguration.h" -#include "hoomd/GPUArray.h" - -#include "hoomd/GPUPolymorph.h" -#include "test_gpu_polymorph.cuh" - -#include "upp11_config.h" -HOOMD_UP_MAIN(); - -void test_gpu_polymorph(const ExecutionConfiguration::executionMode mode) - { - auto exec_conf = std::make_shared(mode); - - // default initialization is empty - hoomd::GPUPolymorph add_op(exec_conf); - auto times_op = std::make_shared>(exec_conf); - UP_ASSERT(add_op.get(access_location::host) == nullptr); - UP_ASSERT(times_op->get(access_location::host) == nullptr); - - // resetting should allocate these members - add_op.reset(7); - const int a = 7; - times_op->reset(a); - - // check host polymorphism - UP_ASSERT(add_op.get(access_location::host) != nullptr); - UP_ASSERT(times_op->get(access_location::host) != nullptr); - UP_ASSERT_EQUAL(add_op.get(access_location::host)->call(3), 10); - UP_ASSERT_EQUAL(times_op->get(access_location::host)->call(3), 21); - -// check device polymorphism -#ifdef ENABLE_HIP - if (exec_conf->isCUDAEnabled()) - { - UP_ASSERT(add_op.get(access_location::device) != nullptr); - UP_ASSERT(times_op->get(access_location::device) != nullptr); - - GPUArray result(2, exec_conf); - // addition operator - { - ArrayHandle d_result(result, access_location::device, access_mode::overwrite); - test_operator(d_result.data, add_op.get(access_location::device), 2); - } - { - ArrayHandle h_result(result, access_location::host, access_mode::read); - UP_ASSERT_EQUAL(h_result.data[0], 7); - UP_ASSERT_EQUAL(h_result.data[1], 8); - } - // multiplication operator - { - ArrayHandle d_result(result, access_location::device, access_mode::overwrite); - test_operator(d_result.data, times_op->get(access_location::device), 2); - } - { - ArrayHandle h_result(result, access_location::host, access_mode::read); - UP_ASSERT_EQUAL(h_result.data[0], 0); - UP_ASSERT_EQUAL(h_result.data[1], 7); - } - } - else - { - UP_ASSERT(add_op.get(access_location::device) == nullptr); - UP_ASSERT(times_op->get(access_location::device) == nullptr); - } -#endif // ENABLE_HIP - } - -//! Test polymorphism on CPU -UP_TEST(test_gpu_polymorph_cpu) - { - test_gpu_polymorph(ExecutionConfiguration::CPU); - } - -#ifdef ENABLE_HIP -//! Test polymorphism on GPU -UP_TEST(test_gpu_polymorph_gpu) - { - test_gpu_polymorph(ExecutionConfiguration::GPU); - } -#endif // ENABLE_HIP diff --git a/hoomd/test/test_gpu_polymorph.cu b/hoomd/test/test_gpu_polymorph.cu deleted file mode 100644 index d38bd79d8c..0000000000 --- a/hoomd/test/test_gpu_polymorph.cu +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#include "hoomd/GPUPolymorph.cuh" -#include "test_gpu_polymorph.cuh" - -__global__ void test_operator_kernel(int* result, const ArithmeticOperator* op, unsigned int N) - { - const int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) - return; - - result[idx] = op->call(idx); - } - -void test_operator(int* result, const ArithmeticOperator* op, unsigned int N) - { - const unsigned int block_size = 32; - const unsigned int num_blocks = (N + block_size - 1) / block_size; - hipLaunchKernelGGL((test_operator_kernel), - dim3(num_blocks), - dim3(block_size), - 0, - 0, - result, - op, - N); - } - -template AdditionOperator* hoomd::gpu::device_new(int); -template MultiplicationOperator* hoomd::gpu::device_new(int); -template void hoomd::gpu::device_delete(ArithmeticOperator*); diff --git a/hoomd/test/test_gpu_polymorph.cuh b/hoomd/test/test_gpu_polymorph.cuh deleted file mode 100644 index 2075b899c7..0000000000 --- a/hoomd/test/test_gpu_polymorph.cuh +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2009-2024 The Regents of the University of Michigan. -// Part of HOOMD-blue, released under the BSD 3-Clause License. - -#ifndef HOOMD_TEST_TEST_GPU_POLYMORPH_CUH_ -#define HOOMD_TEST_TEST_GPU_POLYMORPH_CUH_ - -#include - -#ifdef __HIPCC__ -#define HOSTDEVICE __host__ __device__ -#else -#define HOSTDEVICE -#endif - -class ArithmeticOperator - { - public: - HOSTDEVICE virtual ~ArithmeticOperator() { } - HOSTDEVICE virtual int call(int b) const = 0; - }; - -class AdditionOperator : public ArithmeticOperator - { - public: - HOSTDEVICE AdditionOperator(int a) : a_(a) { } - - HOSTDEVICE virtual int call(int b) const - { - return a_ + b; - } - - private: - int a_; - }; - -class MultiplicationOperator : public ArithmeticOperator - { - public: - HOSTDEVICE MultiplicationOperator(int a) : a_(a) { } - - HOSTDEVICE virtual int call(int b) const - { - return a_ * b; - } - - private: - int a_; - }; - -void test_operator(int* result, const ArithmeticOperator* op, unsigned int N); - -#undef HOSTDEVICE - -#endif // HOOMD_TEST_TEST_GPU_POLYMORPH_CUH_ diff --git a/hoomd/util.py b/hoomd/util.py index ca9911926b..6eaa55e45e 100644 --- a/hoomd/util.py +++ b/hoomd/util.py @@ -253,7 +253,10 @@ def __setitem__(self, namespace, value): super().__setitem__(namespace, value) -def make_example_simulation(device=None, dimensions=3, particle_types=['A']): +def make_example_simulation(device=None, + dimensions=3, + particle_types=['A'], + mpcd_types=None): """Make an example Simulation object. The simulation state contains two particles at positions (-1, 0, 0) and @@ -267,6 +270,9 @@ def make_example_simulation(device=None, dimensions=3, particle_types=['A']): particle_types (list[str]): Particle type names. + mpcd_types (list[str]): If not `None`, also create two MPCD particles, + and include these type names in the snapshot. + Returns: hoomd.Simulation: The simulation object. @@ -280,6 +286,7 @@ def make_example_simulation(device=None, dimensions=3, particle_types=['A']): .. code-block:: python simulation = hoomd.util.make_example_simulation() + """ if device is None: device = hoomd.device.CPU() @@ -294,6 +301,13 @@ def make_example_simulation(device=None, dimensions=3, particle_types=['A']): Lz = 0 snapshot.configuration.box = [10, 10, Lz, 0, 0, 0] + if mpcd_types is not None: + if not hoomd.version.mpcd_built: + raise RuntimeError("MPCD component not built") + snapshot.mpcd.N = 2 + snapshot.mpcd.position[:] = [(-1, 0, 0), (1, 0, 0)] + snapshot.mpcd.types = mpcd_types + simulation = hoomd.Simulation(device=device) simulation.create_state_from_snapshot(snapshot) diff --git a/setup.cfg b/setup.cfg index 1f49224dfb..f5a1446153 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,6 @@ exclude = .git, build, hoomd/extern, hoomd/metal, - hoomd/mpcd, max_line_length = 80 max_doc_length = 80 hang_closing = True @@ -56,6 +55,7 @@ rst-directives = versionchanged, todo, rst-roles = + attr, class, doc, file, diff --git a/sphinx-doc/credits.rst b/sphinx-doc/credits.rst index b9ea0a621d..7c89e4dee0 100644 --- a/sphinx-doc/credits.rst +++ b/sphinx-doc/credits.rst @@ -121,6 +121,7 @@ The following people have contributed to HOOMD-blue: * Wenbo Shen, University of Michigan * William Zygmunt, University of Michigan * Wouter Ellenbroek, Eindhoven University of Technology +* Yashraj Wani, Johannes Gutenberg University Mainz * Yuan Zhou, University of Michigan * Åsmund Ervik, SINTEF * Nathan Barrett, Pritzker School of Molecular Engineering diff --git a/sphinx-doc/index.rst b/sphinx-doc/index.rst index 3168e784fb..f8ebf8baad 100644 --- a/sphinx-doc/index.rst +++ b/sphinx-doc/index.rst @@ -143,6 +143,7 @@ Molecular dynamics: package-hoomd package-hpmc package-md + package-mpcd .. toctree:: :maxdepth: 2 diff --git a/sphinx-doc/migrating.rst b/sphinx-doc/migrating.rst index a3866245a0..bee2d0e6d2 100644 --- a/sphinx-doc/migrating.rst +++ b/sphinx-doc/migrating.rst @@ -97,6 +97,70 @@ HOOMD-blue v4 removes functionalities deprecated in v3.x releases: * ``hoomd.md.pair.aniso.Dipole.mode`` parameter * ``hoomd.device.GPU.memory_traceback`` parameter +Reintroducing `hoomd.mpcd` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`hoomd.mpcd` was previously available in HOOMD 2, but it was not available in HOOMD 3 because its +API needed to be significantly rewritten. It is reintroduced in HOOMD 4 with all classes and methods +renamed to be consistent with the rest of HOOMD's API. The most significant changes for users are: + +* The way MPCD particle data is stored and initialized has changed. MPCD particles are now part of + `hoomd.State`, so HOOMD and MPCD particles need to be initialized together rather than separately. +* After initialization, most objects now need to be attached to the :class:`hoomd.mpcd.Integrator`, + similarly to other features migrated from HOOMD 2 to HOOMD 3. +* The `hoomd.mpcd.geometry.ParallelPlates` and `hoomd.mpcd.geometry.PlanarPore` streaming geometries + have been rotated to the *xy* plane from the *xz* plane. +* MPCD particle sorting is not enabled by default but is still highly recommended for performance. + Users should explicitly create a `hoomd.mpcd.tune.ParticleSorter` and attach it to the + :class:`hoomd.mpcd.Integrator`. + +Please refer to the module-level documentation and examples for full details of the new API. Some +common changes that you may need to make to your HOOMD 2 scripts are: + +.. list-table:: + :header-rows: 1 + + * - Feature + - Change + * - Create snapshots using ``mpcd.data`` + - Use `hoomd.Snapshot.mpcd` + * - Specify cell size using ``mpcd.data`` + - Set through `hoomd.mpcd.Integrator.cell_list` + * - Initialize MPCD particles with ``mpcd.init.read_snapshot`` + - Use `hoomd.Simulation.create_state_from_snapshot` + * - Initialize MPCD particles randomly with ``mpcd.init.make_random`` + - Not currently supported + * - Initialize HOOMD particles from a file, then add MPCD particles through ``mpcd.init``. + - Use `hoomd.Snapshot.from_gsd_frame`, add the MPCD particles, then initialize as above + * - Bounce-back integration of HOOMD particles using ``mpcd.integrate`` + - Use `hoomd.mpcd.methods.BounceBack` with a geometry from `hoomd.mpcd.geometry` + * - Bounce-back streaming of MPCD particles using ``mpcd.stream`` + - Use `hoomd.mpcd.stream.BounceBack` with a geometry from `hoomd.mpcd.geometry` + * - Fill geometry with virtual particles using ``mpcd.fill`` + - Use `hoomd.mpcd.fill.GeometryFiller` with a geometry from `hoomd.mpcd.geometry` + * - Change sorting period of automatically created ``system.sorter`` + - Explicitly create a `hoomd.mpcd.tune.ParticleSorter` with desired period + * - Have HOOMD automatically validate my streaming geometry fits inside my box + - No longer performed. Users should make sure their geometries make sense + * - Have HOOMD automatically validate my particles are inside my streaming geometry + - Call `hoomd.mpcd.stream.BounceBack.check_mpcd_particles` directly + +For developers, the following are the most significant changes to be aware of: + +* The MPCD namespace is ``hoomd::mpcd``. +* ``hoomd::mpcd::SystemData`` has been removed. Classes should accept ``hoomd::SystemDefinition`` + instead and use ``SystemDefinition::getMPCDParticleData()``. +* Force and geometry files have been renamed. +* Bounce-back streaming methods are now templated on both geometries and forces, rather than using + polymorphism for the forces. This means that combinations of geometries and forces need to be + compiled when new classes are added. CMake can automatically generate the necessary files if new + geometries and forces are added to the appropriate lists. Python will automatically deduce the + right C++ class names if standard naming conventions are followed; otherwise, explicit + registration is required. +* The virtual particle filler design has been refactored to enable other methods for virtual + particle filling. Fillers that derived from the previous ``hoomd::mpcd::VirtualParticleFiller`` + should inherit from ``hoomd::mpcd::ManualVirtualParticleFiller`` instead. + Compiling ^^^^^^^^^ diff --git a/sphinx-doc/module-mpcd-collide.rst b/sphinx-doc/module-mpcd-collide.rst new file mode 100644 index 0000000000..5f603a9c6c --- /dev/null +++ b/sphinx-doc/module-mpcd-collide.rst @@ -0,0 +1,27 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.collide +------------ + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.collide + +.. autosummary:: + :nosignatures: + + AndersenThermostat + CellList + CollisionMethod + StochasticRotationDynamics + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.collide + :synopsis: Collision methods. + :members: CellList, + CollisionMethod, + AndersenThermostat, + StochasticRotationDynamics + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-fill.rst b/sphinx-doc/module-mpcd-fill.rst new file mode 100644 index 0000000000..66ad40a738 --- /dev/null +++ b/sphinx-doc/module-mpcd-fill.rst @@ -0,0 +1,23 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.fill +--------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.fill + +.. autosummary:: + :nosignatures: + + GeometryFiller + VirtualParticleFiller + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.fill + :synopsis: Virtual-particle fillers. + :members: VirtualParticleFiller, + GeometryFiller + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-force.rst b/sphinx-doc/module-mpcd-force.rst new file mode 100644 index 0000000000..c6ca9a8abd --- /dev/null +++ b/sphinx-doc/module-mpcd-force.rst @@ -0,0 +1,27 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.force +---------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.force + +.. autosummary:: + :nosignatures: + + BodyForce + BlockForce + ConstantForce + SineForce + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.force + :synopsis: Body forces on MPCD particles. + :members: BodyForce, + BlockForce, + ConstantForce, + SineForce + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-geometry.rst b/sphinx-doc/module-mpcd-geometry.rst new file mode 100644 index 0000000000..a188706292 --- /dev/null +++ b/sphinx-doc/module-mpcd-geometry.rst @@ -0,0 +1,25 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.geometry +------------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.geometry + +.. autosummary:: + :nosignatures: + + Geometry + ParallelPlates + PlanarPore + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.geometry + :synopsis: Geometries. + :members: Geometry, + ParallelPlates, + PlanarPore + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-methods.rst b/sphinx-doc/module-mpcd-methods.rst new file mode 100644 index 0000000000..7aa17b0f42 --- /dev/null +++ b/sphinx-doc/module-mpcd-methods.rst @@ -0,0 +1,21 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.methods +------------ + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.methods + +.. autosummary:: + :nosignatures: + + BounceBack + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.methods + :synopsis: Integration methods. + :members: BounceBack + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-stream.rst b/sphinx-doc/module-mpcd-stream.rst new file mode 100644 index 0000000000..91f4d62e6b --- /dev/null +++ b/sphinx-doc/module-mpcd-stream.rst @@ -0,0 +1,25 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.stream +----------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.stream + +.. autosummary:: + :nosignatures: + + Bulk + BounceBack + StreamingMethod + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.stream + :synopsis: Streaming methods. + :members: StreamingMethod, + Bulk, + BounceBack + :show-inheritance: diff --git a/sphinx-doc/module-mpcd-tune.rst b/sphinx-doc/module-mpcd-tune.rst new file mode 100644 index 0000000000..fd735043a7 --- /dev/null +++ b/sphinx-doc/module-mpcd-tune.rst @@ -0,0 +1,21 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +mpcd.tune +--------- + +.. rubric:: Overview + +.. py:currentmodule:: hoomd.mpcd.tune + +.. autosummary:: + :nosignatures: + + ParticleSorter + +.. rubric:: Details + +.. automodule:: hoomd.mpcd.tune + :synopsis: Tuning operations. + :members: ParticleSorter + :show-inheritance: diff --git a/sphinx-doc/mpcd_slit_pore.png b/sphinx-doc/mpcd_slit_pore.png deleted file mode 100644 index 136576e8c4..0000000000 Binary files a/sphinx-doc/mpcd_slit_pore.png and /dev/null differ diff --git a/sphinx-doc/package-mpcd.rst b/sphinx-doc/package-mpcd.rst new file mode 100644 index 0000000000..beb6fdff26 --- /dev/null +++ b/sphinx-doc/package-mpcd.rst @@ -0,0 +1,25 @@ +.. Copyright (c) 2009-2024 The Regents of the University of Michigan. +.. Part of HOOMD-blue, released under the BSD 3-Clause License. + +hoomd.mpcd +========== + +.. rubric:: Details + +.. automodule:: hoomd.mpcd + :synopsis: Multiparticle Collision Dynamics. + :members: Integrator + :show-inheritance: + +.. rubric:: Modules + +.. toctree:: + :maxdepth: 1 + + module-mpcd-collide + module-mpcd-fill + module-mpcd-force + module-mpcd-geometry + module-mpcd-methods + module-mpcd-stream + module-mpcd-tune